Compare commits

..

19 Commits

Author SHA1 Message Date
opencode 4954edf8ae release: v0.9.0 2025-09-15 07:18:49 +00:00
Dax c1b4e1f19d Upgrade to Zod v4 (#2605)
Co-authored-by: GitHub Action <action@github.com>
2025-09-15 03:12:07 -04:00
Aiden Cline 89d820b1c4 fix: visual token bug (#2603) 2025-09-14 21:23:52 -05:00
Aiden Cline e3e459fc50 fix: reasoning metadata persistence (#2602) 2025-09-14 16:28:06 -05:00
Tommy D. Rossi 4bf0541bd6 log bash output when using opencode run (#2595) 2025-09-14 09:03:40 -05:00
Aiden Cline c81624aef7 tweak: make bash permissions key off of command pattern (#2592) 2025-09-14 09:01:57 -05:00
Kenn Costales df61aa801b fix: fix wrong tool references LS and Agent (#2466) 2025-09-14 08:53:50 -05:00
GitHub Action 6af0c2ec21 ignore: update download stats 2025-09-14 2025-09-14 12:03:54 +00:00
Dax Raad ce9d2ee04f ci: script 2025-09-14 02:07:58 -04:00
opencode 4b30705c42 release: v0.8.0 2025-09-14 06:07:43 +00:00
Mani Sundararajan 1f8d396b76 fix(dev): build tui with correct file ext for windows (#2590) 2025-09-14 01:59:25 -04:00
Aiden Cline 3752bb9717 fix: token counting visual bug (#2587) 2025-09-13 19:46:24 -05:00
Aiden Cline 16d66c209d respect subagent in command, add subtask flag (#2569) 2025-09-13 12:47:18 -05:00
Aiden Cline 6506e48c54 tweak: keep aborted msgs in context (#2583) 2025-09-13 12:25:30 -05:00
GitHub Action f0e8b7c29b ignore: update download stats 2025-09-13 2025-09-13 12:03:56 +00:00
Dax Raad a00b49d65b disable autocompact if context is 0 2025-09-13 05:59:18 -04:00
Dax Raad b1589be4ba add disable OPENCODE_DISABLE_AUTOCOMPACT 2025-09-13 05:55:04 -04:00
Dax Raad eb24d2f847 ignore: fix 2025-09-13 05:53:03 -04:00
Dax 9bb25a9260 Session management and prompt handling improvements (#2577)
Co-authored-by: GitHub Action <action@github.com>
2025-09-13 05:46:14 -04:00
95 changed files with 3084 additions and 3019 deletions
+2
View File
@@ -77,3 +77,5 @@
| 2025-09-10 | 307,287 (+7,251) | 233,435 (+3,647) | 540,722 (+10,898) |
| 2025-09-11 | 314,083 (+6,796) | 237,356 (+3,921) | 551,439 (+10,717) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |
| 2025-09-14 | 328,876 (+3,982) | 248,245 (+2,706) | 577,121 (+6,688) |
+22 -20
View File
@@ -7,7 +7,7 @@
"pulumi-stripe": "0.0.24",
},
"devDependencies": {
"prettier": "3.5.3",
"prettier": "3.6.2",
"sst": "3.17.13",
},
},
@@ -26,7 +26,7 @@
},
"cloud/core": {
"name": "@opencode/cloud-core",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@opencode/cloud-resource": "workspace:*",
@@ -43,7 +43,7 @@
},
"cloud/function": {
"name": "@opencode/cloud-function",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -69,7 +69,7 @@
},
"cloud/scripts": {
"name": "@opencode/cloud-scripts",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@opencode/cloud-core": "workspace:*",
"tsx": "4.20.5",
@@ -81,7 +81,7 @@
},
"packages/function": {
"name": "@opencode/function",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -96,12 +96,13 @@
},
"packages/opencode": {
"name": "opencode",
"version": "0.7.6",
"version": "0.8.0",
"bin": {
"opencode": "./bin/opencode",
},
"dependencies": {
"@clack/prompts": "1.0.0-alpha.1",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "1.15.1",
"@openauthjs/openauth": "0.4.3",
@@ -114,7 +115,7 @@
"diff": "8.0.2",
"gray-matter": "4.0.3",
"hono": "catalog:",
"hono-openapi": "0.4.8",
"hono-openapi": "1.0.7",
"ignore": "7.0.5",
"jsonc-parser": "3.3.1",
"minimatch": "10.0.3",
@@ -129,7 +130,6 @@
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
"zod-openapi": "4.1.0",
},
"devDependencies": {
"@ai-sdk/amazon-bedrock": "2.2.10",
@@ -146,7 +146,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
},
@@ -157,7 +157,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@hey-api/openapi-ts": "0.81.0",
},
@@ -169,7 +169,7 @@
},
"packages/web": {
"name": "@opencode/web",
"version": "0.7.6",
"version": "0.8.0",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -212,7 +212,7 @@
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch",
},
"overrides": {
"zod": "3.25.76",
"zod": "4.1.8",
},
"catalog": {
"@hono/zod-validator": "0.4.2",
@@ -224,7 +224,7 @@
"remeda": "2.26.0",
"solid-js": "1.9.9",
"typescript": "5.8.2",
"zod": "3.25.76",
"zod": "4.1.8",
},
"packages": {
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
@@ -243,8 +243,6 @@
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@11.9.3", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ=="],
"@astrojs/cloudflare": ["@astrojs/cloudflare@12.6.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "@astrojs/underscore-redirects": "1.0.0", "@cloudflare/workers-types": "^4.20250507.0", "tinyglobby": "^0.2.13", "vite": "^6.3.5", "wrangler": "^4.14.1" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-xhJptF5tU2k5eo70nIMyL1Udma0CqmUEnGSlGyFflLqSY82CRQI6nWZ/xZt0ZvmXuErUjIx0YYQNfZsz5CNjLQ=="],
"@astrojs/compiler": ["@astrojs/compiler@2.12.2", "", {}, "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw=="],
@@ -495,6 +493,8 @@
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.81.0", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "js-yaml": "4.1.0", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-PoJukNBkUfHOoMDpN33bBETX49TUhy7Hu8Sa0jslOvFndvZ5VjQr4Nl/Dzjb9LG1Lp5HjybyTJMA6a1zYk/q6A=="],
"@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
"@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="],
"@ibm/plex": ["@ibm/plex@6.4.1", "", { "dependencies": { "@ibm/telemetry-js": "^1.5.1" } }, "sha512-fnsipQywHt3zWvsnlyYKMikcVI7E2fEwpiPnIHFqlbByXVfQfANAAeJk1IV4mNnxhppUIDlhU0TzwYwL++Rn2g=="],
@@ -973,6 +973,10 @@
"@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="],
"@standard-community/standard-json": ["@standard-community/standard-json@0.3.1", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "valibot", "zod", "zod-to-json-schema"] }, "sha512-QYW1sZWWheij2CZnUL8LAFK5oECJe7cQUqtao1dY4Pjp/RPidOmpgS4L3pm9rR2gzFoyjpS5Q0MhF3c0Bxzevg=="],
"@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.4", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.1", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "openapi-types": "^12.1.3", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "valibot", "zod", "zod-openapi"] }, "sha512-guPU+9Y+Y9JN0gpBQbZMlIYzRSaRyTe7f+g6JCV3d0rrMQ5JFngLQKRyg3MP07xIts8nGim167Y9ePfdlkJp0Q=="],
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
"@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="],
@@ -1759,7 +1763,7 @@
"hono": ["hono@4.7.10", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
"hono-openapi": ["hono-openapi@0.4.8", "", { "dependencies": { "json-schema-walker": "^2.0.0" }, "peerDependencies": { "@hono/arktype-validator": "^2.0.0", "@hono/effect-validator": "^1.2.0", "@hono/typebox-validator": "^0.2.0 || ^0.3.0", "@hono/valibot-validator": "^0.5.1", "@hono/zod-validator": "^0.4.1", "@sinclair/typebox": "^0.34.9", "@valibot/to-json-schema": "^1.0.0-beta.3", "arktype": "^2.0.0", "effect": "^3.11.3", "hono": "^4.6.13", "openapi-types": "^12.1.3", "valibot": "^1.0.0-beta.9", "zod": "^3.23.8", "zod-openapi": "^4.0.0" }, "optionalPeers": ["@hono/arktype-validator", "@hono/effect-validator", "@hono/typebox-validator", "@hono/valibot-validator", "@hono/zod-validator", "@sinclair/typebox", "@valibot/to-json-schema", "arktype", "effect", "hono", "valibot", "zod", "zod-openapi"] }, "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA=="],
"hono-openapi": ["hono-openapi@1.0.7", "", { "peerDependencies": { "@hono/standard-validator": "^0.1.2", "@standard-community/standard-json": "^0.3.1", "@standard-community/standard-openapi": "^0.2.4", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-rMn+nn4/HMisyi549L3zT7WCmVvmpiKsyt790GcGfqvJf9mJfhq6txw09l0IhSBxpJpA0pXVKxFijcsnGfshUA=="],
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
@@ -1919,8 +1923,6 @@
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-schema-walker": ["json-schema-walker@2.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "clone": "^2.1.2" } }, "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw=="],
"json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
@@ -2365,7 +2367,7 @@
"precinct": ["precinct@12.2.0", "", { "dependencies": { "@dependents/detective-less": "^5.0.1", "commander": "^12.1.0", "detective-amd": "^6.0.1", "detective-cjs": "^6.0.1", "detective-es6": "^5.0.1", "detective-postcss": "^7.0.1", "detective-sass": "^6.0.1", "detective-scss": "^5.0.1", "detective-stylus": "^5.0.1", "detective-typescript": "^14.0.0", "detective-vue2": "^2.2.0", "module-definition": "^6.0.1", "node-source-walk": "^7.0.1", "postcss": "^8.5.1", "typescript": "^5.7.3" }, "bin": { "precinct": "bin/cli.js" } }, "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w=="],
"prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
@@ -2977,7 +2979,7 @@
"zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
"zod-openapi": ["zod-openapi@4.1.0", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-bRCwRYhEO9CmFLyKgJX8h6j1dRtRiwOe+TLzMVPyV0pRW5vRIgb1rLgIGcuRZ5z3MmSVrZqbv3yva4IJrtZK4g=="],
+1 -1
View File
@@ -7,7 +7,7 @@
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "vinxi build && ../../packages/opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "0.7.9"
"version": "0.9.0"
},
"dependencies": {
"@ibm/plex": "6.4.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.7.9",
"version": "0.9.0",
"private": true,
"type": "module",
"dependencies": {
+3 -3
View File
@@ -1,11 +1,11 @@
import { z } from "zod"
export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.output<T>) => Result) {
const result = (input: z.input<T>) => {
export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.infer<T>) => Result) {
const result = (input: z.infer<T>) => {
const parsed = schema.parse(input)
return cb(parsed)
}
result.force = (input: z.input<T>) => cb(input)
result.force = (input: z.infer<T>) => cb(input)
result.schema = schema
return result
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/cloud-function",
"version": "0.7.9",
"version": "0.9.0",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/cloud-scripts",
"version": "0.7.9",
"version": "0.9.0",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+3 -3
View File
@@ -24,7 +24,7 @@
"ai": "5.0.8",
"hono": "4.7.10",
"typescript": "5.8.2",
"zod": "3.25.76",
"zod": "4.1.8",
"remeda": "2.26.0",
"solid-js": "1.9.9"
}
@@ -33,7 +33,7 @@
"pulumi-stripe": "0.0.24"
},
"devDependencies": {
"prettier": "3.5.3",
"prettier": "3.6.2",
"sst": "3.17.13"
},
"repository": {
@@ -54,7 +54,7 @@
"web-tree-sitter"
],
"overrides": {
"zod": "3.25.76"
"zod": "4.1.8"
},
"patchedDependencies": {
"@solidjs/start@1.1.7": "patches/@solidjs%2Fstart@1.1.7.patch"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/function",
"version": "0.7.9",
"version": "0.9.0",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.7.9",
"version": "0.9.0",
"name": "opencode",
"type": "module",
"private": true,
@@ -28,6 +28,7 @@
},
"dependencies": {
"@clack/prompts": "1.0.0-alpha.1",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "1.15.1",
"@openauthjs/openauth": "0.4.3",
@@ -40,7 +41,7 @@
"diff": "8.0.2",
"gray-matter": "4.0.3",
"hono": "catalog:",
"hono-openapi": "0.4.8",
"hono-openapi": "1.0.7",
"ignore": "7.0.5",
"jsonc-parser": "3.3.1",
"minimatch": "10.0.3",
@@ -54,7 +55,6 @@
"web-tree-sitter": "0.22.6",
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
"zod-openapi": "4.1.0"
"zod": "catalog:"
}
}
+4 -9
View File
@@ -1,13 +1,12 @@
#!/usr/bin/env bun
import "zod-openapi/extend"
import { z } from "zod/v4"
import { Config } from "../src/config/config"
import { zodToJsonSchema } from "zod-to-json-schema"
const file = process.argv[2]
console.log(file)
const result = zodToJsonSchema(Config.Info, {
const result = z.toJSONSchema(Config.Info, {
/**
* We'll use the `default` values of the field as the only value in `examples`.
* This will ensure no docs are needed to be read, as the configuration is
@@ -15,10 +14,8 @@ const result = zodToJsonSchema(Config.Info, {
*
* See https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.9.5
*/
postProcess(jsonSchema) {
const schema = jsonSchema as typeof jsonSchema & {
examples?: unknown[]
}
override(input) {
const schema = input.jsonSchema
if (schema && typeof schema === "object" && "type" in schema && schema.type === "string" && schema?.default) {
if (!schema.examples) {
schema.examples = [schema.default]
@@ -29,8 +26,6 @@ const result = zodToJsonSchema(Config.Info, {
.join("\n\n")
.trim()
}
return jsonSchema
},
}) as Record<string, unknown> & {
allowComments?: boolean
+3 -3
View File
@@ -1,5 +1,5 @@
import { Config } from "../config/config"
import z from "zod"
import z from "zod/v4"
import { Provider } from "../provider/provider"
import { generateObject, type ModelMessage } from "ai"
import PROMPT_GENERATE from "./generate.txt"
@@ -28,10 +28,10 @@ export namespace Agent {
})
.optional(),
prompt: z.string().optional(),
tools: z.record(z.boolean()),
tools: z.record(z.string(), z.boolean()),
options: z.record(z.string(), z.any()),
})
.openapi({
.meta({
ref: "Agent",
})
export type Info = z.infer<typeof Info>
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Auth } from "./index"
import { NamedError } from "../util/error"
+5 -5
View File
@@ -1,7 +1,7 @@
import path from "path"
import { Global } from "../global"
import fs from "fs/promises"
import { z } from "zod"
import z from "zod/v4"
export namespace Auth {
export const Oauth = z
@@ -11,14 +11,14 @@ export namespace Auth {
access: z.string(),
expires: z.number(),
})
.openapi({ ref: "OAuth" })
.meta({ ref: "OAuth" })
export const Api = z
.object({
type: z.literal("api"),
key: z.string(),
})
.openapi({ ref: "ApiAuth" })
.meta({ ref: "ApiAuth" })
export const WellKnown = z
.object({
@@ -26,9 +26,9 @@ export namespace Auth {
key: z.string(),
token: z.string(),
})
.openapi({ ref: "WellKnownAuth" })
.meta({ ref: "WellKnownAuth" })
export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown]).openapi({ ref: "Auth" })
export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown]).meta({ ref: "Auth" })
export type Info = z.infer<typeof Info>
const filepath = path.join(Global.Path.data, "auth.json")
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Global } from "../global"
import { Log } from "../util/log"
import path from "path"
+3 -2
View File
@@ -1,4 +1,5 @@
import { z, type ZodType } from "zod"
import z from "zod/v4"
import type { ZodType } from "zod/v4"
import { Log } from "../util/log"
import { Instance } from "../project/instance"
@@ -38,7 +39,7 @@ export namespace Bus {
type: z.literal(type),
properties: def.properties,
})
.openapi({
.meta({
ref: "Event" + "." + def.type,
}),
)
+10 -2
View File
@@ -11,6 +11,7 @@ import { MessageV2 } from "../../session/message-v2"
import { Identifier } from "../../id/id"
import { Agent } from "../../agent/agent"
import { Command } from "../../command"
import { SessionPrompt } from "../../session/prompt"
const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
@@ -144,6 +145,7 @@ export const RunCommand = cmd({
}
let text = ""
Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
if (evt.properties.part.sessionID !== session.id) return
if (evt.properties.part.messageID === messageID) return
@@ -154,7 +156,13 @@ export const RunCommand = cmd({
const title =
part.state.title ||
(Object.keys(part.state.input).length > 0 ? JSON.stringify(part.state.input) : "Unknown")
printEvent(color, tool, title)
if (part.tool === "bash" && part.state.output && part.state.output.trim()) {
UI.println()
UI.println(part.state.output)
}
}
if (part.type === "text") {
@@ -185,7 +193,7 @@ export const RunCommand = cmd({
})
if (args.command) {
await Session.command({
await SessionPrompt.command({
messageID: Identifier.ascending("message"),
sessionID: session.id,
agent: agent.name,
@@ -197,7 +205,7 @@ export const RunCommand = cmd({
}
const messageID = Identifier.ascending("message")
const result = await Session.prompt({
const result = await SessionPrompt.prompt({
sessionID: session.id,
messageID,
model: {
+4 -3
View File
@@ -123,14 +123,15 @@ export const TuiCommand = cmd({
const file = Bun.file(binary)
if (!(await file.exists())) {
await Bun.write(file, tui, { mode: 0o755 })
await fs.chmod(binary, 0o755)
if (process.platform !== "win32") await fs.chmod(binary, 0o755)
}
cmd = [binary]
}
if (!tui) {
const dir = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
await $`go build -o ./dist/tui ./main.go`.cwd(dir)
cmd = [path.join(dir, "dist/tui")]
let binaryName = `./dist/tui${process.platform === "win32" ? ".exe" : ""}`
await $`go build -o ${binaryName} ./main.go`.cwd(dir)
cmd = [path.join(dir, binaryName)]
}
Log.Default.info("tui", {
cmd,
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { EOL } from "os"
import { NamedError } from "../util/error"
+4 -2
View File
@@ -1,4 +1,4 @@
import z from "zod"
import z from "zod/v4"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
@@ -10,8 +10,9 @@ export namespace Command {
agent: z.string().optional(),
model: z.string().optional(),
template: z.string(),
subtask: z.boolean().optional(),
})
.openapi({
.meta({
ref: "Command",
})
export type Info = z.infer<typeof Info>
@@ -28,6 +29,7 @@ export namespace Command {
model: command.model,
description: command.description,
template: command.template,
subtask: command.subtask,
}
}
+11 -9
View File
@@ -1,7 +1,7 @@
import { Log } from "../util/log"
import path from "path"
import os from "os"
import { z } from "zod"
import z from "zod/v4"
import { Filesystem } from "../util/filesystem"
import { ModelsDev } from "../provider/models"
import { mergeDeep, pipe } from "remeda"
@@ -217,7 +217,7 @@ export namespace Config {
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
})
.strict()
.openapi({
.meta({
ref: "McpLocalConfig",
})
@@ -229,7 +229,7 @@ export namespace Config {
headers: z.record(z.string(), z.string()).optional().describe("Headers to send with the request"),
})
.strict()
.openapi({
.meta({
ref: "McpRemoteConfig",
})
@@ -244,6 +244,7 @@ export namespace Config {
description: z.string().optional(),
agent: z.string().optional(),
model: z.string().optional(),
subtask: z.boolean().optional(),
})
export type Command = z.infer<typeof Command>
@@ -266,7 +267,7 @@ export namespace Config {
.optional(),
})
.catchall(z.any())
.openapi({
.meta({
ref: "AgentConfig",
})
export type Agent = z.infer<typeof Agent>
@@ -341,7 +342,7 @@ export namespace Config {
messages_revert: z.string().optional().default("none").describe("@deprecated use messages_undo. Revert message"),
})
.strict()
.openapi({
.meta({
ref: "KeybindsConfig",
})
@@ -349,7 +350,7 @@ export namespace Config {
scroll_speed: z.number().min(1).optional().default(2).describe("TUI scroll speed"),
})
export const Layout = z.enum(["auto", "stretch"]).openapi({
export const Layout = z.enum(["auto", "stretch"]).meta({
ref: "LayoutConfig",
})
export type Layout = z.infer<typeof Layout>
@@ -406,9 +407,10 @@ export namespace Config {
.describe("Agent configuration, see https://opencode.ai/docs/agent"),
provider: z
.record(
z.string(),
ModelsDev.Provider.partial()
.extend({
models: z.record(ModelsDev.Model.partial()).optional(),
models: z.record(z.string(), ModelsDev.Model.partial()).optional(),
options: z
.object({
apiKey: z.string().optional(),
@@ -504,7 +506,7 @@ export namespace Config {
.optional(),
})
.strict()
.openapi({
.meta({
ref: "Config",
})
@@ -643,7 +645,7 @@ export namespace Config {
"ConfigInvalidError",
z.object({
path: z.string(),
issues: z.custom<z.ZodIssue[]>().optional(),
issues: z.custom<z.core.$ZodIssue[]>().optional(),
message: z.string().optional(),
}),
)
+1 -1
View File
@@ -1,7 +1,7 @@
import path from "path"
import { Global } from "../global"
import fs from "fs/promises"
import { z } from "zod"
import z from "zod/v4"
import { NamedError } from "../util/error"
import { lazy } from "../util/lazy"
import { Log } from "../util/log"
+4 -4
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Bus } from "../bus"
import { $ } from "bun"
import { formatPatch, structuredPatch } from "diff"
@@ -18,7 +18,7 @@ export namespace File {
removed: z.number().int(),
status: z.enum(["added", "deleted", "modified"]),
})
.openapi({
.meta({
ref: "File",
})
@@ -32,7 +32,7 @@ export namespace File {
type: z.enum(["file", "directory"]),
ignored: z.boolean(),
})
.openapi({
.meta({
ref: "FileNode",
})
export type Node = z.infer<typeof Node>
@@ -60,7 +60,7 @@ export namespace File {
})
.optional(),
})
.openapi({
.meta({
ref: "FileContent",
})
export type Content = z.infer<typeof Content>
+1 -1
View File
@@ -2,7 +2,7 @@
import path from "path"
import { Global } from "../global"
import fs from "fs/promises"
import { z } from "zod"
import z from "zod/v4"
import { NamedError } from "../util/error"
import { lazy } from "../util/lazy"
import { $ } from "bun"
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Bus } from "../bus"
import fs from "fs"
import { Log } from "../util/log"
+1
View File
@@ -8,6 +8,7 @@ export namespace Flag {
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { randomBytes } from "crypto"
export namespace Identifier {
+1 -1
View File
@@ -1,5 +1,5 @@
import { spawn } from "bun"
import { z } from "zod"
import z from "zod/v4"
import { NamedError } from "../util/error"
import { Log } from "../util/log"
import { Bus } from "../bus"
-1
View File
@@ -1,4 +1,3 @@
import "zod-openapi/extend"
import yargs from "yargs"
import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run"
+2 -2
View File
@@ -1,6 +1,6 @@
import path from "path"
import { $ } from "bun"
import { z } from "zod"
import z from "zod/v4"
import { NamedError } from "../util/error"
import { Bus } from "../bus"
import { Log } from "../util/log"
@@ -28,7 +28,7 @@ export namespace Installation {
version: z.string(),
latest: z.string(),
})
.openapi({
.meta({
ref: "InstallationInfo",
})
export type Info = z.infer<typeof Info>
+1 -1
View File
@@ -4,7 +4,7 @@ import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types
import { Log } from "../util/log"
import { LANGUAGE_EXTENSIONS } from "./language"
import { Bus } from "../bus"
import z from "zod"
import z from "zod/v4"
import type { LSPServer } from "./server"
import { NamedError } from "../util/error"
import { withTimeout } from "../util/timeout"
+4 -4
View File
@@ -2,7 +2,7 @@ import { Log } from "../util/log"
import { LSPClient } from "./client"
import path from "path"
import { LSPServer } from "./server"
import { z } from "zod"
import z from "zod/v4"
import { Config } from "../config/config"
import { spawn } from "child_process"
import { Instance } from "../project/instance"
@@ -21,7 +21,7 @@ export namespace LSP {
character: z.number(),
}),
})
.openapi({
.meta({
ref: "Range",
})
export type Range = z.infer<typeof Range>
@@ -35,7 +35,7 @@ export namespace LSP {
range: Range,
}),
})
.openapi({
.meta({
ref: "Symbol",
})
export type Symbol = z.infer<typeof Symbol>
@@ -48,7 +48,7 @@ export namespace LSP {
range: Range,
selectionRange: Range,
})
.openapi({
.meta({
ref: "DocumentSymbol",
})
export type DocumentSymbol = z.infer<typeof DocumentSymbol>
+1 -1
View File
@@ -5,7 +5,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { Config } from "../config/config"
import { Log } from "../util/log"
import { NamedError } from "../util/error"
import { z } from "zod"
import z from "zod/v4"
import { Session } from "../session"
import { Bus } from "../bus"
import { Instance } from "../project/instance"
+26 -8
View File
@@ -1,28 +1,38 @@
import { z } from "zod"
import z from "zod/v4"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { Identifier } from "../id/id"
import { Plugin } from "../plugin"
import { Instance } from "../project/instance"
import { Wildcard } from "../util/wildcard"
export namespace Permission {
const log = Log.create({ service: "permission" })
function toKeys(pattern: Info["pattern"], type: string): string[] {
return pattern === undefined ? [type] : Array.isArray(pattern) ? pattern : [pattern]
}
function covered(keys: string[], approved: Record<string, boolean>): boolean {
const pats = Object.keys(approved)
return keys.every((k) => pats.some((p) => Wildcard.match(k, p)))
}
export const Info = z
.object({
id: z.string(),
type: z.string(),
pattern: z.string().optional(),
pattern: z.union([z.string(), z.array(z.string())]).optional(),
sessionID: z.string(),
messageID: z.string(),
callID: z.string().optional(),
title: z.string(),
metadata: z.record(z.any()),
metadata: z.record(z.string(), z.any()),
time: z.object({
created: z.number(),
}),
})
.openapi({
.meta({
ref: "Permission",
})
export type Info = z.infer<typeof Info>
@@ -83,7 +93,9 @@ export namespace Permission {
toolCallID: input.callID,
pattern: input.pattern,
})
if (approved[input.sessionID]?.[input.type]) return
const approvedForSession = approved[input.sessionID] || {}
const keys = toKeys(input.pattern, input.type)
if (covered(keys, approvedForSession)) return
const info: Info = {
id: Identifier.ascending("permission"),
type: input.type,
@@ -141,9 +153,15 @@ export namespace Permission {
})
if (input.response === "always") {
approved[input.sessionID] = approved[input.sessionID] || {}
approved[input.sessionID][match.info.type] = true
for (const item of Object.values(pending[input.sessionID])) {
if (item.info.type === match.info.type) {
const approveKeys = toKeys(match.info.pattern, match.info.type)
for (const k of approveKeys) {
approved[input.sessionID][k] = true
}
const items = pending[input.sessionID]
if (!items) return
for (const item of Object.values(items)) {
const itemKeys = toKeys(item.info.pattern, item.info.type)
if (covered(itemKeys, approved[input.sessionID])) {
respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response })
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import z from "zod"
import z from "zod/v4"
import { Filesystem } from "../util/filesystem"
import path from "path"
import { $ } from "bun"
@@ -17,7 +17,7 @@ export namespace Project {
initialized: z.number().optional(),
}),
})
.openapi({
.meta({
ref: "Project",
})
export type Info = z.infer<typeof Info>
+5 -5
View File
@@ -1,7 +1,7 @@
import { Global } from "../global"
import { Log } from "../util/log"
import path from "path"
import { z } from "zod"
import z from "zod/v4"
import { data } from "./models-macro" with { type: "macro" }
import { Installation } from "../installation"
@@ -29,10 +29,10 @@ export namespace ModelsDev {
output: z.number(),
}),
experimental: z.boolean().optional(),
options: z.record(z.any()),
options: z.record(z.string(), z.any()),
provider: z.object({ npm: z.string() }).optional(),
})
.openapi({
.meta({
ref: "Model",
})
export type Model = z.infer<typeof Model>
@@ -44,9 +44,9 @@ export namespace ModelsDev {
env: z.array(z.string()),
id: z.string(),
npm: z.string().optional(),
models: z.record(Model),
models: z.record(z.string(), Model),
})
.openapi({
.meta({
ref: "Provider",
})
+1 -1
View File
@@ -1,4 +1,4 @@
import z from "zod"
import z from "zod/v4"
import path from "path"
import { Config } from "../config/config"
import { mergeDeep, sortBy } from "remeda"
@@ -1,5 +1,6 @@
import type { ModelMessage } from "ai"
import { unique } from "remeda"
import type { JSONSchema } from "zod/v4/core"
export namespace ProviderTransform {
function normalizeToolCallIds(msgs: ModelMessage[]): ModelMessage[] {
@@ -112,4 +113,29 @@ export namespace ProviderTransform {
}
return outputLimit
}
export function schema(_providerID: string, _modelID: string, schema: JSONSchema.BaseSchema) {
/*
if (["openai", "azure"].includes(providerID)) {
if (schema.type === "object" && schema.properties) {
for (const [key, value] of Object.entries(schema.properties)) {
if (schema.required?.includes(key)) continue
schema.properties[key] = {
anyOf: [
value as JSONSchema.JSONSchema,
{
type: "null",
},
],
}
}
}
}
if (providerID === "google") {
}
*/
return schema
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { Hono } from "hono"
import { describeRoute } from "hono-openapi"
import { resolver } from "hono-openapi/zod"
import { resolver } from "hono-openapi"
import { Instance } from "../project/instance"
import { Project } from "../project/project"
+75 -80
View File
@@ -1,11 +1,10 @@
import { Log } from "../util/log"
import { Bus } from "../bus"
import { describeRoute, generateSpecs, openAPISpecs } from "hono-openapi"
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
import { Hono } from "hono"
import { streamSSE } from "hono/streaming"
import { Session } from "../session"
import { resolver, validator as zValidator } from "hono-openapi/zod"
import { z } from "zod"
import z from "zod/v4"
import { Provider } from "../provider/provider"
import { mapValues } from "remeda"
import { NamedError } from "../util/error"
@@ -25,6 +24,9 @@ import { Global } from "../global"
import { ProjectRoute } from "./project"
import { ToolRegistry } from "../tool/registry"
import { zodToJsonSchema } from "zod-to-json-schema"
import { SessionPrompt } from "../session/prompt"
import { SessionCompaction } from "../session/compaction"
import { SessionRevert } from "../session/revert"
const ERRORS = {
400: {
@@ -36,7 +38,7 @@ const ERRORS = {
.object({
data: z.record(z.string(), z.any()),
})
.openapi({
.meta({
ref: "Error",
}),
),
@@ -56,7 +58,7 @@ export namespace Server {
optional: z.boolean().optional(),
items: z.enum(["string", "number", "boolean"]).optional(),
})
.openapi({ ref: "HttpParamSpec" })
.meta({ ref: "HttpParamSpec" })
const HttpToolRegistration = z
.object({
@@ -64,12 +66,12 @@ export namespace Server {
description: z.string(),
parameters: z.object({
type: z.literal("object"),
properties: z.record(HttpParamSpec),
properties: z.record(z.string(), HttpParamSpec),
}),
callbackUrl: z.string(),
headers: z.record(z.string(), z.string()).optional(),
})
.openapi({ ref: "HttpToolRegistration" })
.meta({ ref: "HttpToolRegistration" })
export const Event = {
Connected: Bus.event("server.connected", z.object({})),
@@ -112,10 +114,9 @@ export namespace Server {
return next()
})
})
.use(zValidator("query", z.object({ directory: z.string().optional() })))
.get(
"/doc",
openAPISpecs(app, {
openAPIRouteHandler(app, {
documentation: {
info: {
title: "opencode",
@@ -126,6 +127,7 @@ export namespace Server {
},
}),
)
.use(validator("query", z.object({ directory: z.string().optional() })))
.route("/project", ProjectRoute)
.get(
"/event",
@@ -138,7 +140,7 @@ export namespace Server {
content: {
"text/event-stream": {
schema: resolver(
Bus.payloads().openapi({
Bus.payloads().meta({
ref: "Event",
}),
),
@@ -208,7 +210,7 @@ export namespace Server {
...ERRORS,
},
}),
zValidator("json", HttpToolRegistration),
validator("json", HttpToolRegistration),
async (c) => {
ToolRegistry.registerHTTP(c.req.valid("json"))
return c.json(true)
@@ -224,7 +226,7 @@ export namespace Server {
description: "Tool IDs",
content: {
"application/json": {
schema: resolver(z.array(z.string()).openapi({ ref: "ToolIDs" })),
schema: resolver(z.array(z.string()).meta({ ref: "ToolIDs" })),
},
},
},
@@ -254,9 +256,9 @@ export namespace Server {
description: z.string(),
parameters: z.any(),
})
.openapi({ ref: "ToolListItem" }),
.meta({ ref: "ToolListItem" }),
)
.openapi({ ref: "ToolList" }),
.meta({ ref: "ToolList" }),
),
},
},
@@ -264,7 +266,7 @@ export namespace Server {
...ERRORS,
},
}),
zValidator(
validator(
"query",
z.object({
provider: z.string(),
@@ -302,7 +304,7 @@ export namespace Server {
worktree: z.string(),
directory: z.string(),
})
.openapi({
.meta({
ref: "Path",
}),
),
@@ -358,7 +360,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
@@ -386,7 +388,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
@@ -415,7 +417,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"json",
z
.object({
@@ -446,7 +448,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
@@ -473,13 +475,13 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
}),
),
zValidator(
validator(
"json",
z.object({
title: z.string().optional(),
@@ -514,13 +516,13 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
id: z.string().meta({ description: "Session ID" }),
}),
),
zValidator(
validator(
"json",
z.object({
messageID: z.string(),
@@ -551,14 +553,14 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
}),
),
async (c) => {
return c.json(Session.abort(c.req.valid("param").id))
return c.json(SessionPrompt.abort(c.req.valid("param").id))
},
)
.post(
@@ -577,7 +579,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
@@ -606,7 +608,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
@@ -635,13 +637,13 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
id: z.string().meta({ description: "Session ID" }),
}),
),
zValidator(
validator(
"json",
z.object({
providerID: z.string(),
@@ -651,7 +653,7 @@ export namespace Server {
async (c) => {
const id = c.req.valid("param").id
const body = c.req.valid("json")
await Session.summarize({ ...body, sessionID: id })
await SessionCompaction.run({ ...body, sessionID: id })
return c.json(true)
},
)
@@ -665,23 +667,16 @@ export namespace Server {
description: "List of messages",
content: {
"application/json": {
schema: resolver(
z
.object({
info: MessageV2.Info,
parts: MessageV2.Part.array(),
})
.array(),
),
schema: resolver(MessageV2.WithParts.array()),
},
},
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
id: z.string().meta({ description: "Session ID" }),
}),
),
async (c) => {
@@ -710,11 +705,11 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
messageID: z.string().openapi({ description: "Message ID" }),
id: z.string().meta({ description: "Session ID" }),
messageID: z.string().meta({ description: "Message ID" }),
}),
),
async (c) => {
@@ -744,17 +739,17 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
id: z.string().meta({ description: "Session ID" }),
}),
),
zValidator("json", Session.PromptInput.omit({ sessionID: true })),
validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.prompt({ ...body, sessionID })
const msg = await SessionPrompt.prompt({ ...body, sessionID })
return c.json(msg)
},
)
@@ -779,17 +774,17 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
id: z.string().meta({ description: "Session ID" }),
}),
),
zValidator("json", Session.CommandInput.omit({ sessionID: true })),
validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.command({ ...body, sessionID })
const msg = await SessionPrompt.command({ ...body, sessionID })
return c.json(msg)
},
)
@@ -809,17 +804,17 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
id: z.string().meta({ description: "Session ID" }),
}),
),
zValidator("json", Session.ShellInput.omit({ sessionID: true })),
validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.shell({ ...body, sessionID })
const msg = await SessionPrompt.shell({ ...body, sessionID })
return c.json(msg)
},
)
@@ -839,17 +834,17 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
}),
),
zValidator("json", Session.RevertInput.omit({ sessionID: true })),
validator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
async (c) => {
const id = c.req.valid("param").id
log.info("revert", c.req.valid("json"))
const session = await Session.revert({ sessionID: id, ...c.req.valid("json") })
const session = await SessionRevert.revert({ sessionID: id, ...c.req.valid("json") })
return c.json(session)
},
)
@@ -869,7 +864,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
@@ -877,7 +872,7 @@ export namespace Server {
),
async (c) => {
const id = c.req.valid("param").id
const session = await Session.unrevert({ sessionID: id })
const session = await SessionRevert.unrevert({ sessionID: id })
return c.json(session)
},
)
@@ -896,14 +891,14 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
permissionID: z.string(),
}),
),
zValidator("json", z.object({ response: Permission.Response })),
validator("json", z.object({ response: Permission.Response })),
async (c) => {
const params = c.req.valid("param")
const id = params.id
@@ -978,7 +973,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"query",
z.object({
pattern: z.string(),
@@ -1010,7 +1005,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"query",
z.object({
query: z.string(),
@@ -1042,7 +1037,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"query",
z.object({
query: z.string(),
@@ -1070,7 +1065,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"query",
z.object({
path: z.string(),
@@ -1098,7 +1093,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"query",
z.object({
path: z.string(),
@@ -1147,16 +1142,16 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"json",
z.object({
service: z.string().openapi({ description: "Service name for the log entry" }),
level: z.enum(["debug", "info", "error", "warn"]).openapi({ description: "Log level" }),
message: z.string().openapi({ description: "Log message" }),
service: z.string().meta({ description: "Service name for the log entry" }),
level: z.enum(["debug", "info", "error", "warn"]).meta({ description: "Log level" }),
message: z.string().meta({ description: "Log message" }),
extra: z
.record(z.string(), z.any())
.optional()
.openapi({ description: "Additional metadata for the log entry" }),
.meta({ description: "Additional metadata for the log entry" }),
}),
),
async (c) => {
@@ -1218,7 +1213,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"json",
z.object({
text: z.string(),
@@ -1350,7 +1345,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"json",
z.object({
command: z.string(),
@@ -1374,7 +1369,7 @@ export namespace Server {
},
},
}),
zValidator(
validator(
"json",
z.object({
title: z.string().optional(),
@@ -1402,13 +1397,13 @@ export namespace Server {
...ERRORS,
},
}),
zValidator(
validator(
"param",
z.object({
id: z.string(),
}),
),
zValidator("json", Auth.Info),
validator("json", Auth.Info),
async (c) => {
const id = c.req.valid("param").id
const info = c.req.valid("json")
+124
View File
@@ -0,0 +1,124 @@
import { generateText, type ModelMessage } from "ai"
import { Session } from "."
import { Identifier } from "../id/id"
import { Instance } from "../project/instance"
import { Provider } from "../provider/provider"
import { defer } from "../util/defer"
import { MessageV2 } from "./message-v2"
import { SystemPrompt } from "./system"
import { Bus } from "../bus"
import z from "zod/v4"
import type { ModelsDev } from "../provider/models"
import { SessionPrompt } from "./prompt"
import { Flag } from "../flag/flag"
export namespace SessionCompaction {
export const Event = {
Compacted: Bus.event(
"session.compacted",
z.object({
sessionID: z.string(),
}),
),
}
export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false
const context = input.model.limit.context
if (context === 0) return false
const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
const output = Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) || SessionPrompt.OUTPUT_TOKEN_MAX
const usable = context - output
return count > usable
}
export async function run(input: { sessionID: string; providerID: string; modelID: string }) {
await Session.update(input.sessionID, (draft) => {
draft.time.compacting = Date.now()
})
await using _ = defer(async () => {
await Session.update(input.sessionID, (draft) => {
draft.time.compacting = undefined
})
})
const toSummarize = await Session.messages(input.sessionID).then(MessageV2.filterSummarized)
const model = await Provider.getModel(input.providerID, input.modelID)
const system = [
...SystemPrompt.summarize(model.providerID),
...(await SystemPrompt.environment()),
...(await SystemPrompt.custom()),
]
const msg = (await Session.updateMessage({
id: Identifier.ascending("message"),
role: "assistant",
sessionID: input.sessionID,
system,
mode: "build",
path: {
cwd: Instance.directory,
root: Instance.worktree,
},
cost: 0,
tokens: {
output: 0,
input: 0,
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: input.modelID,
providerID: model.providerID,
time: {
created: Date.now(),
},
})) as MessageV2.Assistant
const generated = await generateText({
maxRetries: 10,
model: model.language,
messages: [
...system.map(
(x): ModelMessage => ({
role: "system",
content: x,
}),
),
...MessageV2.toModelMessage(toSummarize),
{
role: "user",
content: [
{
type: "text",
text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
},
],
},
],
})
const usage = Session.getUsage(model.info, generated.usage, generated.providerMetadata)
msg.cost += usage.cost
msg.tokens = usage.tokens
msg.summary = true
msg.time.completed = Date.now()
await Session.updateMessage(msg)
const part = await Session.updatePart({
type: "text",
sessionID: input.sessionID,
messageID: msg.id,
id: Identifier.ascending("part"),
text: generated.text,
time: {
start: Date.now(),
end: Date.now(),
},
})
Bus.publish(Event.Compacted, {
sessionID: input.sessionID,
})
return {
info: msg,
parts: [part],
}
}
}
File diff suppressed because it is too large Load Diff
+51 -30
View File
@@ -1,4 +1,4 @@
import z from "zod"
import z from "zod/v4"
import { Bus } from "../bus"
import { NamedError } from "../util/error"
import { Message } from "./message"
@@ -8,7 +8,7 @@ import { LSP } from "../lsp"
export namespace MessageV2 {
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
export const AbortedError = NamedError.create("MessageAbortedError", z.object({}))
export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
export const AuthError = NamedError.create(
"ProviderAuthError",
z.object({
@@ -21,7 +21,7 @@ export namespace MessageV2 {
.object({
status: z.literal("pending"),
})
.openapi({
.meta({
ref: "ToolStatePending",
})
@@ -32,12 +32,12 @@ export namespace MessageV2 {
status: z.literal("running"),
input: z.any(),
title: z.string().optional(),
metadata: z.record(z.any()).optional(),
metadata: z.record(z.string(), z.any()).optional(),
time: z.object({
start: z.number(),
}),
})
.openapi({
.meta({
ref: "ToolStateRunning",
})
export type ToolStateRunning = z.infer<typeof ToolStateRunning>
@@ -45,17 +45,17 @@ export namespace MessageV2 {
export const ToolStateCompleted = z
.object({
status: z.literal("completed"),
input: z.record(z.any()),
input: z.record(z.string(), z.any()),
output: z.string(),
title: z.string(),
metadata: z.record(z.any()),
metadata: z.record(z.string(), z.any()),
time: z.object({
start: z.number(),
end: z.number(),
compacted: z.number().optional(),
}),
})
.openapi({
.meta({
ref: "ToolStateCompleted",
})
export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>
@@ -63,22 +63,22 @@ export namespace MessageV2 {
export const ToolStateError = z
.object({
status: z.literal("error"),
input: z.record(z.any()),
input: z.record(z.string(), z.any()),
error: z.string(),
metadata: z.record(z.any()).optional(),
metadata: z.record(z.string(), z.any()).optional(),
time: z.object({
start: z.number(),
end: z.number(),
}),
})
.openapi({
.meta({
ref: "ToolStateError",
})
export type ToolStateError = z.infer<typeof ToolStateError>
export const ToolState = z
.discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
.openapi({
.meta({
ref: "ToolState",
})
@@ -91,7 +91,7 @@ export namespace MessageV2 {
export const SnapshotPart = PartBase.extend({
type: z.literal("snapshot"),
snapshot: z.string(),
}).openapi({
}).meta({
ref: "SnapshotPart",
})
export type SnapshotPart = z.infer<typeof SnapshotPart>
@@ -100,7 +100,7 @@ export namespace MessageV2 {
type: z.literal("patch"),
hash: z.string(),
files: z.string().array(),
}).openapi({
}).meta({
ref: "PatchPart",
})
export type PatchPart = z.infer<typeof PatchPart>
@@ -115,7 +115,7 @@ export namespace MessageV2 {
end: z.number().optional(),
})
.optional(),
}).openapi({
}).meta({
ref: "TextPart",
})
export type TextPart = z.infer<typeof TextPart>
@@ -123,12 +123,12 @@ export namespace MessageV2 {
export const ReasoningPart = PartBase.extend({
type: z.literal("reasoning"),
text: z.string(),
metadata: z.record(z.any()).optional(),
metadata: z.record(z.string(), z.any()).optional(),
time: z.object({
start: z.number(),
end: z.number().optional(),
}),
}).openapi({
}).meta({
ref: "ReasoningPart",
})
export type ReasoningPart = z.infer<typeof ReasoningPart>
@@ -138,7 +138,7 @@ export namespace MessageV2 {
callID: z.string(),
tool: z.string(),
state: ToolState,
}).openapi({
}).meta({
ref: "ToolPart",
})
export type ToolPart = z.infer<typeof ToolPart>
@@ -150,7 +150,7 @@ export namespace MessageV2 {
start: z.number().int(),
end: z.number().int(),
})
.openapi({
.meta({
ref: "FilePartSourceText",
}),
})
@@ -158,7 +158,7 @@ export namespace MessageV2 {
export const FileSource = FilePartSourceBase.extend({
type: z.literal("file"),
path: z.string(),
}).openapi({
}).meta({
ref: "FileSource",
})
@@ -168,11 +168,11 @@ export namespace MessageV2 {
range: LSP.Range,
name: z.string(),
kind: z.number().int(),
}).openapi({
}).meta({
ref: "SymbolSource",
})
export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource]).openapi({
export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource]).meta({
ref: "FilePartSource",
})
@@ -182,7 +182,7 @@ export namespace MessageV2 {
filename: z.string().optional(),
url: z.string(),
source: FilePartSource.optional(),
}).openapi({
}).meta({
ref: "FilePart",
})
export type FilePart = z.infer<typeof FilePart>
@@ -197,14 +197,14 @@ export namespace MessageV2 {
end: z.number().int(),
})
.optional(),
}).openapi({
}).meta({
ref: "AgentPart",
})
export type AgentPart = z.infer<typeof AgentPart>
export const StepStartPart = PartBase.extend({
type: z.literal("step-start"),
}).openapi({
}).meta({
ref: "StepStartPart",
})
export type StepStartPart = z.infer<typeof StepStartPart>
@@ -221,7 +221,7 @@ export namespace MessageV2 {
write: z.number(),
}),
}),
}).openapi({
}).meta({
ref: "StepFinishPart",
})
export type StepFinishPart = z.infer<typeof StepFinishPart>
@@ -236,7 +236,7 @@ export namespace MessageV2 {
time: z.object({
created: z.number(),
}),
}).openapi({
}).meta({
ref: "UserMessage",
})
export type User = z.infer<typeof User>
@@ -253,7 +253,7 @@ export namespace MessageV2 {
PatchPart,
AgentPart,
])
.openapi({
.meta({
ref: "Part",
})
export type Part = z.infer<typeof Part>
@@ -291,12 +291,12 @@ export namespace MessageV2 {
write: z.number(),
}),
}),
}).openapi({
}).meta({
ref: "AssistantMessage",
})
export type Assistant = z.infer<typeof Assistant>
export const Info = z.discriminatedUnion("role", [User, Assistant]).openapi({
export const Info = z.discriminatedUnion("role", [User, Assistant]).meta({
ref: "Message",
})
export type Info = z.infer<typeof Info>
@@ -331,6 +331,12 @@ export namespace MessageV2 {
),
}
export const WithParts = z.object({
info: Info,
parts: z.array(Part),
})
export type WithParts = z.infer<typeof WithParts>
export function fromV1(v1: Message.Info) {
if (v1.role === "assistant") {
const info: Assistant = {
@@ -543,6 +549,15 @@ export namespace MessageV2 {
},
]
}
if (part.type === "reasoning") {
return [
{
type: "reasoning",
text: part.text,
providerMetadata: part.metadata,
},
]
}
return []
}),
@@ -552,4 +567,10 @@ export namespace MessageV2 {
return convertToModelMessages(result)
}
export function filterSummarized(msgs: { info: MessageV2.Info; parts: MessageV2.Part[] }[]) {
const i = msgs.findLastIndex((m) => m.info.role === "assistant" && !!m.info.summary)
if (i === -1) return msgs.slice()
return msgs.slice(i)
}
}
+16 -16
View File
@@ -1,4 +1,4 @@
import z from "zod"
import z from "zod/v4"
import { NamedError } from "../util/error"
export namespace Message {
@@ -19,7 +19,7 @@ export namespace Message {
toolName: z.string(),
args: z.custom<Required<unknown>>(),
})
.openapi({
.meta({
ref: "ToolCall",
})
export type ToolCall = z.infer<typeof ToolCall>
@@ -32,7 +32,7 @@ export namespace Message {
toolName: z.string(),
args: z.custom<Required<unknown>>(),
})
.openapi({
.meta({
ref: "ToolPartialCall",
})
export type ToolPartialCall = z.infer<typeof ToolPartialCall>
@@ -46,12 +46,12 @@ export namespace Message {
args: z.custom<Required<unknown>>(),
result: z.string(),
})
.openapi({
.meta({
ref: "ToolResult",
})
export type ToolResult = z.infer<typeof ToolResult>
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).openapi({
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
ref: "ToolInvocation",
})
export type ToolInvocation = z.infer<typeof ToolInvocation>
@@ -61,7 +61,7 @@ export namespace Message {
type: z.literal("text"),
text: z.string(),
})
.openapi({
.meta({
ref: "TextPart",
})
export type TextPart = z.infer<typeof TextPart>
@@ -70,9 +70,9 @@ export namespace Message {
.object({
type: z.literal("reasoning"),
text: z.string(),
providerMetadata: z.record(z.any()).optional(),
providerMetadata: z.record(z.string(), z.any()).optional(),
})
.openapi({
.meta({
ref: "ReasoningPart",
})
export type ReasoningPart = z.infer<typeof ReasoningPart>
@@ -82,7 +82,7 @@ export namespace Message {
type: z.literal("tool-invocation"),
toolInvocation: ToolInvocation,
})
.openapi({
.meta({
ref: "ToolInvocationPart",
})
export type ToolInvocationPart = z.infer<typeof ToolInvocationPart>
@@ -93,9 +93,9 @@ export namespace Message {
sourceId: z.string(),
url: z.string(),
title: z.string().optional(),
providerMetadata: z.record(z.any()).optional(),
providerMetadata: z.record(z.string(), z.any()).optional(),
})
.openapi({
.meta({
ref: "SourceUrlPart",
})
export type SourceUrlPart = z.infer<typeof SourceUrlPart>
@@ -107,7 +107,7 @@ export namespace Message {
filename: z.string().optional(),
url: z.string(),
})
.openapi({
.meta({
ref: "FilePart",
})
export type FilePart = z.infer<typeof FilePart>
@@ -116,14 +116,14 @@ export namespace Message {
.object({
type: z.literal("step-start"),
})
.openapi({
.meta({
ref: "StepStartPart",
})
export type StepStartPart = z.infer<typeof StepStartPart>
export const MessagePart = z
.discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
.openapi({
.meta({
ref: "MessagePart",
})
export type MessagePart = z.infer<typeof MessagePart>
@@ -180,9 +180,9 @@ export namespace Message {
.optional(),
snapshot: z.string().optional(),
})
.openapi({ ref: "MessageMetadata" }),
.meta({ ref: "MessageMetadata" }),
})
.openapi({
.meta({
ref: "Message",
})
export type Info = z.infer<typeof Info>
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
import z from "zod/v4"
import { Identifier } from "../id/id"
import { Snapshot } from "../snapshot"
import { MessageV2 } from "./message-v2"
import { Session } from "."
import { Log } from "../util/log"
import { splitWhen } from "remeda"
import { Storage } from "../storage/storage"
import { Bus } from "../bus"
export namespace SessionRevert {
const log = Log.create({ service: "session.revert" })
export const RevertInput = z.object({
sessionID: Identifier.schema("session"),
messageID: Identifier.schema("message"),
partID: Identifier.schema("part").optional(),
})
export type RevertInput = z.infer<typeof RevertInput>
export async function revert(input: RevertInput) {
const all = await Session.messages(input.sessionID)
let lastUser: MessageV2.User | undefined
const session = await Session.get(input.sessionID)
let revert: Session.Info["revert"]
const patches: Snapshot.Patch[] = []
for (const msg of all) {
if (msg.info.role === "user") lastUser = msg.info
const remaining = []
for (const part of msg.parts) {
if (revert) {
if (part.type === "patch") {
patches.push(part)
}
continue
}
if (!revert) {
if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
// if no useful parts left in message, same as reverting whole message
const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
revert = {
messageID: !partID && lastUser ? lastUser.id : msg.info.id,
partID,
}
}
remaining.push(part)
}
}
}
if (revert) {
const session = await Session.get(input.sessionID)
revert.snapshot = session.revert?.snapshot ?? (await Snapshot.track())
await Snapshot.revert(patches)
if (revert.snapshot) revert.diff = await Snapshot.diff(revert.snapshot)
return Session.update(input.sessionID, (draft) => {
draft.revert = revert
})
}
return session
}
export async function unrevert(input: { sessionID: string }) {
log.info("unreverting", input)
const session = await Session.get(input.sessionID)
if (!session.revert) return session
if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot)
const next = await Session.update(input.sessionID, (draft) => {
draft.revert = undefined
})
return next
}
export async function cleanup(session: Session.Info) {
if (!session.revert) return
const sessionID = session.id
let msgs = await Session.messages(sessionID)
const messageID = session.revert.messageID
const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
msgs = preserve
for (const msg of remove) {
await Storage.remove(["message", sessionID, msg.info.id])
await Bus.publish(MessageV2.Event.Removed, { sessionID: sessionID, messageID: msg.info.id })
}
const last = preserve.at(-1)
if (session.revert.partID && last) {
const partID = session.revert.partID
const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
last.parts = preserveParts
for (const part of removeParts) {
await Storage.remove(["part", last.info.id, part.id])
await Bus.publish(MessageV2.Event.PartRemoved, {
sessionID: sessionID,
messageID: last.info.id,
partID: part.id,
})
}
}
await Session.update(sessionID, (draft) => {
draft.revert = undefined
})
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ import path from "path"
import fs from "fs/promises"
import { Log } from "../util/log"
import { Global } from "../global"
import { z } from "zod"
import z from "zod/v4"
import { Config } from "../config/config"
import { Instance } from "../project/instance"
+31 -6
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { exec } from "child_process"
import { Tool } from "./tool"
@@ -59,7 +59,7 @@ export const BashTool = Tool.define("bash", {
const tree = await parser().then((p) => p.parse(params.command))
const permissions = await Agent.get(ctx.agent).then((x) => x.permission.bash)
let needsAsk = false
const askPatterns = new Set<string>()
for (const node of tree.rootNode.descendantsOfType("command")) {
const command = []
for (let i = 0; i < node.childCount; i++) {
@@ -96,27 +96,52 @@ export const BashTool = Tool.define("bash", {
}
// always allow cd if it passes above check
if (!needsAsk && command[0] !== "cd") {
if (command[0] !== "cd") {
const action = Wildcard.all(node.text, permissions)
if (action === "deny") {
throw new Error(
`The user has specifically restricted access to this command, you are not allowed to execute it. Here is the configuration: ${JSON.stringify(permissions)}`,
)
}
if (action === "ask") needsAsk = true
if (action === "ask") {
const pattern = (() => {
let head = ""
let sub: string | undefined
for (let i = 0; i < node.childCount; i++) {
const child = node.child(i)
if (!child) continue
if (child.type === "command_name") {
if (!head) {
head = child.text
}
continue
}
if (!sub && child.type === "word") {
if (!child.text.startsWith("-")) sub = child.text
}
}
if (!head) return
return sub ? `${head} ${sub} *` : `${head} *`
})()
if (pattern) {
askPatterns.add(pattern)
}
}
}
}
if (needsAsk) {
if (askPatterns.size > 0) {
const patterns = Array.from(askPatterns)
await Permission.ask({
type: "bash",
pattern: params.command,
pattern: patterns,
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.callID,
title: params.command,
metadata: {
command: params.command,
patterns,
},
})
}
+3 -3
View File
@@ -3,8 +3,8 @@ Executes a given bash command in a persistent shell session with optional timeou
Before executing the command, please follow these steps:
1. Directory Verification:
- If the command will create new directories or files, first use the LS tool to verify the parent directory exists and is the correct location
- For example, before running "mkdir foo/bar", first use LS to check that "foo" exists and is the intended parent directory
- If the command will create new directories or files, first use the List tool to verify the parent directory exists and is the correct location
- For example, before running "mkdir foo/bar", first use List to check that "foo" exists and is the intended parent directory
2. Command Execution:
- Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
@@ -21,7 +21,7 @@ Usage notes:
- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 120000ms (2 minutes).
- It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
- If the output exceeds 30000 characters, output will be truncated before being returned to you.
- VERY IMPORTANT: You MUST avoid using search commands like `find` and `grep`. Instead use Grep, Glob, or Task to search. You MUST avoid read tools like `cat`, `head`, `tail`, and `ls`, and use Read and LS to read files.
- VERY IMPORTANT: You MUST avoid using search commands like `find` and `grep`. Instead use Grep, Glob, or Task to search. You MUST avoid read tools like `cat`, `head`, `tail`, and `ls`, and use Read and List to read files.
- If you _still_ need to run `grep`, STOP. ALWAYS USE ripgrep at `rg` (or /usr/bin/rg) first, which all opencode users have pre-installed.
- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it.
+1 -2
View File
@@ -3,7 +3,7 @@
// https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/utils/editCorrector.ts
// https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-26-25.ts
import { z } from "zod"
import z from "zod/v4"
import * as path from "path"
import { Tool } from "./tool"
import { LSP } from "../lsp"
@@ -82,7 +82,6 @@ export const EditTool = Tool.define("edit", {
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.callID,
pattern: filePath,
title: "Edit this file: " + filePath,
metadata: {
filePath,
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import path from "path"
import { Tool } from "./tool"
import DESCRIPTION from "./glob.txt"
+1 -1
View File
@@ -2,5 +2,5 @@
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
- Returns matching file paths sorted by modification time
- Use this tool when you need to find files by name patterns
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead
- You have the capability to call multiple tools in a single response. It is always better to speculatively perform multiple searches as a batch that are potentially useful.
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import { Ripgrep } from "../file/ripgrep"
+1 -1
View File
@@ -5,4 +5,4 @@
- Returns file paths with at least one match sorted by modification time
- Use this tool when you need to find files containing specific patterns
- If you need to identify/count the number of matches within files, use the Bash tool with `rg` (ripgrep) directly. Do NOT use `grep`.
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Task tool instead
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
export const InvalidTool = Tool.define("invalid", {
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import * as path from "path"
import DESCRIPTION from "./ls.txt"
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import path from "path"
import { LSP } from "../lsp"
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import path from "path"
import { LSP } from "../lsp"
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import { EditTool } from "./edit"
import DESCRIPTION from "./multiedit.txt"
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import * as path from "path"
import * as fs from "fs/promises"
import { Tool } from "./tool"
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import * as fs from "fs"
import * as path from "path"
import { Tool } from "./tool"
+2 -113
View File
@@ -1,4 +1,4 @@
import z from "zod"
import z from "zod/v4"
import { BashTool } from "./bash"
import { EditTool } from "./edit"
import { GlobTool } from "./glob"
@@ -124,35 +124,13 @@ export namespace ToolRegistry {
return allTools().map((t) => t.id)
}
export async function tools(providerID: string, _modelID: string) {
export async function tools(_providerID: string, _modelID: string) {
const result = await Promise.all(
allTools().map(async (t) => ({
id: t.id,
...(await t.init()),
})),
)
if (providerID === "openai") {
return result.map((t) => ({
...t,
parameters: optionalToNullable(t.parameters as unknown as z.ZodTypeAny),
}))
}
if (providerID === "azure") {
return result.map((t) => ({
...t,
parameters: optionalToNullable(t.parameters as unknown as z.ZodTypeAny),
}))
}
if (providerID === "google") {
return result.map((t) => ({
...t,
parameters: sanitizeGeminiParameters(t.parameters as unknown as z.ZodTypeAny),
}))
}
return result
}
@@ -178,93 +156,4 @@ export namespace ToolRegistry {
return result
}
function sanitizeGeminiParameters(schema: z.ZodTypeAny, visited = new Set()): z.ZodTypeAny {
if (!schema || visited.has(schema)) {
return schema
}
visited.add(schema)
if (schema instanceof z.ZodDefault) {
const innerSchema = schema.removeDefault()
// Handle Gemini's incompatibility with `default` on `anyOf` (unions).
if (innerSchema instanceof z.ZodUnion) {
// The schema was `z.union(...).default(...)`, which is not allowed.
// We strip the default and return the sanitized union.
return sanitizeGeminiParameters(innerSchema, visited)
}
// Otherwise, the default is on a regular type, which is allowed.
// We recurse on the inner type and then re-apply the default.
return sanitizeGeminiParameters(innerSchema, visited).default(schema._def.defaultValue())
}
if (schema instanceof z.ZodOptional) {
return z.optional(sanitizeGeminiParameters(schema.unwrap(), visited))
}
if (schema instanceof z.ZodObject) {
const newShape: Record<string, z.ZodTypeAny> = {}
for (const [key, value] of Object.entries(schema.shape)) {
newShape[key] = sanitizeGeminiParameters(value as z.ZodTypeAny, visited)
}
return z.object(newShape)
}
if (schema instanceof z.ZodArray) {
return z.array(sanitizeGeminiParameters(schema.element, visited))
}
if (schema instanceof z.ZodUnion) {
// This schema corresponds to `anyOf` in JSON Schema.
// We recursively sanitize each option in the union.
const sanitizedOptions = schema.options.map((option: z.ZodTypeAny) => sanitizeGeminiParameters(option, visited))
return z.union(sanitizedOptions as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
}
if (schema instanceof z.ZodString) {
const newSchema = z.string({ description: schema.description })
const safeChecks = ["min", "max", "length", "regex", "startsWith", "endsWith", "includes", "trim"]
// rome-ignore lint/suspicious/noExplicitAny: <explanation>
;(newSchema._def as any).checks = (schema._def as z.ZodStringDef).checks.filter((check) =>
safeChecks.includes(check.kind),
)
return newSchema
}
return schema
}
function optionalToNullable(schema: z.ZodTypeAny): z.ZodTypeAny {
if (schema instanceof z.ZodObject) {
const shape = schema.shape
const newShape: Record<string, z.ZodTypeAny> = {}
for (const [key, value] of Object.entries(shape)) {
const zodValue = value as z.ZodTypeAny
if (zodValue instanceof z.ZodOptional) {
newShape[key] = zodValue.unwrap().nullable()
} else {
newShape[key] = optionalToNullable(zodValue)
}
}
return z.object(newShape)
}
if (schema instanceof z.ZodArray) {
return z.array(optionalToNullable(schema.element))
}
if (schema instanceof z.ZodUnion) {
return z.union(
schema.options.map((option: z.ZodTypeAny) => optionalToNullable(option)) as [
z.ZodTypeAny,
z.ZodTypeAny,
...z.ZodTypeAny[],
],
)
}
return schema
}
}
+4 -3
View File
@@ -1,11 +1,12 @@
import { Tool } from "./tool"
import DESCRIPTION from "./task.txt"
import { z } from "zod"
import z from "zod/v4"
import { Session } from "../session"
import { Bus } from "../bus"
import { MessageV2 } from "../session/message-v2"
import { Identifier } from "../id/id"
import { Agent } from "../agent/agent"
import { SessionPrompt } from "../session/prompt"
export const TaskTool = Tool.define("task", async () => {
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
@@ -49,9 +50,9 @@ export const TaskTool = Tool.define("task", async () => {
}
ctx.abort.addEventListener("abort", () => {
Session.abort(session.id)
SessionPrompt.abort(session.id)
})
const result = await Session.prompt({
const result = await SessionPrompt.prompt({
messageID,
sessionID: session.id,
model: {
+5 -5
View File
@@ -5,13 +5,13 @@ Available agent types and the tools they have access to:
When using the Task tool, you must specify a subagent_type parameter to select which agent type to use.
When to use the Agent tool:
- When you are instructed to execute custom slash commands. Use the Agent tool with the slash command invocation as the entire prompt. The slash command can take arguments. For example: Task(description="Check the file", prompt="/check-file path/to/file.py")
When to use the Task tool:
- When you are instructed to execute custom slash commands. Use the Task tool with the slash command invocation as the entire prompt. The slash command can take arguments. For example: Task(description="Check the file", prompt="/check-file path/to/file.py")
When NOT to use the Agent tool:
- If you want to read a specific file path, use the Read or Glob tool instead of the Agent tool, to find the match more quickly
When NOT to use the Task tool:
- If you want to read a specific file path, use the Read or Glob tool instead of the Task tool, to find the match more quickly
- If you are searching for a specific class definition like "class Foo", use the Glob tool instead, to find the match more quickly
- If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Agent tool, to find the match more quickly
- If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly
- Other tasks that are not related to the agent descriptions above
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import DESCRIPTION_WRITE from "./todowrite.txt"
import { Instance } from "../project/instance"
+4 -4
View File
@@ -1,4 +1,4 @@
import type { StandardSchemaV1 } from "@standard-schema/spec"
import z from "zod/v4"
export namespace Tool {
interface Metadata {
@@ -13,13 +13,13 @@ export namespace Tool {
extra?: { [key: string]: any }
metadata(input: { title?: string; metadata?: M }): void
}
export interface Info<Parameters extends StandardSchemaV1 = StandardSchemaV1, M extends Metadata = Metadata> {
export interface Info<Parameters extends z.ZodType = z.ZodType, M extends Metadata = Metadata> {
id: string
init: () => Promise<{
description: string
parameters: Parameters
execute(
args: StandardSchemaV1.InferOutput<Parameters>,
args: z.infer<Parameters>,
ctx: Context,
): Promise<{
title: string
@@ -29,7 +29,7 @@ export namespace Tool {
}>
}
export function define<Parameters extends StandardSchemaV1, Result extends Metadata>(
export function define<Parameters extends z.ZodType, Result extends Metadata>(
id: string,
init: Info<Parameters, Result>["init"] | Awaited<ReturnType<Info<Parameters, Result>["init"]>>,
): Info<Parameters, Result> {
+1 -2
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import { Tool } from "./tool"
import TurndownService from "turndown"
import DESCRIPTION from "./webfetch.txt"
@@ -28,7 +28,6 @@ export const WebFetchTool = Tool.define("webfetch", {
if (cfg.permission?.webfetch === "ask")
await Permission.ask({
type: "webfetch",
pattern: params.url,
sessionID: ctx.sessionID,
messageID: ctx.messageID,
callID: ctx.callID,
+1 -1
View File
@@ -1,4 +1,4 @@
import { z } from "zod"
import z from "zod/v4"
import * as path from "path"
import { Tool } from "./tool"
import { LSP } from "../lsp"
+4 -4
View File
@@ -1,19 +1,19 @@
import { z, type ZodSchema } from "zod"
import z from "zod/v4"
// import { Log } from "./log"
// const log = Log.create()
export abstract class NamedError extends Error {
abstract schema(): ZodSchema
abstract schema(): z.core.$ZodType
abstract toObject(): { name: string; data: any }
static create<Name extends string, Data extends ZodSchema>(name: Name, data: Data) {
static create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data) {
const schema = z
.object({
name: z.literal(name),
data,
})
.openapi({
.meta({
ref: name,
})
const result = class extends NamedError {
+2 -2
View File
@@ -1,10 +1,10 @@
import path from "path"
import fs from "fs/promises"
import { Global } from "../global"
import z from "zod"
import z from "zod/v4"
export namespace Log {
export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).openapi({ ref: "LogLevel", description: "Log level" })
export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
export type Level = z.infer<typeof Level>
const levelPriority: Record<Level, number> = {
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { Session } from "../../src/session/index"
import { SessionPrompt } from "../../src/session/prompt"
describe("fileRegex", () => {
const template = `This is a @valid/path/to/a/file and it should also match at
@@ -23,7 +23,7 @@ as well as @~/home-files and @~/paths/under/home.txt.
If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
const matches = Array.from(template.matchAll(Session.fileRegex))
const matches = Array.from(template.matchAll(SessionPrompt.fileRegex))
test("should extract exactly 12 file references", () => {
expect(matches.length).toBe(12)
@@ -79,13 +79,13 @@ If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
test("should not match when preceded by backtick", () => {
const backtickTest = "This `@should/not/match` should be ignored"
const backtickMatches = Array.from(backtickTest.matchAll(Session.fileRegex))
const backtickMatches = Array.from(backtickTest.matchAll(SessionPrompt.fileRegex))
expect(backtickMatches.length).toBe(0)
})
test("should not match email addresses", () => {
const emailTest = "Contact user@example.com for help"
const emailMatches = Array.from(emailTest.matchAll(Session.fileRegex))
const emailMatches = Array.from(emailTest.matchAll(SessionPrompt.fileRegex))
expect(emailMatches.length).toBe(0)
})
})
@@ -1,4 +1,3 @@
import "zod-openapi/extend"
import { describe, expect, test } from "bun:test"
import path from "path"
import os from "os"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "0.7.9",
"version": "0.9.0",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
@@ -1,3 +1,3 @@
{
".": "0.9.0"
".": "0.13.0"
}
+2 -2
View File
@@ -1,4 +1,4 @@
configured_endpoints: 43
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-46826ba8640557721614b0c9a3f1860681d825ca8d8b12869652fa25aacb0b4c.yml
openapi_spec_hash: 33b8db6fde3021579b21325ce910197d
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-2e754dafcad0636137256cef499b2bcd72cf17de08f44ec03c3589b2a05341a2.yml
openapi_spec_hash: 2d3cf84d3033068ce6c07386411527ef
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
+32
View File
@@ -1,5 +1,37 @@
# Changelog
## 0.13.0 (2025-09-14)
Full Changelog: [v0.12.0...v0.13.0](https://github.com/sst/opencode-sdk-go/compare/v0.12.0...v0.13.0)
### Features
- **api:** api update ([80da4fb](https://github.com/sst/opencode-sdk-go/commit/80da4fb4ea9c6afb51a7e7135d9f5560ce6f2a6c))
## 0.12.0 (2025-09-14)
Full Changelog: [v0.11.0...v0.12.0](https://github.com/sst/opencode-sdk-go/compare/v0.11.0...v0.12.0)
### Features
- **api:** api update ([7e3808b](https://github.com/sst/opencode-sdk-go/commit/7e3808ba349dc653174b32b48a1120c18d2975c2))
## 0.11.0 (2025-09-14)
Full Changelog: [v0.10.0...v0.11.0](https://github.com/sst/opencode-sdk-go/compare/v0.10.0...v0.11.0)
### Features
- **api:** api update ([a3d37f5](https://github.com/sst/opencode-sdk-go/commit/a3d37f5671545866547d351fc21b49809cc8b3c2))
## 0.10.0 (2025-09-11)
Full Changelog: [v0.9.0...v0.10.0](https://github.com/sst/opencode-sdk-go/compare/v0.9.0...v0.10.0)
### Features
- **api:** api update ([0dc01f6](https://github.com/sst/opencode-sdk-go/commit/0dc01f6695c9b8400a4dc92166c5002bb120cf50))
## 0.9.0 (2025-09-10)
Full Changelog: [v0.8.0...v0.9.0](https://github.com/sst/opencode-sdk-go/compare/v0.8.0...v0.9.0)
+1 -1
View File
@@ -24,7 +24,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->
```sh
go get -u 'github.com/sst/opencode-sdk-go@v0.9.0'
go get -u 'github.com/sst/opencode-sdk-go@v0.13.0'
```
<!-- x-release-please-end -->
+3
View File
@@ -12,6 +12,9 @@ type Error = apierror.Error
// This is an alias to an internal type.
type MessageAbortedError = shared.MessageAbortedError
// This is an alias to an internal type.
type MessageAbortedErrorData = shared.MessageAbortedErrorData
// This is an alias to an internal type.
type MessageAbortedErrorName = shared.MessageAbortedErrorName
+2
View File
@@ -47,6 +47,7 @@ type Command struct {
Agent string `json:"agent"`
Description string `json:"description"`
Model string `json:"model"`
Subtask bool `json:"subtask"`
JSON commandJSON `json:"-"`
}
@@ -57,6 +58,7 @@ type commandJSON struct {
Agent apijson.Field
Description apijson.Field
Model apijson.Field
Subtask apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
+67 -66
View File
@@ -676,6 +676,7 @@ type ConfigCommand struct {
Agent string `json:"agent"`
Description string `json:"description"`
Model string `json:"model"`
Subtask bool `json:"subtask"`
JSON configCommandJSON `json:"-"`
}
@@ -685,6 +686,7 @@ type configCommandJSON struct {
Agent apijson.Field
Description apijson.Field
Model apijson.Field
Subtask apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -698,18 +700,18 @@ func (r configCommandJSON) RawJSON() string {
}
type ConfigExperimental struct {
Hook ConfigExperimentalHook `json:"hook"`
DisablePasteSummary bool `json:"disable_paste_summary"`
Hook ConfigExperimentalHook `json:"hook"`
JSON configExperimentalJSON `json:"-"`
}
// configExperimentalJSON contains the JSON metadata for the struct
// [ConfigExperimental]
type configExperimentalJSON struct {
Hook apijson.Field
SummarizePaste apijson.Field
raw string
ExtraFields map[string]apijson.Field
DisablePasteSummary apijson.Field
Hook apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigExperimental) UnmarshalJSON(data []byte) (err error) {
@@ -1022,16 +1024,14 @@ type ConfigMcpUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigMcpUnion)(nil)).Elem(),
"type",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(McpLocalConfig{}),
DiscriminatorValue: "local",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(McpLocalConfig{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(McpRemoteConfig{}),
DiscriminatorValue: "remote",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(McpRemoteConfig{}),
},
)
}
@@ -1753,15 +1753,15 @@ func (r ConfigShare) IsKnown() bool {
// TUI specific settings
type ConfigTui struct {
// TUI scroll speed
ScrollSpeed float64 `json:"scroll_speed,required"`
JSON configTuiJSON `json:"-"`
ScrollSpeed float64 `json:"scroll_speed"`
JSON configTuiJSON `json:"-"`
}
// configTuiJSON contains the JSON metadata for the struct [ConfigTui]
type configTuiJSON struct {
ScrollSpeed apijson.Field
raw string
ExtraFields map[string]apijson.Field
ScrollSpeed apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigTui) UnmarshalJSON(data []byte) (err error) {
@@ -1772,105 +1772,106 @@ func (r configTuiJSON) RawJSON() string {
return r.raw
}
// Custom keybind configurations
type KeybindsConfig struct {
// Next agent
AgentCycle string `json:"agent_cycle,required"`
AgentCycle string `json:"agent_cycle"`
// Previous agent
AgentCycleReverse string `json:"agent_cycle_reverse,required"`
AgentCycleReverse string `json:"agent_cycle_reverse"`
// List agents
AgentList string `json:"agent_list,required"`
AgentList string `json:"agent_list"`
// Exit the application
AppExit string `json:"app_exit,required"`
AppExit string `json:"app_exit"`
// Show help dialog
AppHelp string `json:"app_help,required"`
AppHelp string `json:"app_help"`
// Open external editor
EditorOpen string `json:"editor_open,required"`
EditorOpen string `json:"editor_open"`
// @deprecated Close file
FileClose string `json:"file_close,required"`
FileClose string `json:"file_close"`
// @deprecated Split/unified diff
FileDiffToggle string `json:"file_diff_toggle,required"`
FileDiffToggle string `json:"file_diff_toggle"`
// @deprecated Currently not available. List files
FileList string `json:"file_list,required"`
FileList string `json:"file_list"`
// @deprecated Search file
FileSearch string `json:"file_search,required"`
FileSearch string `json:"file_search"`
// Clear input field
InputClear string `json:"input_clear,required"`
InputClear string `json:"input_clear"`
// Insert newline in input
InputNewline string `json:"input_newline,required"`
InputNewline string `json:"input_newline"`
// Paste from clipboard
InputPaste string `json:"input_paste,required"`
InputPaste string `json:"input_paste"`
// Submit input
InputSubmit string `json:"input_submit,required"`
InputSubmit string `json:"input_submit"`
// Leader key for keybind combinations
Leader string `json:"leader,required"`
Leader string `json:"leader"`
// Copy message
MessagesCopy string `json:"messages_copy,required"`
MessagesCopy string `json:"messages_copy"`
// Navigate to first message
MessagesFirst string `json:"messages_first,required"`
MessagesFirst string `json:"messages_first"`
// Scroll messages down by half page
MessagesHalfPageDown string `json:"messages_half_page_down,required"`
MessagesHalfPageDown string `json:"messages_half_page_down"`
// Scroll messages up by half page
MessagesHalfPageUp string `json:"messages_half_page_up,required"`
MessagesHalfPageUp string `json:"messages_half_page_up"`
// Navigate to last message
MessagesLast string `json:"messages_last,required"`
MessagesLast string `json:"messages_last"`
// @deprecated Toggle layout
MessagesLayoutToggle string `json:"messages_layout_toggle,required"`
MessagesLayoutToggle string `json:"messages_layout_toggle"`
// @deprecated Navigate to next message
MessagesNext string `json:"messages_next,required"`
MessagesNext string `json:"messages_next"`
// Scroll messages down by one page
MessagesPageDown string `json:"messages_page_down,required"`
MessagesPageDown string `json:"messages_page_down"`
// Scroll messages up by one page
MessagesPageUp string `json:"messages_page_up,required"`
MessagesPageUp string `json:"messages_page_up"`
// @deprecated Navigate to previous message
MessagesPrevious string `json:"messages_previous,required"`
MessagesPrevious string `json:"messages_previous"`
// Redo message
MessagesRedo string `json:"messages_redo,required"`
MessagesRedo string `json:"messages_redo"`
// @deprecated use messages_undo. Revert message
MessagesRevert string `json:"messages_revert,required"`
MessagesRevert string `json:"messages_revert"`
// Undo message
MessagesUndo string `json:"messages_undo,required"`
MessagesUndo string `json:"messages_undo"`
// Next recent model
ModelCycleRecent string `json:"model_cycle_recent,required"`
ModelCycleRecent string `json:"model_cycle_recent"`
// Previous recent model
ModelCycleRecentReverse string `json:"model_cycle_recent_reverse,required"`
ModelCycleRecentReverse string `json:"model_cycle_recent_reverse"`
// List available models
ModelList string `json:"model_list,required"`
ModelList string `json:"model_list"`
// Create/update AGENTS.md
ProjectInit string `json:"project_init,required"`
ProjectInit string `json:"project_init"`
// Cycle to next child session
SessionChildCycle string `json:"session_child_cycle,required"`
SessionChildCycle string `json:"session_child_cycle"`
// Cycle to previous child session
SessionChildCycleReverse string `json:"session_child_cycle_reverse,required"`
SessionChildCycleReverse string `json:"session_child_cycle_reverse"`
// Compact the session
SessionCompact string `json:"session_compact,required"`
SessionCompact string `json:"session_compact"`
// Export session to editor
SessionExport string `json:"session_export,required"`
SessionExport string `json:"session_export"`
// Interrupt current session
SessionInterrupt string `json:"session_interrupt,required"`
SessionInterrupt string `json:"session_interrupt"`
// List all sessions
SessionList string `json:"session_list,required"`
SessionList string `json:"session_list"`
// Create a new session
SessionNew string `json:"session_new,required"`
SessionNew string `json:"session_new"`
// Share current session
SessionShare string `json:"session_share,required"`
SessionShare string `json:"session_share"`
// Show session timeline
SessionTimeline string `json:"session_timeline,required"`
SessionTimeline string `json:"session_timeline"`
// Unshare current session
SessionUnshare string `json:"session_unshare,required"`
SessionUnshare string `json:"session_unshare"`
// @deprecated use agent_cycle. Next agent
SwitchAgent string `json:"switch_agent,required"`
SwitchAgent string `json:"switch_agent"`
// @deprecated use agent_cycle_reverse. Previous agent
SwitchAgentReverse string `json:"switch_agent_reverse,required"`
SwitchAgentReverse string `json:"switch_agent_reverse"`
// @deprecated use agent_cycle. Next mode
SwitchMode string `json:"switch_mode,required"`
SwitchMode string `json:"switch_mode"`
// @deprecated use agent_cycle_reverse. Previous mode
SwitchModeReverse string `json:"switch_mode_reverse,required"`
SwitchModeReverse string `json:"switch_mode_reverse"`
// List available themes
ThemeList string `json:"theme_list,required"`
ThemeList string `json:"theme_list"`
// Toggle thinking blocks
ThinkingBlocks string `json:"thinking_blocks,required"`
ThinkingBlocks string `json:"thinking_blocks"`
// Toggle tool details
ToolDetails string `json:"tool_details,required"`
ToolDetails string `json:"tool_details"`
JSON keybindsConfigJSON `json:"-"`
}
+174 -193
View File
@@ -57,14 +57,14 @@ type EventListResponse struct {
// [EventListResponseEventMessageUpdatedProperties],
// [EventListResponseEventMessageRemovedProperties],
// [EventListResponseEventMessagePartUpdatedProperties],
// [EventListResponseEventMessagePartRemovedProperties], [Permission],
// [EventListResponseEventMessagePartRemovedProperties],
// [EventListResponseEventSessionCompactedProperties], [Permission],
// [EventListResponseEventPermissionRepliedProperties],
// [EventListResponseEventFileEditedProperties],
// [EventListResponseEventSessionIdleProperties],
// [EventListResponseEventSessionUpdatedProperties],
// [EventListResponseEventSessionDeletedProperties],
// [EventListResponseEventSessionIdleProperties],
// [EventListResponseEventSessionErrorProperties],
// [EventListResponseEventSessionCompactedProperties], [interface{}].
// [EventListResponseEventSessionErrorProperties], [interface{}].
Properties interface{} `json:"properties,required"`
Type EventListResponseType `json:"type,required"`
JSON eventListResponseJSON `json:"-"`
@@ -102,11 +102,11 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
// [EventListResponseEventMessagePartUpdated],
// [EventListResponseEventMessagePartRemoved],
// [EventListResponseEventSessionCompacted],
// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
// [EventListResponseEventSessionCompacted],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionUpdated],
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError],
// [EventListResponseEventServerConnected].
func (r EventListResponse) AsUnion() EventListResponseUnion {
return r.union
@@ -117,11 +117,11 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
// [EventListResponseEventMessagePartUpdated],
// [EventListResponseEventMessagePartRemoved],
// [EventListResponseEventSessionCompacted],
// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
// [EventListResponseEventSessionCompacted] or
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionUpdated],
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError] or
// [EventListResponseEventServerConnected].
type EventListResponseUnion interface {
implementsEventListResponse()
@@ -130,81 +130,66 @@ type EventListResponseUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*EventListResponseUnion)(nil)).Elem(),
"type",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventInstallationUpdated{}),
DiscriminatorValue: "installation.updated",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventInstallationUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventLspClientDiagnostics{}),
DiscriminatorValue: "lsp.client.diagnostics",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventLspClientDiagnostics{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessageUpdated{}),
DiscriminatorValue: "message.updated",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessageUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessageRemoved{}),
DiscriminatorValue: "message.removed",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessageRemoved{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessagePartUpdated{}),
DiscriminatorValue: "message.part.updated",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessagePartUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessagePartRemoved{}),
DiscriminatorValue: "message.part.removed",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessagePartRemoved{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
DiscriminatorValue: "permission.updated",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionCompacted{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}),
DiscriminatorValue: "permission.replied",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventPermissionUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
DiscriminatorValue: "file.edited",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventPermissionReplied{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}),
DiscriminatorValue: "session.updated",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventFileEdited{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionDeleted{}),
DiscriminatorValue: "session.deleted",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionIdle{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionIdle{}),
DiscriminatorValue: "session.idle",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionUpdated{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
DiscriminatorValue: "session.error",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionDeleted{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionCompacted{}),
DiscriminatorValue: "session.compacted",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
DiscriminatorValue: "server.connected",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
},
)
}
@@ -577,6 +562,66 @@ func (r EventListResponseEventMessagePartRemovedType) IsKnown() bool {
return false
}
type EventListResponseEventSessionCompacted struct {
Properties EventListResponseEventSessionCompactedProperties `json:"properties,required"`
Type EventListResponseEventSessionCompactedType `json:"type,required"`
JSON eventListResponseEventSessionCompactedJSON `json:"-"`
}
// eventListResponseEventSessionCompactedJSON contains the JSON metadata for the
// struct [EventListResponseEventSessionCompacted]
type eventListResponseEventSessionCompactedJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionCompacted) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionCompactedJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventSessionCompacted) implementsEventListResponse() {}
type EventListResponseEventSessionCompactedProperties struct {
SessionID string `json:"sessionID,required"`
JSON eventListResponseEventSessionCompactedPropertiesJSON `json:"-"`
}
// eventListResponseEventSessionCompactedPropertiesJSON contains the JSON metadata
// for the struct [EventListResponseEventSessionCompactedProperties]
type eventListResponseEventSessionCompactedPropertiesJSON struct {
SessionID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionCompactedProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionCompactedPropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventSessionCompactedType string
const (
EventListResponseEventSessionCompactedTypeSessionCompacted EventListResponseEventSessionCompactedType = "session.compacted"
)
func (r EventListResponseEventSessionCompactedType) IsKnown() bool {
switch r {
case EventListResponseEventSessionCompactedTypeSessionCompacted:
return true
}
return false
}
type EventListResponseEventPermissionUpdated struct {
Properties Permission `json:"properties,required"`
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
@@ -740,6 +785,66 @@ func (r EventListResponseEventFileEditedType) IsKnown() bool {
return false
}
type EventListResponseEventSessionIdle struct {
Properties EventListResponseEventSessionIdleProperties `json:"properties,required"`
Type EventListResponseEventSessionIdleType `json:"type,required"`
JSON eventListResponseEventSessionIdleJSON `json:"-"`
}
// eventListResponseEventSessionIdleJSON contains the JSON metadata for the struct
// [EventListResponseEventSessionIdle]
type eventListResponseEventSessionIdleJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionIdle) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionIdleJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventSessionIdle) implementsEventListResponse() {}
type EventListResponseEventSessionIdleProperties struct {
SessionID string `json:"sessionID,required"`
JSON eventListResponseEventSessionIdlePropertiesJSON `json:"-"`
}
// eventListResponseEventSessionIdlePropertiesJSON contains the JSON metadata for
// the struct [EventListResponseEventSessionIdleProperties]
type eventListResponseEventSessionIdlePropertiesJSON struct {
SessionID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionIdleProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionIdlePropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventSessionIdleType string
const (
EventListResponseEventSessionIdleTypeSessionIdle EventListResponseEventSessionIdleType = "session.idle"
)
func (r EventListResponseEventSessionIdleType) IsKnown() bool {
switch r {
case EventListResponseEventSessionIdleTypeSessionIdle:
return true
}
return false
}
type EventListResponseEventSessionUpdated struct {
Properties EventListResponseEventSessionUpdatedProperties `json:"properties,required"`
Type EventListResponseEventSessionUpdatedType `json:"type,required"`
@@ -860,66 +965,6 @@ func (r EventListResponseEventSessionDeletedType) IsKnown() bool {
return false
}
type EventListResponseEventSessionIdle struct {
Properties EventListResponseEventSessionIdleProperties `json:"properties,required"`
Type EventListResponseEventSessionIdleType `json:"type,required"`
JSON eventListResponseEventSessionIdleJSON `json:"-"`
}
// eventListResponseEventSessionIdleJSON contains the JSON metadata for the struct
// [EventListResponseEventSessionIdle]
type eventListResponseEventSessionIdleJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionIdle) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionIdleJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventSessionIdle) implementsEventListResponse() {}
type EventListResponseEventSessionIdleProperties struct {
SessionID string `json:"sessionID,required"`
JSON eventListResponseEventSessionIdlePropertiesJSON `json:"-"`
}
// eventListResponseEventSessionIdlePropertiesJSON contains the JSON metadata for
// the struct [EventListResponseEventSessionIdleProperties]
type eventListResponseEventSessionIdlePropertiesJSON struct {
SessionID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionIdleProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionIdlePropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventSessionIdleType string
const (
EventListResponseEventSessionIdleTypeSessionIdle EventListResponseEventSessionIdleType = "session.idle"
)
func (r EventListResponseEventSessionIdleType) IsKnown() bool {
switch r {
case EventListResponseEventSessionIdleTypeSessionIdle:
return true
}
return false
}
type EventListResponseEventSessionError struct {
Properties EventListResponseEventSessionErrorProperties `json:"properties,required"`
Type EventListResponseEventSessionErrorType `json:"type,required"`
@@ -970,7 +1015,7 @@ func (r eventListResponseEventSessionErrorPropertiesJSON) RawJSON() string {
type EventListResponseEventSessionErrorPropertiesError struct {
// This field can have the runtime type of [shared.ProviderAuthErrorData],
// [shared.UnknownErrorData], [interface{}].
// [shared.UnknownErrorData], [interface{}], [shared.MessageAbortedErrorData].
Data interface{} `json:"data,required"`
Name EventListResponseEventSessionErrorPropertiesErrorName `json:"name,required"`
JSON eventListResponseEventSessionErrorPropertiesErrorJSON `json:"-"`
@@ -1020,26 +1065,22 @@ type EventListResponseEventSessionErrorPropertiesErrorUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*EventListResponseEventSessionErrorPropertiesErrorUnion)(nil)).Elem(),
"name",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.ProviderAuthError{}),
DiscriminatorValue: "ProviderAuthError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.ProviderAuthError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.UnknownError{}),
DiscriminatorValue: "UnknownError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.UnknownError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError{}),
DiscriminatorValue: "MessageOutputLengthError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventSessionErrorPropertiesErrorMessageOutputLengthError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.MessageAbortedError{}),
DiscriminatorValue: "MessageAbortedError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.MessageAbortedError{}),
},
)
}
@@ -1116,66 +1157,6 @@ func (r EventListResponseEventSessionErrorType) IsKnown() bool {
return false
}
type EventListResponseEventSessionCompacted struct {
Properties EventListResponseEventSessionCompactedProperties `json:"properties,required"`
Type EventListResponseEventSessionCompactedType `json:"type,required"`
JSON eventListResponseEventSessionCompactedJSON `json:"-"`
}
// eventListResponseEventSessionCompactedJSON contains the JSON metadata for the
// struct [EventListResponseEventSessionCompacted]
type eventListResponseEventSessionCompactedJSON struct {
Properties apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionCompacted) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionCompactedJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventSessionCompacted) implementsEventListResponse() {}
type EventListResponseEventSessionCompactedProperties struct {
SessionID string `json:"sessionID,required"`
JSON eventListResponseEventSessionCompactedPropertiesJSON `json:"-"`
}
// eventListResponseEventSessionCompactedPropertiesJSON contains the JSON metadata
// for the struct [EventListResponseEventSessionCompactedProperties]
type eventListResponseEventSessionCompactedPropertiesJSON struct {
SessionID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventSessionCompactedProperties) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventSessionCompactedPropertiesJSON) RawJSON() string {
return r.raw
}
type EventListResponseEventSessionCompactedType string
const (
EventListResponseEventSessionCompactedTypeSessionCompacted EventListResponseEventSessionCompactedType = "session.compacted"
)
func (r EventListResponseEventSessionCompactedType) IsKnown() bool {
switch r {
case EventListResponseEventSessionCompactedTypeSessionCompacted:
return true
}
return false
}
type EventListResponseEventServerConnected struct {
Properties interface{} `json:"properties,required"`
Type EventListResponseEventServerConnectedType `json:"type,required"`
@@ -1224,20 +1205,20 @@ const (
EventListResponseTypeMessageRemoved EventListResponseType = "message.removed"
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
EventListResponseTypeMessagePartRemoved EventListResponseType = "message.part.removed"
EventListResponseTypeSessionCompacted EventListResponseType = "session.compacted"
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
EventListResponseTypePermissionReplied EventListResponseType = "permission.replied"
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
EventListResponseTypeSessionUpdated EventListResponseType = "session.updated"
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
EventListResponseTypeSessionIdle EventListResponseType = "session.idle"
EventListResponseTypeSessionError EventListResponseType = "session.error"
EventListResponseTypeSessionCompacted EventListResponseType = "session.compacted"
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
)
func (r EventListResponseType) IsKnown() bool {
switch r {
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeSessionCompacted, EventListResponseTypeServerConnected:
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeSessionCompacted, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionIdle, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionError, EventListResponseTypeServerConnected:
return true
}
return false
+1 -1
View File
@@ -2,4 +2,4 @@
package internal
const PackageVersion = "0.9.0" // x-release-please-version
const PackageVersion = "0.13.0" // x-release-please-version
+55 -74
View File
@@ -518,7 +518,7 @@ func (r assistantMessageTokensCacheJSON) RawJSON() string {
type AssistantMessageError struct {
// This field can have the runtime type of [shared.ProviderAuthErrorData],
// [shared.UnknownErrorData], [interface{}].
// [shared.UnknownErrorData], [interface{}], [shared.MessageAbortedErrorData].
Data interface{} `json:"data,required"`
Name AssistantMessageErrorName `json:"name,required"`
JSON assistantMessageErrorJSON `json:"-"`
@@ -566,26 +566,22 @@ type AssistantMessageErrorUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*AssistantMessageErrorUnion)(nil)).Elem(),
"name",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.ProviderAuthError{}),
DiscriminatorValue: "ProviderAuthError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.ProviderAuthError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.UnknownError{}),
DiscriminatorValue: "UnknownError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.UnknownError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(AssistantMessageErrorMessageOutputLengthError{}),
DiscriminatorValue: "MessageOutputLengthError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(AssistantMessageErrorMessageOutputLengthError{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.MessageAbortedError{}),
DiscriminatorValue: "MessageAbortedError",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(shared.MessageAbortedError{}),
},
)
}
@@ -778,16 +774,14 @@ type FilePartSourceUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*FilePartSourceUnion)(nil)).Elem(),
"type",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(FileSource{}),
DiscriminatorValue: "file",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(FileSource{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(SymbolSource{}),
DiscriminatorValue: "symbol",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(SymbolSource{}),
},
)
}
@@ -986,16 +980,14 @@ type MessageUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*MessageUnion)(nil)).Elem(),
"role",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(UserMessage{}),
DiscriminatorValue: "user",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(UserMessage{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(AssistantMessage{}),
DiscriminatorValue: "assistant",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(AssistantMessage{}),
},
)
}
@@ -1107,51 +1099,42 @@ type PartUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*PartUnion)(nil)).Elem(),
"type",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(TextPart{}),
DiscriminatorValue: "text",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(TextPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ReasoningPart{}),
DiscriminatorValue: "reasoning",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ReasoningPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(FilePart{}),
DiscriminatorValue: "file",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(FilePart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolPart{}),
DiscriminatorValue: "tool",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(StepStartPart{}),
DiscriminatorValue: "step-start",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(StepStartPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(StepFinishPart{}),
DiscriminatorValue: "step-finish",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(StepFinishPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(SnapshotPart{}),
DiscriminatorValue: "snapshot",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(SnapshotPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(PartPatchPart{}),
DiscriminatorValue: "patch",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(PartPatchPart{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(AgentPart{}),
DiscriminatorValue: "agent",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(AgentPart{}),
},
)
}
@@ -1941,26 +1924,22 @@ type ToolPartStateUnion interface {
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ToolPartStateUnion)(nil)).Elem(),
"status",
"",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStatePending{}),
DiscriminatorValue: "pending",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStatePending{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStateRunning{}),
DiscriminatorValue: "running",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStateRunning{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStateCompleted{}),
DiscriminatorValue: "completed",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStateCompleted{}),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStateError{}),
DiscriminatorValue: "error",
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolStateError{}),
},
)
}
@@ -2044,9 +2023,10 @@ func (r ToolStateCompletedStatus) IsKnown() bool {
}
type ToolStateCompletedTime struct {
End float64 `json:"end,required"`
Start float64 `json:"start,required"`
JSON toolStateCompletedTimeJSON `json:"-"`
End float64 `json:"end,required"`
Start float64 `json:"start,required"`
Compacted float64 `json:"compacted"`
JSON toolStateCompletedTimeJSON `json:"-"`
}
// toolStateCompletedTimeJSON contains the JSON metadata for the struct
@@ -2054,6 +2034,7 @@ type ToolStateCompletedTime struct {
type toolStateCompletedTimeJSON struct {
End apijson.Field
Start apijson.Field
Compacted apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -2171,9 +2152,9 @@ func (r ToolStatePendingStatus) IsKnown() bool {
}
type ToolStateRunning struct {
Input interface{} `json:"input,required"`
Status ToolStateRunningStatus `json:"status,required"`
Time ToolStateRunningTime `json:"time,required"`
Input interface{} `json:"input"`
Metadata map[string]interface{} `json:"metadata"`
Title string `json:"title"`
JSON toolStateRunningJSON `json:"-"`
@@ -2182,9 +2163,9 @@ type ToolStateRunning struct {
// toolStateRunningJSON contains the JSON metadata for the struct
// [ToolStateRunning]
type toolStateRunningJSON struct {
Input apijson.Field
Status apijson.Field
Time apijson.Field
Input apijson.Field
Metadata apijson.Field
Title apijson.Field
raw string
+4 -4
View File
@@ -196,7 +196,7 @@ func TestSessionCommandWithOptionalParams(t *testing.T) {
Command: opencode.F("command"),
Directory: opencode.F("directory"),
Agent: opencode.F("agent"),
MessageID: opencode.F("msg"),
MessageID: opencode.F("msgJ!"),
Model: opencode.F("model"),
},
)
@@ -353,7 +353,7 @@ func TestSessionPromptWithOptionalParams(t *testing.T) {
}}),
Directory: opencode.F("directory"),
Agent: opencode.F("agent"),
MessageID: opencode.F("msg"),
MessageID: opencode.F("msgJ!"),
Model: opencode.F(opencode.SessionPromptParamsModel{
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
@@ -389,9 +389,9 @@ func TestSessionRevertWithOptionalParams(t *testing.T) {
context.TODO(),
"id",
opencode.SessionRevertParams{
MessageID: opencode.F("msg"),
MessageID: opencode.F("msgJ!"),
Directory: opencode.F("directory"),
PartID: opencode.F("prt"),
PartID: opencode.F("prtJ!"),
},
)
if err != nil {
+22 -1
View File
@@ -7,7 +7,7 @@ import (
)
type MessageAbortedError struct {
Data interface{} `json:"data,required"`
Data MessageAbortedErrorData `json:"data,required"`
Name MessageAbortedErrorName `json:"name,required"`
JSON messageAbortedErrorJSON `json:"-"`
}
@@ -33,6 +33,27 @@ func (r MessageAbortedError) ImplementsEventListResponseEventSessionErrorPropert
func (r MessageAbortedError) ImplementsAssistantMessageError() {}
type MessageAbortedErrorData struct {
Message string `json:"message,required"`
JSON messageAbortedErrorDataJSON `json:"-"`
}
// messageAbortedErrorDataJSON contains the JSON metadata for the struct
// [MessageAbortedErrorData]
type messageAbortedErrorDataJSON struct {
Message apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *MessageAbortedErrorData) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r messageAbortedErrorDataJSON) RawJSON() string {
return r.raw
}
type MessageAbortedErrorName string
const (
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "0.7.9",
"version": "0.9.0",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
+5 -9
View File
@@ -58,8 +58,8 @@ import type {
SessionRevertResponses,
SessionUnrevertData,
SessionUnrevertResponses,
PostSessionByIdPermissionsByPermissionIdData,
PostSessionByIdPermissionsByPermissionIdResponses,
PostSessionIdPermissionsPermissionIdData,
PostSessionIdPermissionsPermissionIdResponses,
CommandListData,
CommandListResponses,
ConfigProvidersData,
@@ -675,14 +675,10 @@ export class OpencodeClient extends _HeyApiClient {
/**
* Respond to a permission request
*/
public postSessionByIdPermissionsByPermissionId<ThrowOnError extends boolean = false>(
options: Options<PostSessionByIdPermissionsByPermissionIdData, ThrowOnError>,
public postSessionIdPermissionsPermissionId<ThrowOnError extends boolean = false>(
options: Options<PostSessionIdPermissionsPermissionIdData, ThrowOnError>,
) {
return (options.client ?? this._client).post<
PostSessionByIdPermissionsByPermissionIdResponses,
unknown,
ThrowOnError
>({
return (options.client ?? this._client).post<PostSessionIdPermissionsPermissionIdResponses, unknown, ThrowOnError>({
url: "/session/{id}/permissions/{permissionID}",
...options,
headers: {
File diff suppressed because it is too large Load Diff
@@ -898,8 +898,8 @@ func (m *messagesComponent) renderHeader() string {
continue
}
tokens = (usage.Input +
usage.Cache.Write +
usage.Cache.Read +
usage.Cache.Write +
usage.Output +
usage.Reasoning)
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode/web",
"type": "module",
"version": "0.7.9",
"version": "0.9.0",
"scripts": {
"dev": "astro dev",
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
-2
View File
@@ -81,7 +81,6 @@ A general-purpose agent for researching complex questions, searching for code, a
1. For primary agents, use the **Tab** key to cycle through them during a session. You can also use your configured `switch_agent` keybind.
2. Subagents can be invoked:
- **Automatically** by primary agents for specialized tasks based on their descriptions.
- Manually by **@ mentioning** a subagent in your message. For example.
@@ -90,7 +89,6 @@ A general-purpose agent for researching complex questions, searching for code, a
```
3. **Navigation between sessions**: When subagents create their own child sessions, you can navigate between the parent session and all child sessions using:
- **Ctrl+Right** (or your configured `session_child_cycle` keybind) to cycle forward through parent → child1 → child2 → ... → parent
- **Ctrl+Left** (or your configured `session_child_cycle_reverse` keybind) to cycle backward through parent ← child1 ← child2 ← ... ← parent
-1
View File
@@ -18,7 +18,6 @@ Let's get started.
To use opencode, you'll need:
1. A modern terminal emulator like:
- [WezTerm](https://wezterm.org), cross-platform
- [Alacritty](https://alacritty.org), cross-platform
- [Ghostty](https://ghostty.org), Linux and macOS
@@ -89,7 +89,6 @@ To use Amazon Bedrock with opencode:
:::
1. You'll need either to set one of the following environment variables:
- `AWS_ACCESS_KEY_ID`: You can get this by creating an IAM user and generating
an access key for it.
- `AWS_PROFILE`: First login through AWS IAM Identity Center (or AWS SSO) using
@@ -166,7 +165,6 @@ Or if you already have an API key, you can select **Manually enter API Key** and
### Azure OpenAI
1. Head over to the [Azure portal](https://portal.azure.com/) and create an **Azure OpenAI** resource. You'll need:
- **Resource name**: This becomes part of your API endpoint (`https://RESOURCE_NAME.openai.azure.com/`)
- **API key**: Either `KEY 1` or `KEY 2` from your resource
@@ -823,7 +821,6 @@ You can use any OpenAI-compatible provider with opencode. Most modern AI provide
```
Here are the configuration options:
- **npm**: AI SDK package to use, `@ai-sdk/openai-compatible` for OpenAI-compatible providers
- **name**: Display name in UI.
- **models**: Available models.
@@ -879,7 +876,6 @@ If you are having trouble with configuring a provider, check the following:
This doesn't apply to providers like Amazon Bedrock, that rely on environment variables for their auth.
2. For custom providers, check the opencode config and:
- Make sure the provider ID used in `opencode auth login` matches the ID in your opencode config.
- The right npm package is used for the provider. For example, use `@ai-sdk/cerebras` for Cerebras. And for all other OpenAI-compatible providers, use `@ai-sdk/openai-compatible`.
- Check correct API endpoint is used in the `options.baseURL` field.
+3 -1
View File
@@ -1,3 +1,5 @@
#!/usr/bin/env bash
gh workflow run publish.yml -f bump="patch"
BUMP_TYPE=${1:-patch}
gh workflow run publish.yml -f bump="$BUMP_TYPE"
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "0.7.9",
"version": "0.9.0",
"publisher": "sst-dev",
"repository": {
"type": "git",