Compare commits

..
Author SHA1 Message Date
Kit Langton a0e65ee8fb chore: merge latest v2 into Effect upgrade
# ------------------------ >8 ------------------------
# Do not modify or remove the line above.
# Everything below it will be ignored.
#
# Conflicts:
#	packages/app/src/runtime/persistence/schema.test.ts
#	packages/app/src/runtime/persistence/schema.ts
#	packages/desktop/src/main/files/attachment-picker.ts
#	packages/desktop/src/main/ipc-transport.test.ts
#	packages/gui-extensions/src/ssh/askpass.ts
#	packages/gui-extensions/src/ssh/bootstrap.ts
#	packages/gui-extensions/src/ssh/command.ts
#	packages/gui-extensions/src/ssh/controller.ts
2026-10-04 12:05:02 -07:00
Kit Langton a47a1d7cd2 chore: merge latest v2 into Effect upgrade
# ------------------------ >8 ------------------------
# Do not modify or remove the line above.
# Everything below it will be ignored.
#
# Conflicts:
#	packages/app/e2e/utils/mock-api.ts
#	packages/app/e2e/utils/mock-server.ts
2026-10-04 08:22:07 -07:00
Kit Langton 94c9f366ec chore: merge latest v2 into Effect upgrade 2026-10-03 20:46:58 -07:00
Kit Langton 4ead2614a5 chore: merge latest v2 into Effect upgrade 2026-10-03 11:51:45 -07:00
Kit Langton 15c644ecb4 chore: merge latest v2 into Effect upgrade 2026-10-03 08:29:40 -07:00
Kit Langton 973a3b570b chore: merge latest v2 into Effect upgrade 2026-10-02 15:57:47 -07:00
Kit Langton 20591148f2 chore: merge latest v2 into Effect upgrade 2026-10-02 14:56:12 -07:00
Kit Langton 75e485c095 fix(codemode): keep authored non-finite number alternatives (#51666) 2026-10-02 14:48:10 -07:00
Kit Langton 0d3ea96bb7 fix(codemode): derive empty tool inputs from the encoded schema (#51665) 2026-10-02 14:47:05 -07:00
Kit Langton 583ae4ff26 test(server): cover the session log SSE failure event
Serve the real session.log declaration and verify that a mid-stream defect
is written as the reserved effect/http-api/stream/failure event and that the
generated Effect client fails the stream with the server's defect after
delivering earlier items.
2026-10-02 14:34:03 -07:00
Kit Langton 4aed5b1daa fix(schema): keep string patterns in exported JSON Schema
Effect rc.118 omits isPattern constraints from JSON Schema unless the
regular expression uses the Unicode flag. Add the u flag to the protocol and
browser tool patterns, whose accepted strings are unchanged under Unicode
matching, so the OpenAPI document and model tool definitions keep them.
2026-10-02 14:17:30 -07:00
Kit Langton cf1acfd2fa chore: upgrade Effect to rc.118
Move effect/unstable/* imports to their top-level entry points
(effect/unstable/httpapi becomes effect/http-api), migrate Encoding helpers
to effect/encoding Base64 and Base64Url, rename Schema.isStartsWith to
Schema.isStartingWith, import Arbitrary from effect, and update the HttpApi
codegen templates, regenerated clients, and documentation.
2026-10-02 14:17:30 -07:00
Kit Langton 6f97cea63b fix(schema): define schema statics without assignment
Effect 4.0.0-rc.118 moves Schema make/makeEffect/makeOption to lazy
prototype getters without setters, so Object.assign onto a schema throws
at module load. Define static members as own properties instead, and read
the default SessionsCursor constructor through SchemaParser so the cached
getter does not lock make before it is replaced.
2026-10-02 14:09:32 -07:00
Kit Langton 4270fcc9c2 test(core): pin user-visible configuration map order 2026-10-02 11:15:38 -07:00
Kit Langton c86ee984ba refactor(ai): share one encoded-side rule for empty tool inputs 2026-10-02 11:08:05 -07:00
Kit Langton 05deab90ed fix(core): decode legacy permissions as ordered entries 2026-10-02 11:08:05 -07:00
Kit Langton 643cca88e3 chore: merge latest v2 into Effect upgrade 2026-10-01 19:43:54 -07:00
Kit Langton 54900febfc fix(cli): keep generated config schema closed to unknown keys 2026-09-27 10:30:41 -03:00
Kit Langton c73650371b fix(core): keep legacy agent permission precedence in source order 2026-09-27 10:30:41 -03:00
Kit Langton 15406155aa chore: upgrade Effect to rc.117 2026-09-27 10:05:04 -03:00
Kit Langton abfaa5210b test(ai): align empty tool expectations with V2 Gemini schemas 2026-09-27 10:05:04 -03:00
Kit Langton fbf47807fb test(server): verify real PTY websocket transports 2026-09-27 09:56:40 -03:00
Kit Langton 67146125dc fix: preserve tool schema compatibility with Effect rc.115 2026-09-27 09:56:40 -03:00
Kit Langton 8ba230e2fb refactor(app): declare open legacy persistence shapes 2026-09-27 09:56:31 -03:00
Kit Langton 12e1821cc7 fix(client): accept rc.115 schema metadata 2026-09-27 09:56:23 -03:00
Kit Langton 63e66ca3e6 fix: preserve schema compatibility on rc.115 2026-09-27 09:56:23 -03:00
Kit Langton cc782a0cc8 chore: upgrade Effect to rc.115 2026-09-27 09:56:23 -03:00
587 changed files with 3658 additions and 6993 deletions

No files matched your search

+19 -35
View File
@@ -195,7 +195,7 @@
"zod": "catalog:",
},
"peerDependencies": {
"effect": "4.0.0-rc.112",
"effect": "4.0.0-rc.118",
"solid-js": ">=1.9.0",
},
"optionalPeers": [
@@ -407,6 +407,7 @@
"@types/node": "catalog:",
"@types/which": "3.0.4",
"drizzle-kit": "catalog:",
"fast-check": "catalog:",
},
},
"packages/desktop": {
@@ -556,7 +557,7 @@
"name": "@opencode/http-recorder",
"version": "2.0.22",
"dependencies": {
"@effect/platform-node-shared": "4.0.0-rc.112",
"@effect/platform-node-shared": "4.0.0-rc.118",
},
"devDependencies": {
"@effect/platform-node": "catalog:",
@@ -1148,10 +1149,10 @@
"catalog": {
"@cloudflare/workers-types": "4.20251008.0",
"@corvu/drawer": "0.2.4",
"@effect/opentelemetry": "4.0.0-rc.112",
"@effect/platform-node": "4.0.0-rc.112",
"@effect/platform-node-shared": "4.0.0-rc.112",
"@effect/sql-sqlite-bun": "4.0.0-rc.112",
"@effect/opentelemetry": "4.0.0-rc.118",
"@effect/platform-node": "4.0.0-rc.118",
"@effect/platform-node-shared": "4.0.0-rc.118",
"@effect/sql-sqlite-bun": "4.0.0-rc.118",
"@hono/standard-validator": "0.2.0",
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.13",
@@ -1191,7 +1192,8 @@
"dompurify": "3.4.14",
"drizzle-kit": "1.0.0-rc.5-ab785fc",
"drizzle-orm": "1.0.0-rc.5-169397b",
"effect": "4.0.0-rc.112",
"effect": "4.0.0-rc.118",
"fast-check": "4.9.0",
"fuzzysort": "3.1.0",
"get-east-asian-width": "1.6.0",
"hono": "4.10.7",
@@ -1723,13 +1725,13 @@
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.12.0", "", {}, "sha512-mlUE+rZ8CatQekLhnaiN91Iemdd+e2gFKooGlnRB3oPTL3VghLfX24dx7HrzMNeC1JrIB/0kpsfyty3f5HNfxQ=="],
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.112", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.0-rc.112" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-OTRv1DxTHUmnakgJ6XVM8wVgF1KgZH4UXnOemwSLUwUjXO+RCikzF8oR/rlVmOGq81KtQzj1URM4M4nchlQOuQ=="],
"@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-rc.118", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-node": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-web": ">=2.0.0 <3.0.0", "@opentelemetry/semantic-conventions": ">=1.33.0 <2.0.0", "effect": "^4.0.0-rc.118" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-N5dvEeCj1W/9zgBvyyp/GucOKOEJdV7QNirWp+4g/axFKTB/cfV2gx/ZhMDUb69HzlyRmOelNvKEmB3RA0PdVw=="],
"@effect/platform-node": ["@effect/platform-node@4.0.0-rc.112", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.112", "mime": "^4.1.0", "undici": "^8.10.0" }, "peerDependencies": { "effect": "^4.0.0-rc.112", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/BMAcdNGQQskLmI0Zoa95KfTZkr9HV9N4NSxaSrusG6GeW6Ulp9KvZ+Rlaiw8lnOt43CXjFLdfll5/k5rxL4hQ=="],
"@effect/platform-node": ["@effect/platform-node@4.0.0-rc.118", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-rc.118", "undici": "^8.11.2" }, "peerDependencies": { "effect": "^4.0.0-rc.118", "redis": ">=5.0.0 <7.0.0" } }, "sha512-qdAxPZ+5bKxVnzkwziqcHZTHRHz2hyfFX6NhwEmm75CBpmaIzXIZR2LRqGd1/8eI3J3cOrXcAYzByhln7T1/EA=="],
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.112", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.21.3" }, "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-ttjz0xKamFN7vL8pNDYVwddJLjZvqKePc05djlz2VcdaKbLsnYbtMnL1rbOfHgEnIUSHGh7FkjaN4DM1Ov81sQ=="],
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-rc.118", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.22.0" }, "peerDependencies": { "effect": "^4.0.0-rc.118" } }, "sha512-pNbmIlxbAUxAthUNsp2xm1NLG9kxoYecsPkoYl9mNF2oxPcKDxAvkOar7tYnLSUpXJeATm4pxYVipdZjQfqO4Q=="],
"@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-rc.112", "", { "peerDependencies": { "effect": "^4.0.0-rc.112" } }, "sha512-EqR8pWZo3VzedRvP6qo5HcfqNmFvSa+sQLWQl3oBMx2KY1FCc8NYjep79wYjNkW0qABnffrPUXCMTOncd1A+5g=="],
"@effect/sql-sqlite-bun": ["@effect/sql-sqlite-bun@4.0.0-rc.118", "", { "peerDependencies": { "effect": "^4.0.0-rc.118" } }, "sha512-UF0oVOK7mQvEwpxySR9VTk5YoP5luRI//Z8NMOJKVG/SbZbsORGACd7Np+KZRiXXjgzSXEpfGb7Gs3G1cKfUCg=="],
"@electron-internal/extract-zip": ["@electron-internal/extract-zip@1.0.5", "", {}, "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA=="],
@@ -2063,18 +2065,6 @@
"@motionone/utils": ["@motionone/utils@10.18.0", "", { "dependencies": { "@motionone/types": "^10.17.1", "hey-listen": "^1.0.8", "tslib": "^2.3.1" } }, "sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="],
@@ -3913,7 +3903,7 @@
"editorconfig": ["editorconfig@1.0.7", "", { "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", "minimatch": "^9.0.1", "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw=="],
"effect": ["effect@4.0.0-rc.112", "", { "dependencies": { "fast-check": "^4.9.0", "msgpackr": "^2.0.5" } }, "sha512-wXxwuh1Ywnv4cPRM3Wfa0vDwuOHnZ1TsTgHJkG9XgzND6inhBH9n1vBxhg3iIXOia/OrpmvVmd3lrD4vq6bF3A=="],
"effect": ["effect@4.0.0-rc.118", "", {}, "sha512-hxhpCd+swzrtu9lnyPpEFre2ODQzJ9qxgyL1oHIEY8NKnW4PxTbM1XzplMZXiAsLD62HO+KJc41Nqxr9T791ZQ=="],
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
@@ -4751,7 +4741,7 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime": ["mime@4.1.0", "", { "bin": { "mime": "bin/cli.js" } }, "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw=="],
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
@@ -4799,10 +4789,6 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"msgpackr": ["msgpackr@2.0.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA=="],
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
@@ -4845,8 +4831,6 @@
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="],
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
@@ -5643,7 +5627,7 @@
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici": ["undici@8.10.0", "", {}, "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ=="],
"undici": ["undici@8.11.2", "", {}, "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
@@ -6095,15 +6079,13 @@
"@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
"@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"@cloudflare/vite-plugin/vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="],
"@cloudflare/vite-plugin/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@effect/platform-node-shared/ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
"@effect/platform-node-shared/ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="],
"@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
@@ -6695,6 +6677,8 @@
"tw-to-css/tailwindcss": ["tailwindcss@3.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w=="],
"unifont/undici": ["undici@8.10.0", "", {}, "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ=="],
"unplugin/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
"unused-filename/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+6 -5
View File
@@ -43,10 +43,10 @@
"packages/stats/*"
],
"catalog": {
"@effect/opentelemetry": "4.0.0-rc.112",
"@effect/platform-node": "4.0.0-rc.112",
"@effect/platform-node-shared": "4.0.0-rc.112",
"@effect/sql-sqlite-bun": "4.0.0-rc.112",
"@effect/opentelemetry": "4.0.0-rc.118",
"@effect/platform-node": "4.0.0-rc.118",
"@effect/platform-node-shared": "4.0.0-rc.118",
"@effect/sql-sqlite-bun": "4.0.0-rc.118",
"@npmcli/arborist": "9.4.0",
"@types/bun": "1.4.0",
"@types/cross-spawn": "6.0.6",
@@ -80,7 +80,8 @@
"dompurify": "3.4.14",
"drizzle-kit": "1.0.0-rc.5-ab785fc",
"drizzle-orm": "1.0.0-rc.5-169397b",
"effect": "4.0.0-rc.112",
"effect": "4.0.0-rc.118",
"fast-check": "4.9.0",
"ai": "6.0.168",
"cross-spawn": "7.0.6",
"hono": "4.10.7",
+1 -1
View File
@@ -15,7 +15,7 @@ import { OpenAI } from "@opencode/ai/providers"
* hover imports and local values to see how the public API is typed.
*/
const apiKey = Config.redacted("OPENAI_API_KEY")
const apiKey = Config.Redacted("OPENAI_API_KEY")
// 1. Configure a provider. The configured facade records provider identity,
// deployment options, authentication, and defaults. Per-modality selectors pick
+2 -2
View File
@@ -5,7 +5,7 @@ import * as path from "node:path"
import * as prompts from "@clack/prompts"
import { AwsV4Signer } from "aws4fetch"
import { Config, ConfigProvider, Effect, FileSystem, PlatformError, Redacted } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/unstable/http"
import { FetchHttpClient, HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/http"
import * as ProviderShared from "../src/protocols/shared"
import { CloudflareAIGateway } from "../src/providers/cloudflare-ai-gateway.js"
import { CloudflareWorkersAI } from "../src/providers/cloudflare-workers-ai.js"
@@ -383,7 +383,7 @@ const readEnvFile = Effect.fn("RecordingEnv.readFile")(function* () {
})
const readConfigString = (provider: ConfigProvider.ConfigProvider, name: string) =>
Config.string(name)
Config.String(name)
.parse(provider)
.pipe(
Effect.match({
-1
View File
@@ -42,7 +42,6 @@ const RESPECTS_INLINE_HINTS = new Set([
"alibaba-messages",
"anthropic-messages",
"anthropic-compatible-messages",
"bedrock-mantle-messages",
"cloudflare-ai-gateway-messages",
"google-vertex-messages",
"meta-messages",
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import {
ChoiceQuestion,
EvaluationInput,
+7 -6
View File
@@ -1,7 +1,8 @@
export * as Media from "./media.js"
import { Effect, Encoding, FileSystem, Schema, SchemaGetter } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { Effect, FileSystem, Schema, SchemaGetter } from "effect"
import { Base64 } from "effect/encoding"
import { HttpClientRequest } from "effect/http"
import { ProviderID } from "./schema/ids.js"
import { AIError, HttpContext, InvalidProviderOutputError, InvalidRequestError } from "./schema/errors.js"
import { ProviderMetadata } from "./schema/options.js"
@@ -140,7 +141,7 @@ export class Asset {
inline(): Inline | undefined {
const source = this.source
if (source.type !== "bytes" && source.type !== "base64") return undefined
const base64 = source.type === "base64" ? source.data : (this.#base64 ??= Encoding.encodeBase64(source.data))
const base64 = source.type === "base64" ? source.data : (this.#base64 ??= Base64.encode(source.data))
const mime = this.mediaType.toLowerCase()
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` }
}
@@ -155,7 +156,7 @@ export class Asset {
return Effect.fail(invalid(`Cannot materialize provider ref ${source.provider}:${source.id}`))
const decoded =
source.type === "base64"
? Effect.fromResult(Encoding.decodeBase64(source.data)).pipe(
? Effect.fromResult(Base64.decode(source.data)).pipe(
Effect.mapError((cause) => invalid(`Media asset contains invalid base64 data`, cause)),
)
: download(source, this.headers)
@@ -168,7 +169,7 @@ export class Asset {
const source = this.source
if (source.type === "base64") return Effect.succeed(source.data)
if (this.#base64 !== undefined) return Effect.succeed(this.#base64)
return this.bytes().pipe(Effect.map((data) => (this.#base64 = Encoding.encodeBase64(data))))
return this.bytes().pipe(Effect.map((data) => (this.#base64 = Base64.encode(data))))
})
}
@@ -183,7 +184,7 @@ export class Asset {
toJSON() {
const source = this.source
return {
source: source.type === "bytes" ? { ...source, data: Encoding.encodeBase64(source.data) } : source,
source: source.type === "bytes" ? { ...source, data: Base64.encode(source.data) } : source,
info: this.info,
providerMetadata: this.providerMetadata,
}
@@ -6,7 +6,7 @@ import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
import { Framing } from "../route/framing.js"
import { Protocol } from "../route/protocol.js"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { HttpTransport } from "../route/transport/index.js"
import {
AIError,
@@ -584,7 +584,7 @@ const serverToolResultType = (name: string): AnthropicServerToolResultType | und
return undefined
}
const lowerServerToolResult = Effect.fnUntraced(function* (
const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (
part: ToolResultPart,
providerMetadataKey: string,
) {
@@ -657,7 +657,7 @@ const citationsFromMetadata = (metadata: MediaPart["metadata"]): AnthropicDocume
const isHttpUrl = (value: string) => /^https?:\/\//i.test(value.trim())
const lowerMedia = Effect.fnUntraced(function* (
const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (
part: MediaPart,
breakpoints?: Cache.Breakpoints,
) {
@@ -847,7 +847,7 @@ const splitsLocalToolResults = (messages: LLMRequest["messages"], index: number)
return pending.size > 0
}
const lowerNativeSystemUpdate = Effect.fnUntraced(function* (
const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (
message: LLMRequest["messages"][number],
breakpoints: Cache.Breakpoints,
) {
@@ -862,7 +862,7 @@ const lowerNativeSystemUpdate = Effect.fnUntraced(function* (
}
})
const lowerMessages = Effect.fnUntraced(function* (
const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
request: LLMRequest,
breakpoints: Cache.Breakpoints,
) {
@@ -989,9 +989,6 @@ const lowerMessages = Effect.fnUntraced(function* (
return messages
})
// TODO: Move per-model capability heuristics (`supportsEffortUpdates`, `supportsNativeSystemUpdates`,
// `supportsThinkingBlockBinding`) into explicit model/provider `compatibility` metadata so the protocol
// only reads `request.model.compatibility`.
// Per-turn effort started with Claude Opus 5 and every Claude 5.1 model; later versions of any family inherit it.
const supportsEffortUpdates = (model: LLMRequest["model"]) => {
const override = model.compatibility?.supportsEffortUpdates
@@ -1303,7 +1300,7 @@ const onContentBlockStart = (
return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]]
}
const onContentBlockDelta = Effect.fnUntraced(function* (
const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (
state: ParserState,
event: AnthropicEvent & { readonly delta: AnthropicStreamDelta },
) {
@@ -1371,7 +1368,7 @@ const onContentBlockDelta = Effect.fnUntraced(function* (
return [state, NO_EVENTS] satisfies StepResult
})
const onContentBlockStop = Effect.fnUntraced(function* (
const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(function* (
state: ParserState,
event: AnthropicEvent,
) {
@@ -1442,7 +1439,7 @@ const onMessageDelta = (
]
}
const onMessageStop = Effect.fnUntraced(function* (state: ParserState) {
const onMessageStop = Effect.fn("AnthropicMessages.onMessageStop")(function* (state: ParserState) {
if (Object.keys(state.compactions).length)
return yield* ProviderShared.eventError(ADAPTER, "Response ended with an incomplete compaction block")
const result = yield* ToolStream.finishAll(ADAPTER, state.tools)
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
@@ -1,4 +1,5 @@
import { Effect, Encoding, Schema } from "effect"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import { Route } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import { Protocol } from "../route/protocol.js"
@@ -285,7 +286,7 @@ const lowerToolCall = (part: ToolCallPart, normalizeID: (id: string) => string):
},
})
const lowerToolResultContent = Effect.fnUntraced(function* (
const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (
part: ToolResultPart,
documentNames: Set<string>,
) {
@@ -305,7 +306,7 @@ const lowerToolResultContent = Effect.fnUntraced(function* (
return content
})
const lowerToolResult = Effect.fnUntraced(function* (
const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (
part: ToolResultPart,
documentNames: Set<string>,
normalizeID: (id: string) => string,
@@ -322,7 +323,7 @@ const lowerToolResult = Effect.fnUntraced(function* (
// Keep Claude and Nova tool-result images inline; put other models' images beside the result.
const keepToolImagesInline = (id: string) => id.includes("anthropic.claude-") || id.includes("amazon.nova-")
const lowerMessages = Effect.fnUntraced(function* (
const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (
request: LLMRequest,
breakpoints: BedrockCache.Breakpoints,
) {
@@ -610,7 +611,7 @@ interface ParserState {
readonly reasoningRedactedContent: Readonly<Record<number, Uint8Array[]>>
}
const encodeRedactedContent = (chunks: ReadonlyArray<Uint8Array>) => Encoding.encodeBase64(concatBytes(chunks))
const encodeRedactedContent = (chunks: ReadonlyArray<Uint8Array>) => Base64.encode(concatBytes(chunks))
const step = (state: ParserState, event: BedrockEvent) =>
Effect.gen(function* () {
@@ -659,7 +660,7 @@ const step = (state: ParserState, event: BedrockEvent) =>
const events: LLMEvent[] = []
const redactedChunk = yield* (() => {
if (reasoning.redactedContent === undefined) return Effect.succeed(undefined)
return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe(
return Effect.fromResult(Base64.decode(reasoning.redactedContent)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
@@ -1,6 +1,7 @@
import { EventStreamCodec } from "@smithy/eventstream-codec"
import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
import { Effect, Encoding, Stream } from "effect"
import { Effect, Stream } from "effect"
import { Base64 } from "effect/encoding"
import { AIError, AIErrorReason, InvalidProviderOutputError } from "../schema/index.js"
import { Framing } from "../route/framing.js"
import { ProviderShared } from "./shared.js"
@@ -47,7 +48,7 @@ const consumeFrames = (route: string) => (state: FrameBufferState, input: FrameI
route,
classification: "incomplete-stream",
message: `Incomplete Bedrock Converse event-stream frame: ${remaining.length} buffered bytes remain at end of stream`,
body: Encoding.encodeBase64(remaining),
body: Base64.encode(remaining),
}),
})
return [state, []] as const
@@ -68,7 +69,7 @@ const consumeFrames = (route: string) => (state: FrameBufferState, input: FrameI
`Failed to decode Bedrock Converse event-stream frame: ${
error instanceof Error ? error.message : String(error)
}`,
Encoding.encodeBase64(view.subarray(0, totalLength)),
Base64.encode(view.subarray(0, totalLength)),
error,
),
})
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
+3 -3
View File
@@ -75,7 +75,7 @@ const usesSse = (request: MediaProtocol.Addressed<Request>) => request.mode ===
const CONTAINERS: Readonly<Record<string, "raw" | "wav" | "mp3">> = { pcm: "raw", wav: "wav", mp3: "mp3" }
const outputFormat = Effect.fnUntraced(function* (request: MediaProtocol.Addressed<Request>) {
const outputFormat = Effect.fn("CartesiaSpeech.outputFormat")(function* (request: MediaProtocol.Addressed<Request>) {
const sse = usesSse(request)
const format = request.format ?? (sse ? "pcm" : "mp3")
const container = CONTAINERS[format]
@@ -121,7 +121,7 @@ const fromRequest = Effect.fn("CartesiaSpeech.fromRequest")(function* (request:
// 6. Stream parsing
// ---------------------------------------------------------------------------
const onEvent = Effect.fnUntraced(function* (state: State, frame: string) {
const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state: State, frame: string) {
const event = yield* decodeEvent(frame)
if (event.type === "chunk" && event.data !== undefined) return SpeechStream.delta(state, event.data)
if (event.type === "timestamps" && event.word_timestamps !== undefined) {
@@ -140,7 +140,7 @@ const onEvent = Effect.fnUntraced(function* (state: State, frame: string) {
return [state, []] as const
})
const finish = Effect.fnUntraced(function* (
const finish = Effect.fn("CartesiaSpeech.finish")(function* (
state: State,
context: MediaProtocol.ResponseContext<Request>,
) {
+1 -1
View File
@@ -252,7 +252,7 @@ const mapUsage = (usage: typeof NativeUsage.Type) =>
})
// Lifecycle deltas open blocks on demand and ends are no-ops for closed blocks, so content-start needs no handling.
const step = Effect.fnUntraced(function* (state: State, event: Event) {
const step = Effect.fn("CohereChat.step")(function* (state: State, event: Event) {
const events: LLMEvent[] = []
switch (event.type) {
case "message-start":
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
import { mergeJsonRecords, type OpenString } from "../schema/index.js"
@@ -95,7 +95,7 @@ const OUTPUT_FORMATS: Readonly<Record<string, string>> = {
}
/** WAV is served only by the non-streaming endpoints. */
const outputFormat = Effect.fnUntraced(function* (request: MediaProtocol.Addressed<Request>) {
const outputFormat = Effect.fn("ElevenLabsSpeech.outputFormat")(function* (request: MediaProtocol.Addressed<Request>) {
const format = request.providerOptions?.outputFormat ?? OUTPUT_FORMATS[request.format ?? "mp3"]
if (format === undefined)
return yield* route.unsupported(
@@ -138,7 +138,7 @@ const path = (request: MediaProtocol.Addressed<Request>) =>
// 6. Stream parsing
// ---------------------------------------------------------------------------
const onRecord = Effect.fnUntraced(function* (state: State, frame: string) {
const onRecord = Effect.fn("ElevenLabsSpeech.onRecord")(function* (state: State, frame: string) {
const record = yield* decodeRecord(frame)
const [next, events] = SpeechStream.delta(state, record.audio_base64)
const alignment = record.alignment
@@ -169,7 +169,7 @@ const describeOutput = (format: string) => {
return encoding === undefined ? SpeechStream.container(codec, sampleRate) : SpeechStream.pcm(encoding, sampleRate)
}
const finish = Effect.fnUntraced(function* (
const finish = Effect.fn("ElevenLabsSpeech.finish")(function* (
state: State,
context: MediaProtocol.ResponseContext<Request>,
) {
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
import { mergeJsonRecords, type OpenString } from "../schema/index.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
+2 -2
View File
@@ -283,7 +283,7 @@ const lowerToolConfig = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
tool: (name) => ({ functionCallingConfig: { mode: "ANY" as const, allowedFunctionNames: [name] } }),
})
const lowerContentPart = Effect.fnUntraced(function* (part: TextPart | MediaPart) {
const lowerContentPart = Effect.fn("Gemini.lowerContentPart")(function* (part: TextPart | MediaPart) {
if (part.type === "text") return { text: part.text }
return yield* GeminiGenerateContent.mediaPart("Gemini", part.media)
})
@@ -302,7 +302,7 @@ const lowerToolCall = (part: ToolCallPart, omitIds: boolean, metadataKey: string
thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey),
})
const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest) {
const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request: LLMRequest) {
const contents: GeminiContent[] = []
const metadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider)
const omitCallIds = omitsFunctionCallIds(request.model.id)
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
@@ -182,7 +182,7 @@ type StepResult = readonly [ParserState, ReadonlyArray<LLMEvent>]
// =============================================================================
// Request Body Construction
// =============================================================================
const mediaContent = Effect.fnUntraced(function* (asset: Media.Asset) {
const mediaContent = Effect.fn("GoogleInteractions.mediaContent")(function* (asset: Media.Asset) {
if (
asset.kind !== "image" &&
asset.kind !== "audio" &&
@@ -208,7 +208,7 @@ const signature = (metadata: ProviderMetadata | undefined, key: string) => {
: undefined
}
const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest) {
const lowerMessages = Effect.fn("GoogleInteractions.lowerMessages")(function* (request: LLMRequest) {
const steps: InputStep[] = []
const key = request.model.route.providerMetadataKey ?? String(request.model.provider)
for (const message of request.messages) {
@@ -275,7 +275,7 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest) {
return steps
})
const lowerToolResult = Effect.fnUntraced(function* (part: ToolResultPart) {
const lowerToolResult = Effect.fn("GoogleInteractions.lowerToolResult")(function* (part: ToolResultPart) {
if (part.result.type === "json" && ProviderShared.isRecord(part.result.value)) return part.result.value
if (part.result.type !== "content") return ProviderShared.toolResultText(part)
@@ -364,7 +364,7 @@ const mapUsage = (usage: RawUsage | undefined, key: string) => {
})
}
const onStart = Effect.fnUntraced(function* (
const onStart = Effect.fn("GoogleInteractions.onStart")(function* (
state: ParserState,
index: number,
step: OutputStep,
@@ -402,7 +402,7 @@ const onStart = Effect.fnUntraced(function* (
return [{ ...state, lifecycle, tools, steps: { ...state.steps, [index]: step } }, events] satisfies StepResult
})
const onDelta = Effect.fnUntraced(function* (
const onDelta = Effect.fn("GoogleInteractions.onDelta")(function* (
state: ParserState,
index: number,
delta: typeof Delta.Type,
@@ -447,7 +447,7 @@ const onDelta = Effect.fnUntraced(function* (
return yield* ProviderShared.eventError(ADAPTER, `Unsupported Interactions delta: ${delta.type}`, encodeJson(delta))
})
const onStop = Effect.fnUntraced(function* (state: ParserState, index: number) {
const onStop = Effect.fn("GoogleInteractions.onStop")(function* (state: ParserState, index: number) {
const step = state.steps[index]
if (!step) return yield* ProviderShared.eventError(ADAPTER, "Interactions step.stop without step.start")
const events: LLMEvent[] = []
@@ -465,7 +465,7 @@ const onStop = Effect.fnUntraced(function* (state: ParserState, index: number) {
return [{ ...state, tools: result.tools }, result.events ?? []] satisfies StepResult
})
const step = Effect.fnUntraced(function* (state: ParserState, event: Event) {
const step = Effect.fn("GoogleInteractions.step")(function* (state: ParserState, event: Event) {
switch (event.event_type) {
case "step.start":
return yield* onStart(state, event.index, event.step)
+1 -1
View File
@@ -94,7 +94,7 @@ const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request: Me
// 6. Stream parsing
// ---------------------------------------------------------------------------
const step = Effect.fnUntraced(function* (state: State, frame: string) {
const step = Effect.fn("GoogleSpeech.step")(function* (state: State, frame: string) {
const chunk = yield* decodeChunk(frame)
const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame)
if (blocked !== undefined) return yield* blocked
@@ -138,7 +138,7 @@ const turn = (part: Schema.Schema.Type<typeof AudioTranscription>) => {
}
}
const step = Effect.fnUntraced(function* (state: State, frame: string) {
const step = Effect.fn("GoogleTranscription.step")(function* (state: State, frame: string) {
const chunk = yield* decodeChunk(frame)
const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame)
if (blocked !== undefined) return yield* blocked
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+6 -5
View File
@@ -1,4 +1,5 @@
import { Effect, Encoding, Schema } from "effect"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import { Protocol } from "../route/protocol.js"
import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.js"
import { OpenResponses } from "./open-responses.js"
@@ -95,7 +96,7 @@ const HOSTED_TOOLS = {
image_generation_call: {
name: "image_generation",
input: () => ({}),
result: Effect.fnUntraced(function* (raw: ResponsesHostedTools.Item) {
result: Effect.fn("MetaResponses.imageResult")(function* (raw: ResponsesHostedTools.Item) {
const item = yield* Schema.decodeUnknownEffect(ImageItem)(raw).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
@@ -113,7 +114,7 @@ const HOSTED_TOOLS = {
"Meta returned an image without data",
ProviderShared.encodeJson(raw),
)
const data = yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
const data = yield* Effect.fromResult(Base64.decode(item.result)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
@@ -136,7 +137,7 @@ const HOSTED_TOOLS = {
},
} satisfies ResponsesHostedTools.Definitions
const onEvent = Effect.fnUntraced(function* (
const onEvent = Effect.fn("MetaResponses.onEvent")(function* (
state: OpenResponses.ParserState,
input: OpenResponses.Event,
) {
@@ -173,7 +174,7 @@ const onEvent = Effect.fnUntraced(function* (
] satisfies OpenResponses.StepResult
})
const step = Effect.fnUntraced(function* (state: ParserState, input: OpenResponses.Event) {
const step = Effect.fn("MetaResponses.step")(function* (state: ParserState, input: OpenResponses.Event) {
const completedItems = new Set(state.completedItems)
const event = OpenResponses.normalize(state, input)
if (event.type === "response.output_item.done" && event.item && completedItems.has(event.item.id))
+8 -8
View File
@@ -226,7 +226,7 @@ const MistralEvent = Schema.StructWithRest(
type MistralEvent = Schema.Schema.Type<typeof MistralEvent>
const MistralStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(MistralEvent)])
const lowerMedia = Effect.fnUntraced(function* (part: MediaPart) {
const lowerMedia = Effect.fn("MistralChat.lowerMedia")(function* (part: MediaPart) {
const mime = part.media.mediaType.toLowerCase()
const url =
ProviderShared.mediaUrl(part.media) ??
@@ -236,7 +236,7 @@ const lowerMedia = Effect.fnUntraced(function* (part: MediaPart) {
return yield* ProviderShared.invalidRequest(`Mistral Chat does not support media type ${part.media.mediaType}`)
})
const lowerUser = Effect.fnUntraced(function* (message: LLMRequest["messages"][number]) {
const lowerUser = Effect.fn("MistralChat.lowerUser")(function* (message: LLMRequest["messages"][number]) {
const content: MistralUserContent[] = []
for (const part of message.content) {
if (part.type === "text") {
@@ -260,7 +260,7 @@ const lowerToolCall = (part: ToolCallPart, normalizeID: (id: string) => string):
function: { name: part.name, arguments: ProviderShared.encodeJson(part.input) },
})
const lowerAssistant = Effect.fnUntraced(function* (
const lowerAssistant = Effect.fn("MistralChat.lowerAssistant")(function* (
message: LLMRequest["messages"][number],
normalizeID: (id: string) => string,
prefix: boolean,
@@ -298,7 +298,7 @@ const lowerAssistant = Effect.fnUntraced(function* (
}
})
const lowerToolResults = Effect.fnUntraced(function* (
const lowerToolResults = Effect.fn("MistralChat.lowerToolResults")(function* (
message: LLMRequest["messages"][number],
normalizeID: (id: string) => string,
) {
@@ -335,7 +335,7 @@ const lowerToolResults = Effect.fnUntraced(function* (
return output
})
const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest) {
const lowerMessages = Effect.fn("MistralChat.lowerMessages")(function* (request: LLMRequest) {
const normalizeID = MistralToolID.normalizer(request)
const messages: MistralMessage[] =
request.system.length === 0
@@ -596,7 +596,7 @@ const toolText = (tool: MistralToolDelta) => {
return value === null || value === undefined ? "" : ProviderShared.encodeJson(value)
}
const appendTools = Effect.fnUntraced(function* (
const appendTools = Effect.fn("MistralChat.appendTools")(function* (
initial: ParserState,
events: LLMEvent[],
deltas: ReadonlyArray<MistralToolDelta>,
@@ -662,7 +662,7 @@ const hasLateContent = (event: MistralEvent) => {
)
}
const step = Effect.fnUntraced(function* (state: ParserState, event: MistralEvent) {
const step = Effect.fn("MistralChat.step")(function* (state: ParserState, event: MistralEvent) {
if (event.error) {
const body = ProviderShared.encodeJson(event)
return yield* new AIError({
@@ -726,7 +726,7 @@ const step = Effect.fnUntraced(function* (state: ParserState, event: MistralEven
] as const
})
const finishEvents = Effect.fnUntraced(function* (state: ParserState) {
const finishEvents = Effect.fn("MistralChat.finishEvents")(function* (state: ParserState) {
if (!state.finishReason)
return yield* new AIError({
reason: new InvalidProviderOutputError({
@@ -1,5 +1,5 @@
import { Effect, Schema, Stream } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Framing } from "../route/framing.js"
import type { HttpContext } from "../schema/index.js"
import {
+13 -14
View File
@@ -442,7 +442,7 @@ interface ReasoningStreamItem {
// =============================================================================
// Request Lowering
// =============================================================================
export const lowerTool = Effect.fnUntraced(function* (protocolName: string, tool: ToolDefinition) {
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName: string, tool: ToolDefinition) {
if (tool.native !== undefined)
return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`)
return {
@@ -507,10 +507,7 @@ const lowerReasoning = (part: ReasoningPart, providerMetadataKey: string): OpenR
}
}
const decodeImageDetail = ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesInputImage.fields.detail))
const decodeMessageMetadata = ProviderShared.validateWith(Schema.decodeUnknownEffect(MessageMetadata))
const lowerMedia = Effect.fnUntraced(function* (
const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (
part: MediaPart,
request: LLMRequest,
adapter: ProviderAdapter,
@@ -519,8 +516,9 @@ const lowerMedia = Effect.fnUntraced(function* (
const media = part.media.inline()
const providerMedia = adapter.lowerMedia?.({ part, media, request })
if (providerMedia) return providerMedia
const rawDetail = part.providerMetadata?.[metadataKey(request.model)]?.detail
const detail = rawDetail === undefined ? undefined : yield* decodeImageDetail(rawDetail)
const detail = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesInputImage.fields.detail))(
part.providerMetadata?.[metadataKey(request.model)]?.detail,
)
const mime = part.media.mediaType.toLowerCase()
const url = ProviderShared.mediaUrl(part.media)
const location = url ?? (yield* ProviderShared.requireInlineMedia(adapter.name, part.media)).dataUrl
@@ -593,7 +591,7 @@ const lowerToolResultOutput = Effect.fnUntraced(function* (
const DEFAULT_EFFORT = "medium"
const lowerMessages = Effect.fnUntraced(function* (
const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (
request: LLMRequest,
adapter: ProviderAdapter,
) {
@@ -601,8 +599,9 @@ const lowerMessages = Effect.fnUntraced(function* (
const providerMetadataKey = metadataKey(request.model)
for (const message of request.messages) {
const rawMetadata = message.providerMetadata?.[providerMetadataKey]
const metadata = rawMetadata === undefined ? undefined : yield* decodeMessageMetadata(rawMetadata)
const metadata = yield* ProviderShared.validateWith(
Schema.decodeUnknownEffect(Schema.UndefinedOr(MessageMetadata)),
)(message.providerMetadata?.[providerMetadataKey])
if (message.role === "system") {
const update = effortUpdate(message)
if (update) {
@@ -756,7 +755,7 @@ const lowerMessages = Effect.fnUntraced(function* (
return input
})
export const lowerConversation = Effect.fnUntraced(function* (
export const lowerConversation = Effect.fn("OpenResponses.lowerConversation")(function* (
request: LLMRequest,
adapter: ProviderAdapter,
) {
@@ -1143,7 +1142,7 @@ const onReasoningSummaryPartDone = (state: ParserState, event: Event): StepResul
]
}
const onFunctionCallArgumentsDelta = Effect.fnUntraced(function* (
const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgumentsDelta")(function* (
state: ParserState,
event: Event,
) {
@@ -1174,7 +1173,7 @@ const onFunctionCallArgumentsDelta = Effect.fnUntraced(function* (
return [{ ...state, lifecycle, tools: result.tools }, events] satisfies StepResult
})
const onOutputItemDone = Effect.fnUntraced(function* (
const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
state: ParserState,
item: NormalizedEvent["item"],
) {
@@ -1310,7 +1309,7 @@ const onOutputItemDone = Effect.fnUntraced(function* (
return [state, NO_EVENTS] satisfies StepResult
})
const onResponseFinish = Effect.fnUntraced(function* (state: ParserState, event: Event) {
const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (state: ParserState, event: Event) {
let current = state
const events: LLMEvent[] = []
if (event.type === "response.completed") {
+8 -8
View File
@@ -360,7 +360,7 @@ const lowerToolCall = (
extra_content: decodeExtraContent(part.providerMetadata?.[options.providerMetadataKey]?.extraContent),
})
const lowerMedia = Effect.fnUntraced(function* (part: MediaPart) {
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part: MediaPart) {
// Chat Completions accepts PDFs, and no other documents, as inline `file` parts; file URLs are not supported.
if (part.media.mediaType.toLowerCase() === "application/pdf")
return {
@@ -406,7 +406,7 @@ const lowerReasoningDetail = (detail: ReasoningDetail) => {
const isKimiDetail = (detail: { readonly type: string }) => detail.type === "summary" || detail.type === "encrypted"
const lowerUserMessage = Effect.fnUntraced(function* (
const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (
message: OpenAIChatRequestMessage,
options: LoweringOptions,
) {
@@ -430,7 +430,7 @@ const lowerUserMessage = Effect.fnUntraced(function* (
return { role: "user" as const, content }
})
const lowerAssistantMessage = Effect.fnUntraced(function* (
const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(function* (
message: OpenAIChatRequestMessage,
configuredField: string | undefined,
requireReasoning: boolean,
@@ -495,7 +495,7 @@ const lowerAssistantMessage = Effect.fnUntraced(function* (
return { ...result, [field]: reasoningText }
})
const lowerToolMessages = Effect.fnUntraced(function* (
const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (
message: OpenAIChatRequestMessage,
options: LoweringOptions,
) {
@@ -532,7 +532,7 @@ const toolMessage = (toolCallID: string, text: string, cacheControl: OpenAIChatC
content: cacheControl === undefined ? text : [{ type: "text" as const, text, cache_control: cacheControl }],
})
const lowerMessage = Effect.fnUntraced(function* (
const lowerMessage = Effect.fn("OpenAIChat.lowerMessage")(function* (
message: OpenAIChatRequestMessage,
reasoningField: string | undefined,
requireReasoning: boolean,
@@ -544,7 +544,7 @@ const lowerMessage = Effect.fnUntraced(function* (
return (yield* lowerToolMessages(message, options)).messages
})
const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, options: LoweringOptions) {
const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request: LLMRequest, options: LoweringOptions) {
const system: OpenAIChatMessage[] =
request.system.length === 0
? []
@@ -855,7 +855,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (
// Streaming parsers are small state machines: every event returns a new state
// plus the common `LLMEvent`s produced by that event. Tool calls are accumulated
// because OpenAI streams JSON arguments across multiple deltas.
const mapFinishReason = Effect.fnUntraced(function* (event: OpenAIChatEvent, reason: string) {
const mapFinishReason = Effect.fn("OpenAIChat.mapFinishReason")(function* (event: OpenAIChatEvent, reason: string) {
switch (reason) {
case "error":
return yield* new AIError({
@@ -1214,7 +1214,7 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
] as const
})
const finishEvents = Effect.fnUntraced(function* (state: ParserState) {
const finishEvents = Effect.fn("OpenAIChat.finishEvents")(function* (state: ParserState) {
if (state.finishReason === undefined && state.requireFinishReason)
return yield* new AIError({
reason: new InvalidProviderOutputError({
+2 -2
View File
@@ -208,7 +208,7 @@ const eventImage = (frame: string, label: string, data: string, format: string,
info: info(format, size),
})
const onEvent = Effect.fnUntraced(function* (state: State, frame: string) {
const onEvent = Effect.fn("OpenAIImages.onEvent")(function* (state: State, frame: string) {
const event = yield* decodeEvent(frame)
const format = event.output_format
if ("partial_image_index" in event) {
@@ -229,7 +229,7 @@ const onEvent = Effect.fnUntraced(function* (state: State, frame: string) {
] as const
})
const onDocument = Effect.fnUntraced(function* (frame: Exclude<Frame, string>) {
const onDocument = Effect.fn("OpenAIImages.onDocument")(function* (frame: Exclude<Frame, string>) {
const invalid = (message: string, cause?: unknown) => route.frameError(message, frame.document, cause)
const decoded = yield* decodeDocument(frame.document).pipe(
Effect.mapError((cause) => invalid(`${route.name} returned an invalid response`, cause)),
@@ -1,5 +1,6 @@
import { Effect, Encoding, Schema } from "effect"
import { Headers } from "effect/unstable/http"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import { Headers } from "effect/http"
import { Route } from "../route/client.js"
import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
@@ -157,7 +158,7 @@ const nativeImageTool = (tool: ToolDefinition) => {
return Schema.is(OpenAIResponsesImageGenerationTool)(native) ? native : undefined
}
const lowerTool = Effect.fnUntraced(function* (tool: ToolDefinition) {
const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool: ToolDefinition) {
const native = nativeImageToolInput(tool)
if (native !== undefined) {
if (Schema.is(OpenAIResponsesImageGenerationTool)(native)) return native
@@ -168,7 +169,7 @@ const lowerTool = Effect.fnUntraced(function* (tool: ToolDefinition) {
// Native namespaces hold only function tools, so deeper levels flatten into
// the leaf names the same way non-native protocols flatten the whole tree.
const lowerToolEntry = Effect.fnUntraced(function* (tool: ToolEntry) {
const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool: ToolEntry) {
if (tool.type === "tool") return yield* lowerTool(tool)
// OpenAI requires a namespace description; fall back to a generic one so a
// missing description never blocks the request.
@@ -237,10 +238,10 @@ const checkpointBody = {
}),
}
const hostedToolResult = Effect.fnUntraced(function* (item: ResponsesHostedTools.Item) {
const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item: ResponsesHostedTools.Item) {
const isError = item.error !== undefined && item.error !== null
if (item.type === "image_generation_call" && item.result) {
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
yield* Effect.fromResult(Base64.decode(item.result)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64", undefined, cause),
),
+1 -1
View File
@@ -94,7 +94,7 @@ const fromRequest = Effect.fn("OpenAISpeech.fromRequest")(function* (request: Me
const isSse = (body: MediaProtocol.Body) => body.type === "json" && body.value.stream_format === "sse"
const onEvent = Effect.fnUntraced(function* (state: State, frame: string) {
const onEvent = Effect.fn("OpenAISpeech.onEvent")(function* (state: State, frame: string) {
const event = yield* decodeEvent(frame)
if (event.type === "speech.audio.delta") return SpeechStream.delta(state, event.audio)
const usage = event.usage
@@ -210,7 +210,7 @@ const segment = (value: Schema.Schema.Type<typeof Segment>): TranscriptionSegmen
speaker: value.speaker,
})
const onEvent = Effect.fnUntraced(function* (state: State, frame: string) {
const onEvent = Effect.fn("OpenAITranscription.onEvent")(function* (state: State, frame: string) {
if (!EVENT_TYPES.has((yield* decodeEventType(frame)).type)) return [state, []] as const
const event = yield* decodeEvent(frame)
if (event.type === "error")
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+3 -3
View File
@@ -1,6 +1,6 @@
import { Tool } from "@opencode/schema/tool"
import { Effect, Option, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import { Media } from "../media.js"
import {
InvalidProviderOutputError,
@@ -154,7 +154,7 @@ export const wrapSystemUpdate = (parts: ReadonlyArray<{ readonly text: string }>
* raw retrieved, tool, or web content into privileged updates: keep untrusted
* data in ordinary user/tool messages instead.
*/
export const systemUpdateText = Effect.fnUntraced(function* (
export const systemUpdateText = Effect.fn("ProviderShared.systemUpdateText")(function* (
route: string,
message: LLMRequest["messages"][number],
) {
@@ -167,7 +167,7 @@ export const systemUpdateText = Effect.fnUntraced(function* (
})
/** Lower an unsupported privileged update into visible, in-order user text. */
export const wrappedSystemUpdate = Effect.fnUntraced(function* (
export const wrappedSystemUpdate = Effect.fn("ProviderShared.wrappedSystemUpdate")(function* (
route: string,
message: LLMRequest["messages"][number],
) {
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
@@ -82,7 +82,7 @@ const endpoint = (model: string) => (model.startsWith("sd3") ? "sd3" : model)
const RESERVED_FORM_FIELDS = new Set(["image", "prompt", "mode", "model"])
const form = Effect.fnUntraced(function* (
const form = Effect.fn("StabilityImages.form")(function* (
identity: MediaProtocol.Identity,
fields: Record<string, unknown>,
native: Record<string, unknown> | undefined,
@@ -1,6 +1,6 @@
import { AwsV4Signer } from "aws4fetch"
import { Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Auth, type AuthInput } from "../../route/auth.js"
import { AIError, AuthenticationError } from "../../schema/index.js"
import { ProviderShared } from "../shared.js"
@@ -1,4 +1,5 @@
import { Effect, Encoding, Schema } from "effect"
import { Effect, Schema } from "effect"
import { Base64 } from "effect/encoding"
import type { MediaPart } from "../../schema/index.js"
import { ProviderShared } from "../shared.js"
@@ -76,14 +77,14 @@ function documentName(filename: string | undefined, names: Set<string>) {
return name
}
const mediaBase64 = Effect.fnUntraced(function* (part: MediaPart) {
const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part: MediaPart) {
const media = yield* ProviderShared.requireInlineMedia("Bedrock Converse", part.media)
const bytes = yield* Effect.fromResult(Encoding.decodeBase64(media.base64)).pipe(
const bytes = yield* Effect.fromResult(Base64.decode(media.base64)).pipe(
Effect.mapError((cause) =>
ProviderShared.invalidRequest("Bedrock Converse media data must be valid base64", cause),
),
)
return Encoding.encodeBase64(bytes)
return Base64.encode(bytes)
})
// Route by MIME. Known image/document formats lower into a typed block; anything
@@ -91,7 +92,7 @@ const mediaBase64 = Effect.fnUntraced(function* (part: MediaPart) {
// document block. Image MIME types not in `IMAGE_FORMATS` (e.g. `image/svg+xml`)
// get an image-specific error so the caller knows it's a format-support issue,
// not a kind-detection issue.
export const lower = Effect.fnUntraced(function* (part: MediaPart, documentNames: Set<string>) {
export const lower = Effect.fn("BedrockMedia.lower")(function* (part: MediaPart, documentNames: Set<string>) {
const mime = part.media.mediaType.toLowerCase()
const imageFormat = IMAGE_FORMATS[mime as keyof typeof IMAGE_FORMATS]
if (imageFormat) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../../generation.js"
import type { Media } from "../../media.js"
import { MediaProtocol } from "../../route/media-protocol.js"
@@ -1,4 +1,5 @@
import { Effect, Encoding } from "effect"
import { Effect } from "effect"
import { Base64 } from "effect/encoding"
import { Media } from "../../media.js"
import type { MediaProtocol } from "../../route/media-protocol.js"
import { mergeJsonRecords, type AIError, type ProviderID } from "../../schema/index.js"
@@ -10,7 +11,7 @@ export const inlineBytes = (route: string, asset: Media.Asset): Effect.Effect<Ui
if (asset.source.type === "bytes") return Effect.succeed(asset.source.data)
const inline = asset.inline()
if (!inline) return Effect.fail(ProviderShared.inlineRequired(route, asset))
return Effect.fromResult(Encoding.decodeBase64(inline.base64)).pipe(
return Effect.fromResult(Base64.decode(inline.base64)).pipe(
Effect.mapError((cause) => ProviderShared.invalidRequest(`${route} media contains invalid base64 data`, cause)),
)
}
@@ -52,7 +53,7 @@ export const decodedAsset = (
mediaType: string | undefined,
options?: Media.AssetOptions,
) =>
Effect.fromResult(Encoding.decodeBase64(data)).pipe(
Effect.fromResult(Base64.decode(data)).pipe(
Effect.mapError((cause) => invalid(`${label} contains invalid base64 data`, cause)),
Effect.map((bytes) => Media.bytes(bytes, mediaType, options)),
)
@@ -11,7 +11,7 @@ interface State {
readonly responseID?: string
}
const onOutputItem = Effect.fnUntraced(function* (
const onOutputItem = Effect.fn("ResponsesCheckpoint.onOutputItem")(function* (
state: State,
input: OpenResponses.Event,
) {
@@ -63,7 +63,7 @@ export const make = <Body>(body: RouteBody<Body>): TriggerCompactOperation =>
checkpoints: {},
}),
terminal: OpenResponses.terminal,
step: Effect.fnUntraced(function* (state: State, event: OpenResponses.Event) {
step: Effect.fn("ResponsesCheckpoint.step")(function* (state: State, event: OpenResponses.Event) {
if (event.response?.id && state.responseID && event.response.id !== state.responseID)
return yield* ProviderShared.eventError(source.id, "Compaction response ID changed during execution")
if (event.type === "response.created") return [{ ...state, responseID: event.response?.id }, []] as const
@@ -33,7 +33,7 @@ export const onDone: (
state: OpenResponses.ParserState,
item: Item,
tools: Definitions,
) => Effect.Effect<OpenResponses.StepResult, AIError> = Effect.fnUntraced(
) => Effect.Effect<OpenResponses.StepResult, AIError> = Effect.fn("ResponsesHostedTools.onDone")(
function* (state, item, tools) {
const tool = tools[item.type]
if (!tool) return [state, []] satisfies OpenResponses.StepResult
+8 -15
View File
@@ -60,21 +60,14 @@ const inputStart = (tool: PendingTool) =>
providerMetadata: tool.providerMetadata,
})
const inputDelta = (tool: PendingTool, text: string): LLMEvent => {
const raw = tool.input
let parsed: unknown
return {
...LLMEvent.toolInputDelta({
id: tool.id,
name: tool.name,
namespace: tool.namespace,
text,
}),
get input() {
return (parsed ??= Option.getOrElse(parsePartialInput(raw), () => ({})))
},
}
}
const inputDelta = (tool: PendingTool, text: string) =>
LLMEvent.toolInputDelta({
id: tool.id,
name: tool.name,
namespace: tool.namespace,
text,
input: Option.getOrElse(parsePartialInput(tool.input), () => ({})),
})
const toolCall = (route: string, tool: PendingTool, inputOverride?: string) => {
const raw = inputOverride ?? tool.input
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import type { Status } from "../generation.js"
import { Media } from "../media.js"
import { MediaProtocol } from "../route/media-protocol.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schema } from "effect"
import type { HttpClientResponse } from "effect/unstable/http"
import type { HttpClientResponse } from "effect/http"
import { ImageModel, ImageResponse, type ImageRequestFor } from "../image.js"
import { MediaProtocol } from "../route/media-protocol.js"
import { MediaRoute } from "../route/media.js"
@@ -1,20 +1,15 @@
import { Route, type RouteDefaultsInput } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import type { ProviderPackage } from "../provider-package.js"
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { OpenResponses } from "../protocols/open-responses.js"
import { BedrockAuth, type Credentials } from "../protocols/utils/bedrock-auth.js"
import { claudeVersion } from "../protocols/utils/claude-model.js"
import { ProviderConfigurationError, ProviderID, type ModelID } from "../schema/index.js"
import { withOpenAIOptions, type OpenAIProviderOptionsInput } from "./openai-options.js"
export const id = ProviderID.make("amazon-bedrock")
export type OpenAIOptionsInput = OpenAIProviderOptionsInput
export type MessagesOptionsInput = AnthropicMessages.ProviderOptionsInput
export type Config = Omit<RouteDefaultsInput, "providerOptions"> & {
export type Config = RouteDefaultsInput & {
/** Bedrock API key. Falls back to `AWS_BEARER_TOKEN_BEDROCK`; bearer auth takes precedence over SigV4. */
readonly apiKey?: string
/** `sigv4` ignores `apiKey` fallbacks from the environment; `bearer` requires a token. */
@@ -25,11 +20,11 @@ export type Config = Omit<RouteDefaultsInput, "providerOptions"> & {
/** Shared config profile for the default credential chain. */
readonly profile?: string
readonly region?: string
readonly providerOptions?: OpenAIProviderOptionsInput | AnthropicMessages.ProviderOptionsInput
readonly providerOptions?: OpenAIProviderOptionsInput
}
export type Settings<Options = OpenAIProviderOptionsInput> = ProviderPackage.Settings &
Options & {
export type Settings = ProviderPackage.Settings &
OpenAIProviderOptionsInput & {
readonly apiKey?: string
readonly auth?: "bearer" | "sigv4"
readonly baseURL?: string
@@ -39,8 +34,6 @@ export type Settings<Options = OpenAIProviderOptionsInput> = ProviderPackage.Set
readonly topP?: number
}
export type MessagesSettings = Settings<AnthropicMessages.ProviderOptionsInput>
const responsesRoute = Route.make({
id: "bedrock-mantle-responses",
provider: id,
@@ -57,35 +50,12 @@ const chatRoute = OpenAIChat.route.with({
providerMetadataKey: "mantle",
})
const messagesRoute = Route.make({
id: "bedrock-mantle-messages",
provider: id,
providerMetadataKey: "mantle",
protocol: {
...AnthropicMessages.protocol,
// Mantle rejects mid-conversation `output_config` on Opus 5.0; support starts at 5.1+.
supportsEffortUpdates: (request) => {
const override = request.model.compatibility?.supportsEffortUpdates
if (override !== undefined) return override
const version = claudeVersion(request.model.id)
return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1))
},
},
endpoint: Endpoint.path(AnthropicMessages.PATH),
transport: AnthropicMessages.transport<AnthropicMessages.AnthropicMessagesBody>(),
headers: () => ({ "anthropic-version": "2023-06-01" }),
})
export const routes = [responsesRoute, chatRoute]
export const routes = [responsesRoute, chatRoute, messagesRoute]
const configuredRoute = <Body, Prepared>(
route: Route<Body, Prepared>,
input: Config,
defaultBaseURL = (region: string) => `https://bedrock-mantle.${region}.api.aws/v1`,
) => {
const configuredRoute = <Body, Prepared>(route: Route<Body, Prepared>, input: Config) => {
const region = BedrockAuth.resolveRegion(input)
return route.with({
endpoint: { baseURL: input.baseURL ?? defaultBaseURL(region) },
endpoint: { baseURL: input.baseURL ?? `https://bedrock-mantle.${region}.api.aws/v1` },
auth: BedrockAuth.resolveAuth(input, region, {
service: "bedrock-mantle",
name: "Bedrock Mantle",
@@ -117,11 +87,6 @@ export const configure = (input: Config = {}) => {
})
const configuredResponsesRoute = configuredRoute(responsesRoute, input)
const configuredChatRoute = configuredRoute(chatRoute, input)
const configuredMessagesRoute = configuredRoute(
messagesRoute,
input,
(region) => `https://bedrock-mantle.${region}.api.aws/anthropic/v1`,
)
const modelDefaults = defaults(input)
const responses = (modelID: string | ModelID) =>
configuredResponsesRoute
@@ -131,14 +96,11 @@ export const configure = (input: Config = {}) => {
configuredChatRoute
.with(withOpenAIOptions(modelID, modelDefaults))
.model<OpenAIProviderOptionsInput>({ id: modelID })
const messages = (modelID: string | ModelID) =>
configuredMessagesRoute.with(modelDefaults).model<AnthropicMessages.ProviderOptionsInput>({ id: modelID })
return {
id,
model: responses,
chat,
messages,
responses,
configure,
}
@@ -157,7 +119,7 @@ const fromSettings = ({
region,
topP,
...providerOptions
}: Settings<Config["providerOptions"]>) =>
}: Settings) =>
configure({
apiKey,
auth,
@@ -175,10 +137,6 @@ export const chatModel: ProviderPackage.Definition<Settings, OpenAIProviderOptio
modelID,
settings,
) => fromSettings(settings).chat(modelID)
export const messagesModel: ProviderPackage.Definition<
MessagesSettings,
AnthropicMessages.ProviderOptionsInput
>["model"] = (modelID, settings) => fromSettings(settings).messages(modelID)
export const responsesModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"] = (
modelID,
settings,
@@ -1,2 +0,0 @@
export { messagesModel as model } from "../../amazon-bedrock-mantle.js"
export type { MessagesSettings as Settings } from "../../amazon-bedrock-mantle.js"
+1 -1
View File
@@ -50,7 +50,7 @@ export const gpt5DefaultOptions = (modelID: string): ProviderOptions | undefined
export const openAIDefaultOptions = (modelID: string): ProviderOptions | undefined =>
mergeProviderOptions(openAIProviderOptions({ store: false }), gpt5DefaultOptions(modelID))
export const withOpenAIOptions = <Options extends { readonly providerOptions?: ProviderOptions }>(
export const withOpenAIOptions = <Options extends { readonly providerOptions?: OpenAIProviderOptionsInput }>(
modelID: string,
options: Options,
): Omit<Options, "providerOptions"> & { readonly providerOptions?: ProviderOptions } => {
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import {
EvaluationAnswer,
EvaluationInput,
+2 -2
View File
@@ -1,5 +1,5 @@
import { Config, Effect, Option, Redacted } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { AuthenticationError, AIError, type HttpOptions } from "../schema/index.js"
export class MissingCredentialError extends Error {
@@ -95,7 +95,7 @@ export const optional = (secret: Secret | undefined, source = "optional value")
export const config = (name: string) =>
credential(
Effect.gen(function* () {
const secret = yield* Config.option(Config.redacted(name))
const secret = yield* Config.option(Config.Redacted(name))
if (Option.isSome(secret) && Redacted.value(secret.value) !== "") return secret.value
return yield* Effect.fail(new MissingCredentialError(name, `${name} is not set`))
}),
+1 -1
View File
@@ -1,5 +1,5 @@
import { Context, type Effect } from "effect"
import type { HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import type { HttpClientRequest, HttpClientResponse } from "effect/http"
import type { AIError } from "../schema/errors.js"
// The service tag lives in its own leaf module so `Media.Asset` (imported by the schema layer) can require the
+1 -1
View File
@@ -6,7 +6,7 @@ import {
HttpClientError,
HttpClientRequest,
HttpClientResponse,
} from "effect/unstable/http"
} from "effect/http"
import { HttpContext, HttpRateLimitDetails, AIError, TransportError } from "../schema/index.js"
import { classifyProviderFailure, providerErrorMessage } from "../provider-error.js"
import { Service, type HttpMiddleware, type Interface } from "./executor-service.js"
+31 -27
View File
@@ -1,5 +1,5 @@
import { Effect, Stream } from "effect"
import { makeParser } from "effect/unstable/encoding/Sse"
import { makeParser, type Event } from "effect/encoding/Sse"
import { AIError, InvalidProviderOutputError } from "../schema/index.js"
/**
@@ -42,39 +42,43 @@ export const sseFraming = (
Stream.decodeText(),
Stream.mapAccumEffect(
() => {
const output: string[] = []
const output: Event[] = []
return {
output,
parser: makeParser((event) => {
if (
event._tag === "Event" &&
(events === undefined || events.has(event.event)) &&
event.data.length > 0 &&
// Some OpenAI-compatible proxies serialize an empty flush as a bare
// `data: null`, between events or after `[DONE]`. No protocol has a
// null event, so it carries nothing and must not abort the stream.
event.data !== "null" &&
// Vertex AI partner models (e.g. `xai/grok-4.6`) send their SSE
// keepalive comment as `data: : keepalive` while reasoning.
event.data !== ": keepalive" &&
(event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message"))
)
output.push(event.data)
if (event._tag === "Event") output.push(event)
}),
}
},
(state, chunk) => {
const error = state.parser.feed(chunk)
if (!error) return Effect.succeed([state, state.output.splice(0)] as const)
const reason = new InvalidProviderOutputError({
route: "sse",
message: error.message,
body: chunk,
cause: error,
})
return Effect.fail(new AIError({ reason }))
},
(state, chunk) =>
Effect.gen(function* () {
const error = state.parser.feed(chunk)
if (error)
return yield* new AIError({
reason: new InvalidProviderOutputError({
route: "sse",
message: error.message,
body: chunk,
cause: error,
}),
})
return [state, state.output.splice(0)] as const
}),
),
Stream.filter(
(event) =>
(events === undefined || events.has(event.event)) &&
event.data.length > 0 &&
// Some OpenAI-compatible proxies serialize an empty flush as a bare
// `data: null`, between events or after `[DONE]`. No protocol has a
// null event, so it carries nothing and must not abort the stream.
event.data !== "null" &&
// Vertex AI partner models (e.g. `xai/grok-4.6`) send their SSE
// keepalive comment as `data: : keepalive` while reasoning.
event.data !== ": keepalive" &&
(event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message")),
),
Stream.map((event) => event.data),
)
/** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
+1 -1
View File
@@ -1,5 +1,5 @@
import { Clock, Duration, Effect, Schema, type Stream } from "effect"
import { HttpClientResponse } from "effect/unstable/http"
import { HttpClientResponse } from "effect/http"
import type { Snapshot, Status } from "../generation.js"
import { Media } from "../media.js"
import type { AuthInput } from "./auth.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Duration, Effect, Schedule, Schema, Stream } from "effect"
import { Headers, HttpClientRequest, type HttpClientResponse } from "effect/unstable/http"
import { Headers, HttpClientRequest, type HttpClientResponse } from "effect/http"
import { Auth, type AuthInput } from "./auth.js"
import { Endpoint } from "./endpoint.js"
import { RequestExecutorService, type Interface } from "./executor-service.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Clock, Duration, Effect, Stream } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import { Auth } from "../auth.js"
import { render as renderEndpoint } from "../endpoint.js"
import { Framing } from "../framing.js"
@@ -1,5 +1,5 @@
import type { Effect, Scope, Stream } from "effect"
import type { Headers } from "effect/unstable/http"
import type { Headers } from "effect/http"
import type { AIError, HttpContext } from "../../schema/index.js"
export interface WebSocketChannelExecutor {
+2 -2
View File
@@ -1,6 +1,6 @@
import { Cause, Effect, Queue, Stream } from "effect"
import { Headers } from "effect/unstable/http"
import { Socket } from "effect/unstable/socket"
import { Headers } from "effect/http"
import { Socket } from "effect/socket"
import {
AIError,
AIErrorReason,
+9 -8
View File
@@ -1,4 +1,5 @@
import { Schema } from "effect"
import { withStatics } from "@opencode/schema/schema"
import { Tool } from "@opencode/schema/tool"
import {
CacheHint,
@@ -28,7 +29,7 @@ export type SystemPart = Schema.Schema.Type<typeof systemPartSchema>
const makeSystemPart = (text: string): SystemPart => ({ type: "text", text })
export const SystemPart = Object.assign(systemPartSchema, {
export const SystemPart = withStatics(systemPartSchema, {
make: makeSystemPart,
content: (input?: string | SystemPart | ReadonlyArray<SystemPart>) => {
if (input === undefined) return []
@@ -76,7 +77,7 @@ const toolResultValueSchema = Schema.Union([
export type ToolResultValue = Schema.Schema.Type<typeof toolResultValueSchema>
const isToolResultValue = Schema.is(toolResultValueSchema)
export const ToolResultValue = Object.assign(toolResultValueSchema, {
export const ToolResultValue = withStatics(toolResultValueSchema, {
is: isToolResultValue,
make: (value: unknown, type: ToolResultValue["type"] = "json"): ToolResultValue => {
if (isToolResultValue(value)) return value
@@ -90,7 +91,7 @@ export interface ToolOutput {
readonly content: ReadonlyArray<Tool.Content>
}
export const ToolOutput = Object.assign(
export const ToolOutput = withStatics(
Schema.Struct({
structured: Schema.Unknown,
content: Schema.Array(Tool.Content),
@@ -127,7 +128,7 @@ const toolResultText = (value: unknown) => {
}
}
export const ToolCallPart = Object.assign(
export const ToolCallPart = withStatics(
Schema.Struct({
type: Schema.Literal("tool-call"),
id: Schema.String,
@@ -145,7 +146,7 @@ export const ToolCallPart = Object.assign(
)
export type ToolCallPart = Schema.Schema.Type<typeof ToolCallPart>
export const ToolResultPart = Object.assign(
export const ToolResultPart = withStatics(
Schema.Struct({
type: Schema.Literal("tool-result"),
id: Schema.String,
@@ -209,7 +210,7 @@ const compactionPartSchema = Schema.Struct({
)
.annotate({ identifier: "LLM.Content.Compaction" })
export type CompactionPart = typeof compactionPartSchema.Type
export const CompactionPart = Object.assign(compactionPartSchema, {
export const CompactionPart = withStatics(compactionPartSchema, {
make: (input: Omit<CompactionPart, "type" | "encrypted" | "text"> & CompactionContent): CompactionPart =>
Schema.decodeUnknownSync(compactionPartSchema)({ type: "compaction", ...input }),
})
@@ -326,7 +327,7 @@ export type ToolNamespaceEntryInput = ToolNamespaceInput & { readonly type: "nam
export const ToolNamespace: Schema.Codec<ToolNamespace> & {
readonly make: (input: ToolNamespace | ToolNamespaceInput) => ToolNamespace
} = Object.assign(
} = withStatics(
Schema.Struct({
type: Schema.Literal("namespace"),
name: Schema.String,
@@ -346,7 +347,7 @@ export type ToolEntry = ToolDefinition | ToolNamespace
export type ToolEntryInput = ToolDefinition.Input | ToolNamespaceEntryInput
export const ToolEntry: Schema.Codec<ToolEntry> & {
readonly make: (input: ToolEntryInput) => ToolEntry
} = Object.assign(
} = withStatics(
Schema.Union([ToolDefinition, ToolNamespace]).pipe(
Schema.toTaggedUnion("type"),
Schema.annotate({ identifier: "LLM.ToolEntry" }),
+32 -3
View File
@@ -1,4 +1,4 @@
import { Effect, JsonSchema, Schema } from "effect"
import { Effect, JsonSchema, Schema, SchemaAST } from "effect"
import { Tool } from "@opencode/schema/tool"
import type {
ToolCallPart,
@@ -6,6 +6,7 @@ import type {
ToolOutput as ToolOutputType,
} from "./schema/index.js"
import { ToolDefinition, ToolFailure, ToolOutput } from "./schema/index.js"
import { isRecord } from "./utils/record.js"
/**
* Schema constraint for tool parameters / success values: no decoding or
@@ -200,7 +201,7 @@ export function make(config: TypedToolConfig | DynamicToolConfig): AnyTool {
_definition: new ToolDefinition({
name: "",
description: config.description,
inputSchema: toJsonSchema(config.parameters),
inputSchema: emptyInputJsonSchema(config.parameters) ?? toJsonSchema(config.parameters),
outputSchema: toJsonSchema(config.success),
}),
}
@@ -230,8 +231,36 @@ export const toDefinitions = (tools: Tools): ReadonlyArray<ToolDefinitionClass>
}),
)
/**
* Describes a no-argument Effect tool input as an empty object, or returns
* `undefined` when the input takes arguments.
*
* Effect emits `{ not: { type: "null" } }` for `Struct({})`, but providers
* require tool parameters to describe an object. Emptiness is decided by the
* encoded side, which is what the model sends, so a transformation from an
* empty struct qualifies while one into an empty struct does not. The schema is
* inlined rather than a root `$ref`, and the title, description, and other
* metadata Effect emits are retained. Checked encoded structs are left as
* Effect emits them, because their constraints or JSON Schema overrides could
* contradict the closed object shape.
*/
export const emptyInputJsonSchema = (schema: Schema.Top): JsonSchema.JsonSchema | undefined => {
const encoded = SchemaAST.toEncoded(schema.ast)
if (
!SchemaAST.isObjects(encoded) ||
encoded.propertySignatures.length > 0 ||
encoded.indexSignatures.length > 0 ||
encoded.checks !== undefined
)
return undefined
const { not, ...metadata } = Schema.toJsonSchemaDocument(schema, { referencePolicy: () => undefined }).schema
// Rewrite only the shape Effect is known to emit for an empty struct.
if (!isRecord(not) || not.type !== "null" || Object.keys(not).length !== 1) return undefined
return { ...metadata, type: "object", properties: {}, additionalProperties: false }
}
const toJsonSchema = (schema: Schema.Top): JsonSchema.JsonSchema => {
const document = Schema.toJsonSchemaDocument(schema)
const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: "error" })
if (Object.keys(document.definitions).length === 0) return document.schema
return { ...document.schema, $defs: document.definitions }
}
+1 -1
View File
@@ -29,7 +29,7 @@ type LanguageModel = {
declare const auth: Auth.Definition
declare const optionalAuthModel: LanguageModelFactory<BaseOptions, "optional", LanguageModel>
declare const requiredAuthModel: LanguageModelFactory<BaseOptions, "required", LanguageModel>
const configApiKey = Config.redacted("OPENAI_API_KEY")
const configApiKey = Config.Redacted("OPENAI_API_KEY")
OpenAIChat.route.model({ id: "gpt-4.1-mini" })
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { LLM } from "../src/index.js"
import { Auth } from "../src/route/auth.js"
import * as OpenAIChat from "../src/protocols/openai-chat.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { Effect } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { FetchHttpClient } from "effect/http"
import { LLM, LLMRequest, Message } from "../src/index.js"
import { LLMClient } from "../src/route/client.js"
import { OpenAI } from "../src/providers.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect, Ref, Schema } from "effect"
import { HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { HttpClientRequest, HttpClientResponse } from "effect/http"
import {
LLM,
LLMRequest,
+1 -26
View File
@@ -6,7 +6,7 @@ import { compileRequest } from "../src/route/client.js"
import { AnthropicMessages } from "../src/protocols/anthropic-messages.js"
import { OpenAIResponses } from "../src/protocols/openai-responses.js"
import { Gemini } from "../src/protocols/gemini.js"
import { AmazonBedrockMantle, GoogleVertexMessages, OpenAI } from "../src/providers.js"
import { GoogleVertexMessages, OpenAI } from "../src/providers.js"
import { applyEffortUpdates } from "../src/effort-updates.js"
import { it, testEffect } from "./lib/effect.js"
import { dynamicResponse } from "./lib/http.js"
@@ -241,31 +241,6 @@ describe("Anthropic Messages effort updates", () => {
}),
)
it.effect("strips markers for Opus 5.0 on Bedrock Mantle Messages while lowering Opus 5.5", () =>
Effect.gen(function* () {
const mantle = AmazonBedrockMantle.configure({ apiKey: "test", region: "us-east-1" })
const opus50 = yield* compileRequest(
LLM.request({
model: mantle.messages("anthropic.claude-opus-5"),
messages: conversation,
providerOptions: { effort: "low" },
}),
)
const opus55 = yield* compileRequest(
LLM.request({
model: mantle.messages("anthropic.claude-opus-5-5"),
messages: conversation,
providerOptions: { effort: "low" },
}),
)
expect(systemMessages(opus50.body)).toHaveLength(0)
expect(opus50.body.output_config).toEqual({ effort: "low" })
expect(systemMessages(opus55.body)).toEqual([{ role: "system", content: [], output_config: { effort: "low" } }])
expect(opus55.body.output_config).toEqual({ effort: "high" })
}),
)
it.effect("strips markers on the Vertex Anthropic route, whose protocol wrapper does not forward support", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { Evaluation, EvaluationClient } from "../src/experimental.js"
import { OpenCodeZen, OpenRouter, TypeSafeAI, VercelAIGateway } from "../src/providers.js"
import { it } from "./lib/effect.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Deferred, Effect, Fiber, Layer, Ref, Stream } from "effect"
import { Headers, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { Headers, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/http"
import { LLM, AIError, HttpContext, InvalidProviderOutputError, TransportError, isRetryable } from "../src/index.js"
import {
LLMClient,
@@ -1,57 +0,0 @@
{
"version": 1,
"metadata": {
"model": "anthropic.claude-opus-5-5",
"tags": [
"prefix:bedrock-mantle-messages",
"provider:amazon-bedrock",
"protocol:anthropic-messages",
"reasoning",
"effort-update"
],
"name": "bedrock-mantle-messages/applies-mid-conversation-effort-updates-and-thinking-block-binding-on-opus-5-5",
"recordedAt": "2026-10-04T03:56:23.139Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
"headers": {
"anthropic-beta": "interleaved-thinking-2025-05-14,thinking-binding-controls-2026-08-01",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
"body": "{\"model\":\"anthropic.claude-opus-5-5\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Compute 37 * 43 step by step, then reply with only the integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"adaptive\",\"display\":\"summarized\",\"block_binding\":{\"prefix_mismatch_behavior\":\"drop_block\"}},\"output_config\":{\"effort\":\"high\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-opus-5-5\",\"id\":\"msg_bdrk_y7h2x5bz6w6ggmekuxekbi6rseptglsljkwgj36phisnsnu3syea\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"container\":null,\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":31,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":2,\"service_tier\":\"standard\"},\"input_transformations\":[]}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"They\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" want\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" only\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" the final\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" integer, so\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" I just\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" need\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" to compute 37\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"×43 \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"using the dif\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"ference of squares: 40\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"²−3²\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" = 1600−9 =\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 1591.\\n\\n\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"CAQSogUKEggSEAEYAjgBQgh0aGlua2luZxIM48GlQ7IvNDW3jk2BGgw0Aq5cIs+m8gjYEIQiMFOyG984KYDLRzchzn2ApX6Rs3r7+CtXL0KeCRsXr1XKY3Y0UNK73Qrjyk94TGFPTSq9BCCZALbMiaeiIDqgB3H8jfrU26rP6kORoTJuF84ByBhXGI9AGo30c3RU2ZueXE/oLvRkK12g8r3TPql2fam1gcC/SeZ627fqGykV6G8WrPH7kthjbR45vnRuaqgFZmyA4C/XCxEH0zSSlVAhXf3Um9sjO/RD8lMJttQCAJSurbB5wmKbUQmZeOjIn8augHV/KmsGcFaHBjGLNfnwfEUwLEbRjNh5eIsmcs9dFfIeYHYNRgtLnBkERACoKPQLp2xU090/wCwrFHIGgLYbklR5wUhGqrAERhOAybqMxlNN3dwd7dV5Tbd16xEghDWb6wUtoxnNQdsUsV/0F/b8Ov9TSu93db/QJAkR74OBMtPuEu914SxtC4NBtqX5YZQAo//O4piaQGzFRVE7TahKzMS5bD111w/qz/cLpqEI/bZMqsXKdzBuMMOAox9TvgTumemdiiPu8tn2ONQeAnuNLjArrr7Jz1mbE9GqPl6aJW9HYWPb0BxCQ/hRXFz5d0v+okmmn5KCV2nD55wk2ySWz9OxIMQUsJ6uEUVOZPk7O0ZJ2ppoLAx6GYq2f55nNcUCsbKB4gB4+QlNp3Tf5IemDdZaY/tGW71dgpYI7iWYYEhqNtlC8pXpoEIbO31IvAHS9BGeQRYQQh1h0muEz4S1+YICPTdXnUmjc0uJ9MGBIKOuBsUhg+E4dzZzOfwS4KteNmJgjhj53WVdjJgRslemZle0tZphrTLLgvZpLZDVIYmOkIpbo2tf5Cflx/rf/NPg7RgB\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"1591\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"container\":null},\"usage\":{\"input_tokens\":31,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":86,\"output_tokens_details\":{\"thinking_tokens\":82}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
"headers": {
"anthropic-beta": "interleaved-thinking-2025-05-14,mid-conversation-output-config-2026-07-01,thinking-binding-controls-2026-08-01",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
"body": "{\"model\":\"anthropic.claude-opus-5-5\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Compute 37 * 43 step by step, then reply with only the integer.\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"They want only the final integer, so I just need to compute 37×43 using the difference of squares: 40²−3² = 1600−9 = 1591.\\n\\n\",\"signature\":\"CAQSogUKEggSEAEYAjgBQgh0aGlua2luZxIM48GlQ7IvNDW3jk2BGgw0Aq5cIs+m8gjYEIQiMFOyG984KYDLRzchzn2ApX6Rs3r7+CtXL0KeCRsXr1XKY3Y0UNK73Qrjyk94TGFPTSq9BCCZALbMiaeiIDqgB3H8jfrU26rP6kORoTJuF84ByBhXGI9AGo30c3RU2ZueXE/oLvRkK12g8r3TPql2fam1gcC/SeZ627fqGykV6G8WrPH7kthjbR45vnRuaqgFZmyA4C/XCxEH0zSSlVAhXf3Um9sjO/RD8lMJttQCAJSurbB5wmKbUQmZeOjIn8augHV/KmsGcFaHBjGLNfnwfEUwLEbRjNh5eIsmcs9dFfIeYHYNRgtLnBkERACoKPQLp2xU090/wCwrFHIGgLYbklR5wUhGqrAERhOAybqMxlNN3dwd7dV5Tbd16xEghDWb6wUtoxnNQdsUsV/0F/b8Ov9TSu93db/QJAkR74OBMtPuEu914SxtC4NBtqX5YZQAo//O4piaQGzFRVE7TahKzMS5bD111w/qz/cLpqEI/bZMqsXKdzBuMMOAox9TvgTumemdiiPu8tn2ONQeAnuNLjArrr7Jz1mbE9GqPl6aJW9HYWPb0BxCQ/hRXFz5d0v+okmmn5KCV2nD55wk2ySWz9OxIMQUsJ6uEUVOZPk7O0ZJ2ppoLAx6GYq2f55nNcUCsbKB4gB4+QlNp3Tf5IemDdZaY/tGW71dgpYI7iWYYEhqNtlC8pXpoEIbO31IvAHS9BGeQRYQQh1h0muEz4S1+YICPTdXnUmjc0uJ9MGBIKOuBsUhg+E4dzZzOfwS4KteNmJgjhj53WVdjJgRslemZle0tZphrTLLgvZpLZDVIYmOkIpbo2tf5Cflx/rf/NPg7RgB\"},{\"type\":\"text\",\"text\":\"1591\"}]},{\"role\":\"system\",\"content\":[],\"output_config\":{\"effort\":\"low\"}},{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Add 9 to that result. Reply with only the integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"adaptive\",\"display\":\"summarized\",\"block_binding\":{\"prefix_mismatch_behavior\":\"drop_block\"}},\"output_config\":{\"effort\":\"high\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-opus-5-5\",\"id\":\"msg_bdrk_yzpmbqh3aag3i7kymdm7ctqputcpeuln3cy44msgbdnctkcweena\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"container\":null,\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":139,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":4,\"service_tier\":\"standard\"},\"input_transformations\":[]}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"1600\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"container\":null},\"usage\":{\"input_tokens\":139,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":4,\"output_tokens_details\":{\"thinking_tokens\":0}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -1,37 +0,0 @@
{
"version": 1,
"metadata": {
"model": "anthropic.claude-haiku-4-5",
"tags": [
"prefix:bedrock-mantle-messages",
"provider:amazon-bedrock",
"protocol:anthropic-messages",
"reasoning",
"system-update"
],
"name": "bedrock-mantle-messages/lowers-system-updates-to-wrapped-user-text-on-haiku-4-5-with-budget-thinking",
"recordedAt": "2026-10-04T03:56:18.874Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
"headers": {
"anthropic-beta": "interleaved-thinking-2025-05-14",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
"body": "{\"model\":\"anthropic.claude-haiku-4-5\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 19 multiplied by 23?\"},{\"type\":\"text\",\"text\":\"<system-update>\\nReply with only the integer.\\n</system-update>\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"enabled\",\"budget_tokens\":1024}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-haiku-4-5-20251001\",\"id\":\"msg_bdrk_prlwlyj7mdyfbm6ejq74ibwylnwd6deibs6j5xjq6lsc4dzuyzqa\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"container\":null,\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":65,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"19\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" × 23 = ?\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n\\nLet me calculate\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" this:\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n19 × 23 =\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 19 × (20 +\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 3)\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n= 19 × 20\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" + 19 × 3\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n= 380 + 57\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n= 437\\n\\nOr\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" alternatively\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\":\\n19 × 23 \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"= (\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"20 - 1) × \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"23\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n= 20 × 23 \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"- 23\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n= 460 - 23\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n= 437\\n\\nSo\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" the\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" answer is 437.\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"ErEDCoQBCBIQARgCKkBdVqTX3D5rRVz4O3SlBHJaAUb4Jv4CxOT9igcrHKXsDtU6Wwh9dw7QJbtvpCA0UJh0Wi0PCzu1Tlp8W8jmIU9wMhljbGF1ZGUtaGFpa3UtNC01LTIwMjUxMDAxOABCCHRoaW5raW5nWgwyMzI3NzE4NTY3ODGoAeKch9YGEgyP1xZ80Q63y4Uko9EaDHZo9a+qBGcqeBRLcSIw5/J5NpbvXm6aIqCN58pfjKvW9/35kh6B+t2jS/7dssFzks6J4m+5jtG7BRf9x65GKtkBOhxxxMXrJDYW8RJkXU9iKampxOVFPJuSrvSw43H1MF4V/dt2RFIXeKMNmIJ4umr4nTNVtSlaIEUt+uYmT/F0QL4wXB1Pd40e4VMcBqkJ3CmHkspPJgkyLursJIpDVJQ4TMCHDBzvEObimdAgoAzHM8ahMVOzb9nW9C7ZGE7OFinrq/uR+AzXR0N4zyBIenjcDF76n+OFm3ap3S7ngBQl6+2enaRiseO/UZ4UzgrYYkxk/V4oobGytu64Dn+ngeceb7P+uszDG8BsDs3zXWQdi2w56OvD6HGFVhgB\"}}\n\nevent: content_block_stop\ndata: {Line truncated
}
}
]
}
@@ -1,59 +0,0 @@
{
"version": 1,
"metadata": {
"model": "anthropic.claude-opus-4-8",
"tags": [
"prefix:bedrock-mantle-messages",
"provider:amazon-bedrock",
"protocol:anthropic-messages",
"tool",
"tool-loop",
"reasoning",
"system-update"
],
"name": "bedrock-mantle-messages/replays-signed-thinking-through-a-tool-loop-and-native-system-update",
"recordedAt": "2026-10-04T04:05:26.433Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
"headers": {
"anthropic-beta": "interleaved-thinking-2025-05-14",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
"body": "{\"model\":\"anthropic.claude-opus-4-8\",\"system\":[{\"type\":\"text\",\"text\":\"You are a concise assistant.\",\"cache_control\":{\"type\":\"ephemeral\"}}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"First calculate 37 * 43. Then call get_weather for Paris. After receiving the tool result, state both the product and the weather.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false},\"cache_control\":{\"type\":\"ephemeral\"}}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"adaptive\",\"display\":\"summarized\"},\"output_config\":{\"effort\":\"medium\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-opus-4-8\",\"id\":\"msg_bdrk_fycfxoffsnlt6mj5wu2urbd2psp276vwn4pddgbaazfim6fxcvhq\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"container\":null,\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":439,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":2,\"service_tier\":\"standard\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"37\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" times\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 43\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" is 1591\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\", that\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" one\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"'s\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" quick to do\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" my\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"self. Now I'll go\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" ahead and call get\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"_weather.\\n\\n\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"EqoDCngIEhABGAIqQIGpXRhzl8JEPJWxprG5sxaT1WjZ8/kEJgxZTN2qtzM5uC63GiumUjsHzfEIJikrldkFbwPbc0enJCXIoLr0G5UyDWNsYXVkZS1xdWluY2U4AEIIdGhpbmtpbmdaDDIzMjc3MTg1Njc4MagBhaGH1gYSDI7iCpVUnPgOaCirJBoMR20SZNRICFBH7f84IjDDaA7qNPHUlJN+eQa/aYqKn6Z2LobkjfWtscoBJs9sjR8qTy477EOozPy6lTkt1Pwq3wFcgWmJHdh5a0V2iRnSN4CtMEwxVPqC2im7vDCJDpNeAld8VmAO0u594guMJca+jme4lM/MW5oy3WrkmcL6Uc+qRByhUXhSqTWra8w9Xxf5S2nbPcT+FXjx1z1oDR1I8SIMzZrUM1DGOtQp7L/oWPadGwJYZ9EqKf9beRmSoc23kJKF5eA6+ZE9+HbS5gG1XxMDRFvx5rETS+mYMamOsfdowxWGWP7TzqHMhv+VGSCXoy2IXEIPBkXwen0K+dSAtgy/t21chzEmsGlBdBrAn7Ab0rCSqHvbZNZuA2T8CFPtGAE=\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"37 × 43 \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"= 1591.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":2,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_bdrk_011We3Z1qCBXge7JLoyNwDat\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\": \\\"Pa\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ris\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":2}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null,\"container\":null},\"usage\":{\"input_tokens\":439,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":135,\"output_tokens_details\":{\"thinking_tokens\":73}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
"headers": {
"anthropic-beta": "interleaved-thinking-2025-05-14",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
"body": "{\"model\":\"anthropic.claude-opus-4-8\",\"system\":[{\"type\":\"text\",\"text\":\"You are a concise assistant.\",\"cache_control\":{\"type\":\"ephemeral\"}}],\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"First calculate 37 * 43. Then call get_weather for Paris. After receiving the tool result, state both the product and the weather.\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"37 times 43 is 1591, that one's quick to do myself. Now I'll go ahead and call get_weather.\\n\\n\",\"signature\":\"EqoDCngIEhABGAIqQIGpXRhzl8JEPJWxprG5sxaT1WjZ8/kEJgxZTN2qtzM5uC63GiumUjsHzfEIJikrldkFbwPbc0enJCXIoLr0G5UyDWNsYXVkZS1xdWluY2U4AEIIdGhpbmtpbmdaDDIzMjc3MTg1Njc4MagBhaGH1gYSDI7iCpVUnPgOaCirJBoMR20SZNRICFBH7f84IjDDaA7qNPHUlJN+eQa/aYqKn6Z2LobkjfWtscoBJs9sjR8qTy477EOozPy6lTkt1Pwq3wFcgWmJHdh5a0V2iRnSN4CtMEwxVPqC2im7vDCJDpNeAld8VmAO0u594guMJca+jme4lM/MW5oy3WrkmcL6Uc+qRByhUXhSqTWra8w9Xxf5S2nbPcT+FXjx1z1oDR1I8SIMzZrUM1DGOtQp7L/oWPadGwJYZ9EqKf9beRmSoc23kJKF5eA6+ZE9+HbS5gG1XxMDRFvx5rETS+mYMamOsfdowxWGWP7TzqHMhv+VGSCXoy2IXEIPBkXwen0K+dSAtgy/t21chzEmsGlBdBrAn7Ab0rCSqHvbZNZuA2T8CFPtGAE=\"},{\"type\":\"text\",\"text\":\"37 × 43 = 1591.\"},{\"type\":\"tool_use\",\"id\":\"toolu_bdrk_011We3Z1qCBXge7JLoyNwDat\",\"name\":\"get_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"toolu_bdrk_011We3Z1qCBXge7JLoyNwDat\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperatureC\\\":18}\"}]},{\"role\":\"system\",\"content\":[{\"type\":\"text\",\"text\":\"Reply in French in one short sentence.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false},\"cache_control\":{\"type\":\"ephemeral\"}}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"adaptive\",\"display\":\"summarized\"},\"output_config\":{\"effort\":\"medium\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-opus-4-8\",\"id\":\"msg_bdrk_uak5dibczxblk4xunblaiowmid7zhky7ozwinjhpjtcbtgzvuwyq\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"container\":null,\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":611,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":3,\"service_tier\":\"standard\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Le produ\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"it est \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"1591 \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"et il\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" f\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ait beau à Paris av\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"ec 18°C.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"container\":null},\"usage\":{\"input_tokens\":611,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":28,\"output_tokens_details\":{\"thinking_tokens\":0}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -1,37 +0,0 @@
{
"version": 1,
"metadata": {
"model": "anthropic.claude-opus-5",
"tags": [
"prefix:bedrock-mantle-messages",
"provider:amazon-bedrock",
"protocol:anthropic-messages",
"reasoning",
"effort-update"
],
"name": "bedrock-mantle-messages/strips-unsupported-mid-conversation-effort-updates-on-opus-5-0",
"recordedAt": "2026-10-04T03:56:23.956Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
"headers": {
"anthropic-beta": "interleaved-thinking-2025-05-14",
"anthropic-version": "2023-06-01",
"content-type": "application/json"
},
"body": "{\"model\":\"anthropic.claude-opus-5\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 12 + 30?\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"42\"}]},{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Add 8 to that result. Reply with only the integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"adaptive\",\"display\":\"summarized\"},\"output_config\":{\"effort\":\"low\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-opus-5\",\"id\":\"msg_bdrk_5mwa5agz3cvj44w72lno46ljoylvzjhipc4jmraridpzxktjquha\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"container\":null,\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":38,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"50\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"container\":null},\"usage\":{\"input_tokens\":38,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":3,\"output_tokens_details\":{\"thinking_tokens\":0}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Layer, Stream } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { Image, ImageClient, Media } from "../src/index.js"
import { BlackForestLabs, Fal, Google, OpenAI, Replicate, Stability, XAI, ZAI } from "../src/providers.js"
import { it } from "./lib/effect.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { Effect, Fiber, Layer, Ref } from "effect"
import * as TestClock from "effect/testing/TestClock"
import { HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/http"
import { LLMClient, RequestExecutor } from "../../src/route.js"
import type { Service as LLMClientService } from "../../src/route/client.js"
import type { Service as RequestExecutorService } from "../../src/route/executor.js"
+1 -1
View File
@@ -2,7 +2,7 @@ import { describe, expect } from "bun:test"
import { NodeFileSystem } from "@effect/platform-node"
import { Effect, Ref, Schema } from "effect"
import { FileSystem } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { AIError, Media, Message } from "../src/index.js"
import { it } from "./lib/effect.js"
import { dynamicResponse, scriptedResponses } from "./lib/http.js"
+1 -1
View File
@@ -3,7 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { Effect, Layer } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { AIError, LLMEvent, Media, SpeechEvent, TranscriptionEvent } from "../src/index.js"
import { RequestExecutor } from "../src/route.js"
import { AI } from "../src/promise.js"
+68
View File
@@ -7,6 +7,67 @@ const configuration = (provider: string, message: string) =>
expect.objectContaining({ _tag: "ProviderConfiguration", provider, message })
describe("provider package entrypoints", () => {
test("semantic API aliases expose the same contract", async () => {
const modules = await Promise.all([
import("@opencode/ai/providers/openai"),
import("@opencode/ai/providers/openai/responses"),
import("@opencode/ai/providers/openai/chat"),
import("@opencode/ai/providers/anthropic"),
import("@opencode/ai/providers/anthropic-compatible"),
import("@opencode/ai/providers/openai-compatible"),
import("@opencode/ai/providers/openai-compatible/responses"),
import("@opencode/ai/providers/amazon-bedrock"),
import("@opencode/ai/providers/azure"),
import("@opencode/ai/providers/azure/responses"),
import("@opencode/ai/providers/azure/chat"),
import("@opencode/ai/providers/google"),
import("@opencode/ai/providers/google-vertex"),
import("@opencode/ai/providers/google-vertex/gemini"),
import("@opencode/ai/providers/google-vertex/chat"),
import("@opencode/ai/providers/google-vertex/responses"),
import("@opencode/ai/providers/google-vertex/messages"),
import("@opencode/ai/providers/openrouter"),
import("@opencode/ai/providers/xai"),
import("@opencode/ai/providers/amazon-bedrock/mantle"),
import("@opencode/ai/providers/amazon-bedrock/mantle/chat"),
import("@opencode/ai/providers/amazon-bedrock/mantle/responses"),
import("@opencode/ai/providers/togetherai"),
import("@opencode/ai/providers/cerebras"),
import("@opencode/ai/providers/deepinfra"),
import("@opencode/ai/providers/groq"),
import("@opencode/ai/providers/baseten"),
import("@opencode/ai/providers/deepseek"),
import("@opencode/ai/providers/fireworks"),
import("@opencode/ai/providers/cloudflare-ai-gateway"),
import("@opencode/ai/providers/cloudflare-workers-ai"),
import("@opencode/ai/providers/minimax"),
import("@opencode/ai/providers/minimax/messages"),
import("@opencode/ai/providers/minimax/chat"),
import("@opencode/ai/providers/minimax/responses"),
import("@opencode/ai/providers/moonshot"),
import("@opencode/ai/providers/moonshot/chat"),
import("@opencode/ai/providers/moonshot/messages"),
import("@opencode/ai/providers/moonshot/responses"),
import("@opencode/ai/providers/zai"),
import("@opencode/ai/providers/zai/chat"),
import("@opencode/ai/providers/zai-coding-plan"),
import("@opencode/ai/providers/zai-coding-plan/chat"),
import("@opencode/ai/providers/zai-coding-plan/messages"),
import("@opencode/ai/providers/zai-coding-plan/responses"),
import("@opencode/ai/providers/alibaba"),
import("@opencode/ai/providers/alibaba/chat"),
import("@opencode/ai/providers/alibaba/messages"),
import("@opencode/ai/providers/alibaba/responses"),
])
for (const module of modules) expect(module.model).toBeFunction()
expect(modules[0].model).toBe(modules[1].model)
expect(modules[8].model).toBe(modules[9].model)
expect(modules[12].model).toBe(modules[13].model)
expect(modules[19].model).toBe(modules[21].model)
expect(modules[19].model).not.toBe(modules[20].model)
})
test("maps Alibaba API entrypoints onto explicit regional routes", async () => {
const modules = await Promise.all([
import("@opencode/ai/providers/alibaba"),
@@ -14,6 +75,7 @@ describe("provider package entrypoints", () => {
import("@opencode/ai/providers/alibaba/messages"),
import("@opencode/ai/providers/alibaba/responses"),
])
expect(modules[0].model).toBe(modules[1].model)
const settings = {
region: "eu-central-1",
workspaceID: "llm-fixture",
@@ -41,6 +103,7 @@ describe("provider package entrypoints", () => {
import("@opencode/ai/providers/moonshot/messages"),
import("@opencode/ai/providers/moonshot/responses"),
])
expect(modules[0].model).toBe(modules[1].model)
const settings = {
apiKey: "fixture",
baseURL: "https://gateway.example/v1",
@@ -84,6 +147,7 @@ describe("provider package entrypoints", () => {
import("@opencode/ai/providers/minimax/chat"),
import("@opencode/ai/providers/minimax/responses"),
])
expect(modules[0].model).toBe(modules[1].model)
const settings = {
apiKey: "fixture",
baseURL: "https://gateway.example/v1",
@@ -110,6 +174,8 @@ describe("provider package entrypoints", () => {
import("@opencode/ai/providers/zai-coding-plan/messages"),
import("@opencode/ai/providers/zai-coding-plan/responses"),
])
expect(modules[0].model).toBe(modules[1].model)
expect(modules[2].model).toBe(modules[3].model)
const routes = [
"zai-chat",
"zai-chat",
@@ -392,6 +458,7 @@ describe("provider package entrypoints", () => {
test("selects Vertex entrypoints with the same model contract", async () => {
const GoogleVertex = await import("@opencode/ai/providers/google-vertex")
const GoogleVertexGemini = await import("@opencode/ai/providers/google-vertex/gemini")
const GoogleVertexChat = await import("@opencode/ai/providers/google-vertex/chat")
const GoogleVertexResponses = await import("@opencode/ai/providers/google-vertex/responses")
const GoogleVertexMessages = await import("@opencode/ai/providers/google-vertex/messages")
@@ -416,6 +483,7 @@ describe("provider package entrypoints", () => {
project: "vertex-project",
})
expect(GoogleVertexGemini.model).toBe(GoogleVertex.model)
expect(gemini.route.id).toBe("google-vertex-gemini")
expect(gemini.route.protocol).toBe("gemini")
expect(gemini.route.endpoint.baseURL).toBe("https://aiplatform.googleapis.com/v1/publishers/google")
+1 -1
View File
@@ -1,6 +1,6 @@
import { expect, test } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Auth, LLM, LLMClient, LLMRequest, Message, ReasoningPart, ToolDefinition } from "../../src/index.js"
import { Alibaba } from "../../src/providers.js"
import { compileRequest } from "../../src/route/client.js"
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import {
CacheHint,
LLM,
@@ -1,8 +1,9 @@
import { EventStreamCodec } from "@smithy/eventstream-codec"
import { fromUtf8, toUtf8 } from "@smithy/util-utf8"
import { describe, expect } from "bun:test"
import { Effect, Encoding, Ref, Schema, Stream } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { Effect, Ref, Schema, Stream } from "effect"
import { Base64 } from "effect/encoding"
import { HttpClientRequest } from "effect/http"
import {
Media,
CacheHint,
@@ -865,7 +866,7 @@ describe("Bedrock Converse route", () => {
reason: { _tag: "InvalidProviderOutput", classification: "incomplete-stream" },
message: `Incomplete Bedrock Converse event-stream frame: ${partial.length} buffered bytes remain at end of stream`,
})
expect(error.reason.body).toBe(Encoding.encodeBase64(partial))
expect(error.reason.body).toBe(Base64.encode(partial))
}
}),
)
+22 -255
View File
@@ -1,8 +1,9 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { LLM, LLMEvent, LLMRequest, Message, ToolDefinition } from "../../src/index.js"
import { HttpClientRequest } from "effect/http"
import { LLM, Message } from "../../src/index.js"
import { AmazonBedrockMantle } from "../../src/providers.js"
import { model } from "../../src/providers/amazon-bedrock/mantle.js"
import { OpenResponses } from "../../src/protocols/open-responses.js"
import { compileRequest, LLMClient } from "../../src/route/client.js"
import { it } from "../lib/effect.js"
@@ -18,14 +19,14 @@ const credentials = {
}
describe("Amazon Bedrock Mantle provider", () => {
it.effect("uses Responses by default and exposes Chat and Messages explicitly", () =>
it.effect("uses Responses by default and exposes Chat explicitly", () =>
Effect.gen(function* () {
const provider = AmazonBedrockMantle.configure({ credentials })
expect(provider.model).toBe(provider.responses)
expect(AmazonBedrockMantle.model).toBe(AmazonBedrockMantle.responsesModel)
expect(model).toBe(AmazonBedrockMantle.responsesModel)
expect(provider.model("openai.gpt-oss-120b").route.transport).toBe(OpenResponses.httpTransport)
const chat = yield* compileRequest(LLM.request({ model: provider.chat("openai.gpt-oss-120b"), prompt: "Hi" }))
const messages = yield* compileRequest(
LLM.request({ model: provider.messages("anthropic.claude-opus-4-8"), prompt: "Hi", cache: "none" }),
)
const responses = yield* compileRequest(
LLM.request({ model: provider.model("openai.gpt-oss-120b"), prompt: "Hi" }),
)
@@ -35,11 +36,6 @@ describe("Amazon Bedrock Mantle provider", () => {
protocol: "openai-chat",
body: { model: "openai.gpt-oss-120b" },
})
expect(messages).toMatchObject({
route: "bedrock-mantle-messages",
protocol: "anthropic-messages",
body: { model: "anthropic.claude-opus-4-8", stream: true },
})
expect(responses).toMatchObject({
route: "bedrock-mantle-responses",
protocol: "open-responses",
@@ -47,77 +43,43 @@ describe("Amazon Bedrock Mantle provider", () => {
})
expect(provider.model("openai.gpt-oss-120b").route.providerMetadataKey).toBe("mantle")
expect(provider.chat("openai.gpt-oss-120b").route.providerMetadataKey).toBe("mantle")
expect(provider.messages("anthropic.claude-opus-4-8").route.providerMetadataKey).toBe("mantle")
}),
)
it.effect("preserves configured top-p generation defaults for Chat, Messages, and Responses", () =>
it.effect("preserves configured top-p generation defaults for Chat and Responses", () =>
Effect.gen(function* () {
const settings = { apiKey: "test-key", topP: 0.8 }
const chat = yield* compileRequest(
LLM.request({ model: AmazonBedrockMantle.chatModel("openai.gpt-oss-safeguard-20b", settings), prompt: "Hi" }),
)
const messages = yield* compileRequest(
LLM.request({ model: AmazonBedrockMantle.messagesModel("anthropic.claude-opus-4-8", settings), prompt: "Hi" }),
)
const responses = yield* compileRequest(
LLM.request({ model: AmazonBedrockMantle.responsesModel("openai.gpt-oss-120b", settings), prompt: "Hi" }),
)
expect(chat.body.top_p).toBe(0.8)
expect(messages.body.top_p).toBe(0.8)
expect(responses.body.top_p).toBe(0.8)
}),
)
it.effect("uses the Mantle endpoint and signing service across Responses and Messages", () =>
it.effect("uses the Mantle endpoint and signing service", () =>
Effect.gen(function* () {
const seen: Array<{ readonly url: string; readonly authorization: string | undefined }> = []
const configured = AmazonBedrockMantle.configure({ credentials, region: "us-west-1" })
for (const selected of [
configured.responses("openai.gpt-oss-120b"),
configured.messages("anthropic.claude-opus-4-8"),
]) {
yield* LLMClient.generate(LLM.request({ model: selected, prompt: "Hi" })).pipe(
Effect.provide(
dynamicResponse((input) =>
Effect.gen(function* () {
const request = yield* HttpClientRequest.toWeb(input.request)
seen.push({ url: request.url, authorization: request.headers.get("authorization") ?? undefined })
return input.respond("", { headers: { "content-type": "text/event-stream" } })
}),
),
const model = AmazonBedrockMantle.configure({ credentials, region: "us-west-1" }).responses("openai.gpt-oss-120b")
yield* LLMClient.generate(LLM.request({ model, prompt: "Hi" })).pipe(
Effect.provide(
dynamicResponse((input) =>
Effect.gen(function* () {
const request = yield* HttpClientRequest.toWeb(input.request)
seen.push({ url: request.url, authorization: request.headers.get("authorization") ?? undefined })
return input.respond("", { headers: { "content-type": "text/event-stream" } })
}),
),
Effect.flip,
)
}
expect(seen.map((item) => item.url)).toEqual([
"https://bedrock-mantle.us-west-1.api.aws/v1/responses",
"https://bedrock-mantle.us-west-1.api.aws/anthropic/v1/messages",
])
expect(seen.every((item) => item.authorization?.includes("/us-west-1/bedrock-mantle/aws4_request"))).toBe(true)
}).pipe(withProcessEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined })),
)
it.effect("applies inline cache breakpoints on Mantle Messages", () =>
Effect.gen(function* () {
const model = AmazonBedrockMantle.configure({ apiKey: "test-key" }).messages("anthropic.claude-opus-4-8")
const prepared = yield* compileRequest(
LLM.request({
model,
system: "You are concise.",
messages: [Message.user("Hello")],
cache: "auto",
}),
),
Effect.flip,
)
expect(prepared.body.system).toEqual([
{ type: "text", text: "You are concise.", cache_control: { type: "ephemeral" } },
])
expect(prepared.body.messages).toEqual([
{ role: "user", content: [{ type: "text", text: "Hello", cache_control: { type: "ephemeral" } }] },
])
expect(seen[0]?.url).toBe("https://bedrock-mantle.us-west-1.api.aws/v1/responses")
expect(seen[0]?.authorization).toContain("/us-west-1/bedrock-mantle/aws4_request")
}),
)
@@ -239,198 +201,3 @@ describe("Amazon Bedrock Mantle recorded", () => {
}),
)
})
const recordedMessages = recordedTests({
prefix: "bedrock-mantle-messages",
provider: "amazon-bedrock",
protocol: "anthropic-messages",
requires: ["AWS_BEARER_TOKEN_BEDROCK"],
options: { redact: { allowRequestHeaders: ["anthropic-version", "anthropic-beta"] } },
})
const mantleMessages = (modelID: string) =>
AmazonBedrockMantle.configure({
apiKey: process.env.AWS_BEARER_TOKEN_BEDROCK ?? "fixture",
region: "us-east-1",
}).messages(modelID)
const weatherTool = ToolDefinition.make({
name: "get_weather",
description: "Get the current weather in a city",
inputSchema: {
type: "object",
properties: { city: { type: "string", enum: ["Paris"] } },
required: ["city"],
additionalProperties: false,
},
})
describe("Amazon Bedrock Mantle Messages recorded", () => {
recordedMessages.effect.with(
"replays signed thinking through a tool loop and native system update",
{ tags: ["tool", "tool-loop", "reasoning", "system-update"], metadata: { model: "anthropic.claude-opus-4-8" } },
() =>
Effect.gen(function* () {
const model = mantleMessages("anthropic.claude-opus-4-8")
const initial = LLM.request({
model,
system: "You are a concise assistant.",
prompt:
"First calculate 37 * 43. Then call get_weather for Paris. After receiving the tool result, state both the product and the weather.",
tools: [weatherTool],
providerOptions: {
thinking: { type: "adaptive", display: "summarized" },
effort: "medium",
},
generation: { maxTokens: 2048 },
})
const first = yield* LLMClient.generate(initial)
expect(first.finishReason.normalized).toBe("tool-calls")
expect(first.toolCalls).toMatchObject([{ name: "get_weather", input: { city: "Paris" } }])
expect(first.reasoning.length).toBeGreaterThan(0)
expect(first.events.some(LLMEvent.is.toolInputDelta)).toBe(true)
const reasoningPart = first.message.content.find((part) => part.type === "reasoning")
const signature = (reasoningPart?.providerMetadata?.mantle as { readonly signature?: unknown } | undefined)
?.signature
expect(typeof signature).toBe("string")
const followUp = LLMRequest.update(initial, {
messages: [
...initial.messages,
first.message,
...first.toolCalls.map((call) =>
Message.tool({ id: call.id, name: call.name, result: { condition: "sunny", temperatureC: 18 } }),
),
Message.system("Reply in French in one short sentence."),
],
})
const compiled = yield* compileRequest(followUp)
expect(compiled.body.messages[1]?.content[0]).toEqual({
type: "thinking",
thinking: first.reasoning,
signature: signature as string,
})
expect(compiled.body.messages.at(-1)).toEqual({
role: "system",
content: [
{ type: "text", text: "Reply in French in one short sentence.", cache_control: { type: "ephemeral" } },
],
})
const second = yield* LLMClient.generate(followUp)
expect(second.finishReason.normalized).toBe("stop")
expect(second.text).toContain("1591")
expect(second.text).toContain("18")
}),
120_000,
)
recordedMessages.effect.with(
"lowers system updates to wrapped user text on Haiku 4.5 with budget thinking",
{ tags: ["reasoning", "system-update"], metadata: { model: "anthropic.claude-haiku-4-5" } },
() =>
Effect.gen(function* () {
const request = LLM.request({
model: mantleMessages("anthropic.claude-haiku-4-5"),
messages: [Message.user("What is 19 multiplied by 23?"), Message.system("Reply with only the integer.")],
providerOptions: {
thinking: { type: "enabled", budgetTokens: 1024 },
},
generation: { maxTokens: 2048 },
})
const compiled = yield* compileRequest(request)
expect(compiled.body.thinking).toEqual({ type: "enabled", budget_tokens: 1024 })
expect(compiled.body.messages.some((message) => message.role === "system")).toBe(false)
const response = yield* LLMClient.generate(request)
expect(response.finishReason.normalized).toBe("stop")
expect(response.reasoning.length).toBeGreaterThan(0)
expect(response.text.trim()).toContain("437")
}),
120_000,
)
recordedMessages.effect.with(
"applies mid-conversation effort updates and thinking block binding on Opus 5.5",
{ tags: ["reasoning", "effort-update"], metadata: { model: "anthropic.claude-opus-5-5" } },
() =>
Effect.gen(function* () {
const model = mantleMessages("anthropic.claude-opus-5-5")
const firstRequest = LLM.request({
model,
prompt: "Compute 37 * 43 step by step, then reply with only the integer.",
providerOptions: {
thinking: { type: "adaptive", display: "summarized" },
effort: "high",
},
generation: { maxTokens: 2048 },
})
const firstCompiled = yield* compileRequest(firstRequest)
expect(firstCompiled.body.thinking).toEqual({
type: "adaptive",
display: "summarized",
block_binding: { prefix_mismatch_behavior: "drop_block" },
})
const first = yield* LLMClient.generate(firstRequest)
expect(first.finishReason.normalized).toBe("stop")
expect(first.reasoning.length).toBeGreaterThan(0)
expect(first.text.replaceAll(",", "")).toContain("1591")
const secondRequest = LLM.request({
model,
messages: [
...firstRequest.messages,
first.message,
Message.effort({ effort: "low", previous: "high" }),
Message.user("Add 9 to that result. Reply with only the integer."),
],
providerOptions: {
thinking: { type: "adaptive", display: "summarized" },
effort: "low",
},
generation: { maxTokens: 2048 },
})
const secondCompiled = yield* compileRequest(secondRequest)
expect(secondCompiled.body.output_config).toEqual({ effort: "high" })
expect(secondCompiled.body.messages.filter((message) => message.role === "system")).toEqual([
{ role: "system", content: [], output_config: { effort: "low" } },
])
const second = yield* LLMClient.generate(secondRequest)
expect(second.finishReason.normalized).toBe("stop")
expect(second.text.replaceAll(",", "")).toContain("1600")
}),
120_000,
)
recordedMessages.effect.with(
"strips unsupported mid-conversation effort updates on Opus 5.0",
{ tags: ["reasoning", "effort-update"], metadata: { model: "anthropic.claude-opus-5" } },
() =>
Effect.gen(function* () {
const request = LLM.request({
model: mantleMessages("anthropic.claude-opus-5"),
messages: [
Message.user("What is 12 + 30?"),
Message.assistant("42"),
Message.effort({ effort: "low", previous: "high" }),
Message.user("Add 8 to that result. Reply with only the integer."),
],
providerOptions: {
thinking: { type: "adaptive", display: "summarized" },
effort: "low",
},
generation: { maxTokens: 1024 },
})
const compiled = yield* compileRequest(request)
expect(compiled.body.output_config).toEqual({ effort: "low" })
expect(compiled.body.messages.some((message) => message.role === "system")).toBe(false)
const response = yield* LLMClient.generate(request)
expect(response.finishReason.normalized).toBe("stop")
expect(response.text.trim()).toContain("50")
}),
120_000,
)
})
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { ConfigProvider, Effect, Schema } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { LLM, LLMEvent } from "../../src/index.js"
import { CloudflareAIGateway } from "../../src/providers/cloudflare-ai-gateway.js"
import { CloudflareWorkersAI } from "../../src/providers/cloudflare-workers-ai.js"
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Schema } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { LLM, LLMEvent, Message } from "../../src/index.js"
import { Google } from "../../src/providers.js"
import { LLMClient, RequestExecutor } from "../../src/route.js"
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { LanguageModel, LLM, Message, ToolCallPart } from "../../src/index.js"
import { GoogleVertex, GoogleVertexChat, GoogleVertexMessages, GoogleVertexResponses } from "../../src/providers.js"
import { LLMClient } from "../../src/route.js"
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { Effect, Layer } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { Image, ImageClient, LLM, LLMEvent, LLMRequest, Media, Message, ToolDefinition } from "../../src/index.js"
import { Meta } from "../../src/providers/meta.js"
import { MetaMessages } from "../../src/protocols/meta-messages.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Auth, LLM, LLMClient } from "../../src/index.js"
import { Meta } from "../../src/providers/index.js"
import { OpenAIChat } from "../../src/protocols/openai-chat.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { LLM } from "../../src/index.js"
import { MiniMax } from "../../src/providers.js"
import { AnthropicMessages } from "../../src/protocols/anthropic-messages.js"
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { LLM, LLMEvent, Message, SystemPart, ToolDefinition, Media } from "../../src/index.js"
import { Mistral } from "../../src/providers/index.js"
import { MistralChat } from "../../src/protocols/index.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Auth, LLM, LLMClient, Message, ReasoningPart, ToolDefinition } from "../../src/index.js"
import { Moonshot } from "../../src/providers.js"
import { AnthropicMessages } from "../../src/protocols/anthropic-messages.js"
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { LLM, Message, ToolDefinition } from "../../src/index.js"
import {
AmazonBedrock,
@@ -43,7 +43,6 @@ describe("native OpenAI-compatible providers", () => {
[Azure.configure({ resourceName: "resource", apiKey: "test" }).responses("model"), "azure"],
[AmazonBedrock.configure({ apiKey: "test" }).model("model"), "bedrock"],
[AmazonBedrockMantle.configure({ apiKey: "test" }).chat("model"), "mantle"],
[AmazonBedrockMantle.configure({ apiKey: "test" }).messages("model"), "mantle"],
[AmazonBedrockMantle.configure({ apiKey: "test" }).responses("model"), "mantle"],
[Google.configure({ apiKey: "test" }).model("model"), "google"],
[GoogleVertex.configure(vertex).model("model"), "vertex"],
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Ref, Schema, Stream } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import {
Media,
HttpOptions,
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Schema } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { LLM, LLMRequest, Message, ToolCallPart, ToolChoice, ToolDefinition } from "../../src/index.js"
import { Auth, LLMClient } from "../../src/route.js"
import { compileRequest } from "../../src/route/client.js"
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Stream } from "effect"
import { Socket } from "effect/unstable/socket"
import { Socket } from "effect/socket"
import { LLM, LLMRequest, Message, ToolRuntime } from "../../src/index.js"
import {
LLMClient,
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { ConfigProvider, Effect, Layer, Logger, Ref, Schema, Stream } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import {
Media,
LLM,
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { Headers, HttpClientRequest } from "effect/http"
import { Image, ImageClient } from "../../src/index.js"
import { XAI } from "../../src/providers.js"
import { Auth } from "../../src/route.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { Image, ImageClient } from "../../src/index.js"
import { ZAI } from "../../src/providers.js"
import { it } from "../lib/effect.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { expect } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Headers } from "effect/http"
import { Auth, LLM, LLMClient, Message, ReasoningPart, ToolDefinition } from "../../src/index.js"
import { ZAI, ZAICodingPlan } from "../../src/providers.js"
import { compileRequest } from "../../src/route/client.js"
Loaded 100 of 587 files, more files were not shown because too many files have changed in this diff. Show more