Compare commits

...
Author SHA1 Message Date
Kit Langton 973a3b570b chore: merge latest v2 into Effect upgrade 2026-10-02 15:57:47 -07:00
Kit Langton 54956aac22 chore: enable noUnusedLocals in ai and core (#52858) 2026-10-02 22:45:27 +00:00
Kit Langton c1536734ce chore(cli): enable noUnusedLocals (#52857) 2026-10-02 22:45:05 +00:00
Kit Langton cf7fcfad0a chore(app): enable noUnusedLocals (#52856) 2026-10-02 22:44:02 +00:00
Kit Langton 17955a1100 refactor(core): remove duplicate v1 config migration (#51889) 2026-10-02 15:40:41 -07:00
Kit Langton c0c9c69a27 chore(tui): enable noUnusedLocals (#52851) 2026-10-02 22:36:03 +00:00
Kit Langton eb1a9ea4d9 chore: enable noUnusedLocals in schema, server, sdk and smaller packages (#52850) 2026-10-02 15:23:59 -07:00
Kit Langton 680bca3183 chore: enable noUnusedLocals in already-clean packages (#52849) 2026-10-02 15:23:50 -07:00
Kit Langton 22f81eed0b test(tui): drop title shimmer animation timing tests (#52848) 2026-10-02 15:23:31 -07:00
Aiden Cline 421a04f5fd fix(core): retry transient MCP connect failures (#52614) 2026-10-02 17:22:25 -05:00
Kit Langton 20591148f2 chore: merge latest v2 into Effect upgrade 2026-10-02 14:56:12 -07:00
Jérôme BenoitandTest User ec541abbcd chore(nix): run the nix eval workflow on v2 (#52143)
Co-authored-by: Test User <test@test.com>
2026-10-02 16:52:46 -05: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 4c63a35dbe refactor(schema): drop unused FileAttachment re-export from SessionEvent (#52842) 2026-10-02 21:35:12 +00: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 0e4dd7bfe9 refactor(ai): remove unused declarations in OpenAI chat protocol (#52840) 2026-10-02 21:33:58 +00:00
Kit Langton e28a624b8b refactor(core): remove unused git strategy in worktree layer (#52841) 2026-10-02 21:28:27 +00:00
Kit Langton 9277761af6 refactor(schema): remove unused QuestionV1 contract (#52838) 2026-10-02 21:28:13 +00: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 0bef0eab67 refactor(core): remove legacy id prefix table (#52810) 2026-10-02 18:40:26 +00:00
Shoubhit Dash 66d15a7352 refactor(acp): split module seams and drop dead code (#52803) 2026-10-03 00:04:06 +05:30
Kit Langton 9f8551ab6b refactor(core): require job ids from their subject (#52806) 2026-10-02 18:27:13 +00:00
Kit Langton 8bcda4c718 refactor(core): name tool output files in ToolOutput (#52808) 2026-10-02 18:24:57 +00:00
Kit Langton 4270fcc9c2 test(core): pin user-visible configuration map order 2026-10-02 11:15:38 -07:00
Kit Langton ccd2aca07a test(core): match Code Mode description after allSettled change (#52812) 2026-10-02 18:14:27 +00: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
1391c0d52c docs(core): recommend settled results for independent calls (#52798)
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-10-02 12:26:05 -05:00
Shoubhit Dash ec90ad50d3 fix(acp): settle permissions replied elsewhere and type request failures (#52774) 2026-10-02 22:43:32 +05:30
Shoubhit Dash 2a01ed60b1 perf(core): keep unchanged messages in request shaping (#52784) 2026-10-02 22:12:21 +05:30
Shoubhit Dash 80caae8ad1 test(acp): keep only spec and essential tests (#52775) 2026-10-02 22:01:49 +05:30
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
541 changed files with 4403 additions and 7483 deletions

No files matched your search

+2 -2
View File
@@ -2,9 +2,9 @@ name: nix-eval
on:
push:
branches: [dev]
branches: [dev, v2]
pull_request:
branches: [dev]
branches: [dev, v2]
workflow_dispatch:
concurrency:
+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
@@ -42,10 +42,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",
@@ -79,7 +79,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 -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,
@@ -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"
@@ -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"
@@ -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,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"
+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"
+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"
+3 -2
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 { HttpTransport } from "../route/transport/index.js"
import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.js"
@@ -135,7 +136,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,
@@ -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 {
-7
View File
@@ -14,7 +14,6 @@ import {
ProviderInternalError,
UnknownProviderError,
Usage,
type FinishReason,
type FinishReasonDetails,
type CacheHint,
type LLMRequest,
@@ -46,12 +45,6 @@ const OpenAIChatCacheControl = Schema.Struct({
})
type OpenAIChatCacheControl = Schema.Schema.Type<typeof OpenAIChatCacheControl>
const OpenAIChatFunction = Schema.Struct({
name: Schema.String,
description: Schema.String,
parameters: JsonObject,
})
const OpenAIChatTool = Schema.Struct({
type: Schema.tag("function"),
function: Schema.Struct({
@@ -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"
@@ -249,7 +250,7 @@ const checkpointBody = {
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,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"
+1 -1
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,
@@ -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"
@@ -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"
@@ -78,12 +79,12 @@ function documentName(filename: string | undefined, names: Set<string>) {
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
+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)),
)
+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,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"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Stream } from "effect"
import { makeParser, type Event } from "effect/unstable/encoding/Sse"
import { makeParser, type Event } from "effect/encoding/Sse"
import { AIError, InvalidProviderOutputError } from "../schema/index.js"
/**
+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 -2
View File
@@ -14,7 +14,6 @@ import * as GoogleVertexChat from "../src/providers/google-vertex-chat.js"
import * as GoogleVertexMessages from "../src/providers/google-vertex-messages.js"
import * as GoogleVertexResponses from "../src/providers/google-vertex-responses.js"
import * as OpenAI from "../src/providers/openai.js"
import * as OpenAICompatible from "../src/providers/openai-compatible.js"
import * as OpenRouter from "../src/providers/openrouter.js"
import * as XAI from "../src/providers/xai.js"
@@ -30,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 -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 -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"
+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,
@@ -866,7 +867,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))
}
}),
)
@@ -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 { LLM, Message } from "../../src/index.js"
import { AmazonBedrockMantle } from "../../src/providers.js"
import { model } from "../../src/providers/amazon-bedrock/mantle.js"
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } 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, 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, 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,
@@ -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, test } 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"
+1 -1
View File
@@ -1,7 +1,7 @@
import { HttpRecorder } from "@opencode/http-recorder"
import { NodeSocket } from "@effect/platform-node"
import { Layer } from "effect"
import { Socket } from "effect/unstable/socket"
import { Socket } from "effect/socket"
import * as path from "node:path"
import { fileURLToPath } from "node:url"
import { LLMClient, RequestExecutor } from "../src/route.js"
+4 -3
View File
@@ -1,5 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Encoding, Layer, Stream } from "effect"
import { Effect, Layer, Stream } from "effect"
import { Base64 } from "effect/encoding"
import { Speech, SpeechClient, SpeechEvent } from "../src/index.js"
import { Cartesia, Deepgram, ElevenLabs, Google, OpenAI } from "../src/providers.js"
import { it } from "./lib/effect.js"
@@ -47,7 +48,7 @@ describe("Speech", () => {
JSON.stringify({
candidates: [
{
content: { parts: [{ inlineData: { mimeType: "audio/wav", data: Encoding.encodeBase64(bytes) } }] },
content: { parts: [{ inlineData: { mimeType: "audio/wav", data: Base64.encode(bytes) } }] },
finishReason: "STOP",
},
],
@@ -244,7 +245,7 @@ describe("Speech", () => {
Effect.gen(function* () {
const record = (bytes: ReadonlyArray<number>, character: string, start: number) =>
JSON.stringify({
audio_base64: Encoding.encodeBase64(Uint8Array.from(bytes)),
audio_base64: Base64.encode(Uint8Array.from(bytes)),
alignment: {
characters: [character],
character_start_times_seconds: [start],
+195 -3
View File
@@ -1,14 +1,201 @@
import { describe, expect, test } from "bun:test"
import { Effect, Schema } from "effect"
import { Effect, Schema, SchemaGetter } from "effect"
import { LLM } from "../src/index.js"
import { OpenAIChat } from "../src/protocols.js"
import { AnthropicMessages, Gemini, OpenAIChat, OpenAIResponses } from "../src/protocols.js"
import { ToolSchemaProjection } from "../src/protocols/utils/tool-schema.js"
import { Tool, toDefinitions } from "../src/tool.js"
import { Auth } from "../src/route.js"
import { compileRequest } from "../src/route/client.js"
import { it } from "./lib/effect.js"
const Token = Schema.Struct({ token: Schema.String })
const token = {
type: "object",
properties: { token: { type: "string" } },
required: ["token"],
additionalProperties: false,
}
const fromEmpty = {
decode: SchemaGetter.transform(() => ({ token: "default" })),
encode: SchemaGetter.transform(() => ({})),
}
// Callers send `{}`; the handler receives a token.
const defaulted = Schema.Struct({}).pipe(Schema.decodeTo(Token, fromEmpty))
// Callers send a token; the handler receives an empty struct.
const reversed = Token.pipe(
Schema.decodeTo(Schema.Struct({}), {
decode: SchemaGetter.transform(() => ({})),
encode: SchemaGetter.transform(() => ({ token: "default" })),
}),
)
describe("tool schema projections", () => {
test("only normalizes typed empty input structs, preserving raw schemas and output schemas", () => {
const schema = { not: { type: "null" } }
const definitions = Tool.toDefinitions({
typed: Tool.make({
description: "Typed",
parameters: Schema.Struct({}),
success: Schema.Struct({}),
execute: () => Effect.succeed({}),
}),
raw: Tool.make({ description: "Raw", jsonSchema: schema, execute: () => Effect.succeed({}) }),
})
expect(definitions[0]?.inputSchema).toEqual({ type: "object", properties: {}, additionalProperties: false })
expect(definitions[0]?.outputSchema).toEqual(schema)
expect(definitions[1]?.inputSchema).toEqual(schema)
})
test("retains empty input descriptions and preserves named outputs and explicit raw schemas", () => {
const parameters = Schema.Struct({}).annotate({ identifier: "Ping", description: "No arguments" })
const raw = { $ref: "#/$defs/Raw", $defs: { Raw: { not: { type: "null" }, description: "Raw schema" } } }
const definitions = Tool.toDefinitions({
typed: Tool.make({ description: "Ping", parameters, success: parameters, execute: () => Effect.succeed({}) }),
raw: Tool.make({ description: "Raw", jsonSchema: raw, execute: () => Effect.succeed({}) }),
})
expect(definitions[0]?.inputSchema).toEqual({
type: "object",
properties: {},
additionalProperties: false,
description: "No arguments",
})
expect(definitions[0]?.outputSchema).toEqual({
$ref: "#/$defs/Ping",
$defs: { Ping: { not: { type: "null" }, description: "No arguments" } },
})
expect(definitions[1]?.inputSchema).toEqual(raw)
})
test("normalizes inputs whose encoded side is an unchecked empty struct", () => {
const definitions = Tool.toDefinitions({
defaulted: Tool.make({ description: "Defaulted", parameters: defaulted, success: defaulted }),
named: Tool.make({
description: "Named",
parameters: defaulted.annotate({ identifier: "Defaulted" }),
success: Schema.String,
}),
encodedMetadata: Tool.make({
description: "Encoded metadata",
parameters: Schema.Struct({})
.annotate({ identifier: "Ping", title: "Ping", description: "No arguments" })
.pipe(Schema.decodeTo(Token, fromEmpty)),
success: Schema.String,
}),
decodedCheck: Tool.make({
description: "Decoded check",
parameters: defaulted.check(Schema.makeFilter(() => true, { toJsonSchema: () => ({ minProperties: 1 }) })),
success: Schema.String,
}),
reversed: Tool.make({ description: "Reversed", parameters: reversed, success: Schema.String }),
encodedCheck: Tool.make({
description: "Encoded check",
parameters: Schema.Struct({}).check(Schema.isMinProperties(1)),
success: Schema.String,
}),
})
expect(definitions[0]?.inputSchema).toEqual(empty)
expect(definitions[0]?.outputSchema).toEqual({ not: { type: "null" } })
expect(definitions[1]?.inputSchema).toEqual(empty)
expect(definitions[2]?.inputSchema).toEqual({ ...empty, title: "Ping", description: "No arguments" })
expect(definitions[3]?.inputSchema).toEqual(empty)
expect(definitions[4]?.inputSchema).toEqual(token)
expect(definitions[5]?.inputSchema).toEqual({ not: { type: "null" }, minProperties: 1 })
})
const empty = { type: "object", properties: {}, additionalProperties: false }
const nonempty = {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
additionalProperties: false,
}
const raw = { type: "object", properties: { value: { type: "number" } }, additionalProperties: true }
const names = ["ping", "lookup", "raw", "reversed"]
const schemas = [empty, nonempty, raw, token]
for (const scenario of [
{
route: OpenAIChat.route,
tools: schemas.map((parameters, index) => ({
type: "function",
function: { name: names[index], parameters },
})),
},
{
route: OpenAIResponses.route,
tools: schemas.map((parameters, index) => ({
type: "function",
name: names[index],
parameters,
})),
},
{
route: AnthropicMessages.route,
tools: schemas.map((input_schema, index) => ({
name: names[index],
input_schema,
})),
},
{
route: Gemini.route,
tools: [
{
functionDeclarations: schemas.map((parametersJsonSchema, index) => ({
name: names[index],
parametersJsonSchema,
})),
},
],
},
]) {
for (const input of [
{ name: "plain", schema: Schema.Struct({}) },
{ name: "described", schema: Schema.Struct({}).annotate({ description: "No arguments" }) },
{ name: "named", schema: Schema.Struct({}).annotate({ identifier: "Ping" }) },
{
name: "named and described",
schema: Schema.Struct({}).annotate({ identifier: "Ping", description: "No arguments" }),
},
{ name: "encoded", schema: defaulted },
{ name: "named encoded", schema: defaulted.annotate({ identifier: "Ping" }) },
]) {
it.effect(
`${scenario.route.id} prepares ${input.name} empty Effect Struct tools alongside nonempty and raw schemas`,
() =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: scenario.route.with({ auth: Auth.bearer("test") }).model({ id: "test-model" }),
prompt: "Use a tool.",
tools: Tool.toDefinitions({
ping: Tool.make({
description: "Ping",
parameters: input.schema,
success: Schema.String,
execute: () => Effect.succeed("pong"),
}),
lookup: Tool.make({
description: "Lookup",
parameters: Schema.Struct({ query: Schema.String }),
success: Schema.String,
execute: (input) => Effect.succeed(input.query),
}),
raw: Tool.make({ description: "Raw", jsonSchema: raw, execute: () => Effect.succeed("raw") }),
reversed: Tool.make({
description: "Reversed",
parameters: reversed,
success: Schema.String,
execute: () => Effect.succeed("reversed"),
}),
}),
}),
)
expect(prepared.body.tools).toMatchObject(scenario.tools)
}),
)
}
}
test("moonshot strips $ref siblings and converts tuple arrays to a schema object", () => {
expect(
ToolSchemaProjection.moonshot({
@@ -91,7 +278,12 @@ describe("tool schema projections", () => {
const exclusive = { oneOf: [{ type: "object" }, { type: "object", required: ["a"] }] }
const object = { type: "object", properties: {} }
expect(yield* parameters(parameterless)).toEqual({ type: "object", description: "No input." })
expect(yield* parameters(parameterless)).toEqual({
type: "object",
properties: {},
additionalProperties: false,
description: "No input.",
})
expect(yield* parameters({})).toEqual({ type: "object" })
expect(yield* parameters({ description: "Query", properties: { q: { type: "string" } } })).toEqual({
type: "object",
+1 -1
View File
@@ -1,7 +1,7 @@
import { describe, expect } from "bun:test"
import { Effect, Fiber, Layer, Stream } from "effect"
import * as TestClock from "effect/testing/TestClock"
import { HttpClientRequest } from "effect/unstable/http"
import { HttpClientRequest } from "effect/http"
import { Media, Transcription, TranscriptionClient, type TranscriptionEvent } from "../src/index.js"
import { AssemblyAI, Deepgram, ElevenLabs, Google, OpenAI } from "../src/providers.js"
import { it } from "./lib/effect.js"
+1
View File
@@ -2,6 +2,7 @@
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"noUnusedLocals": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"allowImportingTsExtensions": false,
@@ -1,7 +1,6 @@
import { expect, test } from "@playwright/test"
import {
defineVisualRegions,
mapVisualRegions,
reportVisualStability,
startVisualProbe,
stopVisualProbe,
@@ -242,7 +242,7 @@ test("keeps an older answer selected while scrolling within the interaction buff
test("tracks keyboard scrolling from a focused timeline descendant", async ({ page }, testInfo) => {
const shellID = "prt_descendant_keyboard_01_shell"
const timeline = await setupTimeline(page, {
await setupTimeline(page, {
messages: [...history(12), userMessage(), assistantMessage([shell(shellID, "completed", lines(5))])],
settings: { shellToolPartsExpanded: false },
cpuRate: 4,
@@ -15,7 +15,6 @@ import {
partUpdated,
renderedPartID,
setupTimeline,
shell,
status,
textPart,
toolPart,
@@ -1,4 +1,4 @@
import type { CDPSession, Page } from "@playwright/test"
import type { Page } from "@playwright/test"
import path from "node:path"
import { mkdir, writeFile } from "node:fs/promises"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Schema, SchemaGetter } from "effect"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi"
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/http-api"
import { Pty } from "@opencode/schema/pty"
import { Worktree } from "@opencode/schema/worktree"
+2 -2
View File
@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"
import type { JsonValue, OpenCodeEvent, SessionMessageInfo } from "@opencode/client/promise"
import { Duration, Effect, Layer } from "effect"
import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http"
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
import { HttpRouter, HttpServer, HttpServerResponse } from "effect/http"
import { HttpApiBuilder, HttpApiSchema } from "effect/http-api"
import { SERVER } from "./app"
import { MockApi, MockBadRequest, MockInternal, MockNotFound, MockShellNotFound, MockUnsupported } from "./mock-api"
import { installSseTransport } from "./sse-transport"
+1 -8
View File
@@ -30,14 +30,7 @@ import type { Upload } from "../attachments/uploads"
import { CommentCard } from "@opencode/session-ui/comment-card"
import { typeLabel } from "@opencode/session-ui/message-file"
import { Skill } from "@opencode/schema/skill"
import type {
ComposerAttachment,
ComposerComment,
ComposerOption,
ComposerPersistedState,
ComposerPrompt,
ComposerSuggestion,
} from "../types"
import type { ComposerAttachment, ComposerComment, ComposerOption, ComposerPrompt, ComposerSuggestion } from "../types"
import type { ComposerEditorModel, ComposerSelectControl } from "./interaction"
import { isAttachment } from "../prompt-parts"
import "../attachments/attachments.css"
@@ -35,7 +35,6 @@ import { useIntegrations } from "@/providers/catalog/integrations"
import { CustomProviderForm } from "@/providers/credentials/dialog"
import { ProviderModelGroup, ProviderModelIcon } from "@/providers/models/provider-group"
import type { ModelSelection } from "@/providers/models/selection"
import { OpenCodeLogo } from "@/providers/opencode-logo"
import { decode64 } from "@/runtime/persistence/base64"
import { SettingsList } from "@/settings/list"
import { useTabs } from "@/shell/tabs/tabs"
@@ -45,7 +45,7 @@ const Current = Persistence.struct({ session: SessionsSchema })
export const ModelSelectionSchema = Persistence.migrate(
Current,
Schema.Struct({
Persistence.legacy({
session: Persistence.optional(Schema.Record(Schema.String, Schema.Unknown)),
pick: Persistence.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).pipe(
@@ -32,7 +32,7 @@ describe("persistence schemas", () => {
test("legacy migration observes missing fields before initial defaults are applied", () => {
const current = Persistence.struct({ mode: Schema.Literals(["compact", "full"]), enabled: Schema.Boolean })
const stored = Schema.Struct({
const stored = Persistence.legacy({
mode: Schema.optional(Schema.Unknown),
expanded: Schema.optional(Schema.Boolean),
}).pipe(
@@ -53,6 +53,38 @@ describe("persistence schemas", () => {
expect(Schema.encodeSync(schema)(decode({ expanded: true }))).toEqual({ mode: "full", enabled: true })
})
test("legacy read shapes carry unknown nested fields through to the current schema", () => {
const current = Persistence.struct({
general: Persistence.struct({
mode: Schema.Literals(["compact", "full"]),
theme: Schema.String,
nested: Persistence.struct({ keep: Schema.Boolean }),
}),
other: Persistence.struct({ flag: Schema.Boolean }),
})
const stored = Persistence.legacy({
general: Persistence.optional(Persistence.legacy({ expanded: Persistence.optional(Schema.Boolean) })),
}).pipe(
Schema.decode({
decode: SchemaGetter.transform((value) => {
const general = value.general
if (!general || general.expanded === undefined) return value
return { ...value, general: { ...general, mode: general.expanded ? "full" : "compact" } }
}),
encode: SchemaGetter.passthrough(),
}),
)
const schema = Persistence.withInitial(Persistence.migrate(current, stored), {
general: { mode: "compact", theme: "light", nested: { keep: false } },
other: { flag: false },
})
const decode = Schema.decodeUnknownSync(schema)
expect(decode({ general: { expanded: true, theme: "dark", nested: { keep: true } }, other: { flag: true } })).toEqual({
general: { mode: "full", theme: "dark", nested: { keep: true } },
other: { flag: true },
})
})
test("initial merging preserves field codecs and replaces arrays rather than merging indexes", () => {
const current = Persistence.struct({
amount: Schema.NumberFromString.check(Schema.isFinite()),
@@ -23,13 +23,11 @@ export function withInitial<S extends Schema.ConstraintCodec<object, unknown>>(
initial: NoInfer<S["Type"]>,
) {
const schema = isMigrated(definition) ? definition.current : definition
const read = isMigrated(definition)
? SchemaParser.decodeUnknownResult(definition.read, { onExcessProperty: "preserve" })
: Result.succeed<unknown>
const read = isMigrated(definition) ? SchemaParser.decodeUnknownResult(definition.read) : Result.succeed<unknown>
const encode = Schema.encodeUnknownSync(schema)
return Schema.Unknown.pipe(
Schema.decode<Schema.Unknown>({
decode: SchemaGetter.transformOrFail((value) =>
decode: SchemaGetter.transformEffect((value) =>
Effect.fromResult(Result.map(read(value), (stored) => merge(initial, recover(schema.ast, stored, initial)))),
),
encode: SchemaGetter.transform((value) => encode(value)),
@@ -38,6 +36,12 @@ export function withInitial<S extends Schema.ConstraintCodec<object, unknown>>(
)
}
// A legacy read shape declares only the fields it migrates. Decoding strips unknown keys, so every
// struct level in a `migrate` read schema must stay open for the current fields it does not name.
export function legacy<const Fields extends Schema.Struct.Fields>(fields: Fields) {
return Schema.StructWithRest(Schema.Struct(fields), [Schema.Record(Schema.String, Schema.Unknown)])
}
// Object-level codecs own their recovery. Plain structs can recover fields independently.
function recover(ast: SchemaAST.AST, value: unknown, initial: unknown): unknown {
if (value === undefined) return initial
@@ -53,7 +53,7 @@ const State = Persistence.struct({
export function serverState(canonicalLocalServer: () => string | undefined = () => undefined) {
return Persistence.migrate(
State,
Schema.Struct({ projects: Projects, lastProject: LastProject }).pipe(
Persistence.legacy({ projects: Projects, lastProject: LastProject }).pipe(
Schema.decode({
decode: SchemaGetter.transform((value) => {
const canonical = canonicalLocalServer()
+1 -160
View File
@@ -2,7 +2,6 @@ import { Component, Show, createMemo, createResource } from "solid-js"
import { createMediaQuery } from "@solid-primitives/media"
import { Select } from "@opencode/ui/select"
import { Switch } from "@opencode/ui/switch"
import { TextInput } from "@opencode/ui/text-input"
import { TimelineDetailControl } from "@/settings/timeline-detail"
import { useLanguage } from "@/runtime/i18n/language"
import { usePlatform } from "@/runtime/platform/platform"
@@ -14,42 +13,12 @@ import {
useSettings,
} from "@/settings/model"
import { formatKeybind } from "@/shell/commands/command"
import { ExternalLink } from "@/runtime/platform/external-link"
import { SettingsList } from "@/settings/list"
import { SettingsRow } from "@/settings/row"
import {
createAppearanceSettingsController,
createShellOptions,
type AppearanceSettingsController,
type ShellSettingsController,
} from "./controllers"
import { createShellOptions, type ShellSettingsController } from "./controllers"
import "@/settings/settings.css"
const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"]
const tabLayoutOptions: ("horizontal" | "vertical")[] = ["horizontal", "vertical"]
const fontSettings = {
ui: {
action: "settings-ui-font",
title: "settings.general.row.uiFont.title",
description: "settings.general.row.uiFont.description",
font: "ui",
input: "setUI",
},
code: {
action: "settings-code-font",
title: "settings.general.row.font.title",
description: "settings.general.row.font.description",
font: "code",
input: "setCode",
},
terminal: {
action: "settings-terminal-font",
title: "settings.general.row.terminalFont.title",
description: "settings.general.row.terminalFont.description",
font: "terminal",
input: "setTerminal",
},
} as const
const AutoApprovePermissionsSetting: Component = () => {
const language = useLanguage()
const settings = useSettings()
@@ -183,90 +152,6 @@ const FollowUpBehaviorSetting: Component = () => {
)
}
const AppearanceSection: Component<{ controller: AppearanceSettingsController }> = (props) => {
const language = useLanguage()
return (
<div class="settings-section">
<h3 class="settings-section-title">{language.t("settings.general.section.appearance")}</h3>
<SettingsList>
<SettingsRow
title={language.t("settings.general.row.colorScheme.title")}
description={language.t("settings.general.row.colorScheme.description")}
>
<Select
data-action="settings-color-scheme"
options={schemeOptions}
current={schemeOptions.find((option) => option === props.controller.scheme.current())}
placement="bottom-end"
gutter={6}
label={(option) => {
if (option === "system") return language.t("theme.scheme.system")
if (option === "light") return language.t("theme.scheme.light")
return language.t("theme.scheme.dark")
}}
onSelect={(option) => option && props.controller.scheme.select(option)}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.row.theme.title")}
description={
<>
{language.t("settings.general.row.theme.description")}{" "}
<ExternalLink class="settings-link" href="https://opencode.ai/docs/themes/">
{language.t("common.learnMore")}
</ExternalLink>
</>
}
>
<Select
data-action="settings-theme"
options={props.controller.theme.options()}
current={props.controller.theme.current()}
placement="bottom-end"
gutter={6}
value={(option) => option.id}
label={(option) => option.name}
onSelect={props.controller.theme.select}
/>
</SettingsRow>
<FontSetting kind="ui" fonts={props.controller.fonts} />
<FontSetting kind="code" fonts={props.controller.fonts} />
<FontSetting kind="terminal" fonts={props.controller.fonts} />
</SettingsList>
</div>
)
}
const FontSetting: Component<{
kind: "ui" | "code" | "terminal"
fonts: AppearanceSettingsController["fonts"]
}> = (props) => {
const language = useLanguage()
const config = () => fontSettings[props.kind]
return (
<SettingsRow title={language.t(config().title)} description={language.t(config().description)}>
<div class="w-full sm:w-[220px]">
<TextInput
data-action={config().action}
type="text"
appearance="base"
value={props.fonts[config().font]().value}
onInput={(event) => props.fonts[config().input](event.currentTarget.value)}
placeholder={props.fonts[config().font]().placeholder}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
aria-label={language.t(config().title)}
style={{ "font-family": props.fonts[config().font]().family }}
/>
</div>
</SettingsRow>
)
}
const LanguageSetting = () => {
const language = useLanguage()
const options = createMemo(() =>
@@ -409,50 +294,6 @@ export const SettingsGeneral: Component = () => {
</div>
)
const NotificationsSection = () => (
<div class="settings-section">
<h3 class="settings-section-title">{language.t("settings.general.section.notifications")}</h3>
<SettingsList>
<SettingsRow
title={language.t("settings.general.notifications.agent.title")}
description={language.t("settings.general.notifications.agent.description")}
>
<div data-action="settings-notifications-agent">
<Switch
checked={settings.notifications.agent()}
onChange={(checked) => settings.notifications.setAgent(checked)}
/>
</div>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.notifications.permissions.title")}
description={language.t("settings.general.notifications.permissions.description")}
>
<div data-action="settings-notifications-permissions">
<Switch
checked={settings.notifications.permissions()}
onChange={(checked) => settings.notifications.setPermissions(checked)}
/>
</div>
</SettingsRow>
<SettingsRow
title={language.t("settings.general.notifications.errors.title")}
description={language.t("settings.general.notifications.errors.description")}
>
<div data-action="settings-notifications-errors">
<Switch
checked={settings.notifications.errors()}
onChange={(checked) => settings.notifications.setErrors(checked)}
/>
</div>
</SettingsRow>
</SettingsList>
</div>
)
return (
<>
<div class="settings-tab-header">
+2 -2
View File
@@ -189,9 +189,9 @@ function legacyTimelineActivity(value: boolean | "hidden" | "compact" | "full" |
export const settingsPersistence = Persistence.migrate(
settingsSchema,
Schema.Struct({
Persistence.legacy({
general: Persistence.optional(
Schema.Struct({
Persistence.legacy({
// Keep invalid explicit values distinct from absent values so legacy preferences cannot replace them.
timelineDetail: Schema.optional(
Schema.NullOr(
@@ -15,7 +15,6 @@ import { displayName } from "@opencode/ui/project-avatar"
import { resolveProjectForSession } from "@/shell/layout/helpers"
import { useExtensionHost } from "@/runtime/extension/host"
import { useExtensionAttachment } from "@/runtime/extension/services"
import { useServer } from "@/runtime/server/current"
import { looksLikeSessionID } from "@/session/search"
export type CommandPaletteEntry = {
@@ -1,23 +1,19 @@
import { createStore, reconcile } from "solid-js/store"
import { Schema } from "effect"
import { SessionError } from "@opencode/schema/session-error"
import { type Accessor, batch, createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
import { createSimpleContext } from "@opencode/ui/context"
import { batch, createEffect, onCleanup } from "solid-js"
import type { ServerSDK } from "@/runtime/server/client"
import type { Data } from "@opencode/client/solid"
import { usePlatform } from "@/runtime/platform/platform"
import { useLanguage } from "@/runtime/i18n/language"
import { useSettings } from "@/settings/model"
import { decode64 } from "@/runtime/persistence/base64"
import { Persist, persisted } from "@/runtime/persistence/storage"
import { Persistence } from "@/runtime/persistence/schema"
import { playSoundById } from "@/shell/notifications/sound"
import type { createNotificationCoordinator } from "@/shell/notifications/coordinator"
import { useGlobal } from "@/runtime/server/runtime"
import { ServerConnection, useServers } from "@/runtime/server/registry"
import { ServerConnection } from "@/runtime/server/registry"
import { sessionIDHasOpenTab, useTabs } from "@/shell/tabs/tabs"
import { requireServerKey, sessionHref } from "@/shell/routes/session"
import type { ServerScope } from "@/runtime/server/scope"
import { sessionHref } from "@/shell/routes/session"
import { useServer } from "@/runtime/server/current"
const NotificationBase = {
+4 -4
View File
@@ -120,16 +120,16 @@ export const layoutSchema = Persistence.struct({
export const layoutPersistence = Persistence.migrate(
layoutSchema,
Schema.Struct({
Persistence.legacy({
sidebar: Persistence.optional(
Schema.Struct({
Persistence.legacy({
workspaces: Persistence.optional(Schema.Union([Schema.Boolean, Schema.Record(Schema.String, Schema.Boolean)])),
workspacesDefault: Persistence.optional(Schema.Boolean),
}),
),
review: Persistence.optional(Schema.Struct({ panelOpened: Persistence.optional(Schema.Boolean) })),
review: Persistence.optional(Persistence.legacy({ panelOpened: Persistence.optional(Schema.Boolean) })),
fileTree: Persistence.optional(
Schema.Struct({
Persistence.legacy({
opened: Persistence.optional(Schema.Boolean),
width: Persistence.optional(Schema.Finite),
tab: Persistence.optional(Schema.Literals(["changes", "all"])),
+1 -2
View File
@@ -6,7 +6,7 @@ import { Persist, persisted, removePersisted, draftPersistedKeys } from "@/runti
import { ServerConnection, useServers } from "@/runtime/server/registry"
import { useExtensionServers } from "@/runtime/extension/servers"
import { createEffect, getOwner, onCleanup, startTransition } from "solid-js"
import { useLocation, useNavigate, useParams } from "@solidjs/router"
import { useLocation, useNavigate } from "@solidjs/router"
import { usePlatform } from "@/runtime/platform/platform"
import { uuid } from "@/runtime/persistence/uuid"
import { SessionTabsRemovedDetail } from "@/shell/titlebar/session-events"
@@ -80,7 +80,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), TabStorage.Closed, [])
const [pending, setPending] = createStore<Record<string, PendingSession | undefined>>({})
const params = useParams()
const navigate = useNavigate()
const location = useLocation()
const memory = createTabMemory(getOwner())
+1 -1
View File
@@ -6,7 +6,7 @@ import { createMutation } from "@tanstack/solid-query"
import { IconButton } from "@opencode/ui/icon-button"
import { Icon } from "@opencode/ui/icon"
import { Menu } from "@opencode/ui/menu"
import { useGlobal, useServerCtx } from "@/runtime/server/runtime"
import { useServerCtx } from "@/runtime/server/runtime"
import { useLanguage } from "@/runtime/i18n/language"
import { ServerConnection, serverName, useServers } from "@/runtime/server/registry"
import { displayName } from "@opencode/ui/project-avatar"
+1
View File
@@ -13,6 +13,7 @@
"allowJs": true,
"resolveJsonModule": true,
"strict": true,
"noUnusedLocals": true,
"noEmit": false,
"emitDeclarationOnly": true,
"outDir": "node_modules/.ts-dist",
Loaded 100 of 541 files, more files were not shown because too many files have changed in this diff. Show more