From 37b6a55eb1b2847a6ad216a9d5c9feec8dbff77a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 11 Sep 2025 04:00:17 +0000 Subject: [PATCH 01/62] chore: format code --- cloud/app/src/routes/workspace/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index 19d711e8b..b8f355507 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -551,7 +551,7 @@ function NewUserSection() { ) } -export default function() { +export default function () { const params = useParams() const keys = createAsync(() => listKeys(params.id)) const usage = createAsync(() => getUsageInfo(params.id)) From 4ccf6835274d9bceed426b767c3464822c076b9d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 11 Sep 2025 12:53:10 -0400 Subject: [PATCH 02/62] remove block anchor edit --- packages/opencode/src/tool/edit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 64a0ea167..fdd853657 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -599,7 +599,7 @@ export function replace(content: string, oldString: string, newString: string, r for (const replacer of [ SimpleReplacer, LineTrimmedReplacer, - BlockAnchorReplacer, + // BlockAnchorReplacer, WhitespaceNormalizedReplacer, IndentationFlexibleReplacer, EscapeNormalizedReplacer, From 3e9b451fb4c3ad2a9d392ee0e304a5c7c9d7547d Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 11 Sep 2025 12:54:12 -0400 Subject: [PATCH 03/62] reduce LSP verbosity --- packages/opencode/src/tool/edit.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index fdd853657..88e453029 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -107,14 +107,12 @@ export const EditTool = Tool.define("edit", { for (const [file, issues] of Object.entries(diagnostics)) { if (issues.length === 0) continue if (file === filePath) { - output += `\nThis file has errors, please fix\n\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n\n` + output += `\nThis file has errors, please fix\n\n${issues + .filter((item) => item.severity === 1) + .map(LSP.Diagnostic.pretty) + .join("\n")}\n\n` continue } - output += `\n\n${file}\n${issues - // TODO: may want to make more leniant for eslint - .filter((item) => item.severity === 1) - .map(LSP.Diagnostic.pretty) - .join("\n")}\n\n` } return { From 84f0c63fa15061d7f9c29a32f20a0f60b6debead Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 11 Sep 2025 17:02:59 +0000 Subject: [PATCH 04/62] release: v0.7.2 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/sdk/js/src/gen/types.gen.ts | 11 +++++++++++ packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 11 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index efc292be8..5ae7df59d 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.1" + "version": "0.7.2" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index d949a66dc..2a3cbd399 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.1", + "version": "0.7.2", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index f90c63cf1..d6f68b6ec 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.1", + "version": "0.7.2", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 0615843d3..c2544f55d 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.1", + "version": "0.7.2", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index 6bfa97a64..c45c91e57 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.1", + "version": "0.7.2", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index aa2a58cd5..fc4618ddc 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.1", + "version": "0.7.2", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 03d8524d4..cdf796959 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.1", + "version": "0.7.2", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 05f8031c3..bd36d2942 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.1", + "version": "0.7.2", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index c753228d3..a9c52184a 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -50,6 +50,9 @@ export type Event = | ({ type: "session.error" } & EventSessionError) + | ({ + type: "session.compacted" + } & EventSessionCompacted) | ({ type: "server.connected" } & EventServerConnected) @@ -478,6 +481,7 @@ export type Session = { time: { created: number updated: number + compacting?: number } revert?: { messageID: string @@ -521,6 +525,13 @@ export type EventSessionError = { } } +export type EventSessionCompacted = { + type: "session.compacted" + properties: { + sessionID: string + } +} + export type EventServerConnected = { type: "server.connected" properties: { diff --git a/packages/web/package.json b/packages/web/package.json index 8c1599df5..a45854c8d 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.1", + "version": "0.7.2", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 2a946f06a..ebe636348 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.1", + "version": "0.7.2", "publisher": "sst-dev", "repository": { "type": "git", From 4614e4983ec667c3ab188516793c458fc9ee246b Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:03:12 -0500 Subject: [PATCH 05/62] fix: command being passed as arg when no args present (#2553) --- packages/tui/internal/components/chat/editor.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index dd24dcf15..31de346f5 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -507,7 +507,10 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) { commandName := strings.Split(expandedValue, " ")[0] command := m.app.Commands[commands.CommandName(commandName)] if command.Custom { - args := strings.TrimPrefix(expandedValue, command.PrimaryTrigger()+" ") + args := "" + if strings.HasPrefix(expandedValue, command.PrimaryTrigger()+" ") { + args = strings.TrimPrefix(expandedValue, command.PrimaryTrigger()+" ") + } cmds = append( cmds, util.CmdHandler(app.SendCommand{Command: string(command.Name), Args: args}), From 53f1f161229a5da0ccee92653a13fa0af9656a2d Mon Sep 17 00:00:00 2001 From: Chris Covington Date: Thu, 11 Sep 2025 12:21:08 -0700 Subject: [PATCH 06/62] feat: Add an experimental option to disable paste summaries (#2552) Co-authored-by: rekram1-node --- packages/opencode/src/config/config.ts | 1 + packages/sdk/go/config.go | 22 ++++++++++--------- .../tui/internal/components/chat/editor.go | 5 +++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 786ddcc65..f4b8608d8 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -499,6 +499,7 @@ export namespace Config { .optional(), }) .optional(), + disable_paste_summary: z.boolean().optional(), }) .optional(), }) diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go index b0473f844..b79bcb2ef 100644 --- a/packages/sdk/go/config.go +++ b/packages/sdk/go/config.go @@ -698,16 +698,18 @@ func (r configCommandJSON) RawJSON() string { } type ConfigExperimental struct { - Hook ConfigExperimentalHook `json:"hook"` - JSON configExperimentalJSON `json:"-"` + Hook ConfigExperimentalHook `json:"hook"` + DisablePasteSummary bool `json:"disable_paste_summary"` + JSON configExperimentalJSON `json:"-"` } // configExperimentalJSON contains the JSON metadata for the struct // [ConfigExperimental] type configExperimentalJSON struct { - Hook apijson.Field - raw string - ExtraFields map[string]apijson.Field + Hook apijson.Field + SummarizePaste apijson.Field + raw string + ExtraFields map[string]apijson.Field } func (r *ConfigExperimental) UnmarshalJSON(data []byte) (err error) { @@ -1751,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,required"` + 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) { diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index 31de346f5..2841e2cc8 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -668,6 +668,11 @@ func (m *editorComponent) shouldSummarizePastedText(text string) bool { if m.app.IsBashMode { return false } + + if m.app.Config != nil && m.app.Config.Experimental.DisablePasteSummary { + return false + } + lines := strings.Split(text, "\n") lineCount := len(lines) charCount := len(text) From f5b3992479b616b5900a5f752d8928980837426e Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 11 Sep 2025 16:22:44 -0400 Subject: [PATCH 07/62] properly support model level npm definition --- packages/opencode/src/provider/provider.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index dbed1f74d..834a5b2dc 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -161,7 +161,7 @@ export namespace Provider { string, { providerID: string; modelID: string; info: ModelsDev.Model; language: LanguageModel } >() - const sdk = new Map() + const sdk = new Map() log.info("init") @@ -326,23 +326,24 @@ export namespace Provider { providerID: provider.id, }) const s = await state() - const existing = s.sdk.get(provider.id) - if (existing) return existing const pkg = model.provider?.npm ?? provider.npm ?? provider.id + const options = { ...s.providers[provider.id]?.options } + const key = Bun.hash.xxHash32(JSON.stringify({ pkg, options })) + const existing = s.sdk.get(key) + if (existing) return existing const mod = await import(await BunProc.install(pkg, "latest")) - const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!] - let options = { ...s.providers[provider.id]?.options } if (options["timeout"] !== undefined) { // Only override fetch if user explicitly sets timeout options["fetch"] = async (input: any, init?: any) => { return await fetch(input, { ...init, timeout: options["timeout"] }) } } + const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!] const loaded = fn({ name: provider.id, ...options, }) - s.sdk.set(provider.id, loaded) + s.sdk.set(key, loaded) return loaded as SDK })().catch((e) => { throw new InitError({ providerID: provider.id }, { cause: e }) From 3abca8fd4b08f5d122c2352fcee64755a028228a Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 11 Sep 2025 17:21:49 -0400 Subject: [PATCH 08/62] wip: zen --- cloud/app/src/util/zen.ts | 4 +- cloud/core/migrations/0002_violet_loners.sql | 1 + .../migrations/0003_dusty_clint_barton.sql | 1 + cloud/core/migrations/meta/0002_snapshot.json | 576 +++++++++++++++++ cloud/core/migrations/meta/0003_snapshot.json | 610 ++++++++++++++++++ cloud/core/migrations/meta/_journal.json | 16 +- cloud/core/package.json | 1 + cloud/core/src/key.ts | 26 +- cloud/core/src/schema/key.sql.ts | 7 +- 9 files changed, 1233 insertions(+), 9 deletions(-) create mode 100644 cloud/core/migrations/0002_violet_loners.sql create mode 100644 cloud/core/migrations/0003_dusty_clint_barton.sql create mode 100644 cloud/core/migrations/meta/0002_snapshot.json create mode 100644 cloud/core/migrations/meta/0003_snapshot.json diff --git a/cloud/app/src/util/zen.ts b/cloud/app/src/util/zen.ts index 47cec9f6f..d02a9c614 100644 --- a/cloud/app/src/util/zen.ts +++ b/cloud/app/src/util/zen.ts @@ -1,6 +1,6 @@ import type { APIEvent } from "@solidjs/start/server" import path from "node:path" -import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js" +import { and, Database, eq, isNull, sql } from "@opencode/cloud-core/drizzle/index.js" import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js" import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js" import { centsToMicroCents } from "@opencode/cloud-core/util/price.js" @@ -390,7 +390,7 @@ export async function handler( workspaceID: KeyTable.workspaceID, }) .from(KeyTable) - .where(eq(KeyTable.key, apiKey)) + .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted))) .then((rows) => rows[0]), ) diff --git a/cloud/core/migrations/0002_violet_loners.sql b/cloud/core/migrations/0002_violet_loners.sql new file mode 100644 index 000000000..17273356c --- /dev/null +++ b/cloud/core/migrations/0002_violet_loners.sql @@ -0,0 +1 @@ +ALTER TABLE `key` ADD `old_name` varchar(255); \ No newline at end of file diff --git a/cloud/core/migrations/0003_dusty_clint_barton.sql b/cloud/core/migrations/0003_dusty_clint_barton.sql new file mode 100644 index 000000000..14efae216 --- /dev/null +++ b/cloud/core/migrations/0003_dusty_clint_barton.sql @@ -0,0 +1 @@ +ALTER TABLE `key` ADD CONSTRAINT `name` UNIQUE(`workspace_id`,`name`); \ No newline at end of file diff --git a/cloud/core/migrations/meta/0002_snapshot.json b/cloud/core/migrations/meta/0002_snapshot.json new file mode 100644 index 000000000..45d06926c --- /dev/null +++ b/cloud/core/migrations/meta/0002_snapshot.json @@ -0,0 +1,576 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "9f51ef52-31ac-4ace-8b6d-39b35efe9c81", + "prevId": "79b7ee25-1c1c-41ff-9bbf-754af257102b", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email": { + "name": "email", + "columns": ["email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_name": { + "name": "old_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email": { + "name": "user_email", + "columns": ["workspace_id", "email"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": ["workspace_id", "id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": ["slug"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/cloud/core/migrations/meta/0003_snapshot.json b/cloud/core/migrations/meta/0003_snapshot.json new file mode 100644 index 000000000..c82f8cd6f --- /dev/null +++ b/cloud/core/migrations/meta/0003_snapshot.json @@ -0,0 +1,610 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "26cebd59-f553-441c-a2b2-2f9578a0ad2b", + "prevId": "9f51ef52-31ac-4ace-8b6d-39b35efe9c81", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email": { + "name": "email", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_name": { + "name": "old_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": [ + "key" + ], + "isUnique": true + }, + "name": { + "name": "name", + "columns": [ + "workspace_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email": { + "name": "user_email", + "columns": [ + "workspace_id", + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index 131e85613..f17292abd 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -15,6 +15,20 @@ "when": 1756871639102, "tag": "0001_serious_whistler", "breakpoints": true + }, + { + "idx": 2, + "version": "5", + "when": 1757597611832, + "tag": "0002_violet_loners", + "breakpoints": true + }, + { + "idx": 3, + "version": "5", + "when": 1757600397194, + "tag": "0003_dusty_clint_barton", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/cloud/core/package.json b/cloud/core/package.json index 2a3cbd399..48af18043 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -18,6 +18,7 @@ }, "scripts": { "db": "sst shell drizzle-kit", + "db-prod": "sst shell --stage production -- drizzle-kit", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/cloud/core/src/key.ts b/cloud/core/src/key.ts index b62a8961b..a8bcec401 100644 --- a/cloud/core/src/key.ts +++ b/cloud/core/src/key.ts @@ -1,7 +1,7 @@ import { z } from "zod" import { fn } from "./util/fn" import { Actor } from "./actor" -import { and, Database, eq, sql } from "./drizzle" +import { and, Database, eq, isNull, sql } from "./drizzle" import { Identifier } from "./identifier" import { KeyTable } from "./schema/key.sql" @@ -12,7 +12,7 @@ export namespace Key { tx .select() .from(KeyTable) - .where(eq(KeyTable.workspaceID, workspace)) + .where(and(eq(KeyTable.workspaceID, workspace), isNull(KeyTable.timeDeleted))) .orderBy(sql`${KeyTable.timeCreated} DESC`), ) return keys @@ -48,8 +48,24 @@ export namespace Key { export const remove = fn(z.object({ id: z.string() }), async (input) => { const workspace = Actor.workspace() - await Database.use((tx) => - tx.delete(KeyTable).where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace))), - ) + await Database.transaction(async (tx) => { + const row = await tx + .select({ + name: KeyTable.name, + }) + .from(KeyTable) + .where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace))) + .then((rows) => rows[0]) + if (!row) return + + await tx + .update(KeyTable) + .set({ + timeDeleted: sql`now()`, + oldName: row.name, + name: input.id, // Use the key ID as the name + }) + .where(and(eq(KeyTable.id, input.id), eq(KeyTable.workspaceID, workspace))) + }) }) } diff --git a/cloud/core/src/schema/key.sql.ts b/cloud/core/src/schema/key.sql.ts index 07982779b..98b99c788 100644 --- a/cloud/core/src/schema/key.sql.ts +++ b/cloud/core/src/schema/key.sql.ts @@ -10,8 +10,13 @@ export const KeyTable = mysqlTable( ...timestamps, actor: json("actor").$type(), name: varchar("name", { length: 255 }).notNull(), + oldName: varchar("old_name", { length: 255 }), key: varchar("key", { length: 255 }).notNull(), timeUsed: utc("time_used"), }, - (table) => [...workspaceIndexes(table), uniqueIndex("global_key").on(table.key)], + (table) => [ + ...workspaceIndexes(table), + uniqueIndex("global_key").on(table.key), + uniqueIndex("name").on(table.workspaceID, table.name), + ], ) From 91832bd5d778da88d9273c51008f00672e375f46 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 11 Sep 2025 21:22:56 +0000 Subject: [PATCH 09/62] chore: format code --- cloud/core/migrations/meta/0003_snapshot.json | 53 +++++-------------- cloud/core/migrations/meta/_journal.json | 2 +- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/cloud/core/migrations/meta/0003_snapshot.json b/cloud/core/migrations/meta/0003_snapshot.json index c82f8cd6f..e832ebb00 100644 --- a/cloud/core/migrations/meta/0003_snapshot.json +++ b/cloud/core/migrations/meta/0003_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -140,10 +138,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -216,10 +211,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -320,10 +312,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -408,17 +397,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -426,10 +410,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -507,10 +488,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -518,10 +496,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -578,9 +553,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -588,9 +561,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -607,4 +578,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index f17292abd..c5ea50210 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -31,4 +31,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From 54f7fb5019cc46e8eef758959ca474556cc98c1c Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 11 Sep 2025 21:38:17 +0000 Subject: [PATCH 10/62] release: v0.7.3 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/sdk/js/src/gen/types.gen.ts | 1 + packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 11 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index 5ae7df59d..c5507fa42 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.2" + "version": "0.7.3" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index 48af18043..d064c957e 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.2", + "version": "0.7.3", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index d6f68b6ec..9b9728c7e 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.2", + "version": "0.7.3", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index c2544f55d..fc9491148 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.2", + "version": "0.7.3", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index c45c91e57..8bb1b22f5 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.2", + "version": "0.7.3", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index fc4618ddc..c012c7506 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.2", + "version": "0.7.3", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index cdf796959..fad117b06 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.2", + "version": "0.7.3", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index bd36d2942..ff50048e0 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.2", + "version": "0.7.3", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index a9c52184a..adf9d3f2c 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -744,6 +744,7 @@ export type Config = { } }> } + disable_paste_summary?: boolean } } diff --git a/packages/web/package.json b/packages/web/package.json index a45854c8d..7acf09a78 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.2", + "version": "0.7.3", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index ebe636348..1de6c560a 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.2", + "version": "0.7.3", "publisher": "sst-dev", "repository": { "type": "git", From 79c73267cf83b9d96af50ac60cbda948c26d8a49 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 11 Sep 2025 18:20:22 -0400 Subject: [PATCH 11/62] wip: zen --- cloud/app/src/routes/workspace/[id].tsx | 490 ++++++++++-------------- 1 file changed, 194 insertions(+), 296 deletions(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index b8f355507..222e51a15 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -2,9 +2,10 @@ import "./[id].css" import { Billing } from "@opencode/cloud-core/billing.js" import { Key } from "@opencode/cloud-core/key.js" import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router" -import { createMemo, createSignal, For, Show } from "solid-js" +import { createEffect, createMemo, createSignal, For, Show } from "solid-js" import { withActor } from "~/context/auth.withActor" import { IconCopy, IconCheck } from "~/component/icon" +import { createStore } from "solid-js/store" function formatDateForTable(date: Date) { const options: Intl.DateTimeFormatOptions = { @@ -41,16 +42,24 @@ const listKeys = query(async (workspaceID: string) => { return withActor(() => Key.list(), workspaceID) }, "key.list") -const createKey = action(async (workspaceID: string, name: string) => { +const createKey = action(async (form: FormData) => { "use server" + const name = form.get("name")?.toString().trim() + if (!name) return { error: "Name is required" } + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } return json( withActor(() => Key.create({ name }), workspaceID), { revalidate: listKeys.key }, ) }, "key.create") -const removeKey = action(async (workspaceID: string, id: string) => { +const removeKey = action(async (form: FormData) => { "use server" + const id = form.get("id")?.toString() + if (!id) return { error: "ID is required" } + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } return json( withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key }, @@ -92,127 +101,22 @@ const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, // return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID) // }, "portalUrl") -function KeysSection() { - // Dummy data for testing - const dummyKeys = [ - { - id: "key_1", - name: "Development API Key", - key: "oc_dev_1234567890abcdef1234567890abcdef12345678", - timeCreated: new Date("2024-01-15T10:30:00Z"), - }, - { - id: "key_2", - name: "Production API Key", - key: "oc_prod_abcdef1234567890abcdef1234567890abcdef12", - timeCreated: new Date("2024-02-01T14:22:00Z"), - }, - { - id: "key_3", - name: "Testing Environment", - key: "oc_test_9876543210fedcba9876543210fedcba98765432", - timeCreated: new Date("2024-02-10T09:15:00Z"), - }, - ] - +function KeySection() { const params = useParams() const keys = createAsync(() => listKeys(params.id)) - // const keys = () => dummyKeys - const [showForm, setShowForm] = createSignal(false) - const [name, setName] = createSignal("") - const removeAction = useAction(removeKey) - const createAction = useAction(createKey) - const createSubmission = useSubmission(createKey) - const [copiedId, setCopiedId] = createSignal(null) function formatKey(key: string) { if (key.length <= 11) return key return `${key.slice(0, 7)}...${key.slice(-4)}` } - async function handleCreateKey() { - if (!name().trim()) return - - try { - await createAction(params.id, name().trim()) - setName("") - setShowForm(false) - } catch (error) { - console.error("Failed to create API key:", error) - } - } - - async function copyKeyToClipboard(text: string, keyId: string) { - try { - await navigator.clipboard.writeText(text) - setCopiedId(keyId) - setTimeout(() => setCopiedId(null), 1500) - } catch (error) { - console.error("Failed to copy to clipboard:", error) - } - } - - async function handleDeleteKey(keyId: string) { - if (!confirm("Are you sure you want to delete this API key?")) { - return - } - - try { - await removeAction(params.id, keyId) - } catch (error) { - console.error("Failed to delete API key:", error) - } - } - return (

API Keys

Manage your API keys for accessing opencode services.

- - setName(e.currentTarget.value)} - onKeyPress={(e) => e.key === "Enter" && handleCreateKey()} - /> -
- - -
- - } - > - -
+
- {(key) => ( - - {key.name} - - - - - {formatDateForTable(key.timeCreated)} - - - - - - )} + {formatKey(key.key)} + }> + + + + + + {formatDateForTable(key.timeCreated)} + + +
+ + + +
+ + + ) + }}
@@ -271,27 +182,61 @@ function KeysSection() { ) } +function KeyCreateForm() { + const params = useParams() + const submission = useSubmission(createKey) + const [store, setStore] = createStore({ + show: false, + }) + + let input: HTMLInputElement + + createEffect(() => { + if (!submission.pending && submission.result) { + hide() + } + }) + + function show() { + setStore("show", true) + input.focus() + } + + function hide() { + setStore("show", false) + } + + return ( + show()}> + Create API Key + + } + > +
+ (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" /> + +
+ + +
+
+
+ ) +} + function BalanceSection() { const params = useParams() - const dummyBalanceInfo = { balance: 2500000000 } // $25.00 in cents - const balanceInfo = createAsync(() => getBalanceInfo(params.id)) - // const balanceInfo = () => dummyBalanceInfo const createCheckoutUrlAction = useAction(createCheckoutUrl) const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl) - async function handleBuyCredits() { - try { - const baseUrl = window.location.href - const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl) - if (checkoutUrl) { - window.location.href = checkoutUrl - } - } catch (error) { - console.error("Failed to get checkout URL:", error) - } - } - return (
@@ -314,7 +259,17 @@ function BalanceSection() { })()}
-
@@ -324,43 +279,8 @@ function BalanceSection() { function UsageSection() { const params = useParams() - const dummyUsage = [ - { - id: "usage_1", - model: "claude-3-sonnet-20240229", - inputTokens: 1250, - outputTokens: 890, - cost: 125000000, // $1.25 in cents - timeCreated: "2024-02-10T15:30:00Z", - }, - { - id: "usage_2", - model: "gpt-4-turbo-preview", - inputTokens: 2100, - outputTokens: 1456, - cost: 340000000, // $3.40 in cents - timeCreated: "2024-02-09T09:45:00Z", - }, - { - id: "usage_3", - model: "claude-3-haiku-20240307", - inputTokens: 850, - outputTokens: 620, - cost: 45000000, // $0.45 in cents - timeCreated: "2024-02-08T13:22:00Z", - }, - { - id: "usage_4", - model: "gpt-3.5-turbo", - inputTokens: 1800, - outputTokens: 1200, - cost: 85000000, // $0.85 in cents - timeCreated: "2024-02-07T11:15:00Z", - }, - ] - const usage = createAsync(() => getUsageInfo(params.id)) - // const usage = () => dummyUsage + return (
@@ -411,23 +331,9 @@ function UsageSection() { ) } -function PaymentsSection() { +function PaymentSection() { const params = useParams() - const dummyPayments = [ - { - id: "pi_1234567890", - amount: 5000000000, // $50.00 in cents - timeCreated: "2024-02-01T10:00:00Z", - }, - { - id: "pi_0987654321", - amount: 2500000000, // $25.00 in cents - timeCreated: "2024-01-15T14:30:00Z", - }, - ] - const payments = createAsync(() => getPaymentsInfo(params.id)) - // const payments = () => dummyPayments return ( payments() && @@ -471,97 +377,90 @@ function PaymentsSection() { function NewUserSection() { const params = useParams() - const keys = createAsync(() => listKeys(params.id)) const [copiedKey, setCopiedKey] = createSignal(false) - - async function copyKeyToClipboard(text: string) { - try { - await navigator.clipboard.writeText(text) - setCopiedKey(true) - setTimeout(() => setCopiedKey(false), 2000) - } catch (error) { - console.error("Failed to copy to clipboard:", error) - } - } - - return ( -
-
-
-

Tested & Verified Models

-

We've benchmarked and tested models specifically for coding agents to ensure the best performance.

-
-
-

Highest Quality

-

Access models configured for optimal performance - no downgrades or routing to cheaper providers.

-
-
-

No Lock-in

-

Use Zen with any coding agent, and continue using other providers with opencode whenever you want.

-
-
- -
-
-

Your API Key

-
- - -
-
- {keys()![0].key} - -
-
-
-
- -
-
-

Next Steps

-
-
    -
  1. Copy your API key above
  2. -
  3. - Run opencode auth login and select opencode -
  4. -
  5. Paste your API key when prompted
  6. -
  7. - Run /models to see available models -
  8. -
-
-
- ) -} - -export default function () { - const params = useParams() const keys = createAsync(() => listKeys(params.id)) const usage = createAsync(() => getUsageInfo(params.id)) - - const isNewUser = createMemo(() => { + const isNew = createMemo(() => { const keysList = keys() const usageList = usage() return keysList?.length === 1 && (!usageList || usageList.length === 0) }) + const defaultKey = createMemo(() => keys()?.at(-1)?.key) + return ( + +
+
+
+

Tested & Verified Models

+

We've benchmarked and tested models specifically for coding agents to ensure the best performance.

+
+
+

Highest Quality

+

Access models configured for optimal performance - no downgrades or routing to cheaper providers.

+
+
+

No Lock-in

+

Use Zen with any coding agent, and continue using other providers with opencode whenever you want.

+
+
+ +
+
+

Your API Key

+
+ + +
+
+ {defaultKey()} + +
+
+
+
+ +
+
+

Next Steps

+
+
    +
  1. Copy your API key above
  2. +
  3. + Run opencode auth login and select opencode +
  4. +
  5. Paste your API key when prompted
  6. +
  7. + Run /models to see available models +
  8. +
+
+
+
+ ) +} + +export default function () { return (
@@ -575,14 +474,13 @@ export default function () {

- }> -
- - - - -
-
+
+ + + + + +
) } From a52b352b24db0339fbc178741fe764ad62be0589 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 11 Sep 2025 18:30:13 -0400 Subject: [PATCH 12/62] wip: zen --- .../src/routes/gateway/v1/chat/completions.ts | 308 --------- cloud/app/src/util/zen.ts | 1 + .../migrations/0004_first_mockingbird.sql | 1 + cloud/core/migrations/meta/0004_snapshot.json | 617 ++++++++++++++++++ cloud/core/migrations/meta/_journal.json | 9 +- cloud/core/package.json | 1 + cloud/core/src/schema/billing.sql.ts | 1 + 7 files changed, 629 insertions(+), 309 deletions(-) delete mode 100644 cloud/app/src/routes/gateway/v1/chat/completions.ts create mode 100644 cloud/core/migrations/0004_first_mockingbird.sql create mode 100644 cloud/core/migrations/meta/0004_snapshot.json diff --git a/cloud/app/src/routes/gateway/v1/chat/completions.ts b/cloud/app/src/routes/gateway/v1/chat/completions.ts deleted file mode 100644 index bd33a1ebf..000000000 --- a/cloud/app/src/routes/gateway/v1/chat/completions.ts +++ /dev/null @@ -1,308 +0,0 @@ -/** - * @deprecated Use zen/v1/chat/completions instead - */ -import { Resource } from "@opencode/cloud-resource" -import type { APIEvent } from "@solidjs/start/server" -import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js" -import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js" -import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js" -import { centsToMicroCents } from "@opencode/cloud-core/util/price.js" -import { Identifier } from "@opencode/cloud-core/identifier.js" - -const MODELS = { - // "anthropic/claude-sonnet-4": { - // auth: true, - // api: "https://api.anthropic.com", - // apiKey: Resource.ANTHROPIC_API_KEY.value, - // model: "claude-sonnet-4-20250514", - // cost: { - // input: 0.0000015, - // output: 0.000006, - // reasoning: 0.0000015, - // cacheRead: 0.0000001, - // cacheWrite: 0.0000001, - // }, - // headerMappings: {}, - // }, - "qwen/qwen3-coder": { - id: "qwen/qwen3-coder", - auth: true, - api: "https://inference.baseten.co", - apiKey: Resource.BASETEN_API_KEY.value, - model: "Qwen/Qwen3-Coder-480B-A35B-Instruct", - cost: { - input: 0.00000038, - output: 0.00000153, - reasoning: 0, - cacheRead: 0, - cacheWrite: 0, - }, - headerMappings: {}, - }, - "grok-code": { - id: "x-ai/grok-code-fast-1", - auth: false, - api: "https://api.x.ai", - apiKey: Resource.XAI_API_KEY.value, - model: "grok-code", - cost: { - input: 0, - output: 0, - reasoning: 0, - cacheRead: 0, - cacheWrite: 0, - }, - headerMappings: { - "x-grok-conv-id": "x-opencode-session", - "x-grok-req-id": "x-opencode-request", - }, - }, -} - -class AuthError extends Error {} -class CreditsError extends Error {} -class ModelError extends Error {} - -export async function POST(input: APIEvent) { - try { - const url = new URL(input.request.url) - const body = await input.request.json() - const MODEL = validateModel() - const apiKey = await authenticate() - await checkCredits() - - // Request to model provider - const res = await fetch(new URL(url.pathname.replace(/^\/gateway/, "") + url.search, MODEL.api), { - method: "POST", - headers: (() => { - const headers = input.request.headers - headers.delete("host") - headers.delete("content-length") - headers.set("authorization", `Bearer ${MODEL.apiKey}`) - Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => { - headers.set(k, headers.get(v)!) - }) - return headers - })(), - body: JSON.stringify({ - ...body, - model: MODEL.model, - stream_options: { - include_usage: true, - }, - }), - }) - - // Scrub response headers - const resHeaders = new Headers() - const keepHeaders = ["content-type", "cache-control"] - for (const [k, v] of res.headers.entries()) { - if (keepHeaders.includes(k.toLowerCase())) { - resHeaders.set(k, v) - } - } - - // Handle non-streaming response - if (!body.stream) { - const body = await res.json() - await trackUsage(body) - return new Response(JSON.stringify(body), { - status: res.status, - statusText: res.statusText, - headers: resHeaders, - }) - } - - // Handle streaming response - const stream = new ReadableStream({ - start(c) { - const reader = res.body?.getReader() - const decoder = new TextDecoder() - let buffer = "" - - function pump(): Promise { - return ( - reader?.read().then(async ({ done, value }) => { - if (done) { - c.close() - return - } - - buffer += decoder.decode(value, { stream: true }) - - const parts = buffer.split("\n\n") - buffer = parts.pop() ?? "" - - const usage = parts - .map((part) => part.trim()) - .filter((part) => part.startsWith("data: ")) - .map((part) => { - try { - return JSON.parse(part.slice(6)) - } catch (e) { - return {} - } - }) - .find((part) => part.usage) - if (usage) await trackUsage(usage) - - c.enqueue(value) - - return pump() - }) || Promise.resolve() - ) - } - - return pump() - }, - }) - - return new Response(stream, { - status: res.status, - statusText: res.statusText, - headers: resHeaders, - }) - - function validateModel() { - if (!(body.model in MODELS)) { - throw new ModelError(`Model ${body.model} not supported`) - } - return MODELS[body.model as keyof typeof MODELS] - } - - async function authenticate() { - try { - const authHeader = input.request.headers.get("authorization") - if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.") - - const apiKey = authHeader.split(" ")[1] - const key = await Database.use((tx) => - tx - .select({ - id: KeyTable.id, - workspaceID: KeyTable.workspaceID, - }) - .from(KeyTable) - .where(eq(KeyTable.key, apiKey)) - .then((rows) => rows[0]), - ) - - if (!key) throw new AuthError("Invalid API key.") - return key - } catch (e) { - console.log(e) - // ignore error if model does not require authentication - if (!MODEL.auth) return - throw e - } - } - - async function checkCredits() { - if (!apiKey || !MODEL.auth) return - - const billing = await Database.use((tx) => - tx - .select({ - balance: BillingTable.balance, - }) - .from(BillingTable) - .where(eq(BillingTable.workspaceID, apiKey.workspaceID)) - .then((rows) => rows[0]), - ) - - if (billing.balance <= 0) throw new CreditsError("Insufficient balance") - } - - async function trackUsage(chunk: any) { - console.log(`trackUsage ${apiKey}`) - - if (!apiKey) return - - const usage = chunk.usage - const inputTokens = usage.prompt_tokens ?? 0 - const outputTokens = usage.completion_tokens ?? 0 - const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0 - const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0 - //const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0 - const cacheWriteTokens = 0 - - const inputCost = MODEL.cost.input * inputTokens - const outputCost = MODEL.cost.output * outputTokens - const reasoningCost = MODEL.cost.reasoning * reasoningTokens - const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens - const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens - const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100 - const cost = centsToMicroCents(costInCents) - - await Database.transaction(async (tx) => { - await tx.insert(UsageTable).values({ - workspaceID: apiKey.workspaceID, - id: Identifier.create("usage"), - model: MODEL.id, - inputTokens, - outputTokens, - reasoningTokens, - cacheReadTokens, - cacheWriteTokens, - cost, - }) - await tx - .update(BillingTable) - .set({ - balance: sql`${BillingTable.balance} - ${cost}`, - }) - .where(eq(BillingTable.workspaceID, apiKey.workspaceID)) - }) - - await Database.use((tx) => - tx - .update(KeyTable) - .set({ timeUsed: sql`now()` }) - .where(eq(KeyTable.id, apiKey.id)), - ) - } - } catch (error: any) { - if (error instanceof AuthError) { - return new Response( - JSON.stringify({ - error: { - message: error.message, - type: "invalid_request_error", - param: null, - code: "unauthorized", - }, - }), - { - status: 401, - }, - ) - } - - if (error instanceof CreditsError) { - return new Response( - JSON.stringify({ - error: { - message: error.message, - type: "insufficient_quota", - param: null, - code: "insufficient_quota", - }, - }), - { - status: 401, - }, - ) - } - - if (error instanceof ModelError) { - return new Response(JSON.stringify({ error: { message: error.message } }), { - status: 401, - }) - } - - console.log(error) - return new Response(JSON.stringify({ error: { message: error.message } }), { - status: 500, - }) - } -} diff --git a/cloud/app/src/util/zen.ts b/cloud/app/src/util/zen.ts index d02a9c614..89c91c604 100644 --- a/cloud/app/src/util/zen.ts +++ b/cloud/app/src/util/zen.ts @@ -474,6 +474,7 @@ export async function handler( workspaceID: apiKey.workspaceID, id: Identifier.create("usage"), model: MODEL.id, + provider: providerName, inputTokens, outputTokens, reasoningTokens, diff --git a/cloud/core/migrations/0004_first_mockingbird.sql b/cloud/core/migrations/0004_first_mockingbird.sql new file mode 100644 index 000000000..2a6b11067 --- /dev/null +++ b/cloud/core/migrations/0004_first_mockingbird.sql @@ -0,0 +1 @@ +ALTER TABLE `usage` ADD `provider` varchar(255); \ No newline at end of file diff --git a/cloud/core/migrations/meta/0004_snapshot.json b/cloud/core/migrations/meta/0004_snapshot.json new file mode 100644 index 000000000..b9b1bfc8c --- /dev/null +++ b/cloud/core/migrations/meta/0004_snapshot.json @@ -0,0 +1,617 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "06dc6226-bfbb-4ccc-b4bc-f26070c3bed5", + "prevId": "26cebd59-f553-441c-a2b2-2f9578a0ad2b", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email": { + "name": "email", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_name": { + "name": "old_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": [ + "key" + ], + "isUnique": true + }, + "name": { + "name": "name", + "columns": [ + "workspace_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email": { + "name": "user_email", + "columns": [ + "workspace_id", + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index c5ea50210..25fd94a99 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1757600397194, "tag": "0003_dusty_clint_barton", "breakpoints": true + }, + { + "idx": 4, + "version": "5", + "when": 1757627357232, + "tag": "0004_first_mockingbird", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/cloud/core/package.json b/cloud/core/package.json index d064c957e..fa401cea8 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -18,6 +18,7 @@ }, "scripts": { "db": "sst shell drizzle-kit", + "db-dev": "sst shell --stage dev -- drizzle-kit", "db-prod": "sst shell --stage production -- drizzle-kit", "typecheck": "tsc --noEmit" }, diff --git a/cloud/core/src/schema/billing.sql.ts b/cloud/core/src/schema/billing.sql.ts index eff1f6550..0473df142 100644 --- a/cloud/core/src/schema/billing.sql.ts +++ b/cloud/core/src/schema/billing.sql.ts @@ -34,6 +34,7 @@ export const UsageTable = mysqlTable( ...workspaceColumns, ...timestamps, model: varchar("model", { length: 255 }).notNull(), + provider: varchar("provider", { length: 255 }), inputTokens: int("input_tokens").notNull(), outputTokens: int("output_tokens").notNull(), reasoningTokens: int("reasoning_tokens"), From 3c502861a719fde49903dd029a33e84252d2a2ad Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 11 Sep 2025 22:30:52 +0000 Subject: [PATCH 13/62] chore: format code --- cloud/core/migrations/meta/0004_snapshot.json | 53 +++++-------------- cloud/core/migrations/meta/_journal.json | 2 +- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/cloud/core/migrations/meta/0004_snapshot.json b/cloud/core/migrations/meta/0004_snapshot.json index b9b1bfc8c..6d2695c48 100644 --- a/cloud/core/migrations/meta/0004_snapshot.json +++ b/cloud/core/migrations/meta/0004_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -140,10 +138,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -216,10 +211,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -327,10 +319,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -415,17 +404,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -433,10 +417,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -514,10 +495,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -525,10 +503,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -585,9 +560,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -595,9 +568,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -614,4 +585,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index 25fd94a99..206d5e69b 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -38,4 +38,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From 1bd198eb3482fae6f80d1f0e48c097504a1d8b0d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 11 Sep 2025 22:34:21 +0000 Subject: [PATCH 14/62] chore: format code --- cloud/core/migrations/meta/0004_snapshot.json | 53 +++++-------------- cloud/core/migrations/meta/_journal.json | 2 +- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/cloud/core/migrations/meta/0004_snapshot.json b/cloud/core/migrations/meta/0004_snapshot.json index b9b1bfc8c..6d2695c48 100644 --- a/cloud/core/migrations/meta/0004_snapshot.json +++ b/cloud/core/migrations/meta/0004_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -140,10 +138,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -216,10 +211,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -327,10 +319,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -415,17 +404,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -433,10 +417,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -514,10 +495,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -525,10 +503,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -585,9 +560,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -595,9 +568,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -614,4 +585,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index 25fd94a99..206d5e69b 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -38,4 +38,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From 983e3b2ee38fcfc98fddcc2e2cfdb0806f430d99 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 05:59:53 -0400 Subject: [PATCH 15/62] fix compaction issues --- packages/opencode/src/session/index.ts | 293 +++++++++++++++---------- packages/opencode/src/util/token.ts | 7 + 2 files changed, 185 insertions(+), 115 deletions(-) create mode 100644 packages/opencode/src/util/token.ts diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 21eaa3be8..cdbecbb44 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -53,6 +53,7 @@ import { defer } from "../util/defer" import { Command } from "../command" import { $ } from "bun" import { ListTool } from "../tool/ls" +import { Token } from "../util/token" export namespace Session { const log = Log.create({ service: "session" }) @@ -83,6 +84,12 @@ export namespace Session { .optional(), title: z.string(), version: z.string(), + compaction: z + .object({ + full: z.string().optional(), + micro: z.string().optional(), + }) + .optional(), time: z.object({ created: z.number(), updated: z.number(), @@ -361,6 +368,7 @@ export namespace Session { Bus.publish(MessageV2.Event.Updated, { info: msg, }) + return msg } async function updatePart(part: MessageV2.Part) { @@ -717,14 +725,34 @@ export namespace Session { } return Provider.defaultModel() })().then((x) => Provider.getModel(x.providerID, x.modelID)) + let msgs = await messages(input.sessionID) + const lastSummary = Math.max( + 0, + msgs.findLastIndex((msg) => msg.info.role === "assistant" && msg.info.summary === true), + ) + msgs = msgs.slice(lastSummary) + + const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant") + if ( + lastAssistant?.info.role === "assistant" && + needsCompaction({ + tokens: lastAssistant.info.tokens, + model: model.info, + }) + ) { + const msg = await summarize({ + sessionID: input.sessionID, + providerID: model.providerID, + modelID: model.info.id, + }) + msgs = [msg] + } const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX using abort = lock(input.sessionID) - const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true) - if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id) const numRealUserMsgs = msgs.filter( (m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic), ).length @@ -819,39 +847,21 @@ export namespace Session { const [first, ...rest] = system system = [first, rest.join("\n")] - const assistantMsg: MessageV2.Info = { - id: Identifier.ascending("message"), - role: "assistant", - system, - mode: inputAgent, - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - cost: 0, - tokens: { - input: 0, - output: 0, - reasoning: 0, - cache: { read: 0, write: 0 }, - }, - modelID: model.modelID, - providerID: model.providerID, - time: { - created: Date.now(), - }, + const processor = await createProcessor({ sessionID: input.sessionID, - } - await updateMessage(assistantMsg) + model: model.info, + providerID: model.providerID, + agent: inputAgent, + system, + }) + await using _ = defer(async () => { - if (assistantMsg.time.completed) return - await Storage.remove(["session", "message", input.sessionID, assistantMsg.id]) - await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: assistantMsg.id }) + if (processor.message.time.completed) return + await Storage.remove(["session", "message", input.sessionID, processor.message.id]) + await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: processor.message.id }) }) const tools: Record = {} - const processor = createProcessor(assistantMsg, model.info) - const enabledTools = pipe( agent.tools, mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)), @@ -878,7 +888,7 @@ export namespace Session { const result = await item.execute(args, { sessionID: input.sessionID, abort: options.abortSignal!, - messageID: assistantMsg.id, + messageID: processor.message.id, callID: options.toolCallId, agent: agent.name, metadata: async (val) => { @@ -982,6 +992,8 @@ export namespace Session { }, }, ) + + let pointer = 0 const stream = streamText({ onError(e) { log.error("streamText error", { @@ -989,39 +1001,32 @@ export namespace Session { }) }, async prepareStep({ messages, steps }) { - // Auto compact if too long - const tokens = (() => { - if (steps.length) { - const previous = steps.at(-1) - if (previous) return getUsage(model.info, previous.usage, previous.providerMetadata).tokens - } - const msg = msgs.findLast((x) => x.info.role === "assistant")?.info as MessageV2.Assistant - if (msg && msg.tokens) { - return msg.tokens - } - })() - if (tokens) { - log.info("compact check", tokens) - const count = tokens.input + tokens.cache.read + tokens.cache.write + tokens.output - if (model.info.limit.context && count > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) { - log.info("compacting in prepareStep") - const summarized = await summarize({ - sessionID: input.sessionID, - providerID: model.providerID, - modelID: model.info.id, - }) - const msgs = await Session.messages(input.sessionID).then((x) => - x.filter((x) => x.info.id >= summarized.id), - ) - return { - messages: MessageV2.toModelMessage(msgs), - } - } + log.info("search", { + length: messages.length, + }) + const step = steps.at(-1) + if ( + step && + needsCompaction({ + tokens: getUsage(model.info, step.usage, step.providerMetadata).tokens, + model: model.info, + }) + ) { + await processor.end() + const msg = await Session.summarize({ + sessionID: input.sessionID, + providerID: model.providerID, + modelID: model.info.id, + }) + await processor.next() + pointer = messages.length - 1 + messages.push(...MessageV2.toModelMessage([msg])) } // Add queued messages to the stream const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed) if (queue.length) { + await processor.end() for (const item of queue) { if (item.processed) continue messages.push( @@ -1034,35 +1039,10 @@ export namespace Session { ) item.processed = true } - assistantMsg.time.completed = Date.now() - await updateMessage(assistantMsg) - Object.assign(assistantMsg, { - id: Identifier.ascending("message"), - role: "assistant", - system, - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - cost: 0, - tokens: { - input: 0, - output: 0, - reasoning: 0, - cache: { read: 0, write: 0 }, - }, - modelID: model.modelID, - providerID: model.providerID, - mode: inputAgent, - time: { - created: Date.now(), - }, - sessionID: input.sessionID, - }) - await updateMessage(assistantMsg) + await processor.next() } return { - messages, + messages: messages.slice(pointer), } }, async experimental_repairToolCall(input) { @@ -1421,11 +1401,60 @@ export namespace Session { }) } - function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) { + async function createProcessor(input: { + sessionID: string + providerID: string + model: ModelsDev.Model + system: string[] + agent: string + }) { const toolcalls: Record = {} let snapshot: string | undefined let shouldStop = false - return { + + async function createMessage() { + const msg: MessageV2.Info = { + id: Identifier.ascending("message"), + role: "assistant", + system: input.system, + mode: input.agent, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + cost: 0, + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: input.model.id, + providerID: input.providerID, + time: { + created: Date.now(), + }, + sessionID: input.sessionID, + } + await updateMessage(msg) + return msg + } + + let assistantMsg = await createMessage() + + const result = { + async end() { + if (assistantMsg) { + assistantMsg.time.completed = Date.now() + await updateMessage(assistantMsg) + } + }, + async next() { + assistantMsg = await createMessage() + }, + get message() { + return assistantMsg + }, partFromToolCall(toolCallID: string) { return toolcalls[toolCallID] }, @@ -1581,7 +1610,7 @@ export namespace Session { break case "finish-step": - const usage = getUsage(model, value.usage, value.providerMetadata) + const usage = getUsage(input.model, value.usage, value.providerMetadata) assistantMsg.cost += usage.cost assistantMsg.tokens = usage.tokens await updatePart({ @@ -1672,7 +1701,7 @@ export namespace Session { case LoadAPIKeyError.isInstance(e): assistantMsg.error = new MessageV2.AuthError( { - providerID: model.id, + providerID: input.providerID, message: e.message, }, { cause: e }, @@ -1711,6 +1740,7 @@ export namespace Session { return { info: assistantMsg, parts: p } }, } + return result } export const RevertInput = z.object({ @@ -1789,9 +1819,8 @@ export namespace Session { 0, msgs.findLastIndex((msg) => msg.info.role === "assistant" && msg.info.summary === true), ) - const split = start + Math.floor((msgs.length - start) / 2) - log.info("summarizing", { start, split }) - const toSummarize = msgs.slice(start, split) + log.info("summarizing", { start }) + const toSummarize = msgs.slice(start) const model = await Provider.getModel(input.providerID, input.modelID) const system = [ ...SystemPrompt.summarize(model.providerID), @@ -1799,6 +1828,29 @@ export namespace Session { ...(await SystemPrompt.custom()), ] + const msg = (await 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, @@ -1822,28 +1874,12 @@ export namespace Session { ], }) const usage = getUsage(model.info, generated.usage, generated.providerMetadata) - const msg: MessageV2.Info = { - id: Identifier.create("message", false, toSummarize.at(-1)!.info.time.created + 1), - role: "assistant", - sessionID: input.sessionID, - system, - mode: "build", - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - summary: true, - cost: usage.cost, - tokens: usage.tokens, - modelID: input.modelID, - providerID: model.providerID, - time: { - created: Date.now(), - completed: Date.now(), - }, - } + msg.cost += usage.cost + msg.tokens = usage.tokens + msg.summary = true + msg.time.completed = Date.now() await updateMessage(msg) - await updatePart({ + const part = await updatePart({ type: "text", sessionID: input.sessionID, messageID: msg.id, @@ -1859,7 +1895,34 @@ export namespace Session { sessionID: input.sessionID, }) - return msg + return { + info: msg, + parts: [part], + } + } + + function needsCompaction(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) { + const count = input.tokens.input + input.tokens.cache.read + input.tokens.output + const output = Math.min(input.model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX + const usable = input.model.limit.context - output + return count > usable / 2 + } + + export async function microcompact(input: { sessionID: string }) { + const msgs = await messages(input.sessionID) + let sum = 0 + for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) { + const msg = msgs[msgIndex] + for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) { + const part = msg.parts[partIndex] + if (part.type === "tool") + if (part.state.status === "completed") { + sum += Token.estimate(part.state.output) + if (sum > 40_000) { + } + } + } + } } function isLocked(sessionID: string) { diff --git a/packages/opencode/src/util/token.ts b/packages/opencode/src/util/token.ts new file mode 100644 index 000000000..cee5adc37 --- /dev/null +++ b/packages/opencode/src/util/token.ts @@ -0,0 +1,7 @@ +export namespace Token { + const CHARS_PER_TOKEN = 4 + + export function estimate(input: string) { + return Math.max(0, Math.round((input || "").length / CHARS_PER_TOKEN)) + } +} From 3978a8e6361fcba00e45ea7a3183d8dacad1e871 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 12 Sep 2025 10:08:33 +0000 Subject: [PATCH 16/62] release: v0.7.4 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/sdk/js/src/gen/types.gen.ts | 4 ++++ packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 11 files changed, 14 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index c5507fa42..2bed9ee5c 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.3" + "version": "0.7.4" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index fa401cea8..debb6226b 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.3", + "version": "0.7.4", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index 9b9728c7e..f85f6a8f3 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.3", + "version": "0.7.4", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index fc9491148..3efb3bd29 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.3", + "version": "0.7.4", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index 8bb1b22f5..e5392516d 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.3", + "version": "0.7.4", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index c012c7506..a2fab855e 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.3", + "version": "0.7.4", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index fad117b06..ae2350a30 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.3", + "version": "0.7.4", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index ff50048e0..6f02ff819 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.3", + "version": "0.7.4", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index adf9d3f2c..3ac0c4dde 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -478,6 +478,10 @@ export type Session = { } title: string version: string + compaction?: { + full?: string + micro?: string + } time: { created: number updated: number diff --git a/packages/web/package.json b/packages/web/package.json index 7acf09a78..1cceb4c01 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.3", + "version": "0.7.4", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 1de6c560a..6ae2b6ce4 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.3", + "version": "0.7.4", "publisher": "sst-dev", "repository": { "type": "git", From 661d50f95f57e024e885a35fae9d354351d5c169 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 12 Sep 2025 10:25:57 +0000 Subject: [PATCH 17/62] release: v0.7.5 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index 2bed9ee5c..a2cf47b2c 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.4" + "version": "0.7.5" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index debb6226b..cee3e2423 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.4", + "version": "0.7.5", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index f85f6a8f3..d1ab33813 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.4", + "version": "0.7.5", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 3efb3bd29..248e4ea64 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.4", + "version": "0.7.5", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index e5392516d..fb0bb5049 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.4", + "version": "0.7.5", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index a2fab855e..093992e44 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.4", + "version": "0.7.5", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index ae2350a30..b7520a8fa 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.4", + "version": "0.7.5", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 6f02ff819..e552c7b44 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.4", + "version": "0.7.5", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/web/package.json b/packages/web/package.json index 1cceb4c01..d7e1d64d4 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.4", + "version": "0.7.5", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 6ae2b6ce4..abb270657 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.4", + "version": "0.7.5", "publisher": "sst-dev", "repository": { "type": "git", From 469dc9095f9446108f88cc6b54156af6cd937a45 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 06:33:59 -0400 Subject: [PATCH 18/62] add microcompact --- packages/opencode/src/session/index.ts | 47 ++++++++++----------- packages/opencode/src/session/message-v2.ts | 3 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index cdbecbb44..8f8f42e04 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -84,12 +84,6 @@ export namespace Session { .optional(), title: z.string(), version: z.string(), - compaction: z - .object({ - full: z.string().optional(), - micro: z.string().optional(), - }) - .optional(), time: z.object({ created: z.number(), updated: z.number(), @@ -726,12 +720,7 @@ export namespace Session { return Provider.defaultModel() })().then((x) => Provider.getModel(x.providerID, x.modelID)) - let msgs = await messages(input.sessionID) - const lastSummary = Math.max( - 0, - msgs.findLastIndex((msg) => msg.info.role === "assistant" && msg.info.summary === true), - ) - msgs = msgs.slice(lastSummary) + let msgs = await messages(input.sessionID).then((x) => sinceSummary(x)) const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant") if ( @@ -1001,9 +990,6 @@ export namespace Session { }) }, async prepareStep({ messages, steps }) { - log.info("search", { - length: messages.length, - }) const step = steps.at(-1) if ( step && @@ -1130,6 +1116,7 @@ export namespace Session { item.callback(result) } state().queued.delete(input.sessionID) + Session.microcompact(input) return result } @@ -1814,13 +1801,7 @@ export namespace Session { draft.time.compacting = undefined }) }) - const msgs = await messages(input.sessionID) - const start = Math.max( - 0, - msgs.findLastIndex((msg) => msg.info.role === "assistant" && msg.info.summary === true), - ) - log.info("summarizing", { start }) - const toSummarize = msgs.slice(start) + const toSummarize = await messages(input.sessionID).then((x) => sinceSummary(x)) const model = await Provider.getModel(input.providerID, input.modelID) const system = [ ...SystemPrompt.summarize(model.providerID), @@ -1901,24 +1882,42 @@ export namespace Session { } } + function sinceSummary(msgs: { info: MessageV2.Info; parts: MessageV2.Part[] }[]) { + const result = [] + for (let i = msgs.length - 1; i >= 0; i--) { + const msg = msgs[i] + result.push(msg) + if (msg.info.role === "assistant" && msg.info.summary) break + } + return result.toReversed() + } + function needsCompaction(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) { const count = input.tokens.input + input.tokens.cache.read + input.tokens.output const output = Math.min(input.model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX const usable = input.model.limit.context - output - return count > usable / 2 + return count > usable } export async function microcompact(input: { sessionID: string }) { const msgs = await messages(input.sessionID) let sum = 0 - for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) { + for (let msgIndex = msgs.length - 2; msgIndex >= 0; msgIndex--) { const msg = msgs[msgIndex] + if (msg.info.role === "assistant" && msg.info.summary) return for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) { const part = msg.parts[partIndex] if (part.type === "tool") if (part.state.status === "completed") { + if (part.state.time.compacted) return sum += Token.estimate(part.state.output) if (sum > 40_000) { + log.info("microcompacting", { + sum, + id: part.id, + }) + part.state.time.compacted = Date.now() + await updatePart(part) } } } diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index fd14afbd7..3102a611c 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -52,6 +52,7 @@ export namespace MessageV2 { time: z.object({ start: z.number(), end: z.number(), + compacted: z.number().optional(), }), }) .openapi({ @@ -528,7 +529,7 @@ export namespace MessageV2 { state: "output-available", toolCallId: part.callID, input: part.state.input, - output: part.state.output, + output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output, }, ] if (part.state.status === "error") From 4ceee534809dd3b0646c0d1baaf7863628bdff0b Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 12 Sep 2025 10:45:44 +0000 Subject: [PATCH 19/62] release: v0.7.6 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/sdk/js/src/gen/types.gen.ts | 5 +---- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 11 files changed, 11 insertions(+), 14 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index a2cf47b2c..d72c6dbb5 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.5" + "version": "0.7.6" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index cee3e2423..932be89c1 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.5", + "version": "0.7.6", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index d1ab33813..09a1be0f0 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.5", + "version": "0.7.6", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 248e4ea64..03e8a11ae 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.5", + "version": "0.7.6", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index fb0bb5049..91662a0fe 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.5", + "version": "0.7.6", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 093992e44..32319226c 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.5", + "version": "0.7.6", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index b7520a8fa..600ac8630 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.5", + "version": "0.7.6", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index e552c7b44..458dca7f8 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.5", + "version": "0.7.6", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 3ac0c4dde..7a2f964de 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -343,6 +343,7 @@ export type ToolStateCompleted = { time: { start: number end: number + compacted?: number } } @@ -478,10 +479,6 @@ export type Session = { } title: string version: string - compaction?: { - full?: string - micro?: string - } time: { created: number updated: number diff --git a/packages/web/package.json b/packages/web/package.json index d7e1d64d4..6f24c4082 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.5", + "version": "0.7.6", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index abb270657..8273d8cc0 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.5", + "version": "0.7.6", "publisher": "sst-dev", "repository": { "type": "git", From 0290b4aaf0fddfbc6030f8758b8c2309e4300909 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 06:41:55 -0400 Subject: [PATCH 20/62] ignore: internal --- packages/opencode/src/session/index.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 8f8f42e04..d333828b8 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1116,7 +1116,7 @@ export namespace Session { item.callback(result) } state().queued.delete(input.sessionID) - Session.microcompact(input) + Session.prune(input) return result } @@ -1899,7 +1899,10 @@ export namespace Session { return count > usable } - export async function microcompact(input: { sessionID: string }) { + // goes backwards through parts until there are 40_000 tokens worth of tool + // calls. then erases output of previous tool calls. idea is to throw away old + // tool calls that are no longer relevant. + export async function prune(input: { sessionID: string }) { const msgs = await messages(input.sessionID) let sum = 0 for (let msgIndex = msgs.length - 2; msgIndex >= 0; msgIndex--) { @@ -1912,7 +1915,7 @@ export namespace Session { if (part.state.time.compacted) return sum += Token.estimate(part.state.output) if (sum > 40_000) { - log.info("microcompacting", { + log.info("pruning", { sum, id: part.id, }) From ef3425a177d8bc431c69ef7350732723e0c37d62 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Sep 2025 12:04:14 +0000 Subject: [PATCH 21/62] ignore: update download stats 2025-09-12 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index 97d05fd30..43faa489b 100644 --- a/STATS.md +++ b/STATS.md @@ -76,3 +76,4 @@ | 2025-09-09 | 300,036 (+6,695) | 229,788 (+2,715) | 529,824 (+9,410) | | 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) | From c3dc6d6df647f8e21dfcd9aeaf4d69bc45580020 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 11 Sep 2025 19:10:53 -0400 Subject: [PATCH 22/62] wip: zen --- cloud/scripts/package.json | 5 +++-- cloud/scripts/src/backfill-usage-provider.ts | 10 ++++++++++ cloud/scripts/src/placeholder.ts | 1 - 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 cloud/scripts/src/backfill-usage-provider.ts delete mode 100644 cloud/scripts/src/placeholder.ts diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 03e8a11ae..0b8a5c5fd 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -5,8 +5,9 @@ "private": true, "type": "module", "scripts": { - "start": "tsx", - "shell": "sst shell" + "shell": "sst shell -- bun tsx", + "shell-dev": "sst shell --stage dev -- bun tsx", + "shell-prod": "sst shell --stage production -- bun tsx" }, "dependencies": { "@opencode/cloud-core": "workspace:*", diff --git a/cloud/scripts/src/backfill-usage-provider.ts b/cloud/scripts/src/backfill-usage-provider.ts new file mode 100644 index 000000000..553dcb478 --- /dev/null +++ b/cloud/scripts/src/backfill-usage-provider.ts @@ -0,0 +1,10 @@ +import { Database, eq } from "@opencode/cloud-core/drizzle/index.js" +import { UsageTable } from "@opencode/cloud-core/schema/billing.sql.js" + +await Database.use(async (tx) => { + await tx + .update(UsageTable) + .set({ model: "grok-code" }) + .where(eq(UsageTable.model, "x-ai/grok-code-fast-1")) + .limit(90000) +}) diff --git a/cloud/scripts/src/placeholder.ts b/cloud/scripts/src/placeholder.ts deleted file mode 100644 index ff7bd09c0..000000000 --- a/cloud/scripts/src/placeholder.ts +++ /dev/null @@ -1 +0,0 @@ -// placeholder From c294a181559185dedf6a65e00e1db597721a815b Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Sep 2025 11:57:12 -0400 Subject: [PATCH 23/62] wip: zen --- .../app/src/routes/zen/v1/chat/completions.ts | 28 +- cloud/app/src/routes/zen/v1/messages.ts | 6 +- cloud/app/src/routes/zen/v1/responses.ts | 26 +- cloud/app/src/util/zen.ts | 51 +- cloud/core/migrations/0005_jazzy_skrulls.sql | 1 + .../migrations/0006_parallel_gauntlet.sql | 2 + cloud/core/migrations/meta/0005_snapshot.json | 617 +++++++++++++++++ cloud/core/migrations/meta/0006_snapshot.json | 631 ++++++++++++++++++ cloud/core/migrations/meta/_journal.json | 14 + cloud/core/src/schema/billing.sql.ts | 6 +- 10 files changed, 1342 insertions(+), 40 deletions(-) create mode 100644 cloud/core/migrations/0005_jazzy_skrulls.sql create mode 100644 cloud/core/migrations/0006_parallel_gauntlet.sql create mode 100644 cloud/core/migrations/meta/0005_snapshot.json create mode 100644 cloud/core/migrations/meta/0006_snapshot.json diff --git a/cloud/app/src/routes/zen/v1/chat/completions.ts b/cloud/app/src/routes/zen/v1/chat/completions.ts index dc69bb514..13a75a4a4 100644 --- a/cloud/app/src/routes/zen/v1/chat/completions.ts +++ b/cloud/app/src/routes/zen/v1/chat/completions.ts @@ -1,8 +1,26 @@ import type { APIEvent } from "@solidjs/start/server" import { handler } from "~/util/zen" +type Usage = { + prompt_tokens?: number + completion_tokens?: number + total_tokens?: number + prompt_tokens_details?: { + text_tokens?: number + audio_tokens?: number + image_tokens?: number + cached_tokens?: number + } + completion_tokens_details?: { + reasoning_tokens?: number + audio_tokens?: number + accepted_prediction_tokens?: number + rejected_prediction_tokens?: number + } +} + export function POST(input: APIEvent) { - let usage: any + let usage: Usage return handler(input, { modifyBody: (body: any) => ({ ...body, @@ -17,7 +35,7 @@ export function POST(input: APIEvent) { let json try { - json = JSON.parse(chunk.slice(6)) + json = JSON.parse(chunk.slice(6)) as { usage?: Usage } } catch (e) { return } @@ -26,11 +44,11 @@ export function POST(input: APIEvent) { usage = json.usage }, getStreamUsage: () => usage, - normalizeUsage: (usage: any) => ({ + normalizeUsage: (usage: Usage) => ({ inputTokens: usage.prompt_tokens ?? 0, outputTokens: usage.completion_tokens ?? 0, - reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0, - cacheReadTokens: usage.prompt_tokens_details?.cached_tokens ?? 0, + reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? undefined, + cacheReadTokens: usage.prompt_tokens_details?.cached_tokens ?? undefined, }), }) } diff --git a/cloud/app/src/routes/zen/v1/messages.ts b/cloud/app/src/routes/zen/v1/messages.ts index ff399b034..b2e9e275a 100644 --- a/cloud/app/src/routes/zen/v1/messages.ts +++ b/cloud/app/src/routes/zen/v1/messages.ts @@ -53,9 +53,9 @@ export function POST(input: APIEvent) { normalizeUsage: (usage: Usage) => ({ inputTokens: usage.input_tokens ?? 0, outputTokens: usage.output_tokens ?? 0, - cacheReadTokens: usage.cache_read_input_tokens ?? 0, - cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens, - cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens, + cacheReadTokens: usage.cache_read_input_tokens ?? undefined, + cacheWrite5mTokens: usage.cache_creation?.ephemeral_5m_input_tokens ?? undefined, + cacheWrite1hTokens: usage.cache_creation?.ephemeral_1h_input_tokens ?? undefined, }), }) } diff --git a/cloud/app/src/routes/zen/v1/responses.ts b/cloud/app/src/routes/zen/v1/responses.ts index 518a431ab..1bca91f52 100644 --- a/cloud/app/src/routes/zen/v1/responses.ts +++ b/cloud/app/src/routes/zen/v1/responses.ts @@ -1,8 +1,20 @@ import type { APIEvent } from "@solidjs/start/server" import { handler } from "~/util/zen" +type Usage = { + input_tokens?: number + input_tokens_details?: { + cached_tokens?: number + } + output_tokens?: number + output_tokens_details?: { + reasoning_tokens?: number + } + total_tokens?: number +} + export function POST(input: APIEvent) { - let usage: any + let usage: Usage return handler(input, { setAuthHeader: (headers: Headers, apiKey: string) => { headers.set("authorization", `Bearer ${apiKey}`) @@ -15,7 +27,7 @@ export function POST(input: APIEvent) { let json try { - json = JSON.parse(data.slice(6)) + json = JSON.parse(data.slice(6)) as { response?: { usage?: Usage } } } catch (e) { return } @@ -24,14 +36,14 @@ export function POST(input: APIEvent) { usage = json.response.usage }, getStreamUsage: () => usage, - normalizeUsage: (usage: any) => { + normalizeUsage: (usage: Usage) => { const inputTokens = usage.input_tokens ?? 0 const outputTokens = usage.output_tokens ?? 0 - const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? 0 - const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? 0 + const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? undefined + const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? undefined return { - inputTokens: inputTokens - cacheReadTokens, - outputTokens: outputTokens - reasoningTokens, + inputTokens: inputTokens - (cacheReadTokens ?? 0), + outputTokens: outputTokens - (reasoningTokens ?? 0), reasoningTokens, cacheReadTokens, } diff --git a/cloud/app/src/util/zen.ts b/cloud/app/src/util/zen.ts index 89c91c604..bff2df40e 100644 --- a/cloud/app/src/util/zen.ts +++ b/cloud/app/src/util/zen.ts @@ -10,10 +10,11 @@ import { Resource } from "@opencode/cloud-resource" type ModelCost = { input: number output: number - cacheRead: number - cacheWrite5m: number - cacheWrite1h: number + cacheRead?: number + cacheWrite5m?: number + cacheWrite1h?: number } + type Model = { id: string auth: boolean @@ -42,7 +43,7 @@ export async function handler( inputTokens: number outputTokens: number reasoningTokens?: number - cacheReadTokens: number + cacheReadTokens?: number cacheWrite5mTokens?: number cacheWrite1hTokens?: number } @@ -129,8 +130,6 @@ export async function handler( input: 0.00000125, output: 0.00001, cacheRead: 0.000000125, - cacheWrite5m: 0, - cacheWrite1h: 0, }, headerMappings: {}, providers: { @@ -147,9 +146,6 @@ export async function handler( cost: { input: 0.00000045, output: 0.0000018, - cacheRead: 0, - cacheWrite5m: 0, - cacheWrite1h: 0, }, headerMappings: {}, providers: { @@ -173,9 +169,6 @@ export async function handler( cost: { input: 0.0000006, output: 0.0000025, - cacheRead: 0, - cacheWrite5m: 0, - cacheWrite1h: 0, }, headerMappings: {}, providers: { @@ -200,8 +193,6 @@ export async function handler( input: 0, output: 0, cacheRead: 0, - cacheWrite5m: 0, - cacheWrite1h: 0, }, headerMappings: { "x-grok-conv-id": "x-opencode-session", @@ -222,9 +213,6 @@ export async function handler( cost: { input: 0.00000038, output: 0.00000153, - cacheRead: 0, - cacheWrite5m: 0, - cacheWrite1h: 0, }, headerMappings: {}, providers: { @@ -438,15 +426,30 @@ export async function handler( const inputCost = modelCost.input * inputTokens * 100 const outputCost = modelCost.output * outputTokens * 100 - const reasoningCost = reasoningTokens ? modelCost.output * reasoningTokens * 100 : undefined - const cacheReadCost = modelCost.cacheRead * cacheReadTokens * 100 - const cacheWrite5mCost = cacheWrite5mTokens ? modelCost.cacheWrite5m * cacheWrite5mTokens * 100 : undefined - const cacheWrite1hCost = cacheWrite1hTokens ? modelCost.cacheWrite1h * cacheWrite1hTokens * 100 : undefined + const reasoningCost = (() => { + if (!reasoningTokens) return undefined + return modelCost.output * reasoningTokens * 100 + })() + const cacheReadCost = (() => { + if (!cacheReadTokens) return undefined + if (!modelCost.cacheRead) return undefined + return modelCost.cacheRead * cacheReadTokens * 100 + })() + const cacheWrite5mCost = (() => { + if (!cacheWrite5mTokens) return undefined + if (!modelCost.cacheWrite5m) return undefined + return modelCost.cacheWrite5m * cacheWrite5mTokens * 100 + })() + const cacheWrite1hCost = (() => { + if (!cacheWrite1hTokens) return undefined + if (!modelCost.cacheWrite1h) return undefined + return modelCost.cacheWrite1h * cacheWrite1hTokens * 100 + })() const totalCostInCent = inputCost + outputCost + (reasoningCost ?? 0) + - cacheReadCost + + (cacheReadCost ?? 0) + (cacheWrite5mCost ?? 0) + (cacheWrite1hCost ?? 0) @@ -460,7 +463,7 @@ export async function handler( "cost.input": Math.round(inputCost), "cost.output": Math.round(outputCost), "cost.reasoning": reasoningCost ? Math.round(reasoningCost) : undefined, - "cost.cache_read": Math.round(cacheReadCost), + "cost.cache_read": cacheReadCost ? Math.round(cacheReadCost) : undefined, "cost.cache_write_5m": cacheWrite5mCost ? Math.round(cacheWrite5mCost) : undefined, "cost.cache_write_1h": cacheWrite1hCost ? Math.round(cacheWrite1hCost) : undefined, "cost.total": Math.round(totalCostInCent), @@ -480,6 +483,8 @@ export async function handler( reasoningTokens, cacheReadTokens, cacheWriteTokens: (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0), + cacheWrite5mTokens, + cacheWrite1hTokens, cost, }) await tx diff --git a/cloud/core/migrations/0005_jazzy_skrulls.sql b/cloud/core/migrations/0005_jazzy_skrulls.sql new file mode 100644 index 000000000..774c38dd8 --- /dev/null +++ b/cloud/core/migrations/0005_jazzy_skrulls.sql @@ -0,0 +1 @@ +ALTER TABLE `usage` MODIFY COLUMN `provider` varchar(255) NOT NULL; \ No newline at end of file diff --git a/cloud/core/migrations/0006_parallel_gauntlet.sql b/cloud/core/migrations/0006_parallel_gauntlet.sql new file mode 100644 index 000000000..a1ff78e78 --- /dev/null +++ b/cloud/core/migrations/0006_parallel_gauntlet.sql @@ -0,0 +1,2 @@ +ALTER TABLE `usage` ADD `cache_write_5m_tokens` int;--> statement-breakpoint +ALTER TABLE `usage` ADD `cache_write_1h_tokens` int; \ No newline at end of file diff --git a/cloud/core/migrations/meta/0005_snapshot.json b/cloud/core/migrations/meta/0005_snapshot.json new file mode 100644 index 000000000..28f9d22c9 --- /dev/null +++ b/cloud/core/migrations/meta/0005_snapshot.json @@ -0,0 +1,617 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "d13af80e-3c70-4866-8f14-48e7ff6ff0ff", + "prevId": "06dc6226-bfbb-4ccc-b4bc-f26070c3bed5", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email": { + "name": "email", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_name": { + "name": "old_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": [ + "key" + ], + "isUnique": true + }, + "name": { + "name": "name", + "columns": [ + "workspace_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email": { + "name": "user_email", + "columns": [ + "workspace_id", + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/cloud/core/migrations/meta/0006_snapshot.json b/cloud/core/migrations/meta/0006_snapshot.json new file mode 100644 index 000000000..8fe57e32a --- /dev/null +++ b/cloud/core/migrations/meta/0006_snapshot.json @@ -0,0 +1,631 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "b0ad4b11-b607-46c7-8e2d-3b9823cdc5f7", + "prevId": "d13af80e-3c70-4866-8f14-48e7ff6ff0ff", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email": { + "name": "email", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_5m_tokens": { + "name": "cache_write_5m_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_1h_tokens": { + "name": "cache_write_1h_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_name": { + "name": "old_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": [ + "key" + ], + "isUnique": true + }, + "name": { + "name": "name", + "columns": [ + "workspace_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email": { + "name": "user_email", + "columns": [ + "workspace_id", + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index 206d5e69b..50713706e 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -36,6 +36,20 @@ "when": 1757627357232, "tag": "0004_first_mockingbird", "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1757632304856, + "tag": "0005_jazzy_skrulls", + "breakpoints": true + }, + { + "idx": 6, + "version": "5", + "when": 1757643108507, + "tag": "0006_parallel_gauntlet", + "breakpoints": true } ] } diff --git a/cloud/core/src/schema/billing.sql.ts b/cloud/core/src/schema/billing.sql.ts index 0473df142..d94415ce9 100644 --- a/cloud/core/src/schema/billing.sql.ts +++ b/cloud/core/src/schema/billing.sql.ts @@ -1,4 +1,4 @@ -import { bigint, boolean, int, mysqlTable, varchar } from "drizzle-orm/mysql-core" +import { bigint, boolean, int, mysqlTable, varchar, json } from "drizzle-orm/mysql-core" import { timestamps, workspaceColumns } from "../drizzle/types" import { workspaceIndexes } from "./workspace.sql" @@ -34,12 +34,14 @@ export const UsageTable = mysqlTable( ...workspaceColumns, ...timestamps, model: varchar("model", { length: 255 }).notNull(), - provider: varchar("provider", { length: 255 }), + provider: varchar("provider", { length: 255 }).notNull(), inputTokens: int("input_tokens").notNull(), outputTokens: int("output_tokens").notNull(), reasoningTokens: int("reasoning_tokens"), cacheReadTokens: int("cache_read_tokens"), cacheWriteTokens: int("cache_write_tokens"), + cacheWrite5mTokens: int("cache_write_5m_tokens"), + cacheWrite1hTokens: int("cache_write_1h_tokens"), cost: bigint("cost", { mode: "number" }).notNull(), }, (table) => [...workspaceIndexes(table)], From c5fa3ee9f8b0de8e5f5d88797abd01f62862f624 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Sep 2025 15:57:50 +0000 Subject: [PATCH 24/62] chore: format code --- cloud/core/migrations/meta/0005_snapshot.json | 53 +++++-------------- cloud/core/migrations/meta/0006_snapshot.json | 53 +++++-------------- 2 files changed, 24 insertions(+), 82 deletions(-) diff --git a/cloud/core/migrations/meta/0005_snapshot.json b/cloud/core/migrations/meta/0005_snapshot.json index 28f9d22c9..12246a6d6 100644 --- a/cloud/core/migrations/meta/0005_snapshot.json +++ b/cloud/core/migrations/meta/0005_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -140,10 +138,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -216,10 +211,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -327,10 +319,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -415,17 +404,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -433,10 +417,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -514,10 +495,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -525,10 +503,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -585,9 +560,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -595,9 +568,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -614,4 +585,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/cloud/core/migrations/meta/0006_snapshot.json b/cloud/core/migrations/meta/0006_snapshot.json index 8fe57e32a..d726b6f67 100644 --- a/cloud/core/migrations/meta/0006_snapshot.json +++ b/cloud/core/migrations/meta/0006_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -140,10 +138,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -216,10 +211,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -341,10 +333,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -429,17 +418,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -447,10 +431,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -528,10 +509,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -539,10 +517,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -599,9 +574,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -609,9 +582,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -628,4 +599,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} From 9a346a00fb3751d8e8b355aeeedf0adc1226e2da Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Sep 2025 12:18:32 -0400 Subject: [PATCH 25/62] wip: zen --- bun.lock | 36 +- cloud/app/src/util/zen.ts | 1 - .../migrations/0007_familiar_nightshade.sql | 1 + cloud/core/migrations/meta/0007_snapshot.json | 624 ++++++++++++++++++ cloud/core/migrations/meta/_journal.json | 9 +- cloud/core/src/schema/billing.sql.ts | 1 - package.json | 2 +- 7 files changed, 652 insertions(+), 22 deletions(-) create mode 100644 cloud/core/migrations/0007_familiar_nightshade.sql create mode 100644 cloud/core/migrations/meta/0007_snapshot.json diff --git a/bun.lock b/bun.lock index 6453c76da..b52e91121 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,7 @@ }, "devDependencies": { "prettier": "3.5.3", - "sst": "3.17.12", + "sst": "3.17.13", }, }, "cloud/app": { @@ -26,7 +26,7 @@ }, "cloud/core": { "name": "@opencode/cloud-core", - "version": "0.6.10", + "version": "0.7.6", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@opencode/cloud-resource": "workspace:*", @@ -43,7 +43,7 @@ }, "cloud/function": { "name": "@opencode/cloud-function", - "version": "0.6.10", + "version": "0.7.6", "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.6.10", + "version": "0.7.6", "dependencies": { "@opencode/cloud-core": "workspace:*", "tsx": "4.20.5", @@ -81,7 +81,7 @@ }, "packages/function": { "name": "@opencode/function", - "version": "0.6.10", + "version": "0.7.6", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -96,7 +96,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "0.6.10", + "version": "0.7.6", "bin": { "opencode": "./bin/opencode", }, @@ -146,7 +146,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "0.6.10", + "version": "0.7.6", "dependencies": { "@opencode-ai/sdk": "workspace:*", }, @@ -157,7 +157,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "0.6.10", + "version": "0.7.6", "dependencies": { "@hey-api/openapi-ts": "0.81.0", }, @@ -169,7 +169,7 @@ }, "packages/web": { "name": "@opencode/web", - "version": "0.6.10", + "version": "0.7.6", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -2647,23 +2647,23 @@ "ssri": ["ssri@10.0.6", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ=="], - "sst": ["sst@3.17.12", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.12", "sst-darwin-x64": "3.17.12", "sst-linux-arm64": "3.17.12", "sst-linux-x64": "3.17.12", "sst-linux-x86": "3.17.12", "sst-win32-arm64": "3.17.12", "sst-win32-x64": "3.17.12", "sst-win32-x86": "3.17.12" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-UwUbucNZRLp9GHgPAwkat1sBsNGaJfHsLXZHCMKsolCW7CEuugJfvBl2vOyJrhKP4N+Xnv1QFh0BGsOmN0kQeA=="], + "sst": ["sst@3.17.13", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.13", "sst-darwin-x64": "3.17.13", "sst-linux-arm64": "3.17.13", "sst-linux-x64": "3.17.13", "sst-linux-x86": "3.17.13", "sst-win32-arm64": "3.17.13", "sst-win32-x64": "3.17.13", "sst-win32-x86": "3.17.13" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-NaNTZL7uk2AsXzPBySQy7aqXlStXorR+bA785NxvCbskwkc44nVSQcEsvX5tdsD6/jrWpw9tDy4sStv2ycLAng=="], - "sst-darwin-arm64": ["sst-darwin-arm64@3.17.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9Oky2ZmJoeEN97ALWtFRt3kvSIZLjYoQoOtJvTaNQJTFi/9OsUE/6I5zdedf5GhMKCT1JvY+Ngpv3U3Y6SEYOg=="], + "sst-darwin-arm64": ["sst-darwin-arm64@3.17.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZaDReT/c+2CcEnFkYjMty63II2ckQrUniiSdoEH6eAWyU1Iy7UwKK4I2GYm+5dy9xeSBaOKga6FMdLqFxIiUg=="], - "sst-darwin-x64": ["sst-darwin-x64@3.17.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-n6tWCjFF9Pb+QzxXJmuTGfQ4GW96Nf6ATtb7Wpa+9RDLRHrEBdOjXAp7osr7MB9djPRkt4942nwUZ7wX/EULpg=="], + "sst-darwin-x64": ["sst-darwin-x64@3.17.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-1DlYMrmrI5RY3/Ob039JatgvDKZ5QNtyRkVu0WcnsOvcxFE4dzrCiYKYHg2A+FMDl+H1qcwy2gGA3BTwC9in1w=="], - "sst-linux-arm64": ["sst-linux-arm64@3.17.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-iMflBzQWhF5kmRdXu402dwVpQI9LfFR3yFok3HUTV0ema5Pq2kPphEatEEw1dyG2ZXCBLeKN+T3Ujjfer+ddRA=="], + "sst-linux-arm64": ["sst-linux-arm64@3.17.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-A4+ZamchUdaX0pqvYWZ+r7OP1bruwEj9qgWT5kU7Q5pqaotIsEitYQi0q9nZFKH+5mXYesUwSy5FA+Q8T3X/Rg=="], - "sst-linux-x64": ["sst-linux-x64@3.17.12", "", { "os": "linux", "cpu": "x64" }, "sha512-89rZXs3IfGrY9yiDNuLfcJvHnAUX1gRVeB+lqQ1M2sbJD2iMpN+fx93owcApAndtZYzYNfQYEZ/xYwI6HFfu4w=="], + "sst-linux-x64": ["sst-linux-x64@3.17.13", "", { "os": "linux", "cpu": "x64" }, "sha512-yhKZc5CojqjB2DnyeVka5jeRb4oc3lBx8Qf6or0w4cs47SBIqyvO0iR/3IeKvRRL1hiEUeUF8r/q83rQo9jZMw=="], - "sst-linux-x86": ["sst-linux-x86@3.17.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zc2nd2syaq/DfNxtDcn4NOh8RBCaCZ1qsjLFpvGGfMMRnGiWjofuE6eFX3fhchGL3uvaqwlENvtzj4UC/MF5wQ=="], + "sst-linux-x86": ["sst-linux-x86@3.17.13", "", { "os": "linux", "cpu": "none" }, "sha512-G1FIUmpUaECB/3CV5EO/y1QmV5mQ8RUkFeZq64oyILEEaMzSWWKz0glHzQ3+p316VE74MzbktiWRqsCKQy8GeA=="], - "sst-win32-arm64": ["sst-win32-arm64@3.17.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-Bb7M6PoImmGeyzJu75QbNHBs0mDp21DsKFyMucn2dwxYwahuFPjjMbG+tlziWtxcNgdZMdEcy9jR8ot1jAIh0Q=="], + "sst-win32-arm64": ["sst-win32-arm64@3.17.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-9uCiIXmsGoxGeNWgM81x/d6V/vecjoiHXhBUPDGlQ1Dct1SbtHAgaf/g2ec5XwSQb9B/tne4qk81eMlTl83Z1A=="], - "sst-win32-x64": ["sst-win32-x64@3.17.12", "", { "os": "win32", "cpu": "x64" }, "sha512-7f41o1WhxdcuLhHijoavkX5O3L/Pnma6zCoL3kG6f9Njc6Zyj8Oha2fQz6Tesb/Qt8deG04WU4bL3FmxgNHU6g=="], + "sst-win32-x64": ["sst-win32-x64@3.17.13", "", { "os": "win32", "cpu": "x64" }, "sha512-hTuj4rFSCI/9tX4NMUpNJ69Q9td/giekELDRNv03ys8TpJGoGvPT8D6VD1eX7j1CQnOZIgeEphfW9WmCXkLaIA=="], - "sst-win32-x86": ["sst-win32-x86@3.17.12", "", { "os": "win32", "cpu": "none" }, "sha512-AfsNJQMTlefHitaVRWh5Uf3AaICIaomFbSo5qDbibgkvhbppCxgMFpW0IxiWySjWrCN5hMMkxdxlZP9IHqqxjQ=="], + "sst-win32-x86": ["sst-win32-x86@3.17.13", "", { "os": "win32", "cpu": "none" }, "sha512-AuMDGux+H1kPckKJ7Szgi04EpBoOKh/v5zFNAPjvWSkcWcGZ+hsBUx3h/FO/AkGK3RnlLMRj4CQQLoa10RSSIg=="], "stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="], diff --git a/cloud/app/src/util/zen.ts b/cloud/app/src/util/zen.ts index bff2df40e..7a7643568 100644 --- a/cloud/app/src/util/zen.ts +++ b/cloud/app/src/util/zen.ts @@ -482,7 +482,6 @@ export async function handler( outputTokens, reasoningTokens, cacheReadTokens, - cacheWriteTokens: (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0), cacheWrite5mTokens, cacheWrite1hTokens, cost, diff --git a/cloud/core/migrations/0007_familiar_nightshade.sql b/cloud/core/migrations/0007_familiar_nightshade.sql new file mode 100644 index 000000000..89cf77f92 --- /dev/null +++ b/cloud/core/migrations/0007_familiar_nightshade.sql @@ -0,0 +1 @@ +ALTER TABLE `usage` DROP COLUMN `cache_write_tokens`; \ No newline at end of file diff --git a/cloud/core/migrations/meta/0007_snapshot.json b/cloud/core/migrations/meta/0007_snapshot.json new file mode 100644 index 000000000..5665dc6c5 --- /dev/null +++ b/cloud/core/migrations/meta/0007_snapshot.json @@ -0,0 +1,624 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "91067cc9-d492-47b3-932a-42dcc0920b3c", + "prevId": "b0ad4b11-b607-46c7-8e2d-3b9823cdc5f7", + "tables": { + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email": { + "name": "email", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "billing": { + "name": "billing", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_id": { + "name": "payment_method_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_method_last4": { + "name": "payment_method_last4", + "type": "varchar(4)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "balance": { + "name": "balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reload": { + "name": "reload", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "billing_workspace_id_id_pk": { + "name": "billing_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "payment": { + "name": "payment", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_id": { + "name": "payment_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "payment_workspace_id_id_pk": { + "name": "payment_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "usage": { + "name": "usage", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model": { + "name": "model", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_5m_tokens": { + "name": "cache_write_5m_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cache_write_1h_tokens": { + "name": "cache_write_1h_tokens", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "usage_workspace_id_id_pk": { + "name": "usage_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "key": { + "name": "key", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor": { + "name": "actor", + "type": "json", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "old_name": { + "name": "old_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_used": { + "name": "time_used", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "global_key": { + "name": "global_key", + "columns": [ + "key" + ], + "isUnique": true + }, + "name": { + "name": "name", + "columns": [ + "workspace_id", + "name" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "key_workspace_id_id_pk": { + "name": "key_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "time_seen": { + "name": "time_seen", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email": { + "name": "user_email", + "columns": [ + "workspace_id", + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_workspace_id_id_pk": { + "name": "user_workspace_id_id_pk", + "columns": [ + "workspace_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "time_created": { + "name": "time_created", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "time_updated": { + "name": "time_updated", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)" + }, + "time_deleted": { + "name": "time_deleted", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "slug": { + "name": "slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workspace_id": { + "name": "workspace_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index 50713706e..7a879609b 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1757643108507, "tag": "0006_parallel_gauntlet", "breakpoints": true + }, + { + "idx": 7, + "version": "5", + "when": 1757693869142, + "tag": "0007_familiar_nightshade", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/cloud/core/src/schema/billing.sql.ts b/cloud/core/src/schema/billing.sql.ts index d94415ce9..7d73b922c 100644 --- a/cloud/core/src/schema/billing.sql.ts +++ b/cloud/core/src/schema/billing.sql.ts @@ -39,7 +39,6 @@ export const UsageTable = mysqlTable( outputTokens: int("output_tokens").notNull(), reasoningTokens: int("reasoning_tokens"), cacheReadTokens: int("cache_read_tokens"), - cacheWriteTokens: int("cache_write_tokens"), cacheWrite5mTokens: int("cache_write_5m_tokens"), cacheWrite1hTokens: int("cache_write_1h_tokens"), cost: bigint("cost", { mode: "number" }).notNull(), diff --git a/package.json b/package.json index 68ca4f414..5d3ab0f90 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "devDependencies": { "prettier": "3.5.3", - "sst": "3.17.12" + "sst": "3.17.13" }, "repository": { "type": "git", From 1f4e8b4954a52572c7bf8e287d3d444eb75c963b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Sep 2025 16:19:09 +0000 Subject: [PATCH 26/62] chore: format code --- cloud/core/migrations/meta/0007_snapshot.json | 53 +++++-------------- cloud/core/migrations/meta/_journal.json | 2 +- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/cloud/core/migrations/meta/0007_snapshot.json b/cloud/core/migrations/meta/0007_snapshot.json index 5665dc6c5..122db42cb 100644 --- a/cloud/core/migrations/meta/0007_snapshot.json +++ b/cloud/core/migrations/meta/0007_snapshot.json @@ -48,9 +48,7 @@ "indexes": { "email": { "name": "email", - "columns": [ - "email" - ], + "columns": ["email"], "isUnique": true } }, @@ -140,10 +138,7 @@ "compositePrimaryKeys": { "billing_workspace_id_id_pk": { "name": "billing_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -216,10 +211,7 @@ "compositePrimaryKeys": { "payment_workspace_id_id_pk": { "name": "payment_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -334,10 +326,7 @@ "compositePrimaryKeys": { "usage_workspace_id_id_pk": { "name": "usage_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -422,17 +411,12 @@ "indexes": { "global_key": { "name": "global_key", - "columns": [ - "key" - ], + "columns": ["key"], "isUnique": true }, "name": { "name": "name", - "columns": [ - "workspace_id", - "name" - ], + "columns": ["workspace_id", "name"], "isUnique": true } }, @@ -440,10 +424,7 @@ "compositePrimaryKeys": { "key_workspace_id_id_pk": { "name": "key_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -521,10 +502,7 @@ "indexes": { "user_email": { "name": "user_email", - "columns": [ - "workspace_id", - "email" - ], + "columns": ["workspace_id", "email"], "isUnique": true } }, @@ -532,10 +510,7 @@ "compositePrimaryKeys": { "user_workspace_id_id_pk": { "name": "user_workspace_id_id_pk", - "columns": [ - "workspace_id", - "id" - ] + "columns": ["workspace_id", "id"] } }, "uniqueConstraints": {}, @@ -592,9 +567,7 @@ "indexes": { "slug": { "name": "slug", - "columns": [ - "slug" - ], + "columns": ["slug"], "isUnique": true } }, @@ -602,9 +575,7 @@ "compositePrimaryKeys": { "workspace_id": { "name": "workspace_id", - "columns": [ - "id" - ] + "columns": ["id"] } }, "uniqueConstraints": {}, @@ -621,4 +592,4 @@ "tables": {}, "indexes": {} } -} \ No newline at end of file +} diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json index 7a879609b..c0b787313 100644 --- a/cloud/core/migrations/meta/_journal.json +++ b/cloud/core/migrations/meta/_journal.json @@ -59,4 +59,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From 3e2478ebf965e09417b696eb3da1dde5053a4f87 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 13:20:00 -0400 Subject: [PATCH 27/62] undo session pruning --- packages/opencode/src/session/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index d333828b8..4a42d320f 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1116,7 +1116,7 @@ export namespace Session { item.callback(result) } state().queued.delete(input.sessionID) - Session.prune(input) + // Session.prune(input) return result } From f7d9a031e69e3ceb68ab0ffd8197a499fb14d1b0 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 12 Sep 2025 17:28:35 +0000 Subject: [PATCH 28/62] release: v0.7.7 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index d72c6dbb5..4f963c5e8 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.6" + "version": "0.7.7" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index 932be89c1..293ee516e 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.6", + "version": "0.7.7", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index 09a1be0f0..dbc38dbef 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.6", + "version": "0.7.7", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 0b8a5c5fd..003289bb1 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.6", + "version": "0.7.7", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index 91662a0fe..a00b30d3b 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.6", + "version": "0.7.7", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 32319226c..43d09faf9 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.6", + "version": "0.7.7", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 600ac8630..4a06afd94 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.6", + "version": "0.7.7", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 458dca7f8..a892f5bae 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.6", + "version": "0.7.7", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/web/package.json b/packages/web/package.json index 6f24c4082..dc6f7205e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.6", + "version": "0.7.7", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 8273d8cc0..90b40291c 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.6", + "version": "0.7.7", "publisher": "sst-dev", "repository": { "type": "git", From 176dc51b2e6f77da231b22ce59bf80e3f24da614 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 13:41:28 -0400 Subject: [PATCH 29/62] ci: exclude production branch from format workflow --- .github/workflows/format.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 7a42fe823..227e9f808 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -2,7 +2,11 @@ name: Format on: push: + branches-ignore: + - production pull_request: + branches-ignore: + - production workflow_dispatch: jobs: format: From f2094b7bb345ad6815abe822c3ee3c8778c9fa93 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 14:00:54 -0400 Subject: [PATCH 30/62] temporarily disable midstream compaction --- packages/opencode/src/session/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 4a42d320f..619e8cc98 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -996,7 +996,8 @@ export namespace Session { needsCompaction({ tokens: getUsage(model.info, step.usage, step.providerMetadata).tokens, model: model.info, - }) + }) && + false ) { await processor.end() const msg = await Session.summarize({ @@ -1005,7 +1006,7 @@ export namespace Session { modelID: model.info.id, }) await processor.next() - pointer = messages.length - 1 + pointer = messages.length messages.push(...MessageV2.toModelMessage([msg])) } From 417e8f619cea713e307c1efbf0b56964834b3731 Mon Sep 17 00:00:00 2001 From: opencode Date: Fri, 12 Sep 2025 18:09:55 +0000 Subject: [PATCH 31/62] release: v0.7.8 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index 4f963c5e8..25ebc9df4 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.7" + "version": "0.7.8" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index 293ee516e..3efba75b6 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.7", + "version": "0.7.8", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index dbc38dbef..ef47b0600 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.7", + "version": "0.7.8", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 003289bb1..a7efdd00c 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.7", + "version": "0.7.8", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index a00b30d3b..ace559ad6 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.7", + "version": "0.7.8", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 43d09faf9..cc5c80d30 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.7", + "version": "0.7.8", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 4a06afd94..b6ca74ec8 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.7", + "version": "0.7.8", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index a892f5bae..f241b7754 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.7", + "version": "0.7.8", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/web/package.json b/packages/web/package.json index dc6f7205e..36046b537 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.7", + "version": "0.7.8", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 90b40291c..ab962baf5 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.7", + "version": "0.7.8", "publisher": "sst-dev", "repository": { "type": "git", From c6c153de95668b6e3a3e08e122cad06b8e0f40e6 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Sep 2025 14:22:40 -0400 Subject: [PATCH 32/62] wip: zen --- cloud/app/src/routes/workspace/[id].css | 14 +++++++++++ cloud/app/src/routes/workspace/[id].tsx | 33 ++++++++++++++++++++----- cloud/core/src/key.ts | 6 ++++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/cloud/app/src/routes/workspace/[id].css b/cloud/app/src/routes/workspace/[id].css index 2d9b0e638..6cf8a0cff 100644 --- a/cloud/app/src/routes/workspace/[id].css +++ b/cloud/app/src/routes/workspace/[id].css @@ -115,11 +115,18 @@ [data-component="api-keys-section"] { [data-slot="create-form"] { display: flex; + flex-direction: column; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--border-radius-sm); + [data-slot="input-container"] { + display: flex; + flex-direction: column; + gap: var(--space-1); + } + @media (max-width: 30rem) { gap: var(--space-2); } @@ -148,6 +155,13 @@ display: flex; gap: var(--space-2); } + + [data-slot="form-error"] { + color: var(--color-danger); + font-size: var(--font-size-sm); + margin-top: var(--space-1); + line-height: 1.4; + } } [data-slot="api-keys-table"] { diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index 222e51a15..39db5cc40 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -49,7 +49,13 @@ const createKey = action(async (form: FormData) => { const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } return json( - withActor(() => Key.create({ name }), workspaceID), + withActor( + () => + Key.create({ name }) + .then((data) => ({ data })) + .catch((e) => ({ error: e.message })), + workspaceID, + ), { revalidate: listKeys.key }, ) }, "key.create") @@ -185,19 +191,27 @@ function KeySection() { function KeyCreateForm() { const params = useParams() const submission = useSubmission(createKey) - const [store, setStore] = createStore({ - show: false, - }) + const [store, setStore] = createStore({ show: false }) let input: HTMLInputElement createEffect(() => { - if (!submission.pending && submission.result) { + // @ts-expect-error + if (!submission.pending && submission.result?.data) { hide() } }) function show() { + // submission.clear() does not clear the result in some cases, ie. + // 1. Create key with empty name => error shows + // 2. Put in a key name and creates the key => form hides + // 3. Click add key button again => form shows with the same error if + // submission.clear() is called only once + for (let i = 0; i < 3; i++) { + submission.clear() + if (!submission.result) break + } setStore("show", true) input.focus() } @@ -216,7 +230,14 @@ function KeyCreateForm() { } >
- (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" /> +
+ (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" /> + {/* @ts-expect-error */} + + {/* @ts-expect-error */} +
{submission.result.error}
+
+
-
-

Your API Key

-
-
@@ -462,15 +458,12 @@ function NewUserSection() {
-
-

Next Steps

-
    -
  1. Copy your API key above
  2. Run opencode auth login and select opencode
  3. -
  4. Paste your API key when prompted
  5. +
  6. Paste your API key
  7. +
  8. Start opencode
  9. Run /models to see available models
  10. @@ -481,7 +474,7 @@ function NewUserSection() { ) } -export default function () { +export default function() { return (
    From efcb5abbf78e2f90dd35b30e80094a20a58e05d5 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Sep 2025 18:33:14 +0000 Subject: [PATCH 34/62] chore: format code --- cloud/app/src/routes/workspace/[id].tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index 937da16a8..ad4e58c2c 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -474,7 +474,7 @@ function NewUserSection() { ) } -export default function() { +export default function () { return (
    From f2b4891ff04b694e21b7af5b7f908e9d08fb7a57 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 14:46:08 -0400 Subject: [PATCH 35/62] wip: zen --- cloud/app/src/routes/workspace/[id].tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index ad4e58c2c..8f0b9bd55 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -49,7 +49,7 @@ const createKey = action(async (form: FormData) => { const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } return json( - withActor( + await withActor( () => Key.create({ name }) .then((data) => ({ data })) @@ -67,7 +67,7 @@ const removeKey = action(async (form: FormData) => { const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } return json( - withActor(() => Key.remove({ id }), workspaceID), + await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key }, ) }, "key.remove") From 4983d255dd2c6b5879001a8898e97ccd13188410 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 12 Sep 2025 18:46:43 +0000 Subject: [PATCH 36/62] chore: format code --- cloud/app/src/routes/workspace/[id].tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index 8f0b9bd55..de4e47a8a 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -66,10 +66,7 @@ const removeKey = action(async (form: FormData) => { if (!id) return { error: "ID is required" } const workspaceID = form.get("workspaceID")?.toString() if (!workspaceID) return { error: "Workspace ID is required" } - return json( - await withActor(() => Key.remove({ id }), workspaceID), - { revalidate: listKeys.key }, - ) + return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key }) }, "key.remove") ///////////////////////////////////// From c8f4d54f7f25860cd76335d74df1f3142ac17fda Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 12 Sep 2025 14:52:33 -0400 Subject: [PATCH 37/62] wip: zen --- cloud/app/src/routes/workspace/[id].tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/cloud/app/src/routes/workspace/[id].tsx b/cloud/app/src/routes/workspace/[id].tsx index de4e47a8a..8c20fc722 100644 --- a/cloud/app/src/routes/workspace/[id].tsx +++ b/cloud/app/src/routes/workspace/[id].tsx @@ -52,8 +52,8 @@ const createKey = action(async (form: FormData) => { await withActor( () => Key.create({ name }) - .then((data) => ({ data })) - .catch((e) => ({ error: e.message })), + .then((data) => ({ error: undefined, data })) + .catch((e) => ({ error: e.message as string })), workspaceID, ), { revalidate: listKeys.key }, @@ -193,8 +193,7 @@ function KeyCreateForm() { let input: HTMLInputElement createEffect(() => { - // @ts-expect-error - if (!submission.pending && submission.result?.data) { + if (!submission.pending && submission.result && !submission.result.error) { hide() } }) @@ -205,7 +204,7 @@ function KeyCreateForm() { // 2. Put in a key name and creates the key => form hides // 3. Click add key button again => form shows with the same error if // submission.clear() is called only once - for (let i = 0; i < 3; i++) { + while (true) { submission.clear() if (!submission.result) break } @@ -229,10 +228,8 @@ function KeyCreateForm() {
    (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" /> - {/* @ts-expect-error */} - - {/* @ts-expect-error */} -
    {submission.result.error}
    + + {(err) =>
    {err()}
    }
    From c02f58c2af1c65849e4a6e427081948ed3157e8d Mon Sep 17 00:00:00 2001 From: Stephen Murray Date: Fri, 12 Sep 2025 19:42:39 -0400 Subject: [PATCH 38/62] fix: await cleanupRevert() to prevent dupe msgs after undo (#2572) --- packages/opencode/src/session/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 619e8cc98..ddd668b8e 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -463,7 +463,7 @@ export namespace Session { // Process revert cleanup first, before creating new messages const session = await get(input.sessionID) if (session.revert) { - cleanupRevert(session) + await cleanupRevert(session) } const userMsg: MessageV2.Info = { id: input.messageID ?? Identifier.ascending("message"), @@ -1131,7 +1131,7 @@ export namespace Session { using abort = lock(input.sessionID) const session = await get(input.sessionID) if (session.revert) { - cleanupRevert(session) + await cleanupRevert(session) } const userMsg: MessageV2.User = { id: Identifier.ascending("message"), From aebd50da7e7402938ff3caeaf5c359bdeb67eaa0 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:59:38 -0500 Subject: [PATCH 39/62] fix: make permission always behavior match expectation (#2573) --- packages/opencode/src/permission/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index b0c3ccfee..e8b4e6812 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -83,7 +83,7 @@ export namespace Permission { toolCallID: input.callID, pattern: input.pattern, }) - if (approved[input.sessionID]?.[input.pattern ?? input.type]) return + if (approved[input.sessionID]?.[input.type]) return const info: Info = { id: Identifier.ascending("permission"), type: input.type, @@ -141,9 +141,9 @@ export namespace Permission { }) if (input.response === "always") { approved[input.sessionID] = approved[input.sessionID] || {} - approved[input.sessionID][match.info.pattern ?? match.info.type] = true + approved[input.sessionID][match.info.type] = true for (const item of Object.values(pending[input.sessionID])) { - if ((item.info.pattern ?? item.info.type) === (match.info.pattern ?? match.info.type)) { + if (item.info.type === match.info.type) { respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response }) } } From e03f27381f155b9c653f004c0815423ceeda72db Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Fri, 12 Sep 2025 18:22:54 -0700 Subject: [PATCH 40/62] docs: add tip block for finding available models (#2501) Co-authored-by: GitHub Action --- packages/web/src/content/docs/agents.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/web/src/content/docs/agents.mdx b/packages/web/src/content/docs/agents.mdx index 1527a1b08..9281bd47c 100644 --- a/packages/web/src/content/docs/agents.mdx +++ b/packages/web/src/content/docs/agents.mdx @@ -542,6 +542,10 @@ For example, with OpenAI's reasoning models, you can control the reasoning effor These additional options are model and provider-specific. Check your provider's documentation for available parameters. +:::tip +Run `opencode models` to see a lit of the available models. +::: + --- ## Create agents From 2b69bcccdfd9d65f64c6ed33722096150e4275b3 Mon Sep 17 00:00:00 2001 From: Nicholas Hamilton <86306035+nhamilton1@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:26:52 -0500 Subject: [PATCH 41/62] docs: typo in web agents.mdx (#2574) --- packages/web/src/content/docs/agents.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/agents.mdx b/packages/web/src/content/docs/agents.mdx index 9281bd47c..16b194c3d 100644 --- a/packages/web/src/content/docs/agents.mdx +++ b/packages/web/src/content/docs/agents.mdx @@ -543,7 +543,7 @@ For example, with OpenAI's reasoning models, you can control the reasoning effor These additional options are model and provider-specific. Check your provider's documentation for available parameters. :::tip -Run `opencode models` to see a lit of the available models. +Run `opencode models` to see a list of the available models. ::: --- From b1e0a2335112ddfaf5e89f01cd33972acccae46f Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Sat, 13 Sep 2025 07:06:07 +0200 Subject: [PATCH 42/62] fix: ShellError: exit code 1 errors (#2568) Co-authored-by: rekram1-node --- packages/opencode/src/snapshot/index.ts | 37 ++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index c26ac55ba..4152498d3 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -55,7 +55,15 @@ export namespace Snapshot { export async function patch(hash: string): Promise { const git = gitdir() await $`git --git-dir ${git} add .`.quiet().cwd(Instance.directory).nothrow() - const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(Instance.directory).text() + const result = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.quiet().cwd(Instance.directory).nothrow() + + // If git diff fails, return empty patch + if (result.exitCode !== 0) { + log.warn("failed to get diff", { hash, exitCode: result.exitCode }) + return { hash, files: [] } + } + + const files = result.text() return { hash, files: files @@ -70,9 +78,19 @@ export namespace Snapshot { export async function restore(snapshot: string) { log.info("restore", { commit: snapshot }) const git = gitdir() - await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f` + const result = await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f` .quiet() .cwd(Instance.worktree) + .nothrow() + + if (result.exitCode !== 0) { + log.error("failed to restore snapshot", { + snapshot, + exitCode: result.exitCode, + stderr: result.stderr.toString(), + stdout: result.stdout.toString(), + }) + } } export async function revert(patches: Patch[]) { @@ -97,8 +115,19 @@ export namespace Snapshot { export async function diff(hash: string) { const git = gitdir() - const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Instance.worktree).text() - return result.trim() + const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Instance.worktree).nothrow() + + if (result.exitCode !== 0) { + log.warn("failed to get diff", { + hash, + exitCode: result.exitCode, + stderr: result.stderr.toString(), + stdout: result.stdout.toString(), + }) + return "" + } + + return result.text().trim() } function gitdir() { From 555fb535054285d18869de2290f33be6f275a5a8 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 13 Sep 2025 01:23:54 -0400 Subject: [PATCH 43/62] nudge llm to continue properly after compaction --- packages/opencode/src/session/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index ddd668b8e..edbb6f95a 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -735,7 +735,7 @@ export namespace Session { providerID: model.providerID, modelID: model.info.id, }) - msgs = [msg] + msgs = [msg, { info: userMsg, parts: userParts }] } const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX From 535230dce404e386393928a75c42124c42734535 Mon Sep 17 00:00:00 2001 From: opencode Date: Sat, 13 Sep 2025 05:29:37 +0000 Subject: [PATCH 44/62] release: v0.7.9 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index 25ebc9df4..6118db3e0 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.8" + "version": "0.7.9" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index 3efba75b6..2461bfdc9 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.8", + "version": "0.7.9", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index ef47b0600..15d6a5811 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.8", + "version": "0.7.9", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index a7efdd00c..5922eeda1 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.8", + "version": "0.7.9", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index ace559ad6..1b3766c7a 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.8", + "version": "0.7.9", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index cc5c80d30..e69663509 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.8", + "version": "0.7.9", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index b6ca74ec8..7694e5f15 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.8", + "version": "0.7.9", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index f241b7754..3653ba93d 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.8", + "version": "0.7.9", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/web/package.json b/packages/web/package.json index 36046b537..85bd08c60 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.8", + "version": "0.7.9", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index ab962baf5..e8086b9db 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.8", + "version": "0.7.9", "publisher": "sst-dev", "repository": { "type": "git", From 9bb25a9260329c429acf704ddb4b57da3042383d Mon Sep 17 00:00:00 2001 From: Dax Date: Sat, 13 Sep 2025 05:46:14 -0400 Subject: [PATCH 45/62] Session management and prompt handling improvements (#2577) Co-authored-by: GitHub Action --- packages/opencode/src/cli/cmd/run.ts | 5 +- packages/opencode/src/server/server.ts | 34 +- packages/opencode/src/session/compaction.ts | 120 ++ packages/opencode/src/session/index.ts | 1678 +---------------- packages/opencode/src/session/message-v2.ts | 12 + packages/opencode/src/session/prompt.ts | 1470 +++++++++++++++ packages/opencode/src/session/revert.ts | 105 ++ packages/opencode/src/tool/task.ts | 5 +- .../opencode/test/session/fileRegex.test.ts | 8 +- 9 files changed, 1755 insertions(+), 1682 deletions(-) create mode 100644 packages/opencode/src/session/compaction.ts create mode 100644 packages/opencode/src/session/prompt.ts create mode 100644 packages/opencode/src/session/revert.ts diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index acee12194..e9229623c 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -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 = { todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD], @@ -185,7 +186,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 +198,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: { diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 108a67ec0..c2c08e3b0 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -25,6 +25,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: { @@ -558,7 +561,7 @@ export namespace Server { }), ), async (c) => { - return c.json(Session.abort(c.req.valid("param").id)) + return c.json(SessionPrompt.abort(c.req.valid("param").id)) }, ) .post( @@ -651,7 +654,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,14 +668,7 @@ 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()), }, }, }, @@ -750,11 +746,11 @@ export namespace Server { id: z.string().openapi({ description: "Session ID" }), }), ), - zValidator("json", Session.PromptInput.omit({ sessionID: true })), + zValidator("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) }, ) @@ -785,11 +781,11 @@ export namespace Server { id: z.string().openapi({ description: "Session ID" }), }), ), - zValidator("json", Session.CommandInput.omit({ sessionID: true })), + zValidator("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) }, ) @@ -815,11 +811,11 @@ export namespace Server { id: z.string().openapi({ description: "Session ID" }), }), ), - zValidator("json", Session.ShellInput.omit({ sessionID: true })), + zValidator("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) }, ) @@ -845,11 +841,11 @@ export namespace Server { id: z.string(), }), ), - zValidator("json", Session.RevertInput.omit({ sessionID: true })), + zValidator("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) }, ) @@ -877,7 +873,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) }, ) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts new file mode 100644 index 000000000..3a216255e --- /dev/null +++ b/packages/opencode/src/session/compaction.ts @@ -0,0 +1,120 @@ +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" +import type { ModelsDev } from "../provider/models" +import { SessionPrompt } from "./prompt" + +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 }) { + 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 = input.model.limit.context - output + return count > usable / 2 + } + + 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], + } + } +} diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index edbb6f95a..92f0afb03 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1,65 +1,27 @@ -import os from "os" -import path from "path" -import fs from "fs/promises" -import { spawn } from "child_process" import { Decimal } from "decimal.js" -import { z, ZodSchema } from "zod" -import { - generateText, - LoadAPIKeyError, - streamText, - tool, - wrapLanguageModel, - type Tool as AITool, - type LanguageModelUsage, - type ProviderMetadata, - type ModelMessage, - type StreamTextResult, -} from "ai" +import { z } from "zod" +import { type LanguageModelUsage, type ProviderMetadata } from "ai" import PROMPT_INITIALIZE from "../session/prompt/initialize.txt" -import PROMPT_PLAN from "../session/prompt/plan.txt" -import BUILD_SWITCH from "../session/prompt/build-switch.txt" import { Bus } from "../bus" import { Config } from "../config/config" import { Flag } from "../flag/flag" import { Identifier } from "../id/id" import { Installation } from "../installation" -import { MCP } from "../mcp" -import { Provider } from "../provider/provider" -import { ProviderTransform } from "../provider/transform" import type { ModelsDev } from "../provider/models" import { Share } from "../share/share" -import { Snapshot } from "../snapshot" import { Storage } from "../storage/storage" import { Log } from "../util/log" -import { NamedError } from "../util/error" -import { SystemPrompt } from "./system" -import { FileTime } from "../file/time" import { MessageV2 } from "./message-v2" -import { LSP } from "../lsp" -import { ReadTool } from "../tool/read" -import { mergeDeep, pipe, splitWhen } from "remeda" -import { ToolRegistry } from "../tool/registry" -import { Plugin } from "../plugin" import { Project } from "../project/project" import { Instance } from "../project/instance" -import { Agent } from "../agent/agent" -import { Permission } from "../permission" -import { Wildcard } from "../util/wildcard" -import { ulid } from "ulid" -import { defer } from "../util/defer" -import { Command } from "../command" -import { $ } from "bun" -import { ListTool } from "../tool/ls" import { Token } from "../util/token" +import { SessionPrompt } from "./prompt" export namespace Session { const log = Log.create({ service: "session" }) - const OUTPUT_TOKEN_MAX = 32_000 - const parentSessionTitlePrefix = "New session - " const childSessionTitlePrefix = "Child session - " @@ -67,10 +29,6 @@ export namespace Session { return (isChild ? childSessionTitlePrefix : parentSessionTitlePrefix) + new Date().toISOString() } - function isDefaultTitle(title: string) { - return title.startsWith(parentSessionTitlePrefix) - } - export const Info = z .object({ id: Identifier.schema("session"), @@ -126,12 +84,6 @@ export namespace Session { info: Info, }), ), - Idle: Bus.event( - "session.idle", - z.object({ - sessionID: z.string(), - }), - ), Error: Bus.event( "session.error", z.object({ @@ -139,40 +91,8 @@ export namespace Session { error: MessageV2.Assistant.shape.error, }), ), - Compacted: Bus.event( - "session.compacted", - z.object({ - sessionID: z.string(), - }), - ), } - const state = Instance.state( - () => { - const pending = new Map() - const queued = new Map< - string, - { - input: ChatInput - message: MessageV2.User - parts: MessageV2.Part[] - processed: boolean - callback: (input: { info: MessageV2.Assistant; parts: MessageV2.Part[] }) => void - }[] - >() - - return { - pending, - queued, - } - }, - async (state) => { - for (const [_, controller] of state.pending) { - controller.abort() - } - }, - ) - export async function create(parentID?: string, title?: string) { return createNext({ parentID, @@ -181,6 +101,12 @@ export namespace Session { }) } + export async function touch(sessionID: string) { + await update(sessionID, (draft) => { + draft.time.updated = Date.now() + }) + } + export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) { const result: Info = { id: Identifier.descending("session", input.id), @@ -270,10 +196,7 @@ export namespace Session { } export async function messages(sessionID: string) { - const result = [] as { - info: MessageV2.Info - parts: MessageV2.Part[] - }[] + const result = [] as MessageV2.WithParts[] for (const p of await Storage.list(["message", sessionID])) { const read = await Storage.read(p) result.push({ @@ -320,21 +243,9 @@ export namespace Session { return result } - export function abort(sessionID: string) { - const controller = state().pending.get(sessionID) - if (!controller) return false - log.info("aborting", { - sessionID, - }) - controller.abort() - state().pending.delete(sessionID) - return true - } - export async function remove(sessionID: string, emitEvent = true) { const project = Instance.project try { - abort(sessionID) const session = await get(sessionID) for (const child of await children(sessionID)) { await remove(child.id, false) @@ -357,7 +268,7 @@ export namespace Session { } } - async function updateMessage(msg: MessageV2.Info) { + export async function updateMessage(msg: MessageV2.Info) { await Storage.write(["message", msg.sessionID, msg.id], msg) Bus.publish(MessageV2.Event.Updated, { info: msg, @@ -365,7 +276,16 @@ export namespace Session { return msg } - async function updatePart(part: MessageV2.Part) { + export async function removeMessage(sessionID: string, messageID: string) { + await Storage.remove(["message", sessionID, messageID]) + Bus.publish(MessageV2.Event.Removed, { + sessionID, + messageID, + }) + return messageID + } + + export async function updatePart(part: MessageV2.Part) { await Storage.write(["part", part.messageID, part.id], part) Bus.publish(MessageV2.Event.PartUpdated, { part, @@ -373,1533 +293,6 @@ export namespace Session { return part } - async function cleanupRevert(session: Info) { - if (!session.revert) return - const sessionID = session.id - let msgs = await 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 update(sessionID, (draft) => { - draft.revert = undefined - }) - } - - export const PromptInput = z.object({ - sessionID: Identifier.schema("session"), - messageID: Identifier.schema("message").optional(), - model: z - .object({ - providerID: z.string(), - modelID: z.string(), - }) - .optional(), - agent: z.string().optional(), - system: z.string().optional(), - tools: z.record(z.boolean()).optional(), - parts: z.array( - z.discriminatedUnion("type", [ - MessageV2.TextPart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .openapi({ - ref: "TextPartInput", - }), - MessageV2.FilePart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .openapi({ - ref: "FilePartInput", - }), - MessageV2.AgentPart.omit({ - messageID: true, - sessionID: true, - }) - .partial({ - id: true, - }) - .openapi({ - ref: "AgentPartInput", - }), - ]), - ), - }) - export type ChatInput = z.infer - - export async function prompt( - input: z.infer, - ): Promise<{ info: MessageV2.Assistant; parts: MessageV2.Part[] }> { - const l = log.clone().tag("session", input.sessionID) - l.info("chatting") - - const inputAgent = input.agent ?? "build" - - // Process revert cleanup first, before creating new messages - const session = await get(input.sessionID) - if (session.revert) { - await cleanupRevert(session) - } - const userMsg: MessageV2.Info = { - id: input.messageID ?? Identifier.ascending("message"), - role: "user", - sessionID: input.sessionID, - time: { - created: Date.now(), - }, - } - - const userParts = await Promise.all( - input.parts.map(async (part): Promise => { - if (part.type === "file") { - const url = new URL(part.url) - switch (url.protocol) { - case "data:": - if (part.mime === "text/plain") { - return [ - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`, - }, - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: Buffer.from(part.url, "base64url").toString(), - }, - { - ...part, - id: part.id ?? Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - }, - ] - } - break - case "file:": - // have to normalize, symbol search returns absolute paths - // Decode the pathname since URL constructor doesn't automatically decode it - const filePath = decodeURIComponent(url.pathname) - - if (part.mime === "text/plain") { - let offset: number | undefined = undefined - let limit: number | undefined = undefined - const range = { - start: url.searchParams.get("start"), - end: url.searchParams.get("end"), - } - if (range.start != null) { - const filePath = part.url.split("?")[0] - let start = parseInt(range.start) - let end = range.end ? parseInt(range.end) : undefined - // some LSP servers (eg, gopls) don't give full range in - // workspace/symbol searches, so we'll try to find the - // symbol in the document to get the full range - if (start === end) { - const symbols = await LSP.documentSymbol(filePath) - for (const symbol of symbols) { - let range: LSP.Range | undefined - if ("range" in symbol) { - range = symbol.range - } else if ("location" in symbol) { - range = symbol.location.range - } - if (range?.start?.line && range?.start?.line === start) { - start = range.start.line - end = range?.end?.line ?? start - break - } - } - } - offset = Math.max(start - 1, 0) - if (end) { - limit = end - offset - } - } - const args = { filePath, offset, limit } - const result = await ReadTool.init().then((t) => - t.execute(args, { - sessionID: input.sessionID, - abort: new AbortController().signal, - agent: input.agent!, - messageID: userMsg.id, - extra: { bypassCwdCheck: true }, - metadata: async () => {}, - }), - ) - return [ - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: `Called the Read tool with the following input: ${JSON.stringify(args)}`, - }, - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: result.output, - }, - { - ...part, - id: part.id ?? Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - }, - ] - } - - if (part.mime === "application/x-directory") { - const args = { path: filePath } - const result = await ListTool.init().then((t) => - t.execute(args, { - sessionID: input.sessionID, - abort: new AbortController().signal, - agent: input.agent!, - messageID: userMsg.id, - extra: { bypassCwdCheck: true }, - metadata: async () => {}, - }), - ) - return [ - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: `Called the list tool with the following input: ${JSON.stringify(args)}`, - }, - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: result.output, - }, - { - ...part, - id: part.id ?? Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - }, - ] - } - - const file = Bun.file(filePath) - FileTime.read(input.sessionID, filePath) - return [ - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - text: `Called the Read tool with the following input: {\"filePath\":\"${filePath}\"}`, - synthetic: true, - }, - { - id: part.id ?? Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "file", - url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"), - mime: part.mime, - filename: part.filename!, - source: part.source, - }, - ] - } - } - - if (part.type === "agent") { - return [ - { - id: Identifier.ascending("part"), - ...part, - messageID: userMsg.id, - sessionID: input.sessionID, - }, - { - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - synthetic: true, - text: - "Use the above message and context to generate a prompt and call the task tool with subagent: " + - part.name, - }, - ] - } - - return [ - { - id: Identifier.ascending("part"), - ...part, - messageID: userMsg.id, - sessionID: input.sessionID, - }, - ] - }), - ).then((x) => x.flat()) - await Plugin.trigger( - "chat.message", - {}, - { - message: userMsg, - parts: userParts, - }, - ) - await updateMessage(userMsg) - for (const part of userParts) { - await updatePart(part) - } - - // mark session as updated - // used for session list sorting (indicates when session was most recently interacted with) - await update(input.sessionID, (_draft) => {}) - - if (isLocked(input.sessionID)) { - return new Promise((resolve) => { - const queue = state().queued.get(input.sessionID) ?? [] - queue.push({ - input: input, - message: userMsg, - parts: userParts, - processed: false, - callback: resolve, - }) - state().queued.set(input.sessionID, queue) - }) - } - - const agent = await Agent.get(inputAgent) - const model = await (async () => { - if (input.model) { - return input.model - } - if (agent.model) { - return agent.model - } - return Provider.defaultModel() - })().then((x) => Provider.getModel(x.providerID, x.modelID)) - - let msgs = await messages(input.sessionID).then((x) => sinceSummary(x)) - - const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant") - if ( - lastAssistant?.info.role === "assistant" && - needsCompaction({ - tokens: lastAssistant.info.tokens, - model: model.info, - }) - ) { - const msg = await summarize({ - sessionID: input.sessionID, - providerID: model.providerID, - modelID: model.info.id, - }) - msgs = [msg, { info: userMsg, parts: userParts }] - } - - const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX - - using abort = lock(input.sessionID) - - const numRealUserMsgs = msgs.filter( - (m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic), - ).length - if (numRealUserMsgs === 1 && !session.parentID && isDefaultTitle(session.title)) { - const small = (await Provider.getSmallModel(model.providerID)) ?? model - const options = { - ...ProviderTransform.options(small.providerID, small.modelID, input.sessionID), - ...small.info.options, - } - if (small.providerID === "openai") { - options["reasoningEffort"] = "minimal" - } - if (small.providerID === "google") { - options["thinkingConfig"] = { - thinkingBudget: 0, - } - } - generateText({ - maxOutputTokens: small.info.reasoning ? 1500 : 20, - providerOptions: { - [model.providerID]: options, - }, - messages: [ - ...SystemPrompt.title(model.providerID).map( - (x): ModelMessage => ({ - role: "system", - content: x, - }), - ), - ...MessageV2.toModelMessage([ - { - info: { - id: Identifier.ascending("message"), - role: "user", - sessionID: input.sessionID, - time: { - created: Date.now(), - }, - }, - parts: userParts, - }, - ]), - ], - model: small.language, - }) - .then((result) => { - if (result.text) - return Session.update(input.sessionID, (draft) => { - const cleaned = result.text.replace(/[\s\S]*?<\/think>\s*/g, "") - const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned - draft.title = title.trim() - }) - }) - .catch((error) => { - log.error("failed to generate title", { error, model: small.info.id }) - }) - } - - if (agent.name === "plan") { - msgs.at(-1)?.parts.push({ - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - text: PROMPT_PLAN, - synthetic: true, - }) - } - - const wasPlan = msgs.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan") - if (wasPlan && agent.name === "build") { - msgs.at(-1)?.parts.push({ - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - type: "text", - text: BUILD_SWITCH, - synthetic: true, - }) - } - let system = SystemPrompt.header(model.providerID) - system.push( - ...(() => { - if (input.system) return [input.system] - if (agent.prompt) return [agent.prompt] - return SystemPrompt.provider(model.modelID) - })(), - ) - system.push(...(await SystemPrompt.environment())) - system.push(...(await SystemPrompt.custom())) - // max 2 system prompt messages for caching purposes - const [first, ...rest] = system - system = [first, rest.join("\n")] - - const processor = await createProcessor({ - sessionID: input.sessionID, - model: model.info, - providerID: model.providerID, - agent: inputAgent, - system, - }) - - await using _ = defer(async () => { - if (processor.message.time.completed) return - await Storage.remove(["session", "message", input.sessionID, processor.message.id]) - await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: processor.message.id }) - }) - const tools: Record = {} - - const enabledTools = pipe( - agent.tools, - mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)), - mergeDeep(input.tools ?? {}), - ) - for (const item of await ToolRegistry.tools(model.providerID, model.modelID)) { - if (Wildcard.all(item.id, enabledTools) === false) continue - tools[item.id] = tool({ - id: item.id as any, - description: item.description, - inputSchema: item.parameters as ZodSchema, - async execute(args, options) { - await Plugin.trigger( - "tool.execute.before", - { - tool: item.id, - sessionID: input.sessionID, - callID: options.toolCallId, - }, - { - args, - }, - ) - const result = await item.execute(args, { - sessionID: input.sessionID, - abort: options.abortSignal!, - messageID: processor.message.id, - callID: options.toolCallId, - agent: agent.name, - metadata: async (val) => { - const match = processor.partFromToolCall(options.toolCallId) - if (match && match.state.status === "running") { - await updatePart({ - ...match, - state: { - title: val.title, - metadata: val.metadata, - status: "running", - input: args, - time: { - start: Date.now(), - }, - }, - }) - } - }, - }) - await Plugin.trigger( - "tool.execute.after", - { - tool: item.id, - sessionID: input.sessionID, - callID: options.toolCallId, - }, - result, - ) - return result - }, - toModelOutput(result) { - return { - type: "text", - value: result.output, - } - }, - }) - } - - for (const [key, item] of Object.entries(await MCP.tools())) { - if (Wildcard.all(key, enabledTools) === false) continue - const execute = item.execute - if (!execute) continue - item.execute = async (args, opts) => { - await Plugin.trigger( - "tool.execute.before", - { - tool: key, - sessionID: input.sessionID, - callID: opts.toolCallId, - }, - { - args, - }, - ) - const result = await execute(args, opts) - const output = result.content - .filter((x: any) => x.type === "text") - .map((x: any) => x.text) - .join("\n\n") - await Plugin.trigger( - "tool.execute.after", - { - tool: key, - sessionID: input.sessionID, - callID: opts.toolCallId, - }, - result, - ) - - return { - output, - } - } - item.toModelOutput = (result) => { - return { - type: "text", - value: result.output, - } - } - tools[key] = item - } - - const params = await Plugin.trigger( - "chat.params", - { - model: model.info, - provider: await Provider.getProvider(model.providerID), - message: userMsg, - }, - { - temperature: model.info.temperature - ? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID)) - : undefined, - topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID), - options: { - ...ProviderTransform.options(model.providerID, model.modelID, input.sessionID), - ...model.info.options, - ...agent.options, - }, - }, - ) - - let pointer = 0 - const stream = streamText({ - onError(e) { - log.error("streamText error", { - error: e, - }) - }, - async prepareStep({ messages, steps }) { - const step = steps.at(-1) - if ( - step && - needsCompaction({ - tokens: getUsage(model.info, step.usage, step.providerMetadata).tokens, - model: model.info, - }) && - false - ) { - await processor.end() - const msg = await Session.summarize({ - sessionID: input.sessionID, - providerID: model.providerID, - modelID: model.info.id, - }) - await processor.next() - pointer = messages.length - messages.push(...MessageV2.toModelMessage([msg])) - } - - // Add queued messages to the stream - const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed) - if (queue.length) { - await processor.end() - for (const item of queue) { - if (item.processed) continue - messages.push( - ...MessageV2.toModelMessage([ - { - info: item.message, - parts: item.parts, - }, - ]), - ) - item.processed = true - } - await processor.next() - } - return { - messages: messages.slice(pointer), - } - }, - async experimental_repairToolCall(input) { - const lower = input.toolCall.toolName.toLowerCase() - if (lower !== input.toolCall.toolName && tools[lower]) { - log.info("repairing tool call", { - tool: input.toolCall.toolName, - repaired: lower, - }) - return { - ...input.toolCall, - toolName: lower, - } - } - return { - ...input.toolCall, - input: JSON.stringify({ - tool: input.toolCall.toolName, - error: input.error.message, - }), - toolName: "invalid", - } - }, - headers: - model.providerID === "opencode" - ? { - "x-opencode-session": input.sessionID, - "x-opencode-request": userMsg.id, - } - : undefined, - maxRetries: 3, - activeTools: Object.keys(tools).filter((x) => x !== "invalid"), - maxOutputTokens: ProviderTransform.maxOutputTokens(model.providerID, outputLimit, params.options), - abortSignal: abort.signal, - stopWhen: async ({ steps }) => { - if (steps.length >= 1000) { - return true - } - - // Check if processor flagged that we should stop - if (processor.getShouldStop()) { - return true - } - - return false - }, - providerOptions: { - [model.providerID]: params.options, - }, - temperature: params.temperature, - topP: params.topP, - messages: [ - ...system.map( - (x): ModelMessage => ({ - role: "system", - content: x, - }), - ), - ...MessageV2.toModelMessage(msgs.filter((m) => !(m.info.role === "assistant" && m.info.error))), - ], - tools: model.info.tool_call === false ? undefined : tools, - model: wrapLanguageModel({ - model: model.language, - middleware: [ - { - async transformParams(args) { - if (args.type === "stream") { - // @ts-expect-error - args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID) - } - return args.params - }, - }, - ], - }), - }) - const result = await processor.process(stream) - const queued = state().queued.get(input.sessionID) ?? [] - const unprocessed = queued.find((x) => !x.processed) - if (unprocessed) { - unprocessed.processed = true - return prompt(unprocessed.input) - } - for (const item of queued) { - item.callback(result) - } - state().queued.delete(input.sessionID) - // Session.prune(input) - return result - } - - export const ShellInput = z.object({ - sessionID: Identifier.schema("session"), - agent: z.string(), - command: z.string(), - }) - export type ShellInput = z.infer - export async function shell(input: ShellInput) { - using abort = lock(input.sessionID) - const session = await get(input.sessionID) - if (session.revert) { - await cleanupRevert(session) - } - const userMsg: MessageV2.User = { - id: Identifier.ascending("message"), - sessionID: input.sessionID, - time: { - created: Date.now(), - }, - role: "user", - } - await updateMessage(userMsg) - const userPart: MessageV2.Part = { - type: "text", - id: Identifier.ascending("part"), - messageID: userMsg.id, - sessionID: input.sessionID, - text: "The following tool was executed by the user", - synthetic: true, - } - await updatePart(userPart) - - const msg: MessageV2.Assistant = { - id: Identifier.ascending("message"), - sessionID: input.sessionID, - system: [], - mode: input.agent, - cost: 0, - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - time: { - created: Date.now(), - }, - role: "assistant", - tokens: { - input: 0, - output: 0, - reasoning: 0, - cache: { read: 0, write: 0 }, - }, - modelID: "", - providerID: "", - } - await updateMessage(msg) - const part: MessageV2.Part = { - type: "tool", - id: Identifier.ascending("part"), - messageID: msg.id, - sessionID: input.sessionID, - tool: "bash", - callID: ulid(), - state: { - status: "running", - time: { - start: Date.now(), - }, - input: { - command: input.command, - }, - }, - } - await updatePart(part) - const shell = process.env["SHELL"] ?? "bash" - const shellName = path.basename(shell) - - const scripts: Record = { - nu: input.command, - fish: `eval "${input.command}"`, - } - - const script = - scripts[shellName] ?? - `[[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true - [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true - [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true - eval "${input.command}"` - - const isFishOrNu = shellName === "fish" || shellName === "nu" - const args = isFishOrNu ? ["-c", script] : ["-c", "-l", script] - - const proc = spawn(shell, args, { - cwd: Instance.directory, - signal: abort.signal, - detached: true, - stdio: ["ignore", "pipe", "pipe"], - env: { - ...process.env, - TERM: "dumb", - }, - }) - - abort.signal.addEventListener("abort", () => { - if (!proc.pid) return - process.kill(-proc.pid) - }) - - let output = "" - - proc.stdout?.on("data", (chunk) => { - output += chunk.toString() - if (part.state.status === "running") { - part.state.metadata = { - output: output, - description: "", - } - updatePart(part) - } - }) - - proc.stderr?.on("data", (chunk) => { - output += chunk.toString() - if (part.state.status === "running") { - part.state.metadata = { - output: output, - description: "", - } - updatePart(part) - } - }) - - await new Promise((resolve) => { - proc.on("close", () => { - resolve() - }) - }) - msg.time.completed = Date.now() - await updateMessage(msg) - if (part.state.status === "running") { - part.state = { - status: "completed", - time: { - ...part.state.time, - end: Date.now(), - }, - input: part.state.input, - title: "", - metadata: { - output, - description: "", - }, - output, - } - await updatePart(part) - } - return { info: msg, parts: [part] } - } - - export const CommandInput = z.object({ - messageID: Identifier.schema("message").optional(), - sessionID: Identifier.schema("session"), - agent: z.string().optional(), - model: z.string().optional(), - arguments: z.string(), - command: z.string(), - }) - export type CommandInput = z.infer - const bashRegex = /!`([^`]+)`/g - /** - * Regular expression to match @ file references in text - * Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks - * Does not match when preceded by word characters or backticks (to avoid email addresses and quoted references) - */ - export const fileRegex = /(? 0) { - const results = await Promise.all( - bash.map(async ([, cmd]) => { - try { - return await $`${{ raw: cmd }}`.nothrow().text() - } catch (error) { - return `Error executing command: ${error instanceof Error ? error.message : String(error)}` - } - }), - ) - let index = 0 - template = template.replace(bashRegex, () => results[index++]) - } - - const parts = [ - { - type: "text", - text: template, - }, - ] as ChatInput["parts"] - - const matches = Array.from(template.matchAll(fileRegex)) - await Promise.all( - matches.map(async (match) => { - const name = match[1] - const filepath = name.startsWith("~/") - ? path.join(os.homedir(), name.slice(2)) - : path.resolve(Instance.worktree, name) - - const stats = await fs.stat(filepath).catch(() => undefined) - if (!stats) { - const agent = await Agent.get(name) - if (agent) { - parts.push({ - type: "agent", - name: agent.name, - }) - } - return - } - - if (stats.isDirectory()) { - parts.push({ - type: "file", - url: `file://${filepath}`, - filename: name, - mime: "application/x-directory", - }) - return - } - - parts.push({ - type: "file", - url: `file://${filepath}`, - filename: name, - mime: "text/plain", - }) - }), - ) - - const model = await (async () => { - if (command.model) { - return Provider.parseModel(command.model) - } - if (command.agent) { - const agent = await Agent.get(command.agent) - if (agent.model) { - return agent.model - } - } - if (input.model) { - return Provider.parseModel(input.model) - } - return undefined - })() - - return prompt({ - sessionID: input.sessionID, - messageID: input.messageID, - model, - agent, - parts, - }) - } - - async function createProcessor(input: { - sessionID: string - providerID: string - model: ModelsDev.Model - system: string[] - agent: string - }) { - const toolcalls: Record = {} - let snapshot: string | undefined - let shouldStop = false - - async function createMessage() { - const msg: MessageV2.Info = { - id: Identifier.ascending("message"), - role: "assistant", - system: input.system, - mode: input.agent, - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - cost: 0, - tokens: { - input: 0, - output: 0, - reasoning: 0, - cache: { read: 0, write: 0 }, - }, - modelID: input.model.id, - providerID: input.providerID, - time: { - created: Date.now(), - }, - sessionID: input.sessionID, - } - await updateMessage(msg) - return msg - } - - let assistantMsg = await createMessage() - - const result = { - async end() { - if (assistantMsg) { - assistantMsg.time.completed = Date.now() - await updateMessage(assistantMsg) - } - }, - async next() { - assistantMsg = await createMessage() - }, - get message() { - return assistantMsg - }, - partFromToolCall(toolCallID: string) { - return toolcalls[toolCallID] - }, - getShouldStop() { - return shouldStop - }, - async process(stream: StreamTextResult, never>) { - try { - let currentText: MessageV2.TextPart | undefined - let reasoningMap: Record = {} - - for await (const value of stream.fullStream) { - log.info("part", { - type: value.type, - }) - switch (value.type) { - case "start": - break - - case "reasoning-start": - if (value.id in reasoningMap) { - continue - } - reasoningMap[value.id] = { - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "reasoning", - text: "", - time: { - start: Date.now(), - }, - } - break - - case "reasoning-delta": - if (value.id in reasoningMap) { - const part = reasoningMap[value.id] - part.text += value.text - if (part.text) await updatePart(part) - } - break - - case "reasoning-end": - if (value.id in reasoningMap) { - const part = reasoningMap[value.id] - part.text = part.text.trimEnd() - part.metadata = value.providerMetadata - part.time = { - ...part.time, - end: Date.now(), - } - await updatePart(part) - delete reasoningMap[value.id] - } - break - - case "tool-input-start": - const part = await updatePart({ - id: toolcalls[value.id]?.id ?? Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "tool", - tool: value.toolName, - callID: value.id, - state: { - status: "pending", - }, - }) - toolcalls[value.id] = part as MessageV2.ToolPart - break - - case "tool-input-delta": - break - - case "tool-input-end": - break - - case "tool-call": { - const match = toolcalls[value.toolCallId] - if (match) { - const part = await updatePart({ - ...match, - tool: value.toolName, - state: { - status: "running", - input: value.input, - time: { - start: Date.now(), - }, - }, - }) - toolcalls[value.toolCallId] = part as MessageV2.ToolPart - } - break - } - case "tool-result": { - const match = toolcalls[value.toolCallId] - if (match && match.state.status === "running") { - await updatePart({ - ...match, - state: { - status: "completed", - input: value.input, - output: value.output.output, - metadata: value.output.metadata, - title: value.output.title, - time: { - start: match.state.time.start, - end: Date.now(), - }, - }, - }) - delete toolcalls[value.toolCallId] - } - break - } - - case "tool-error": { - const match = toolcalls[value.toolCallId] - if (match && match.state.status === "running") { - if (value.error instanceof Permission.RejectedError) { - shouldStop = true - } - await updatePart({ - ...match, - state: { - status: "error", - input: value.input, - error: (value.error as any).toString(), - metadata: value.error instanceof Permission.RejectedError ? value.error.metadata : undefined, - time: { - start: match.state.time.start, - end: Date.now(), - }, - }, - }) - delete toolcalls[value.toolCallId] - } - break - } - case "error": - throw value.error - - case "start-step": - await updatePart({ - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "step-start", - }) - snapshot = await Snapshot.track() - break - - case "finish-step": - const usage = getUsage(input.model, value.usage, value.providerMetadata) - assistantMsg.cost += usage.cost - assistantMsg.tokens = usage.tokens - await updatePart({ - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "step-finish", - tokens: usage.tokens, - cost: usage.cost, - }) - await updateMessage(assistantMsg) - if (snapshot) { - const patch = await Snapshot.patch(snapshot) - if (patch.files.length) { - await updatePart({ - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "patch", - hash: patch.hash, - files: patch.files, - }) - } - snapshot = undefined - } - break - - case "text-start": - currentText = { - id: Identifier.ascending("part"), - messageID: assistantMsg.id, - sessionID: assistantMsg.sessionID, - type: "text", - text: "", - time: { - start: Date.now(), - }, - } - break - - case "text-delta": - if (currentText) { - currentText.text += value.text - if (currentText.text) await updatePart(currentText) - } - break - - case "text-end": - if (currentText) { - currentText.text = currentText.text.trimEnd() - currentText.time = { - start: Date.now(), - end: Date.now(), - } - await updatePart(currentText) - } - currentText = undefined - break - - case "finish": - assistantMsg.time.completed = Date.now() - await updateMessage(assistantMsg) - break - - default: - log.info("unhandled", { - ...value, - }) - continue - } - } - } catch (e) { - log.error("", { - error: e, - }) - switch (true) { - case e instanceof DOMException && e.name === "AbortError": - assistantMsg.error = new MessageV2.AbortedError( - { message: e.message }, - { - cause: e, - }, - ).toObject() - break - case MessageV2.OutputLengthError.isInstance(e): - assistantMsg.error = e - break - case LoadAPIKeyError.isInstance(e): - assistantMsg.error = new MessageV2.AuthError( - { - providerID: input.providerID, - message: e.message, - }, - { cause: e }, - ).toObject() - break - case e instanceof Error: - assistantMsg.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject() - break - default: - assistantMsg.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }) - } - Bus.publish(Event.Error, { - sessionID: assistantMsg.sessionID, - error: assistantMsg.error, - }) - } - const p = await getParts(assistantMsg.id) - for (const part of p) { - if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") { - updatePart({ - ...part, - state: { - status: "error", - error: "Tool execution aborted", - time: { - start: Date.now(), - end: Date.now(), - }, - input: {}, - }, - }) - } - } - assistantMsg.time.completed = Date.now() - await updateMessage(assistantMsg) - return { info: assistantMsg, parts: p } - }, - } - return result - } - - export const RevertInput = z.object({ - sessionID: Identifier.schema("session"), - messageID: Identifier.schema("message"), - partID: Identifier.schema("part").optional(), - }) - export type RevertInput = z.infer - - export async function revert(input: RevertInput) { - const all = await messages(input.sessionID) - let lastUser: MessageV2.User | undefined - const session = await get(input.sessionID) - - let revert: 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 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 update(input.sessionID, (draft) => { - draft.revert = revert - }) - } - return session - } - - export async function unrevert(input: { sessionID: string }) { - log.info("unreverting", input) - const session = await get(input.sessionID) - if (!session.revert) return session - if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot) - const next = await update(input.sessionID, (draft) => { - draft.revert = undefined - }) - return next - } - - export async function summarize(input: { sessionID: string; providerID: string; modelID: string }) { - await update(input.sessionID, (draft) => { - draft.time.compacting = Date.now() - }) - await using _ = defer(async () => { - await update(input.sessionID, (draft) => { - draft.time.compacting = undefined - }) - }) - const toSummarize = await messages(input.sessionID).then((x) => sinceSummary(x)) - const model = await Provider.getModel(input.providerID, input.modelID) - const system = [ - ...SystemPrompt.summarize(model.providerID), - ...(await SystemPrompt.environment()), - ...(await SystemPrompt.custom()), - ] - - const msg = (await 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 = getUsage(model.info, generated.usage, generated.providerMetadata) - msg.cost += usage.cost - msg.tokens = usage.tokens - msg.summary = true - msg.time.completed = Date.now() - await updateMessage(msg) - const part = await 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], - } - } - - function sinceSummary(msgs: { info: MessageV2.Info; parts: MessageV2.Part[] }[]) { - const result = [] - for (let i = msgs.length - 1; i >= 0; i--) { - const msg = msgs[i] - result.push(msg) - if (msg.info.role === "assistant" && msg.info.summary) break - } - return result.toReversed() - } - - function needsCompaction(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) { - const count = input.tokens.input + input.tokens.cache.read + input.tokens.output - const output = Math.min(input.model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX - const usable = input.model.limit.context - output - return count > usable - } - // goes backwards through parts until there are 40_000 tokens worth of tool // calls. then erases output of previous tool calls. idea is to throw away old // tool calls that are no longer relevant. @@ -1928,32 +321,7 @@ export namespace Session { } } - function isLocked(sessionID: string) { - return state().pending.has(sessionID) - } - - function lock(sessionID: string) { - log.info("locking", { sessionID }) - if (state().pending.has(sessionID)) throw new BusyError(sessionID) - const controller = new AbortController() - state().pending.set(sessionID, controller) - return { - signal: controller.signal, - async [Symbol.dispose]() { - log.info("unlocking", { sessionID }) - state().pending.delete(sessionID) - - const session = await get(sessionID) - if (session.parentID) return - - Bus.publish(Event.Idle, { - sessionID, - }) - }, - } - } - - function getUsage(model: ModelsDev.Model, usage: LanguageModelUsage, metadata?: ProviderMetadata) { + export function getUsage(model: ModelsDev.Model, usage: LanguageModelUsage, metadata?: ProviderMetadata) { const tokens = { input: usage.inputTokens ?? 0, output: usage.outputTokens ?? 0, @@ -1989,7 +357,7 @@ export namespace Session { providerID: string messageID: string }) { - await Session.prompt({ + await SessionPrompt.prompt({ sessionID: input.sessionID, messageID: input.messageID, model: { diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 3102a611c..d0f7f3170 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -331,6 +331,12 @@ export namespace MessageV2 { ), } + export const WithParts = z.object({ + info: Info, + parts: z.array(Part), + }) + export type WithParts = z.infer + export function fromV1(v1: Message.Info) { if (v1.role === "assistant") { const info: Assistant = { @@ -552,4 +558,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) + } } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts new file mode 100644 index 000000000..b804c9404 --- /dev/null +++ b/packages/opencode/src/session/prompt.ts @@ -0,0 +1,1470 @@ +import path from "path" +import os from "os" +import fs from "fs/promises" +import z, { ZodSchema } from "zod" +import { Identifier } from "../id/id" +import { MessageV2 } from "./message-v2" +import { Log } from "../util/log" +import { SessionRevert } from "./revert" +import { Session } from "." +import { Agent } from "../agent/agent" +import { Provider } from "../provider/provider" +import { + generateText, + streamText, + type ModelMessage, + type Tool as AITool, + tool, + wrapLanguageModel, + type StreamTextResult, + LoadAPIKeyError, + stepCountIs, +} from "ai" +import { SessionCompaction } from "./compaction" +import { Instance } from "../project/instance" +import { Bus } from "../bus" +import { ProviderTransform } from "../provider/transform" +import { SystemPrompt } from "./system" +import { Plugin } from "../plugin" + +import PROMPT_PLAN from "../session/prompt/plan.txt" +import BUILD_SWITCH from "../session/prompt/build-switch.txt" +import { ModelsDev } from "../provider/models" +import { defer } from "../util/defer" +import { mergeDeep, pipe } from "remeda" +import { ToolRegistry } from "../tool/registry" +import { Wildcard } from "../util/wildcard" +import { MCP } from "../mcp" +import { LSP } from "../lsp" +import { ReadTool } from "../tool/read" +import { ListTool } from "../tool/ls" +import { FileTime } from "../file/time" +import { Permission } from "../permission" +import { Snapshot } from "../snapshot" +import { NamedError } from "../util/error" +import { ulid } from "ulid" +import { spawn } from "child_process" +import { Command } from "../command" +import { $ } from "bun" + +export namespace SessionPrompt { + const log = Log.create({ service: "session.prompt" }) + export const OUTPUT_TOKEN_MAX = 32_000 + + export const Event = { + Idle: Bus.event( + "session.idle", + z.object({ + sessionID: z.string(), + }), + ), + } + + const state = Instance.state( + () => { + const pending = new Map() + const queued = new Map< + string, + { + messageID: string + callback: (input: MessageV2.WithParts) => void + }[] + >() + + return { + pending, + queued, + } + }, + async (state) => { + for (const [_, controller] of state.pending) { + controller.abort() + } + }, + ) + + export const PromptInput = z.object({ + sessionID: Identifier.schema("session"), + messageID: Identifier.schema("message").optional(), + model: z + .object({ + providerID: z.string(), + modelID: z.string(), + }) + .optional(), + agent: z.string().optional(), + system: z.string().optional(), + tools: z.record(z.boolean()).optional(), + parts: z.array( + z.discriminatedUnion("type", [ + MessageV2.TextPart.omit({ + messageID: true, + sessionID: true, + }) + .partial({ + id: true, + }) + .openapi({ + ref: "TextPartInput", + }), + MessageV2.FilePart.omit({ + messageID: true, + sessionID: true, + }) + .partial({ + id: true, + }) + .openapi({ + ref: "FilePartInput", + }), + MessageV2.AgentPart.omit({ + messageID: true, + sessionID: true, + }) + .partial({ + id: true, + }) + .openapi({ + ref: "AgentPartInput", + }), + ]), + ), + }) + export type PromptInput = z.infer + export async function prompt(input: PromptInput): Promise { + const l = log.clone().tag("session", input.sessionID) + l.info("prompt") + + const session = await Session.get(input.sessionID) + await SessionRevert.cleanup(session) + + const userMsg = await createUserMessage(input) + await Session.touch(input.sessionID) + + if (isBusy(input.sessionID)) { + return new Promise((resolve) => { + const queue = state().queued.get(input.sessionID) ?? [] + queue.push({ + messageID: userMsg.info.id, + callback: resolve, + }) + state().queued.set(input.sessionID, queue) + }) + } + const agent = await Agent.get(input.agent ?? "build") + const model = await resolveModel({ + agent, + model: input.model, + }).then((x) => Provider.getModel(x.providerID, x.modelID)) + const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX + using abort = lock(input.sessionID) + + const system = await resolveSystemPrompt({ + providerID: model.providerID, + modelID: model.info.id, + agent, + system: input.system, + }) + + const processor = await createProcessor({ + sessionID: input.sessionID, + model: model.info, + providerID: model.providerID, + agent: agent.name, + system, + abort: abort.signal, + }) + + const tools = await resolveTools({ + agent, + sessionID: input.sessionID, + modelID: model.modelID, + providerID: model.providerID, + tools: input.tools, + processor, + }) + + const params = await Plugin.trigger( + "chat.params", + { + model: model.info, + provider: await Provider.getProvider(model.providerID), + message: userMsg, + }, + { + temperature: model.info.temperature + ? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID)) + : undefined, + topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID), + options: { + ...ProviderTransform.options(model.providerID, model.modelID, input.sessionID), + ...model.info.options, + ...agent.options, + }, + }, + ) + + let step = 0 + while (true) { + const msgs: MessageV2.WithParts[] = pipe( + await getMessages({ + sessionID: input.sessionID, + model: model.info, + providerID: model.providerID, + }), + (messages) => insertReminders({ messages, agent }), + ) + if (step === 0) + ensureTitle({ + session, + history: msgs, + message: userMsg, + providerID: model.providerID, + modelID: model.info.id, + }) + step++ + await processor.next() + await using _ = defer(async () => { + await processor.end() + }) + const stream = streamText({ + onError(error) { + log.error("stream error", { + error, + }) + }, + async experimental_repairToolCall(input) { + const lower = input.toolCall.toolName.toLowerCase() + if (lower !== input.toolCall.toolName && tools[lower]) { + log.info("repairing tool call", { + tool: input.toolCall.toolName, + repaired: lower, + }) + return { + ...input.toolCall, + toolName: lower, + } + } + return { + ...input.toolCall, + input: JSON.stringify({ + tool: input.toolCall.toolName, + error: input.error.message, + }), + toolName: "invalid", + } + }, + headers: + model.providerID === "opencode" + ? { + "x-opencode-session": input.sessionID, + "x-opencode-request": userMsg.info.id, + } + : undefined, + maxRetries: 10, + activeTools: Object.keys(tools).filter((x) => x !== "invalid"), + maxOutputTokens: ProviderTransform.maxOutputTokens(model.providerID, outputLimit, params.options), + abortSignal: abort.signal, + providerOptions: { + [model.providerID]: params.options, + }, + stopWhen: stepCountIs(1), + temperature: params.temperature, + topP: params.topP, + messages: [ + ...system.map( + (x): ModelMessage => ({ + role: "system", + content: x, + }), + ), + ...MessageV2.toModelMessage(msgs.filter((m) => !(m.info.role === "assistant" && m.info.error))), + ], + tools: model.info.tool_call === false ? undefined : tools, + model: wrapLanguageModel({ + model: model.language, + middleware: [ + { + async transformParams(args) { + if (args.type === "stream") { + // @ts-expect-error + args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID) + } + return args.params + }, + }, + ], + }), + }) + const result = await processor.process(stream) + await processor.end() + + const queued = state().queued.get(input.sessionID) ?? [] + + if (!result.blocked && !result.info.error) { + if ((await stream.finishReason) === "tool-calls") { + continue + } + + const unprocessed = queued.filter((x) => x.messageID > result.info.id) + if (unprocessed.length) { + continue + } + } + for (const item of queued) { + item.callback(result) + } + state().queued.delete(input.sessionID) + // Session.prune(input) + return result + } + } + + async function getMessages(input: { sessionID: string; model: ModelsDev.Model; providerID: string }) { + let msgs = await Session.messages(input.sessionID).then(MessageV2.filterSummarized) + const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant") + if ( + lastAssistant?.info.role === "assistant" && + SessionCompaction.isOverflow({ + tokens: lastAssistant.info.tokens, + model: input.model, + }) + ) { + const msg = await SessionCompaction.run({ + sessionID: input.sessionID, + providerID: input.providerID, + modelID: input.model.id, + }) + msgs = [msg] + } + return msgs + } + + async function resolveModel(input: { model: PromptInput["model"]; agent: Agent.Info }) { + if (input.model) { + return input.model + } + if (input.agent.model) { + return input.agent.model + } + return Provider.defaultModel() + } + + async function resolveSystemPrompt(input: { + system?: string + agent: Agent.Info + providerID: string + modelID: string + }) { + let system = SystemPrompt.header(input.providerID) + system.push( + ...(() => { + if (input.system) return [input.system] + if (input.agent.prompt) return [input.agent.prompt] + return SystemPrompt.provider(input.modelID) + })(), + ) + system.push(...(await SystemPrompt.environment())) + system.push(...(await SystemPrompt.custom())) + // max 2 system prompt messages for caching purposes + const [first, ...rest] = system + system = [first, rest.join("\n")] + return system + } + + async function resolveTools(input: { + agent: Agent.Info + sessionID: string + modelID: string + providerID: string + tools?: Record + processor: Processor + }) { + const tools: Record = {} + const enabledTools = pipe( + input.agent.tools, + mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID, input.agent)), + mergeDeep(input.tools ?? {}), + ) + for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) { + if (Wildcard.all(item.id, enabledTools) === false) continue + tools[item.id] = tool({ + id: item.id as any, + description: item.description, + inputSchema: item.parameters as ZodSchema, + async execute(args, options) { + await Plugin.trigger( + "tool.execute.before", + { + tool: item.id, + sessionID: input.sessionID, + callID: options.toolCallId, + }, + { + args, + }, + ) + const result = await item.execute(args, { + sessionID: input.sessionID, + abort: options.abortSignal!, + messageID: input.processor.message.id, + callID: options.toolCallId, + agent: input.agent.name, + metadata: async (val) => { + const match = input.processor.partFromToolCall(options.toolCallId) + if (match && match.state.status === "running") { + await Session.updatePart({ + ...match, + state: { + title: val.title, + metadata: val.metadata, + status: "running", + input: args, + time: { + start: Date.now(), + }, + }, + }) + } + }, + }) + await Plugin.trigger( + "tool.execute.after", + { + tool: item.id, + sessionID: input.sessionID, + callID: options.toolCallId, + }, + result, + ) + return result + }, + toModelOutput(result) { + return { + type: "text", + value: result.output, + } + }, + }) + } + + for (const [key, item] of Object.entries(await MCP.tools())) { + if (Wildcard.all(key, enabledTools) === false) continue + const execute = item.execute + if (!execute) continue + item.execute = async (args, opts) => { + await Plugin.trigger( + "tool.execute.before", + { + tool: key, + sessionID: input.sessionID, + callID: opts.toolCallId, + }, + { + args, + }, + ) + const result = await execute(args, opts) + const output = result.content + .filter((x: any) => x.type === "text") + .map((x: any) => x.text) + .join("\n\n") + await Plugin.trigger( + "tool.execute.after", + { + tool: key, + sessionID: input.sessionID, + callID: opts.toolCallId, + }, + result, + ) + + return { + output, + } + } + item.toModelOutput = (result) => { + return { + type: "text", + value: result.output, + } + } + tools[key] = item + } + return tools + } + + async function createUserMessage(input: PromptInput) { + const info: MessageV2.Info = { + id: input.messageID ?? Identifier.ascending("message"), + role: "user", + sessionID: input.sessionID, + time: { + created: Date.now(), + }, + } + + const parts = await Promise.all( + input.parts.map(async (part): Promise => { + if (part.type === "file") { + const url = new URL(part.url) + switch (url.protocol) { + case "data:": + if (part.mime === "text/plain") { + return [ + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`, + }, + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: Buffer.from(part.url, "base64url").toString(), + }, + { + ...part, + id: part.id ?? Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + }, + ] + } + break + case "file:": + // have to normalize, symbol search returns absolute paths + // Decode the pathname since URL constructor doesn't automatically decode it + const filePath = decodeURIComponent(url.pathname) + + if (part.mime === "text/plain") { + let offset: number | undefined = undefined + let limit: number | undefined = undefined + const range = { + start: url.searchParams.get("start"), + end: url.searchParams.get("end"), + } + if (range.start != null) { + const filePath = part.url.split("?")[0] + let start = parseInt(range.start) + let end = range.end ? parseInt(range.end) : undefined + // some LSP servers (eg, gopls) don't give full range in + // workspace/symbol searches, so we'll try to find the + // symbol in the document to get the full range + if (start === end) { + const symbols = await LSP.documentSymbol(filePath) + for (const symbol of symbols) { + let range: LSP.Range | undefined + if ("range" in symbol) { + range = symbol.range + } else if ("location" in symbol) { + range = symbol.location.range + } + if (range?.start?.line && range?.start?.line === start) { + start = range.start.line + end = range?.end?.line ?? start + break + } + } + } + offset = Math.max(start - 1, 0) + if (end) { + limit = end - offset + } + } + const args = { filePath, offset, limit } + const result = await ReadTool.init().then((t) => + t.execute(args, { + sessionID: input.sessionID, + abort: new AbortController().signal, + agent: input.agent!, + messageID: info.id, + extra: { bypassCwdCheck: true }, + metadata: async () => {}, + }), + ) + return [ + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: `Called the Read tool with the following input: ${JSON.stringify(args)}`, + }, + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: result.output, + }, + { + ...part, + id: part.id ?? Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + }, + ] + } + + if (part.mime === "application/x-directory") { + const args = { path: filePath } + const result = await ListTool.init().then((t) => + t.execute(args, { + sessionID: input.sessionID, + abort: new AbortController().signal, + agent: input.agent!, + messageID: info.id, + extra: { bypassCwdCheck: true }, + metadata: async () => {}, + }), + ) + return [ + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: `Called the list tool with the following input: ${JSON.stringify(args)}`, + }, + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: result.output, + }, + { + ...part, + id: part.id ?? Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + }, + ] + } + + const file = Bun.file(filePath) + FileTime.read(input.sessionID, filePath) + return [ + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + text: `Called the Read tool with the following input: {\"filePath\":\"${filePath}\"}`, + synthetic: true, + }, + { + id: part.id ?? Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "file", + url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"), + mime: part.mime, + filename: part.filename!, + source: part.source, + }, + ] + } + } + + if (part.type === "agent") { + return [ + { + id: Identifier.ascending("part"), + ...part, + messageID: info.id, + sessionID: input.sessionID, + }, + { + id: Identifier.ascending("part"), + messageID: info.id, + sessionID: input.sessionID, + type: "text", + synthetic: true, + text: + "Use the above message and context to generate a prompt and call the task tool with subagent: " + + part.name, + }, + ] + } + + return [ + { + id: Identifier.ascending("part"), + ...part, + messageID: info.id, + sessionID: input.sessionID, + }, + ] + }), + ).then((x) => x.flat()) + + await Session.updateMessage(info) + for (const part of parts) { + await Session.updatePart(part) + } + + return { + info, + parts, + } + } + + function insertReminders(input: { messages: MessageV2.WithParts[]; agent: Agent.Info }) { + const userMessage = input.messages.findLast((msg) => msg.info.role === "user") + if (!userMessage) return input.messages + if (input.agent.name === "plan") { + userMessage.parts.push({ + id: Identifier.ascending("part"), + messageID: userMessage.info.id, + sessionID: userMessage.info.sessionID, + type: "text", + text: PROMPT_PLAN, + synthetic: true, + }) + } + const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan") + if (wasPlan && input.agent.name === "build") { + userMessage.parts.push({ + id: Identifier.ascending("part"), + messageID: userMessage.info.id, + sessionID: userMessage.info.sessionID, + type: "text", + text: BUILD_SWITCH, + synthetic: true, + }) + } + return input.messages + } + + export type Processor = Awaited> + async function createProcessor(input: { + sessionID: string + providerID: string + model: ModelsDev.Model + system: string[] + agent: string + abort: AbortSignal + }) { + const toolcalls: Record = {} + let snapshot: string | undefined + let blocked = false + + async function createMessage() { + const msg: MessageV2.Info = { + id: Identifier.ascending("message"), + role: "assistant", + system: input.system, + mode: input.agent, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + cost: 0, + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: input.model.id, + providerID: input.providerID, + time: { + created: Date.now(), + }, + sessionID: input.sessionID, + } + await Session.updateMessage(msg) + return msg + } + + let assistantMsg: MessageV2.Assistant | undefined + + const result = { + async end() { + if (assistantMsg) { + assistantMsg.time.completed = Date.now() + await Session.updateMessage(assistantMsg) + assistantMsg = undefined + } + }, + async next() { + if (assistantMsg) { + throw new Error("end previous assistant message first") + } + assistantMsg = await createMessage() + return assistantMsg + }, + get message() { + if (!assistantMsg) throw new Error("call next() first before accessing message") + return assistantMsg + }, + partFromToolCall(toolCallID: string) { + return toolcalls[toolCallID] + }, + async process(stream: StreamTextResult, never>) { + log.info("process") + if (!assistantMsg) throw new Error("call next() first before processing") + try { + let currentText: MessageV2.TextPart | undefined + let reasoningMap: Record = {} + + for await (const value of stream.fullStream) { + input.abort.throwIfAborted() + log.info("part", { + type: value.type, + }) + switch (value.type) { + case "start": + break + + case "reasoning-start": + if (value.id in reasoningMap) { + continue + } + reasoningMap[value.id] = { + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: assistantMsg.sessionID, + type: "reasoning", + text: "", + time: { + start: Date.now(), + }, + } + break + + case "reasoning-delta": + if (value.id in reasoningMap) { + const part = reasoningMap[value.id] + part.text += value.text + if (part.text) await Session.updatePart(part) + } + break + + case "reasoning-end": + if (value.id in reasoningMap) { + const part = reasoningMap[value.id] + part.text = part.text.trimEnd() + part.metadata = value.providerMetadata + part.time = { + ...part.time, + end: Date.now(), + } + await Session.updatePart(part) + delete reasoningMap[value.id] + } + break + + case "tool-input-start": + const part = await Session.updatePart({ + id: toolcalls[value.id]?.id ?? Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: assistantMsg.sessionID, + type: "tool", + tool: value.toolName, + callID: value.id, + state: { + status: "pending", + }, + }) + toolcalls[value.id] = part as MessageV2.ToolPart + break + + case "tool-input-delta": + break + + case "tool-input-end": + break + + case "tool-call": { + const match = toolcalls[value.toolCallId] + if (match) { + const part = await Session.updatePart({ + ...match, + tool: value.toolName, + state: { + status: "running", + input: value.input, + time: { + start: Date.now(), + }, + }, + }) + toolcalls[value.toolCallId] = part as MessageV2.ToolPart + } + break + } + case "tool-result": { + const match = toolcalls[value.toolCallId] + if (match && match.state.status === "running") { + await Session.updatePart({ + ...match, + state: { + status: "completed", + input: value.input, + output: value.output.output, + metadata: value.output.metadata, + title: value.output.title, + time: { + start: match.state.time.start, + end: Date.now(), + }, + }, + }) + delete toolcalls[value.toolCallId] + } + break + } + + case "tool-error": { + const match = toolcalls[value.toolCallId] + if (match && match.state.status === "running") { + await Session.updatePart({ + ...match, + state: { + status: "error", + input: value.input, + error: (value.error as any).toString(), + metadata: value.error instanceof Permission.RejectedError ? value.error.metadata : undefined, + time: { + start: match.state.time.start, + end: Date.now(), + }, + }, + }) + if (value.error instanceof Permission.RejectedError) { + blocked = true + } + delete toolcalls[value.toolCallId] + } + break + } + case "error": + throw value.error + + case "start-step": + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: assistantMsg.sessionID, + type: "step-start", + }) + snapshot = await Snapshot.track() + break + + case "finish-step": + const usage = Session.getUsage(input.model, value.usage, value.providerMetadata) + assistantMsg.cost += usage.cost + assistantMsg.tokens = usage.tokens + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: assistantMsg.sessionID, + type: "step-finish", + tokens: usage.tokens, + cost: usage.cost, + }) + await Session.updateMessage(assistantMsg) + if (snapshot) { + const patch = await Snapshot.patch(snapshot) + if (patch.files.length) { + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: assistantMsg.sessionID, + type: "patch", + hash: patch.hash, + files: patch.files, + }) + } + snapshot = undefined + } + break + + case "text-start": + currentText = { + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: assistantMsg.sessionID, + type: "text", + text: "", + time: { + start: Date.now(), + }, + } + break + + case "text-delta": + if (currentText) { + currentText.text += value.text + if (currentText.text) await Session.updatePart(currentText) + } + break + + case "text-end": + if (currentText) { + currentText.text = currentText.text.trimEnd() + currentText.time = { + start: Date.now(), + end: Date.now(), + } + await Session.updatePart(currentText) + } + currentText = undefined + break + + case "finish": + assistantMsg.time.completed = Date.now() + await Session.updateMessage(assistantMsg) + break + + default: + log.info("unhandled", { + ...value, + }) + continue + } + } + } catch (e) { + log.error("process", { + error: e, + }) + switch (true) { + case e instanceof DOMException && e.name === "AbortError": + assistantMsg.error = new MessageV2.AbortedError( + { message: e.message }, + { + cause: e, + }, + ).toObject() + break + case MessageV2.OutputLengthError.isInstance(e): + assistantMsg.error = e + break + case LoadAPIKeyError.isInstance(e): + assistantMsg.error = new MessageV2.AuthError( + { + providerID: input.providerID, + message: e.message, + }, + { cause: e }, + ).toObject() + break + case e instanceof Error: + assistantMsg.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject() + break + default: + assistantMsg.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }) + } + Bus.publish(Session.Event.Error, { + sessionID: assistantMsg.sessionID, + error: assistantMsg.error, + }) + } + const p = await Session.getParts(assistantMsg.id) + for (const part of p) { + if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") { + Session.updatePart({ + ...part, + state: { + status: "error", + error: "Tool execution aborted", + time: { + start: Date.now(), + end: Date.now(), + }, + input: {}, + }, + }) + } + } + assistantMsg.time.completed = Date.now() + await Session.updateMessage(assistantMsg) + return { info: assistantMsg, parts: p, blocked } + }, + } + return result + } + + function isBusy(sessionID: string) { + return state().pending.has(sessionID) + } + + export function abort(sessionID: string) { + const controller = state().pending.get(sessionID) + if (!controller) return false + log.info("aborting", { + sessionID, + }) + controller.abort() + state().pending.delete(sessionID) + return true + } + + function lock(sessionID: string) { + log.info("locking", { sessionID }) + if (state().pending.has(sessionID)) throw new Error("TODO") + const controller = new AbortController() + state().pending.set(sessionID, controller) + return { + signal: controller.signal, + async [Symbol.dispose]() { + log.info("unlocking", { sessionID }) + state().pending.delete(sessionID) + + const session = await Session.get(sessionID) + if (session.parentID) return + + Bus.publish(Event.Idle, { + sessionID, + }) + }, + } + } + + export const ShellInput = z.object({ + sessionID: Identifier.schema("session"), + agent: z.string(), + command: z.string(), + }) + export type ShellInput = z.infer + export async function shell(input: ShellInput) { + using abort = lock(input.sessionID) + const session = await Session.get(input.sessionID) + if (session.revert) { + SessionRevert.cleanup(session) + } + const userMsg: MessageV2.User = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + time: { + created: Date.now(), + }, + role: "user", + } + await Session.updateMessage(userMsg) + const userPart: MessageV2.Part = { + type: "text", + id: Identifier.ascending("part"), + messageID: userMsg.id, + sessionID: input.sessionID, + text: "The following tool was executed by the user", + synthetic: true, + } + await Session.updatePart(userPart) + + const msg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + system: [], + mode: input.agent, + cost: 0, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + time: { + created: Date.now(), + }, + role: "assistant", + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: "", + providerID: "", + } + await Session.updateMessage(msg) + const part: MessageV2.Part = { + type: "tool", + id: Identifier.ascending("part"), + messageID: msg.id, + sessionID: input.sessionID, + tool: "bash", + callID: ulid(), + state: { + status: "running", + time: { + start: Date.now(), + }, + input: { + command: input.command, + }, + }, + } + await Session.updatePart(part) + const shell = process.env["SHELL"] ?? "bash" + const shellName = path.basename(shell) + + const scripts: Record = { + nu: input.command, + fish: `eval "${input.command}"`, + } + + const script = + scripts[shellName] ?? + `[[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true + [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true + [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true + eval "${input.command}"` + + const isFishOrNu = shellName === "fish" || shellName === "nu" + const args = isFishOrNu ? ["-c", script] : ["-c", "-l", script] + + const proc = spawn(shell, args, { + cwd: Instance.directory, + signal: abort.signal, + detached: true, + stdio: ["ignore", "pipe", "pipe"], + env: { + ...process.env, + TERM: "dumb", + }, + }) + + abort.signal.addEventListener("abort", () => { + if (!proc.pid) return + process.kill(-proc.pid) + }) + + let output = "" + + proc.stdout?.on("data", (chunk) => { + output += chunk.toString() + if (part.state.status === "running") { + part.state.metadata = { + output: output, + description: "", + } + Session.updatePart(part) + } + }) + + proc.stderr?.on("data", (chunk) => { + output += chunk.toString() + if (part.state.status === "running") { + part.state.metadata = { + output: output, + description: "", + } + Session.updatePart(part) + } + }) + + await new Promise((resolve) => { + proc.on("close", () => { + resolve() + }) + }) + msg.time.completed = Date.now() + await Session.updateMessage(msg) + if (part.state.status === "running") { + part.state = { + status: "completed", + time: { + ...part.state.time, + end: Date.now(), + }, + input: part.state.input, + title: "", + metadata: { + output, + description: "", + }, + output, + } + await Session.updatePart(part) + } + return { info: msg, parts: [part] } + } + + export const CommandInput = z.object({ + messageID: Identifier.schema("message").optional(), + sessionID: Identifier.schema("session"), + agent: z.string().optional(), + model: z.string().optional(), + arguments: z.string(), + command: z.string(), + }) + export type CommandInput = z.infer + const bashRegex = /!`([^`]+)`/g + /** + * Regular expression to match @ file references in text + * Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks + * Does not match when preceded by word characters or backticks (to avoid email addresses and quoted references) + */ + export const fileRegex = /(? 0) { + const results = await Promise.all( + bash.map(async ([, cmd]) => { + try { + return await $`${{ raw: cmd }}`.nothrow().text() + } catch (error) { + return `Error executing command: ${error instanceof Error ? error.message : String(error)}` + } + }), + ) + let index = 0 + template = template.replace(bashRegex, () => results[index++]) + } + + const parts = [ + { + type: "text", + text: template, + }, + ] as PromptInput["parts"] + + const matches = Array.from(template.matchAll(fileRegex)) + await Promise.all( + matches.map(async (match) => { + const name = match[1] + const filepath = name.startsWith("~/") + ? path.join(os.homedir(), name.slice(2)) + : path.resolve(Instance.worktree, name) + + const stats = await fs.stat(filepath).catch(() => undefined) + if (!stats) { + const agent = await Agent.get(name) + if (agent) { + parts.push({ + type: "agent", + name: agent.name, + }) + } + return + } + + if (stats.isDirectory()) { + parts.push({ + type: "file", + url: `file://${filepath}`, + filename: name, + mime: "application/x-directory", + }) + return + } + + parts.push({ + type: "file", + url: `file://${filepath}`, + filename: name, + mime: "text/plain", + }) + }), + ) + + const model = await (async () => { + if (command.model) { + return Provider.parseModel(command.model) + } + if (command.agent) { + const agent = await Agent.get(command.agent) + if (agent.model) { + return agent.model + } + } + if (input.model) { + return Provider.parseModel(input.model) + } + return undefined + })() + + return prompt({ + sessionID: input.sessionID, + messageID: input.messageID, + model, + agent, + parts, + }) + } + + async function ensureTitle(input: { + session: Session.Info + message: MessageV2.WithParts + history: MessageV2.WithParts[] + providerID: string + modelID: string + }) { + if (input.session.parentID) return + const isFirst = + input.history.filter((m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)) + .length === 1 + if (!isFirst) return + const small = + (await Provider.getSmallModel(input.providerID)) ?? (await Provider.getModel(input.providerID, input.modelID)) + const options = { + ...ProviderTransform.options(small.providerID, small.modelID, input.session.id), + ...small.info.options, + } + if (small.providerID === "openai") { + options["reasoningEffort"] = "minimal" + } + if (small.providerID === "google") { + options["thinkingConfig"] = { + thinkingBudget: 0, + } + } + generateText({ + maxOutputTokens: small.info.reasoning ? 1500 : 20, + providerOptions: { + [small.providerID]: options, + }, + messages: [ + ...SystemPrompt.title(small.providerID).map( + (x): ModelMessage => ({ + role: "system", + content: x, + }), + ), + ...MessageV2.toModelMessage([ + { + info: { + id: Identifier.ascending("message"), + role: "user", + sessionID: input.session.id, + time: { + created: Date.now(), + }, + }, + parts: input.message.parts, + }, + ]), + ], + model: small.language, + }) + .then((result) => { + if (result.text) + return Session.update(input.session.id, (draft) => { + const cleaned = result.text.replace(/[\s\S]*?<\/think>\s*/g, "") + const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned + draft.title = title.trim() + }) + }) + .catch((error) => { + log.error("failed to generate title", { error, model: small.info.id }) + }) + } +} diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts new file mode 100644 index 000000000..694834c49 --- /dev/null +++ b/packages/opencode/src/session/revert.ts @@ -0,0 +1,105 @@ +import z from "zod" +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 + + 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 + }) + } +} diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 5c226f137..a16d94b82 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -6,6 +6,7 @@ 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: { diff --git a/packages/opencode/test/session/fileRegex.test.ts b/packages/opencode/test/session/fileRegex.test.ts index 201a877fc..a1f0875e0 100644 --- a/packages/opencode/test/session/fileRegex.test.ts +++ b/packages/opencode/test/session/fileRegex.test.ts @@ -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) }) }) From eb24d2f8474c22ee3cb4d02ea40df8132728d8ca Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 13 Sep 2025 05:53:03 -0400 Subject: [PATCH 46/62] ignore: fix --- packages/opencode/src/session/compaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 3a216255e..6039ed263 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -25,7 +25,7 @@ export namespace SessionCompaction { 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 = input.model.limit.context - output - return count > usable / 2 + return count > usable } export async function run(input: { sessionID: string; providerID: string; modelID: string }) { From b1589be4ba03fb35ecf204ee17e9ecc16a83fb94 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 13 Sep 2025 05:55:04 -0400 Subject: [PATCH 47/62] add disable OPENCODE_DISABLE_AUTOCOMPACT --- packages/opencode/src/flag/flag.ts | 1 + packages/opencode/src/session/compaction.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 4af827d8b..418f32283 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -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() diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 6039ed263..e5b42469d 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -10,6 +10,7 @@ import { Bus } from "../bus" import z from "zod" import type { ModelsDev } from "../provider/models" import { SessionPrompt } from "./prompt" +import { Flag } from "../flag/flag" export namespace SessionCompaction { export const Event = { @@ -22,6 +23,7 @@ export namespace SessionCompaction { } export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) { + if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) 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 = input.model.limit.context - output From a00b49d65b1b98727c187a335f14218c4ec05185 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 13 Sep 2025 05:59:18 -0400 Subject: [PATCH 48/62] disable autocompact if context is 0 --- packages/opencode/src/session/compaction.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index e5b42469d..6af398f1d 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -24,9 +24,11 @@ export namespace SessionCompaction { 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 = input.model.limit.context - output + const usable = context - output return count > usable } From f0e8b7c29b294686036bdf74fdbe93a6e560ed3f Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 13 Sep 2025 12:03:56 +0000 Subject: [PATCH 49/62] ignore: update download stats 2025-09-13 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index 43faa489b..11e15cfcc 100644 --- a/STATS.md +++ b/STATS.md @@ -77,3 +77,4 @@ | 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) | From 6506e48c54dc6b3cdf07089d420c136206ef710c Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 13 Sep 2025 12:25:30 -0500 Subject: [PATCH 50/62] tweak: keep aborted msgs in context (#2583) --- packages/opencode/src/session/prompt.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index b804c9404..8e70fa73f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -278,7 +278,11 @@ export namespace SessionPrompt { content: x, }), ), - ...MessageV2.toModelMessage(msgs.filter((m) => !(m.info.role === "assistant" && m.info.error))), + ...MessageV2.toModelMessage( + msgs.filter( + (m) => !(m.info.role === "assistant" && m.info.error && !MessageV2.AbortedError.isInstance(m.info.error)), + ), + ), ], tools: model.info.tool_call === false ? undefined : tools, model: wrapLanguageModel({ From 16d66c209ded6475967cdd5d20815eafb2be6a1d Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 13 Sep 2025 12:47:18 -0500 Subject: [PATCH 51/62] respect subagent in command, add `subtask` flag (#2569) --- packages/opencode/src/command/index.ts | 2 + packages/opencode/src/config/config.ts | 1 + packages/opencode/src/session/prompt.ts | 125 ++++++++++++++++++++++-- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index a9356cdd4..8ee1df2b4 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -10,6 +10,7 @@ export namespace Command { agent: z.string().optional(), model: z.string().optional(), template: z.string(), + subtask: z.boolean().optional(), }) .openapi({ ref: "Command", @@ -28,6 +29,7 @@ export namespace Command { model: command.model, description: command.description, template: command.template, + subtask: command.subtask, } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index f4b8608d8..22ff58db8 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -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 diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 8e70fa73f..ef3deeb3a 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -38,6 +38,7 @@ import { MCP } from "../mcp" import { LSP } from "../lsp" import { ReadTool } from "../tool/read" import { ListTool } from "../tool/ls" +import { TaskTool } from "../tool/task" import { FileTime } from "../file/time" import { Permission } from "../permission" import { Snapshot } from "../snapshot" @@ -1315,7 +1316,7 @@ export namespace SessionPrompt { export async function command(input: CommandInput) { log.info("command", input) const command = await Command.get(input.command) - const agent = command.agent ?? input.agent ?? "build" + const agentName = command.agent ?? input.agent ?? "build" let template = command.template.replace("$ARGUMENTS", input.arguments) @@ -1385,22 +1386,134 @@ export namespace SessionPrompt { return Provider.parseModel(command.model) } if (command.agent) { - const agent = await Agent.get(command.agent) - if (agent.model) { - return agent.model + const cmdAgent = await Agent.get(command.agent) + if (cmdAgent.model) { + return cmdAgent.model } } if (input.model) { return Provider.parseModel(input.model) } - return undefined + return await Provider.defaultModel() })() + const agent = await Agent.get(agentName) + if (agent.mode === "subagent" || command.subtask) { + using abort = lock(input.sessionID) + + const userMsg: MessageV2.User = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + time: { + created: Date.now(), + }, + role: "user", + } + await Session.updateMessage(userMsg) + const userPart: MessageV2.Part = { + type: "text", + id: Identifier.ascending("part"), + messageID: userMsg.id, + sessionID: input.sessionID, + text: "The following tool was executed by the user", + synthetic: true, + } + await Session.updatePart(userPart) + + const assistantMsg: MessageV2.Assistant = { + id: Identifier.ascending("message"), + sessionID: input.sessionID, + system: [], + mode: agentName, + cost: 0, + path: { + cwd: Instance.directory, + root: Instance.worktree, + }, + time: { + created: Date.now(), + }, + role: "assistant", + tokens: { + input: 0, + output: 0, + reasoning: 0, + cache: { read: 0, write: 0 }, + }, + modelID: model.modelID, + providerID: model.providerID, + } + await Session.updateMessage(assistantMsg) + + const args = { + description: "Consulting " + agent.name, + subagent_type: agent.name, + prompt: template, + } + const toolPart: MessageV2.ToolPart = { + type: "tool", + id: Identifier.ascending("part"), + messageID: assistantMsg.id, + sessionID: input.sessionID, + tool: "task", + callID: ulid(), + state: { + status: "running", + time: { + start: Date.now(), + }, + input: { + description: args.description, + subagent_type: args.subagent_type, + // truncate prompt to preserve context + prompt: args.prompt.length > 100 ? args.prompt.substring(0, 97) + "..." : args.prompt, + }, + }, + } + await Session.updatePart(toolPart) + + const result = await TaskTool.init().then((t) => + t.execute(args, { + sessionID: input.sessionID, + abort: abort.signal, + agent: agent.name, + messageID: assistantMsg.id, + extra: {}, + metadata: async (metadata) => { + if (toolPart.state.status === "running") { + toolPart.state.metadata = metadata.metadata + toolPart.state.title = metadata.title + await Session.updatePart(toolPart) + } + }, + }), + ) + + assistantMsg.time.completed = Date.now() + await Session.updateMessage(assistantMsg) + if (toolPart.state.status === "running") { + toolPart.state = { + status: "completed", + time: { + ...toolPart.state.time, + end: Date.now(), + }, + input: toolPart.state.input, + title: "", + metadata: result.metadata, + output: result.output, + } + await Session.updatePart(toolPart) + } + + return { info: assistantMsg, parts: [toolPart] } + } + return prompt({ sessionID: input.sessionID, messageID: input.messageID, model, - agent, + agent: agentName, parts, }) } From 3752bb9717161370f87d6e657056942b21e55213 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sat, 13 Sep 2025 19:46:24 -0500 Subject: [PATCH 52/62] fix: token counting visual bug (#2587) --- packages/tui/internal/components/chat/messages.go | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index 8e36d99c1..f06443de1 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -899,7 +899,6 @@ func (m *messagesComponent) renderHeader() string { } tokens = (usage.Input + usage.Cache.Write + - usage.Cache.Read + usage.Output + usage.Reasoning) } From 1f8d396b76f3911fe589deb78ecf466e415bb9af Mon Sep 17 00:00:00 2001 From: Mani Sundararajan <10191300+itsrainingmani@users.noreply.github.com> Date: Sun, 14 Sep 2025 01:59:25 -0400 Subject: [PATCH 53/62] fix(dev): build tui with correct file ext for windows (#2590) --- packages/opencode/src/cli/cmd/tui.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts index 86b9f31b8..e0fe49019 100644 --- a/packages/opencode/src/cli/cmd/tui.ts +++ b/packages/opencode/src/cli/cmd/tui.ts @@ -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, From 4b30705c425ceb832b7c91940c6f4f0674e815aa Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 14 Sep 2025 06:07:43 +0000 Subject: [PATCH 54/62] release: v0.8.0 --- cloud/app/package.json | 2 +- cloud/core/package.json | 2 +- cloud/function/package.json | 2 +- cloud/scripts/package.json | 2 +- packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- packages/sdk/js/src/gen/types.gen.ts | 42 +++++++++++++++------------- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 11 files changed, 32 insertions(+), 30 deletions(-) diff --git a/cloud/app/package.json b/cloud/app/package.json index 6118db3e0..1318c2cec 100644 --- a/cloud/app/package.json +++ b/cloud/app/package.json @@ -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.8.0" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/cloud/core/package.json b/cloud/core/package.json index 2461bfdc9..453bea0a1 100644 --- a/cloud/core/package.json +++ b/cloud/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode/cloud-core", - "version": "0.7.9", + "version": "0.8.0", "private": true, "type": "module", "dependencies": { diff --git a/cloud/function/package.json b/cloud/function/package.json index 15d6a5811..65126433e 100644 --- a/cloud/function/package.json +++ b/cloud/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-function", - "version": "0.7.9", + "version": "0.8.0", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/cloud/scripts/package.json b/cloud/scripts/package.json index 5922eeda1..571e3876d 100644 --- a/cloud/scripts/package.json +++ b/cloud/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/cloud-scripts", - "version": "0.7.9", + "version": "0.8.0", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/function/package.json b/packages/function/package.json index 1b3766c7a..2649b483d 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode/function", - "version": "0.7.9", + "version": "0.8.0", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e69663509..14280c6c6 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "0.7.9", + "version": "0.8.0", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 7694e5f15..b6bd6a8fa 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "0.7.9", + "version": "0.8.0", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 3653ba93d..e2fc29ec0 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "0.7.9", + "version": "0.8.0", "type": "module", "scripts": { "typecheck": "tsc --noEmit" diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 7a2f964de..864afa39f 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -29,6 +29,9 @@ export type Event = | ({ type: "message.part.removed" } & EventMessagePartRemoved) + | ({ + type: "session.compacted" + } & EventSessionCompacted) | ({ type: "permission.updated" } & EventPermissionUpdated) @@ -38,21 +41,18 @@ export type Event = | ({ type: "file.edited" } & EventFileEdited) + | ({ + type: "session.idle" + } & EventSessionIdle) | ({ type: "session.updated" } & EventSessionUpdated) | ({ type: "session.deleted" } & EventSessionDeleted) - | ({ - type: "session.idle" - } & EventSessionIdle) | ({ type: "session.error" } & EventSessionError) - | ({ - type: "session.compacted" - } & EventSessionCompacted) | ({ type: "server.connected" } & EventServerConnected) @@ -425,6 +425,13 @@ export type EventMessagePartRemoved = { } } +export type EventSessionCompacted = { + type: "session.compacted" + properties: { + sessionID: string + } +} + export type EventPermissionUpdated = { type: "permission.updated" properties: Permission @@ -462,6 +469,13 @@ export type EventFileEdited = { } } +export type EventSessionIdle = { + type: "session.idle" + properties: { + sessionID: string + } +} + export type EventSessionUpdated = { type: "session.updated" properties: { @@ -499,13 +513,6 @@ export type EventSessionDeleted = { } } -export type EventSessionIdle = { - type: "session.idle" - properties: { - sessionID: string - } -} - export type EventSessionError = { type: "session.error" properties: { @@ -526,13 +533,6 @@ export type EventSessionError = { } } -export type EventSessionCompacted = { - type: "session.compacted" - properties: { - sessionID: string - } -} - export type EventServerConnected = { type: "server.connected" properties: { @@ -571,6 +571,7 @@ export type Config = { description?: string agent?: string model?: string + subtask?: boolean } } plugin?: Array @@ -1156,6 +1157,7 @@ export type Command = { agent?: string model?: string template: string + subtask?: boolean } export type Symbol = { diff --git a/packages/web/package.json b/packages/web/package.json index 85bd08c60..ed615d550 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode/web", "type": "module", - "version": "0.7.9", + "version": "0.8.0", "scripts": { "dev": "astro dev", "dev:remote": "sst shell --stage=dev --target=Web astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index e8086b9db..bb10a3a1d 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "0.7.9", + "version": "0.8.0", "publisher": "sst-dev", "repository": { "type": "git", From ce9d2ee04fc6ac1b040245f19555fbd9c55c2252 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 14 Sep 2025 02:07:45 -0400 Subject: [PATCH 55/62] ci: script --- script/release | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/script/release b/script/release index 5cb938114..13761a1ec 100755 --- a/script/release +++ b/script/release @@ -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" From 6af0c2ec21823bcc123d3e82c9872bf2787e4d90 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sun, 14 Sep 2025 12:03:54 +0000 Subject: [PATCH 56/62] ignore: update download stats 2025-09-14 --- STATS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/STATS.md b/STATS.md index 11e15cfcc..7dd46268c 100644 --- a/STATS.md +++ b/STATS.md @@ -78,3 +78,4 @@ | 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) | From df61aa801b97eb6b73a5dd3bf8a1e6e4644c15da Mon Sep 17 00:00:00 2001 From: Kenn Costales Date: Sun, 14 Sep 2025 21:53:50 +0800 Subject: [PATCH 57/62] fix: fix wrong tool references `LS` and `Agent` (#2466) --- packages/opencode/src/tool/bash.txt | 6 +++--- packages/opencode/src/tool/glob.txt | 2 +- packages/opencode/src/tool/grep.txt | 2 +- packages/opencode/src/tool/task.txt | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/tool/bash.txt b/packages/opencode/src/tool/bash.txt index 342073b9b..67c54677f 100644 --- a/packages/opencode/src/tool/bash.txt +++ b/packages/opencode/src/tool/bash.txt @@ -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. diff --git a/packages/opencode/src/tool/glob.txt b/packages/opencode/src/tool/glob.txt index 3873a4761..2cf16a05f 100644 --- a/packages/opencode/src/tool/glob.txt +++ b/packages/opencode/src/tool/glob.txt @@ -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. diff --git a/packages/opencode/src/tool/grep.txt b/packages/opencode/src/tool/grep.txt index 3caef001a..d964a3d1f 100644 --- a/packages/opencode/src/tool/grep.txt +++ b/packages/opencode/src/tool/grep.txt @@ -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 diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index 27e7bc5de..2be3819f0 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -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 From c81624aef743f5b62adef62ca99934b6fe7fb6c3 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 14 Sep 2025 09:01:57 -0500 Subject: [PATCH 58/62] tweak: make bash permissions key off of command pattern (#2592) --- packages/opencode/src/permission/index.ts | 28 ++++++++++++++---- packages/opencode/src/tool/bash.ts | 35 +++++++++++++++++++---- packages/opencode/src/tool/edit.ts | 1 - packages/opencode/src/tool/webfetch.ts | 1 - 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index e8b4e6812..dd198daca 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -4,15 +4,25 @@ 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): 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(), @@ -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 }) } } diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index a7b6ec249..8149360ff 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -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() 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, }, }) } diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 88e453029..7218575f9 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -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, diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index e4519c0cb..621421fe9 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -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, From 4bf0541bd65e7e69a5b48f44545328e21306a0ef Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Sun, 14 Sep 2025 16:03:40 +0200 Subject: [PATCH 59/62] log bash output when using `opencode run` (#2595) --- packages/opencode/src/cli/cmd/run.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index e9229623c..e5aa70106 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -145,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 @@ -155,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") { From e3e459fc50a81657930cc73d6bc63adf08ad9d62 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 14 Sep 2025 16:28:06 -0500 Subject: [PATCH 60/62] fix: reasoning metadata persistence (#2602) --- packages/opencode/src/session/message-v2.ts | 9 +++++++++ packages/opencode/src/session/prompt.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index d0f7f3170..c031dbe7a 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -549,6 +549,15 @@ export namespace MessageV2 { }, ] } + if (part.type === "reasoning") { + return [ + { + type: "reasoning", + text: part.text, + providerMetadata: part.metadata, + }, + ] + } return [] }), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index ef3deeb3a..f6f0ffb9b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -853,6 +853,7 @@ export namespace SessionPrompt { if (value.id in reasoningMap) { const part = reasoningMap[value.id] part.text += value.text + if (value.providerMetadata) part.metadata = value.providerMetadata if (part.text) await Session.updatePart(part) } break @@ -861,7 +862,7 @@ export namespace SessionPrompt { if (value.id in reasoningMap) { const part = reasoningMap[value.id] part.text = part.text.trimEnd() - part.metadata = value.providerMetadata + part.time = { ...part.time, end: Date.now(), From 89d820b1c44028e673f2b4317a2104abb2be2e67 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 14 Sep 2025 21:23:52 -0500 Subject: [PATCH 61/62] fix: visual token bug (#2603) --- packages/tui/internal/components/chat/messages.go | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index f06443de1..0ef0169f2 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -898,6 +898,7 @@ func (m *messagesComponent) renderHeader() string { continue } tokens = (usage.Input + + usage.Cache.Read + usage.Cache.Write + usage.Output + usage.Reasoning) From c1b4e1f19dd45eeea791354cb041c520f55e1cdb Mon Sep 17 00:00:00 2001 From: Dax Date: Mon, 15 Sep 2025 03:12:07 -0400 Subject: [PATCH 62/62] Upgrade to Zod v4 (#2605) Co-authored-by: GitHub Action --- bun.lock | 42 +- cloud/core/src/util/fn.ts | 6 +- package.json | 6 +- packages/opencode/package.json | 6 +- packages/opencode/script/schema.ts | 13 +- packages/opencode/src/agent/agent.ts | 6 +- packages/opencode/src/auth/github-copilot.ts | 2 +- packages/opencode/src/auth/index.ts | 10 +- packages/opencode/src/bun/index.ts | 2 +- packages/opencode/src/bus/index.ts | 5 +- packages/opencode/src/cli/ui.ts | 2 +- packages/opencode/src/command/index.ts | 4 +- packages/opencode/src/config/config.ts | 19 +- packages/opencode/src/file/fzf.ts | 2 +- packages/opencode/src/file/index.ts | 8 +- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/file/watch.ts | 2 +- packages/opencode/src/id/id.ts | 2 +- packages/opencode/src/ide/index.ts | 2 +- packages/opencode/src/index.ts | 1 - packages/opencode/src/installation/index.ts | 4 +- packages/opencode/src/lsp/client.ts | 2 +- packages/opencode/src/lsp/index.ts | 8 +- packages/opencode/src/mcp/index.ts | 2 +- packages/opencode/src/permission/index.ts | 6 +- packages/opencode/src/project/project.ts | 4 +- packages/opencode/src/provider/models.ts | 10 +- packages/opencode/src/provider/provider.ts | 2 +- packages/opencode/src/provider/transform.ts | 26 + packages/opencode/src/server/project.ts | 2 +- packages/opencode/src/server/server.ts | 129 +- packages/opencode/src/session/compaction.ts | 2 +- packages/opencode/src/session/index.ts | 6 +- packages/opencode/src/session/message-v2.ts | 60 +- packages/opencode/src/session/message.ts | 32 +- packages/opencode/src/session/prompt.ts | 14 +- packages/opencode/src/session/revert.ts | 2 +- packages/opencode/src/snapshot/index.ts | 2 +- packages/opencode/src/tool/bash.ts | 2 +- packages/opencode/src/tool/edit.ts | 2 +- packages/opencode/src/tool/glob.ts | 2 +- packages/opencode/src/tool/grep.ts | 2 +- packages/opencode/src/tool/invalid.ts | 2 +- packages/opencode/src/tool/ls.ts | 2 +- packages/opencode/src/tool/lsp-diagnostics.ts | 2 +- packages/opencode/src/tool/lsp-hover.ts | 2 +- packages/opencode/src/tool/multiedit.ts | 2 +- packages/opencode/src/tool/patch.ts | 2 +- packages/opencode/src/tool/read.ts | 2 +- packages/opencode/src/tool/registry.ts | 115 +- packages/opencode/src/tool/task.ts | 2 +- packages/opencode/src/tool/todo.ts | 2 +- packages/opencode/src/tool/tool.ts | 8 +- packages/opencode/src/tool/webfetch.ts | 2 +- packages/opencode/src/tool/write.ts | 2 +- packages/opencode/src/util/error.ts | 8 +- packages/opencode/src/util/log.ts | 4 +- packages/opencode/test/tool/register.test.ts | 1 - packages/sdk/go/.release-please-manifest.json | 2 +- packages/sdk/go/.stats.yml | 4 +- packages/sdk/go/CHANGELOG.md | 32 + packages/sdk/go/README.md | 2 +- packages/sdk/go/aliases.go | 3 + packages/sdk/go/command.go | 2 + packages/sdk/go/config.go | 133 ++- packages/sdk/go/event.go | 367 +++--- packages/sdk/go/internal/version.go | 2 +- packages/sdk/go/session.go | 129 +- packages/sdk/go/session_test.go | 8 +- packages/sdk/go/shared/shared.go | 23 +- packages/sdk/js/src/gen/sdk.gen.ts | 14 +- packages/sdk/js/src/gen/types.gen.ts | 1061 ++++++++--------- packages/web/src/content/docs/agents.mdx | 2 - packages/web/src/content/docs/index.mdx | 1 - packages/web/src/content/docs/providers.mdx | 4 - 75 files changed, 1106 insertions(+), 1304 deletions(-) diff --git a/bun.lock b/bun.lock index b52e91121..7219ac329 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/cloud/core/src/util/fn.ts b/cloud/core/src/util/fn.ts index 52df65e2b..9efe4622f 100644 --- a/cloud/core/src/util/fn.ts +++ b/cloud/core/src/util/fn.ts @@ -1,11 +1,11 @@ import { z } from "zod" -export function fn(schema: T, cb: (input: z.output) => Result) { - const result = (input: z.input) => { +export function fn(schema: T, cb: (input: z.infer) => Result) { + const result = (input: z.infer) => { const parsed = schema.parse(input) return cb(parsed) } - result.force = (input: z.input) => cb(input) + result.force = (input: z.infer) => cb(input) result.schema = schema return result } diff --git a/package.json b/package.json index 5d3ab0f90..1e339eadf 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 14280c6c6..544bb6097 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -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:" } } diff --git a/packages/opencode/script/schema.ts b/packages/opencode/script/schema.ts index 27450fd34..7e450df92 100755 --- a/packages/opencode/script/schema.ts +++ b/packages/opencode/script/schema.ts @@ -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 & { allowComments?: boolean diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 710589902..252c0bd6b 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -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 diff --git a/packages/opencode/src/auth/github-copilot.ts b/packages/opencode/src/auth/github-copilot.ts index ba5274e51..c9d90cd56 100644 --- a/packages/opencode/src/auth/github-copilot.ts +++ b/packages/opencode/src/auth/github-copilot.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { Auth } from "./index" import { NamedError } from "../util/error" diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index a09143438..ef9846a37 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -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 const filepath = path.join(Global.Path.data, "auth.json") diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts index cd413ff4a..d6fb29d62 100644 --- a/packages/opencode/src/bun/index.ts +++ b/packages/opencode/src/bun/index.ts @@ -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" diff --git a/packages/opencode/src/bus/index.ts b/packages/opencode/src/bus/index.ts index be42bf807..cfd3958d2 100644 --- a/packages/opencode/src/bus/index.ts +++ b/packages/opencode/src/bus/index.ts @@ -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, }), ) diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index 0fa4d1ce6..bdbaed911 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { EOL } from "os" import { NamedError } from "../util/error" diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 8ee1df2b4..f879e627a 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -1,4 +1,4 @@ -import z from "zod" +import z from "zod/v4" import { Config } from "../config/config" import { Instance } from "../project/instance" @@ -12,7 +12,7 @@ export namespace Command { template: z.string(), subtask: z.boolean().optional(), }) - .openapi({ + .meta({ ref: "Command", }) export type Info = z.infer diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 22ff58db8..74413657c 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -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", }) @@ -267,7 +267,7 @@ export namespace Config { .optional(), }) .catchall(z.any()) - .openapi({ + .meta({ ref: "AgentConfig", }) export type Agent = z.infer @@ -342,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", }) @@ -350,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 @@ -407,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(), @@ -505,7 +506,7 @@ export namespace Config { .optional(), }) .strict() - .openapi({ + .meta({ ref: "Config", }) @@ -644,7 +645,7 @@ export namespace Config { "ConfigInvalidError", z.object({ path: z.string(), - issues: z.custom().optional(), + issues: z.custom().optional(), message: z.string().optional(), }), ) diff --git a/packages/opencode/src/file/fzf.ts b/packages/opencode/src/file/fzf.ts index 3b7320279..7a35351fa 100644 --- a/packages/opencode/src/file/fzf.ts +++ b/packages/opencode/src/file/fzf.ts @@ -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" diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index dda220be4..40f52aa3a 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -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 @@ -60,7 +60,7 @@ export namespace File { }) .optional(), }) - .openapi({ + .meta({ ref: "FileContent", }) export type Content = z.infer diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index f21cbdef9..1cbf6b8a8 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -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" diff --git a/packages/opencode/src/file/watch.ts b/packages/opencode/src/file/watch.ts index 587ad54dd..526b29c9e 100644 --- a/packages/opencode/src/file/watch.ts +++ b/packages/opencode/src/file/watch.ts @@ -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" diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index f2b961e45..ec24f30d9 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { randomBytes } from "crypto" export namespace Identifier { diff --git a/packages/opencode/src/ide/index.ts b/packages/opencode/src/ide/index.ts index 300aa9f5e..7ab2e7ff4 100644 --- a/packages/opencode/src/ide/index.ts +++ b/packages/opencode/src/ide/index.ts @@ -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" diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 6e224abd7..a45ec3ac1 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -1,4 +1,3 @@ -import "zod-openapi/extend" import yargs from "yargs" import { hideBin } from "yargs/helpers" import { RunCommand } from "./cli/cmd/run" diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 343d9615b..b01ce5f7b 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -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 diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index 20c11c9f1..66cc8eaa8 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -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" diff --git a/packages/opencode/src/lsp/index.ts b/packages/opencode/src/lsp/index.ts index a0133b272..0cf98c86a 100644 --- a/packages/opencode/src/lsp/index.ts +++ b/packages/opencode/src/lsp/index.ts @@ -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 @@ -35,7 +35,7 @@ export namespace LSP { range: Range, }), }) - .openapi({ + .meta({ ref: "Symbol", }) export type Symbol = z.infer @@ -48,7 +48,7 @@ export namespace LSP { range: Range, selectionRange: Range, }) - .openapi({ + .meta({ ref: "DocumentSymbol", }) export type DocumentSymbol = z.infer diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 702f644d0..dc90dfe5f 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -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" diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index dd198daca..f4b178234 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { Bus } from "../bus" import { Log } from "../util/log" import { Identifier } from "../id/id" @@ -27,12 +27,12 @@ export namespace Permission { 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 diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index b6dfc58b7..a1f79dccb 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -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 diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index e41221c2a..514203e91 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -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 @@ -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", }) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 834a5b2dc..2d30a738a 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -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" diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index a9616cfd1..094d02448 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -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 + } } diff --git a/packages/opencode/src/server/project.ts b/packages/opencode/src/server/project.ts index a89061b7f..616a2f7e7 100644 --- a/packages/opencode/src/server/project.ts +++ b/packages/opencode/src/server/project.ts @@ -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" diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index c2c08e3b0..19727e849 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -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" @@ -39,7 +38,7 @@ const ERRORS = { .object({ data: z.record(z.string(), z.any()), }) - .openapi({ + .meta({ ref: "Error", }), ), @@ -59,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({ @@ -67,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({})), @@ -115,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", @@ -129,6 +127,7 @@ export namespace Server { }, }), ) + .use(validator("query", z.object({ directory: z.string().optional() }))) .route("/project", ProjectRoute) .get( "/event", @@ -141,7 +140,7 @@ export namespace Server { content: { "text/event-stream": { schema: resolver( - Bus.payloads().openapi({ + Bus.payloads().meta({ ref: "Event", }), ), @@ -211,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) @@ -227,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" })), }, }, }, @@ -257,9 +256,9 @@ export namespace Server { description: z.string(), parameters: z.any(), }) - .openapi({ ref: "ToolListItem" }), + .meta({ ref: "ToolListItem" }), ) - .openapi({ ref: "ToolList" }), + .meta({ ref: "ToolList" }), ), }, }, @@ -267,7 +266,7 @@ export namespace Server { ...ERRORS, }, }), - zValidator( + validator( "query", z.object({ provider: z.string(), @@ -305,7 +304,7 @@ export namespace Server { worktree: z.string(), directory: z.string(), }) - .openapi({ + .meta({ ref: "Path", }), ), @@ -361,7 +360,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -389,7 +388,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -418,7 +417,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "json", z .object({ @@ -449,7 +448,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -476,13 +475,13 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), }), ), - zValidator( + validator( "json", z.object({ title: z.string().optional(), @@ -517,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(), @@ -554,7 +553,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -580,7 +579,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -609,7 +608,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -638,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(), @@ -674,10 +673,10 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ - id: z.string().openapi({ description: "Session ID" }), + id: z.string().meta({ description: "Session ID" }), }), ), async (c) => { @@ -706,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) => { @@ -740,13 +739,13 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ - id: z.string().openapi({ description: "Session ID" }), + id: z.string().meta({ description: "Session ID" }), }), ), - zValidator("json", SessionPrompt.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") @@ -775,13 +774,13 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ - id: z.string().openapi({ description: "Session ID" }), + id: z.string().meta({ description: "Session ID" }), }), ), - zValidator("json", SessionPrompt.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") @@ -805,13 +804,13 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ - id: z.string().openapi({ description: "Session ID" }), + id: z.string().meta({ description: "Session ID" }), }), ), - zValidator("json", SessionPrompt.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") @@ -835,13 +834,13 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), }), ), - zValidator("json", SessionRevert.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")) @@ -865,7 +864,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "param", z.object({ id: z.string(), @@ -892,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 @@ -974,7 +973,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "query", z.object({ pattern: z.string(), @@ -1006,7 +1005,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "query", z.object({ query: z.string(), @@ -1038,7 +1037,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "query", z.object({ query: z.string(), @@ -1066,7 +1065,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "query", z.object({ path: z.string(), @@ -1094,7 +1093,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "query", z.object({ path: z.string(), @@ -1143,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) => { @@ -1214,7 +1213,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "json", z.object({ text: z.string(), @@ -1346,7 +1345,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "json", z.object({ command: z.string(), @@ -1370,7 +1369,7 @@ export namespace Server { }, }, }), - zValidator( + validator( "json", z.object({ title: z.string().optional(), @@ -1398,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") diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index 6af398f1d..3f4d4b835 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -7,7 +7,7 @@ import { defer } from "../util/defer" import { MessageV2 } from "./message-v2" import { SystemPrompt } from "./system" import { Bus } from "../bus" -import z from "zod" +import z from "zod/v4" import type { ModelsDev } from "../provider/models" import { SessionPrompt } from "./prompt" import { Flag } from "../flag/flag" diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 92f0afb03..18bb7aef6 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1,5 +1,5 @@ import { Decimal } from "decimal.js" -import { z } from "zod" +import z from "zod/v4" import { type LanguageModelUsage, type ProviderMetadata } from "ai" import PROMPT_INITIALIZE from "../session/prompt/initialize.txt" @@ -56,7 +56,7 @@ export namespace Session { }) .optional(), }) - .openapi({ + .meta({ ref: "Session", }) export type Info = z.output @@ -66,7 +66,7 @@ export namespace Session { secret: z.string(), url: z.string(), }) - .openapi({ + .meta({ ref: "SessionShare", }) export type ShareInfo = z.output diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index c031dbe7a..2d850e0a4 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -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 @@ -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 @@ -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 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 @@ -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 @@ -115,7 +115,7 @@ export namespace MessageV2 { end: z.number().optional(), }) .optional(), - }).openapi({ + }).meta({ ref: "TextPart", }) export type TextPart = z.infer @@ -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 @@ -138,7 +138,7 @@ export namespace MessageV2 { callID: z.string(), tool: z.string(), state: ToolState, - }).openapi({ + }).meta({ ref: "ToolPart", }) export type ToolPart = z.infer @@ -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 @@ -197,14 +197,14 @@ export namespace MessageV2 { end: z.number().int(), }) .optional(), - }).openapi({ + }).meta({ ref: "AgentPart", }) export type AgentPart = z.infer export const StepStartPart = PartBase.extend({ type: z.literal("step-start"), - }).openapi({ + }).meta({ ref: "StepStartPart", }) export type StepStartPart = z.infer @@ -221,7 +221,7 @@ export namespace MessageV2 { write: z.number(), }), }), - }).openapi({ + }).meta({ ref: "StepFinishPart", }) export type StepFinishPart = z.infer @@ -236,7 +236,7 @@ export namespace MessageV2 { time: z.object({ created: z.number(), }), - }).openapi({ + }).meta({ ref: "UserMessage", }) export type User = z.infer @@ -253,7 +253,7 @@ export namespace MessageV2 { PatchPart, AgentPart, ]) - .openapi({ + .meta({ ref: "Part", }) export type Part = z.infer @@ -291,12 +291,12 @@ export namespace MessageV2 { write: z.number(), }), }), - }).openapi({ + }).meta({ ref: "AssistantMessage", }) export type Assistant = z.infer - 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 diff --git a/packages/opencode/src/session/message.ts b/packages/opencode/src/session/message.ts index e71c35c5a..f8b5115fb 100644 --- a/packages/opencode/src/session/message.ts +++ b/packages/opencode/src/session/message.ts @@ -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>(), }) - .openapi({ + .meta({ ref: "ToolCall", }) export type ToolCall = z.infer @@ -32,7 +32,7 @@ export namespace Message { toolName: z.string(), args: z.custom>(), }) - .openapi({ + .meta({ ref: "ToolPartialCall", }) export type ToolPartialCall = z.infer @@ -46,12 +46,12 @@ export namespace Message { args: z.custom>(), result: z.string(), }) - .openapi({ + .meta({ ref: "ToolResult", }) export type ToolResult = z.infer - 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 @@ -61,7 +61,7 @@ export namespace Message { type: z.literal("text"), text: z.string(), }) - .openapi({ + .meta({ ref: "TextPart", }) export type TextPart = z.infer @@ -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 @@ -82,7 +82,7 @@ export namespace Message { type: z.literal("tool-invocation"), toolInvocation: ToolInvocation, }) - .openapi({ + .meta({ ref: "ToolInvocationPart", }) export type ToolInvocationPart = z.infer @@ -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 @@ -107,7 +107,7 @@ export namespace Message { filename: z.string().optional(), url: z.string(), }) - .openapi({ + .meta({ ref: "FilePart", }) export type FilePart = z.infer @@ -116,14 +116,14 @@ export namespace Message { .object({ type: z.literal("step-start"), }) - .openapi({ + .meta({ ref: "StepStartPart", }) export type StepStartPart = z.infer export const MessagePart = z .discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart]) - .openapi({ + .meta({ ref: "MessagePart", }) export type MessagePart = z.infer @@ -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 diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f6f0ffb9b..25e0c8366 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1,7 +1,7 @@ import path from "path" import os from "os" import fs from "fs/promises" -import z, { ZodSchema } from "zod" +import z from "zod/v4" import { Identifier } from "../id/id" import { MessageV2 } from "./message-v2" import { Log } from "../util/log" @@ -19,6 +19,7 @@ import { type StreamTextResult, LoadAPIKeyError, stepCountIs, + jsonSchema, } from "ai" import { SessionCompaction } from "./compaction" import { Instance } from "../project/instance" @@ -95,7 +96,7 @@ export namespace SessionPrompt { .optional(), agent: z.string().optional(), system: z.string().optional(), - tools: z.record(z.boolean()).optional(), + tools: z.record(z.string(), z.boolean()).optional(), parts: z.array( z.discriminatedUnion("type", [ MessageV2.TextPart.omit({ @@ -105,7 +106,7 @@ export namespace SessionPrompt { .partial({ id: true, }) - .openapi({ + .meta({ ref: "TextPartInput", }), MessageV2.FilePart.omit({ @@ -115,7 +116,7 @@ export namespace SessionPrompt { .partial({ id: true, }) - .openapi({ + .meta({ ref: "FilePartInput", }), MessageV2.AgentPart.omit({ @@ -125,7 +126,7 @@ export namespace SessionPrompt { .partial({ id: true, }) - .openapi({ + .meta({ ref: "AgentPartInput", }), ]), @@ -393,10 +394,11 @@ export namespace SessionPrompt { ) for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) { if (Wildcard.all(item.id, enabledTools) === false) continue + const schema = ProviderTransform.schema(input.providerID, input.modelID, z.toJSONSchema(item.parameters)) tools[item.id] = tool({ id: item.id as any, description: item.description, - inputSchema: item.parameters as ZodSchema, + inputSchema: jsonSchema(schema as any), async execute(args, options) { await Plugin.trigger( "tool.execute.before", diff --git a/packages/opencode/src/session/revert.ts b/packages/opencode/src/session/revert.ts index 694834c49..052e582f1 100644 --- a/packages/opencode/src/session/revert.ts +++ b/packages/opencode/src/session/revert.ts @@ -1,4 +1,4 @@ -import z from "zod" +import z from "zod/v4" import { Identifier } from "../id/id" import { Snapshot } from "../snapshot" import { MessageV2 } from "./message-v2" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 4152498d3..f301c81f3 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -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" diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 8149360ff..ddf8227e9 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { exec } from "child_process" import { Tool } from "./tool" diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts index 7218575f9..b7f43126e 100644 --- a/packages/opencode/src/tool/edit.ts +++ b/packages/opencode/src/tool/edit.ts @@ -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" diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index 9534f0aff..dbbe88680 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -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" diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index a8a42a825..215659ced 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { Tool } from "./tool" import { Ripgrep } from "../file/ripgrep" diff --git a/packages/opencode/src/tool/invalid.ts b/packages/opencode/src/tool/invalid.ts index 4695f1b74..318c4b134 100644 --- a/packages/opencode/src/tool/invalid.ts +++ b/packages/opencode/src/tool/invalid.ts @@ -1,4 +1,4 @@ -import { z } from "zod" +import z from "zod/v4" import { Tool } from "./tool" export const InvalidTool = Tool.define("invalid", { diff --git a/packages/opencode/src/tool/ls.ts b/packages/opencode/src/tool/ls.ts index 5a8173ef8..819e6fdea 100644 --- a/packages/opencode/src/tool/ls.ts +++ b/packages/opencode/src/tool/ls.ts @@ -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" diff --git a/packages/opencode/src/tool/lsp-diagnostics.ts b/packages/opencode/src/tool/lsp-diagnostics.ts index b69e84851..6ea1b0593 100644 --- a/packages/opencode/src/tool/lsp-diagnostics.ts +++ b/packages/opencode/src/tool/lsp-diagnostics.ts @@ -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" diff --git a/packages/opencode/src/tool/lsp-hover.ts b/packages/opencode/src/tool/lsp-hover.ts index b33a4e804..2999d17ae 100644 --- a/packages/opencode/src/tool/lsp-hover.ts +++ b/packages/opencode/src/tool/lsp-hover.ts @@ -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" diff --git a/packages/opencode/src/tool/multiedit.ts b/packages/opencode/src/tool/multiedit.ts index 8ae81ab96..2a1b2fbbb 100644 --- a/packages/opencode/src/tool/multiedit.ts +++ b/packages/opencode/src/tool/multiedit.ts @@ -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" diff --git a/packages/opencode/src/tool/patch.ts b/packages/opencode/src/tool/patch.ts index 77fac225e..9397f114b 100644 --- a/packages/opencode/src/tool/patch.ts +++ b/packages/opencode/src/tool/patch.ts @@ -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" diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index 2aaaf7a41..2ed3accbd 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -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" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index f4ca584c8..379ca542b 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -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 = {} - 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: - ;(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 = {} - - 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 - } } diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index a16d94b82..163b5a2f7 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -1,6 +1,6 @@ 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" diff --git a/packages/opencode/src/tool/todo.ts b/packages/opencode/src/tool/todo.ts index 96404458c..9b4efddb0 100644 --- a/packages/opencode/src/tool/todo.ts +++ b/packages/opencode/src/tool/todo.ts @@ -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" diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 871a10c81..6e2b95112 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -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 { + export interface Info { id: string init: () => Promise<{ description: string parameters: Parameters execute( - args: StandardSchemaV1.InferOutput, + args: z.infer, ctx: Context, ): Promise<{ title: string @@ -29,7 +29,7 @@ export namespace Tool { }> } - export function define( + export function define( id: string, init: Info["init"] | Awaited["init"]>>, ): Info { diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index 621421fe9..4d1849417 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -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" diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index dbd6e294b..aa79c9bfb 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -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" diff --git a/packages/opencode/src/util/error.ts b/packages/opencode/src/util/error.ts index 53b434c63..f74947b18 100644 --- a/packages/opencode/src/util/error.ts +++ b/packages/opencode/src/util/error.ts @@ -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: Name, data: Data) { + static create(name: Name, data: Data) { const schema = z .object({ name: z.literal(name), data, }) - .openapi({ + .meta({ ref: name, }) const result = class extends NamedError { diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts index b4cdd920a..5844a114f 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/opencode/src/util/log.ts @@ -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 const levelPriority: Record = { diff --git a/packages/opencode/test/tool/register.test.ts b/packages/opencode/test/tool/register.test.ts index 2a72c119b..834d18984 100644 --- a/packages/opencode/test/tool/register.test.ts +++ b/packages/opencode/test/tool/register.test.ts @@ -1,4 +1,3 @@ -import "zod-openapi/extend" import { describe, expect, test } from "bun:test" import path from "path" import os from "os" diff --git a/packages/sdk/go/.release-please-manifest.json b/packages/sdk/go/.release-please-manifest.json index 76d5538a4..ed21d28cb 100644 --- a/packages/sdk/go/.release-please-manifest.json +++ b/packages/sdk/go/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.9.0" + ".": "0.13.0" } diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml index db9407052..7f4a9c119 100644 --- a/packages/sdk/go/.stats.yml +++ b/packages/sdk/go/.stats.yml @@ -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 diff --git a/packages/sdk/go/CHANGELOG.md b/packages/sdk/go/CHANGELOG.md index fad7e6837..018017066 100644 --- a/packages/sdk/go/CHANGELOG.md +++ b/packages/sdk/go/CHANGELOG.md @@ -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) diff --git a/packages/sdk/go/README.md b/packages/sdk/go/README.md index 0fe3d32bf..2c48f53ce 100644 --- a/packages/sdk/go/README.md +++ b/packages/sdk/go/README.md @@ -24,7 +24,7 @@ Or to pin the 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' ``` diff --git a/packages/sdk/go/aliases.go b/packages/sdk/go/aliases.go index 6ab36d04c..50beeae91 100644 --- a/packages/sdk/go/aliases.go +++ b/packages/sdk/go/aliases.go @@ -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 diff --git a/packages/sdk/go/command.go b/packages/sdk/go/command.go index 2638fc607..44e3beb1c 100644 --- a/packages/sdk/go/command.go +++ b/packages/sdk/go/command.go @@ -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 } diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go index b79bcb2ef..d469bdff1 100644 --- a/packages/sdk/go/config.go +++ b/packages/sdk/go/config.go @@ -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:"-"` } diff --git a/packages/sdk/go/event.go b/packages/sdk/go/event.go index 00ba202cc..5d3bffcc2 100644 --- a/packages/sdk/go/event.go +++ b/packages/sdk/go/event.go @@ -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 diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go index 0e818c5b2..871f0965d 100644 --- a/packages/sdk/go/internal/version.go +++ b/packages/sdk/go/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.9.0" // x-release-please-version +const PackageVersion = "0.13.0" // x-release-please-version diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go index 6696e0faf..260724e9a 100644 --- a/packages/sdk/go/session.go +++ b/packages/sdk/go/session.go @@ -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 diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go index f4cbc04b1..61404d8b5 100644 --- a/packages/sdk/go/session_test.go +++ b/packages/sdk/go/session_test.go @@ -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 { diff --git a/packages/sdk/go/shared/shared.go b/packages/sdk/go/shared/shared.go index 58baf3d9c..f2c6db0d1 100644 --- a/packages/sdk/go/shared/shared.go +++ b/packages/sdk/go/shared/shared.go @@ -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 ( diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index ea9cfe9a1..7e0f0dc8b 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -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( - options: Options, + public postSessionIdPermissionsPermissionId( + options: Options, ) { - return (options.client ?? this._client).post< - PostSessionByIdPermissionsByPermissionIdResponses, - unknown, - ThrowOnError - >({ + return (options.client ?? this._client).post({ url: "/session/{id}/permissions/{permissionID}", ...options, headers: { diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 864afa39f..47668750e 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -10,53 +10,6 @@ export type Project = { } } -export type Event = - | ({ - type: "installation.updated" - } & EventInstallationUpdated) - | ({ - type: "lsp.client.diagnostics" - } & EventLspClientDiagnostics) - | ({ - type: "message.updated" - } & EventMessageUpdated) - | ({ - type: "message.removed" - } & EventMessageRemoved) - | ({ - type: "message.part.updated" - } & EventMessagePartUpdated) - | ({ - type: "message.part.removed" - } & EventMessagePartRemoved) - | ({ - type: "session.compacted" - } & EventSessionCompacted) - | ({ - type: "permission.updated" - } & EventPermissionUpdated) - | ({ - type: "permission.replied" - } & EventPermissionReplied) - | ({ - type: "file.edited" - } & EventFileEdited) - | ({ - type: "session.idle" - } & EventSessionIdle) - | ({ - type: "session.updated" - } & EventSessionUpdated) - | ({ - type: "session.deleted" - } & EventSessionDeleted) - | ({ - type: "session.error" - } & EventSessionError) - | ({ - type: "server.connected" - } & EventServerConnected) - export type EventInstallationUpdated = { type: "installation.updated" properties: { @@ -72,21 +25,6 @@ export type EventLspClientDiagnostics = { } } -export type EventMessageUpdated = { - type: "message.updated" - properties: { - info: Message - } -} - -export type Message = - | ({ - role: "user" - } & UserMessage) - | ({ - role: "assistant" - } & AssistantMessage) - export type UserMessage = { id: string sessionID: string @@ -96,48 +34,6 @@ export type UserMessage = { } } -export type AssistantMessage = { - id: string - sessionID: string - role: "assistant" - time: { - created: number - completed?: number - } - error?: - | ({ - name: "ProviderAuthError" - } & ProviderAuthError) - | ({ - name: "UnknownError" - } & UnknownError) - | ({ - name: "MessageOutputLengthError" - } & MessageOutputLengthError) - | ({ - name: "MessageAbortedError" - } & MessageAbortedError) - system: Array - modelID: string - providerID: string - mode: string - path: { - cwd: string - root: string - } - summary?: boolean - cost: number - tokens: { - input: number - output: number - reasoning: number - cache: { - read: number - write: number - } - } -} - export type ProviderAuthError = { name: "ProviderAuthError" data: { @@ -163,7 +59,46 @@ export type MessageOutputLengthError = { export type MessageAbortedError = { name: "MessageAbortedError" data: { - [key: string]: unknown + message: string + } +} + +export type AssistantMessage = { + id: string + sessionID: string + role: "assistant" + time: { + created: number + completed?: number + } + error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError + system: Array + modelID: string + providerID: string + mode: string + path: { + cwd: string + root: string + } + summary?: boolean + cost: number + tokens: { + input: number + output: number + reasoning: number + cache: { + read: number + write: number + } + } +} + +export type Message = UserMessage | AssistantMessage + +export type EventMessageUpdated = { + type: "message.updated" + properties: { + info: Message } } @@ -175,42 +110,6 @@ export type EventMessageRemoved = { } } -export type EventMessagePartUpdated = { - type: "message.part.updated" - properties: { - part: Part - } -} - -export type Part = - | ({ - type: "text" - } & TextPart) - | ({ - type: "reasoning" - } & ReasoningPart) - | ({ - type: "file" - } & FilePart) - | ({ - type: "tool" - } & ToolPart) - | ({ - type: "step-start" - } & StepStartPart) - | ({ - type: "step-finish" - } & StepFinishPart) - | ({ - type: "snapshot" - } & SnapshotPart) - | ({ - type: "patch" - } & PatchPart) - | ({ - type: "agent" - } & AgentPart) - export type TextPart = { id: string sessionID: string @@ -239,44 +138,16 @@ export type ReasoningPart = { } } -export type FilePart = { - id: string - sessionID: string - messageID: string - type: "file" - mime: string - filename?: string - url: string - source?: FilePartSource -} - -export type FilePartSource = - | ({ - type: "file" - } & FileSource) - | ({ - type: "symbol" - } & SymbolSource) - -export type FileSource = { - text: FilePartSourceText - type: "file" - path: string -} - export type FilePartSourceText = { value: string start: number end: number } -export type SymbolSource = { +export type FileSource = { text: FilePartSourceText - type: "symbol" + type: "file" path: string - range: Range - name: string - kind: number } export type Range = { @@ -290,37 +161,35 @@ export type Range = { } } -export type ToolPart = { +export type SymbolSource = { + text: FilePartSourceText + type: "symbol" + path: string + range: Range + name: string + kind: number +} + +export type FilePartSource = FileSource | SymbolSource + +export type FilePart = { id: string sessionID: string messageID: string - type: "tool" - callID: string - tool: string - state: ToolState + type: "file" + mime: string + filename?: string + url: string + source?: FilePartSource } -export type ToolState = - | ({ - status: "pending" - } & ToolStatePending) - | ({ - status: "running" - } & ToolStateRunning) - | ({ - status: "completed" - } & ToolStateCompleted) - | ({ - status: "error" - } & ToolStateError) - export type ToolStatePending = { status: "pending" } export type ToolStateRunning = { status: "running" - input?: unknown + input: unknown title?: string metadata?: { [key: string]: unknown @@ -362,6 +231,18 @@ export type ToolStateError = { } } +export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError + +export type ToolPart = { + id: string + sessionID: string + messageID: string + type: "tool" + callID: string + tool: string + state: ToolState +} + export type StepStartPart = { id: string sessionID: string @@ -416,6 +297,24 @@ export type AgentPart = { } } +export type Part = + | TextPart + | ReasoningPart + | FilePart + | ToolPart + | StepStartPart + | StepFinishPart + | SnapshotPart + | PatchPart + | AgentPart + +export type EventMessagePartUpdated = { + type: "message.part.updated" + properties: { + part: Part + } +} + export type EventMessagePartRemoved = { type: "message.part.removed" properties: { @@ -432,11 +331,6 @@ export type EventSessionCompacted = { } } -export type EventPermissionUpdated = { - type: "permission.updated" - properties: Permission -} - export type Permission = { id: string type: string @@ -453,6 +347,11 @@ export type Permission = { } } +export type EventPermissionUpdated = { + type: "permission.updated" + properties: Permission +} + export type EventPermissionReplied = { type: "permission.replied" properties: { @@ -476,13 +375,6 @@ export type EventSessionIdle = { } } -export type EventSessionUpdated = { - type: "session.updated" - properties: { - info: Session - } -} - export type Session = { id: string projectID: string @@ -506,6 +398,13 @@ export type Session = { } } +export type EventSessionUpdated = { + type: "session.updated" + properties: { + info: Session + } +} + export type EventSessionDeleted = { type: "session.deleted" properties: { @@ -517,19 +416,7 @@ export type EventSessionError = { type: "session.error" properties: { sessionID?: string - error?: - | ({ - name: "ProviderAuthError" - } & ProviderAuthError) - | ({ - name: "UnknownError" - } & UnknownError) - | ({ - name: "MessageOutputLengthError" - } & MessageOutputLengthError) - | ({ - name: "MessageAbortedError" - } & MessageAbortedError) + error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError } } @@ -540,6 +427,316 @@ export type EventServerConnected = { } } +export type Event = + | EventInstallationUpdated + | EventLspClientDiagnostics + | EventMessageUpdated + | EventMessageRemoved + | EventMessagePartUpdated + | EventMessagePartRemoved + | EventSessionCompacted + | EventPermissionUpdated + | EventPermissionReplied + | EventFileEdited + | EventSessionIdle + | EventSessionUpdated + | EventSessionDeleted + | EventSessionError + | EventServerConnected + +/** + * Custom keybind configurations + */ +export type KeybindsConfig = { + /** + * Leader key for keybind combinations + */ + leader?: string + /** + * Show help dialog + */ + app_help?: string + /** + * Exit the application + */ + app_exit?: string + /** + * Open external editor + */ + editor_open?: string + /** + * List available themes + */ + theme_list?: string + /** + * Create/update AGENTS.md + */ + project_init?: string + /** + * Toggle tool details + */ + tool_details?: string + /** + * Toggle thinking blocks + */ + thinking_blocks?: string + /** + * Export session to editor + */ + session_export?: string + /** + * Create a new session + */ + session_new?: string + /** + * List all sessions + */ + session_list?: string + /** + * Show session timeline + */ + session_timeline?: string + /** + * Share current session + */ + session_share?: string + /** + * Unshare current session + */ + session_unshare?: string + /** + * Interrupt current session + */ + session_interrupt?: string + /** + * Compact the session + */ + session_compact?: string + /** + * Cycle to next child session + */ + session_child_cycle?: string + /** + * Cycle to previous child session + */ + session_child_cycle_reverse?: string + /** + * Scroll messages up by one page + */ + messages_page_up?: string + /** + * Scroll messages down by one page + */ + messages_page_down?: string + /** + * Scroll messages up by half page + */ + messages_half_page_up?: string + /** + * Scroll messages down by half page + */ + messages_half_page_down?: string + /** + * Navigate to first message + */ + messages_first?: string + /** + * Navigate to last message + */ + messages_last?: string + /** + * Copy message + */ + messages_copy?: string + /** + * Undo message + */ + messages_undo?: string + /** + * Redo message + */ + messages_redo?: string + /** + * List available models + */ + model_list?: string + /** + * Next recent model + */ + model_cycle_recent?: string + /** + * Previous recent model + */ + model_cycle_recent_reverse?: string + /** + * List agents + */ + agent_list?: string + /** + * Next agent + */ + agent_cycle?: string + /** + * Previous agent + */ + agent_cycle_reverse?: string + /** + * Clear input field + */ + input_clear?: string + /** + * Paste from clipboard + */ + input_paste?: string + /** + * Submit input + */ + input_submit?: string + /** + * Insert newline in input + */ + input_newline?: string + /** + * @deprecated use agent_cycle. Next mode + */ + switch_mode?: string + /** + * @deprecated use agent_cycle_reverse. Previous mode + */ + switch_mode_reverse?: string + /** + * @deprecated use agent_cycle. Next agent + */ + switch_agent?: string + /** + * @deprecated use agent_cycle_reverse. Previous agent + */ + switch_agent_reverse?: string + /** + * @deprecated Currently not available. List files + */ + file_list?: string + /** + * @deprecated Close file + */ + file_close?: string + /** + * @deprecated Search file + */ + file_search?: string + /** + * @deprecated Split/unified diff + */ + file_diff_toggle?: string + /** + * @deprecated Navigate to previous message + */ + messages_previous?: string + /** + * @deprecated Navigate to next message + */ + messages_next?: string + /** + * @deprecated Toggle layout + */ + messages_layout_toggle?: string + /** + * @deprecated use messages_undo. Revert message + */ + messages_revert?: string +} + +export type AgentConfig = { + model?: string + temperature?: number + top_p?: number + prompt?: string + tools?: { + [key: string]: boolean + } + disable?: boolean + /** + * Description of when to use the agent + */ + description?: string + mode?: "subagent" | "primary" | "all" + permission?: { + edit?: "ask" | "allow" | "deny" + bash?: + | ("ask" | "allow" | "deny") + | { + [key: string]: "ask" | "allow" | "deny" + } + webfetch?: "ask" | "allow" | "deny" + } + [key: string]: + | unknown + | string + | number + | { + [key: string]: boolean + } + | boolean + | ("subagent" | "primary" | "all") + | { + edit?: "ask" | "allow" | "deny" + bash?: + | ("ask" | "allow" | "deny") + | { + [key: string]: "ask" | "allow" | "deny" + } + webfetch?: "ask" | "allow" | "deny" + } + | undefined +} + +export type McpLocalConfig = { + /** + * Type of MCP server connection + */ + type: "local" + /** + * Command and arguments to run the MCP server + */ + command: Array + /** + * Environment variables to set when running the MCP server + */ + environment?: { + [key: string]: string + } + /** + * Enable or disable the MCP server on startup + */ + enabled?: boolean +} + +export type McpRemoteConfig = { + /** + * Type of MCP server connection + */ + type: "remote" + /** + * URL of the remote MCP server + */ + url: string + /** + * Enable or disable the MCP server on startup + */ + enabled?: boolean + /** + * Headers to send with the request + */ + headers?: { + [key: string]: string + } +} + +/** + * @deprecated Always uses stretch layout. + */ +export type LayoutConfig = "auto" | "stretch" + export type Config = { /** * JSON schema reference for configuration validation @@ -549,9 +746,6 @@ export type Config = { * Theme name to use for the interface */ theme?: string - /** - * Custom keybind configurations - */ keybinds?: KeybindsConfig /** * TUI specific settings @@ -560,7 +754,7 @@ export type Config = { /** * TUI scroll speed */ - scroll_speed: number + scroll_speed?: number } /** * Command configuration, see https://opencode.ai/docs/commands @@ -674,13 +868,7 @@ export type Config = { * MCP (Model Context Protocol) server configurations */ mcp?: { - [key: string]: - | ({ - type: "local" - } & McpLocalConfig) - | ({ - type: "remote" - } & McpRemoteConfig) + [key: string]: McpLocalConfig | McpRemoteConfig } formatter?: { [key: string]: { @@ -713,9 +901,6 @@ export type Config = { * Additional instruction files or patterns to include */ instructions?: Array - /** - * @deprecated Always uses stretch layout. - */ layout?: LayoutConfig permission?: { edit?: "ask" | "allow" | "deny" @@ -750,337 +935,19 @@ export type Config = { } } -export type KeybindsConfig = { - /** - * Leader key for keybind combinations - */ - leader: string - /** - * Show help dialog - */ - app_help: string - /** - * Exit the application - */ - app_exit: string - /** - * Open external editor - */ - editor_open: string - /** - * List available themes - */ - theme_list: string - /** - * Create/update AGENTS.md - */ - project_init: string - /** - * Toggle tool details - */ - tool_details: string - /** - * Toggle thinking blocks - */ - thinking_blocks: string - /** - * Export session to editor - */ - session_export: string - /** - * Create a new session - */ - session_new: string - /** - * List all sessions - */ - session_list: string - /** - * Show session timeline - */ - session_timeline: string - /** - * Share current session - */ - session_share: string - /** - * Unshare current session - */ - session_unshare: string - /** - * Interrupt current session - */ - session_interrupt: string - /** - * Compact the session - */ - session_compact: string - /** - * Cycle to next child session - */ - session_child_cycle: string - /** - * Cycle to previous child session - */ - session_child_cycle_reverse: string - /** - * Scroll messages up by one page - */ - messages_page_up: string - /** - * Scroll messages down by one page - */ - messages_page_down: string - /** - * Scroll messages up by half page - */ - messages_half_page_up: string - /** - * Scroll messages down by half page - */ - messages_half_page_down: string - /** - * Navigate to first message - */ - messages_first: string - /** - * Navigate to last message - */ - messages_last: string - /** - * Copy message - */ - messages_copy: string - /** - * Undo message - */ - messages_undo: string - /** - * Redo message - */ - messages_redo: string - /** - * List available models - */ - model_list: string - /** - * Next recent model - */ - model_cycle_recent: string - /** - * Previous recent model - */ - model_cycle_recent_reverse: string - /** - * List agents - */ - agent_list: string - /** - * Next agent - */ - agent_cycle: string - /** - * Previous agent - */ - agent_cycle_reverse: string - /** - * Clear input field - */ - input_clear: string - /** - * Paste from clipboard - */ - input_paste: string - /** - * Submit input - */ - input_submit: string - /** - * Insert newline in input - */ - input_newline: string - /** - * @deprecated use agent_cycle. Next mode - */ - switch_mode: string - /** - * @deprecated use agent_cycle_reverse. Previous mode - */ - switch_mode_reverse: string - /** - * @deprecated use agent_cycle. Next agent - */ - switch_agent: string - /** - * @deprecated use agent_cycle_reverse. Previous agent - */ - switch_agent_reverse: string - /** - * @deprecated Currently not available. List files - */ - file_list: string - /** - * @deprecated Close file - */ - file_close: string - /** - * @deprecated Search file - */ - file_search: string - /** - * @deprecated Split/unified diff - */ - file_diff_toggle: string - /** - * @deprecated Navigate to previous message - */ - messages_previous: string - /** - * @deprecated Navigate to next message - */ - messages_next: string - /** - * @deprecated Toggle layout - */ - messages_layout_toggle: string - /** - * @deprecated use messages_undo. Revert message - */ - messages_revert: string -} - -export type AgentConfig = { - model?: string - temperature?: number - top_p?: number - prompt?: string - tools?: { - [key: string]: boolean - } - disable?: boolean - /** - * Description of when to use the agent - */ - description?: string - mode?: "subagent" | "primary" | "all" - permission?: { - edit?: "ask" | "allow" | "deny" - bash?: - | ("ask" | "allow" | "deny") - | { - [key: string]: "ask" | "allow" | "deny" - } - webfetch?: "ask" | "allow" | "deny" - } - [key: string]: - | unknown - | string - | number - | { - [key: string]: boolean - } - | boolean - | ("subagent" | "primary" | "all") - | { - edit?: "ask" | "allow" | "deny" - bash?: - | ("ask" | "allow" | "deny") - | { - [key: string]: "ask" | "allow" | "deny" - } - webfetch?: "ask" | "allow" | "deny" - } - | undefined -} - -export type Provider = { - api?: string - name: string - env: Array - id: string - npm?: string - models: { - [key: string]: Model - } -} - -export type Model = { - id: string - name: string - release_date: string - attachment: boolean - reasoning: boolean - temperature: boolean - tool_call: boolean - cost: { - input: number - output: number - cache_read?: number - cache_write?: number - } - limit: { - context: number - output: number - } - experimental?: boolean - options: { - [key: string]: unknown - } - provider?: { - npm: string - } -} - -export type McpLocalConfig = { - /** - * Type of MCP server connection - */ - type: "local" - /** - * Command and arguments to run the MCP server - */ - command: Array - /** - * Environment variables to set when running the MCP server - */ - environment?: { - [key: string]: string - } - /** - * Enable or disable the MCP server on startup - */ - enabled?: boolean -} - -export type McpRemoteConfig = { - /** - * Type of MCP server connection - */ - type: "remote" - /** - * URL of the remote MCP server - */ - url: string - /** - * Enable or disable the MCP server on startup - */ - enabled?: boolean - /** - * Headers to send with the request - */ - headers?: { - [key: string]: string - } -} - -export type LayoutConfig = "auto" | "stretch" - export type _Error = { data: { [key: string]: unknown } } +export type HttpParamSpec = { + type: "string" | "number" | "boolean" | "array" + description?: string + optional?: boolean + items?: "string" | "number" | "boolean" +} + export type HttpToolRegistration = { id: string description: string @@ -1096,23 +963,16 @@ export type HttpToolRegistration = { } } -export type HttpParamSpec = { - type: "string" | "number" | "boolean" | "array" - description?: string - optional?: boolean - items?: "string" | "number" | "boolean" -} - export type ToolIds = Array -export type ToolList = Array - export type ToolListItem = { id: string description: string - parameters?: unknown + parameters: unknown } +export type ToolList = Array + export type Path = { state: string config: string @@ -1160,6 +1020,44 @@ export type Command = { subtask?: boolean } +export type Model = { + id: string + name: string + release_date: string + attachment: boolean + reasoning: boolean + temperature: boolean + tool_call: boolean + cost: { + input: number + output: number + cache_read?: number + cache_write?: number + } + limit: { + context: number + output: number + } + experimental?: boolean + options: { + [key: string]: unknown + } + provider?: { + npm: string + } +} + +export type Provider = { + api?: string + name: string + env: Array + id: string + npm?: string + models: { + [key: string]: Model + } +} + export type Symbol = { name: string kind: number @@ -1230,17 +1128,6 @@ export type Agent = { } } -export type Auth = - | ({ - type: "oauth" - } & OAuth) - | ({ - type: "api" - } & ApiAuth) - | ({ - type: "wellknown" - } & WellKnownAuth) - export type OAuth = { type: "oauth" refresh: string @@ -1259,6 +1146,8 @@ export type WellKnownAuth = { token: string } +export type Auth = OAuth | ApiAuth | WellKnownAuth + export type ProjectListData = { body?: never path?: never @@ -1713,17 +1602,7 @@ export type SessionPromptData = { tools?: { [key: string]: boolean } - parts: Array< - | ({ - type: "text" - } & TextPartInput) - | ({ - type: "file" - } & FilePartInput) - | ({ - type: "agent" - } & AgentPartInput) - > + parts: Array } path: { /** @@ -1880,7 +1759,7 @@ export type SessionUnrevertResponses = { export type SessionUnrevertResponse = SessionUnrevertResponses[keyof SessionUnrevertResponses] -export type PostSessionByIdPermissionsByPermissionIdData = { +export type PostSessionIdPermissionsPermissionIdData = { body?: { response: "once" | "always" | "reject" } @@ -1894,15 +1773,15 @@ export type PostSessionByIdPermissionsByPermissionIdData = { url: "/session/{id}/permissions/{permissionID}" } -export type PostSessionByIdPermissionsByPermissionIdResponses = { +export type PostSessionIdPermissionsPermissionIdResponses = { /** * Permission processed successfully */ 200: boolean } -export type PostSessionByIdPermissionsByPermissionIdResponse = - PostSessionByIdPermissionsByPermissionIdResponses[keyof PostSessionByIdPermissionsByPermissionIdResponses] +export type PostSessionIdPermissionsPermissionIdResponse = + PostSessionIdPermissionsPermissionIdResponses[keyof PostSessionIdPermissionsPermissionIdResponses] export type CommandListData = { body?: never diff --git a/packages/web/src/content/docs/agents.mdx b/packages/web/src/content/docs/agents.mdx index 16b194c3d..4a4459280 100644 --- a/packages/web/src/content/docs/agents.mdx +++ b/packages/web/src/content/docs/agents.mdx @@ -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 diff --git a/packages/web/src/content/docs/index.mdx b/packages/web/src/content/docs/index.mdx index 928f89c34..c8875bc1a 100644 --- a/packages/web/src/content/docs/index.mdx +++ b/packages/web/src/content/docs/index.mdx @@ -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 diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 7b109265d..ab4ac3ed0 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -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.