summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--STATS.md1
-rw-r--r--cloud/app/package.json2
-rw-r--r--cloud/app/src/routes/zen/v1/chat/completions.ts4
-rw-r--r--cloud/app/src/util/zen.ts174
-rw-r--r--cloud/core/migrations/0002_violet_loners.sql1
-rw-r--r--cloud/core/migrations/0003_dusty_clint_barton.sql1
-rw-r--r--cloud/core/migrations/meta/0002_snapshot.json576
-rw-r--r--cloud/core/migrations/meta/0003_snapshot.json581
-rw-r--r--cloud/core/migrations/meta/_journal.json14
-rw-r--r--cloud/core/package.json3
-rw-r--r--cloud/core/src/key.ts26
-rw-r--r--cloud/core/src/schema/key.sql.ts7
-rw-r--r--cloud/function/package.json2
-rw-r--r--cloud/function/sst-env.d.ts4
-rw-r--r--cloud/resource/sst-env.d.ts4
-rw-r--r--cloud/scripts/package.json2
-rw-r--r--infra/cloud.ts2
-rw-r--r--packages/function/package.json2
-rw-r--r--packages/function/sst-env.d.ts4
-rw-r--r--packages/opencode/package.json2
-rw-r--r--packages/opencode/src/cli/cmd/tui.ts10
-rw-r--r--packages/opencode/src/config/config.ts1
-rw-r--r--packages/opencode/src/id/id.ts6
-rw-r--r--packages/opencode/src/provider/provider.ts13
-rw-r--r--packages/opencode/src/session/index.ts154
-rw-r--r--packages/opencode/src/tool/edit.ts12
-rw-r--r--packages/plugin/package.json2
-rw-r--r--packages/sdk/go/.release-please-manifest.json2
-rw-r--r--packages/sdk/go/.stats.yml4
-rw-r--r--packages/sdk/go/CHANGELOG.md8
-rw-r--r--packages/sdk/go/README.md2
-rw-r--r--packages/sdk/go/app.go70
-rw-r--r--packages/sdk/go/config.go93
-rw-r--r--packages/sdk/go/event.go75
-rw-r--r--packages/sdk/go/file.go89
-rw-r--r--packages/sdk/go/internal/version.go2
-rw-r--r--packages/sdk/go/session.go8
-rw-r--r--packages/sdk/js/package.json2
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts11
-rw-r--r--packages/tui/internal/app/app.go11
-rw-r--r--packages/tui/internal/components/chat/editor.go13
-rw-r--r--packages/tui/internal/components/chat/messages.go6
-rw-r--r--packages/tui/internal/tui/tui.go38
-rw-r--r--packages/web/package.json2
-rw-r--r--sdks/vscode/package.json2
-rw-r--r--sst-env.d.ts4
46 files changed, 1828 insertions, 224 deletions
diff --git a/STATS.md b/STATS.md
index 72be57928..97d05fd30 100644
--- a/STATS.md
+++ b/STATS.md
@@ -75,3 +75,4 @@
| 2025-09-08 | 293,341 (+4,718) | 227,073 (+1,207) | 520,414 (+5,925) |
| 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) |
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/app/src/routes/zen/v1/chat/completions.ts b/cloud/app/src/routes/zen/v1/chat/completions.ts
index b68264cc4..dc69bb514 100644
--- a/cloud/app/src/routes/zen/v1/chat/completions.ts
+++ b/cloud/app/src/routes/zen/v1/chat/completions.ts
@@ -6,9 +6,7 @@ export function POST(input: APIEvent) {
return handler(input, {
modifyBody: (body: any) => ({
...body,
- stream_options: {
- include_usage: true,
- },
+ ...(body.stream ? { stream_options: { include_usage: true } } : {}),
}),
setAuthHeader: (headers: Headers, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`)
diff --git a/cloud/app/src/util/zen.ts b/cloud/app/src/util/zen.ts
index 9ccf9e567..d02a9c614 100644
--- a/cloud/app/src/util/zen.ts
+++ b/cloud/app/src/util/zen.ts
@@ -1,11 +1,35 @@
import type { APIEvent } from "@solidjs/start/server"
-import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
+import path from "node:path"
+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"
import { Identifier } from "@opencode/cloud-core/identifier.js"
import { Resource } from "@opencode/cloud-resource"
+type ModelCost = {
+ input: number
+ output: number
+ cacheRead: number
+ cacheWrite5m: number
+ cacheWrite1h: number
+}
+type Model = {
+ id: string
+ auth: boolean
+ cost: ModelCost | ((usage: any) => ModelCost)
+ headerMappings: Record<string, string>
+ providers: Record<
+ string,
+ {
+ api: string
+ apiKey: string
+ model: string
+ weight?: number
+ }
+ >
+}
+
export async function handler(
input: APIEvent,
opts: {
@@ -28,13 +52,10 @@ export async function handler(
class CreditsError extends Error {}
class ModelError extends Error {}
- const MODELS = {
+ const MODELS: Record<string, Model> = {
"claude-opus-4-1": {
id: "claude-opus-4-1" as const,
auth: true,
- api: "https://api.anthropic.com",
- apiKey: Resource.ANTHROPIC_API_KEY.value,
- model: "claude-opus-4-1-20250805",
cost: {
input: 0.000015,
output: 0.000075,
@@ -43,13 +64,17 @@ export async function handler(
cacheWrite1h: 0.00003,
},
headerMappings: {},
+ providers: {
+ anthropic: {
+ api: "https://api.anthropic.com",
+ apiKey: Resource.ANTHROPIC_API_KEY.value,
+ model: "claude-opus-4-1-20250805",
+ },
+ },
},
"claude-sonnet-4": {
id: "claude-sonnet-4" as const,
auth: true,
- api: "https://api.anthropic.com",
- apiKey: Resource.ANTHROPIC_API_KEY.value,
- model: "claude-sonnet-4-20250514",
cost: (usage: any) => {
const totalInputTokens =
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
@@ -70,13 +95,17 @@ export async function handler(
}
},
headerMappings: {},
+ providers: {
+ anthropic: {
+ api: "https://api.anthropic.com",
+ apiKey: Resource.ANTHROPIC_API_KEY.value,
+ model: "claude-sonnet-4-20250514",
+ },
+ },
},
"claude-3-5-haiku": {
id: "claude-3-5-haiku" as const,
auth: true,
- api: "https://api.anthropic.com",
- apiKey: Resource.ANTHROPIC_API_KEY.value,
- model: "claude-3-5-haiku-20241022",
cost: {
input: 0.0000008,
output: 0.000004,
@@ -85,13 +114,17 @@ export async function handler(
cacheWrite1h: 0.0000016,
},
headerMappings: {},
+ providers: {
+ anthropic: {
+ api: "https://api.anthropic.com",
+ apiKey: Resource.ANTHROPIC_API_KEY.value,
+ model: "claude-3-5-haiku-20241022",
+ },
+ },
},
"gpt-5": {
id: "gpt-5" as const,
auth: true,
- api: "https://api.openai.com",
- apiKey: Resource.OPENAI_API_KEY.value,
- model: "gpt-5",
cost: {
input: 0.00000125,
output: 0.00001,
@@ -100,28 +133,43 @@ export async function handler(
cacheWrite1h: 0,
},
headerMappings: {},
+ providers: {
+ openai: {
+ api: "https://api.openai.com",
+ apiKey: Resource.OPENAI_API_KEY.value,
+ model: "gpt-5",
+ },
+ },
},
"qwen3-coder": {
id: "qwen3-coder" as const,
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,
+ input: 0.00000045,
+ output: 0.0000018,
cacheRead: 0,
cacheWrite5m: 0,
cacheWrite1h: 0,
},
headerMappings: {},
+ providers: {
+ baseten: {
+ api: "https://inference.baseten.co",
+ apiKey: Resource.BASETEN_API_KEY.value,
+ model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
+ weight: 4,
+ },
+ fireworks: {
+ api: "https://api.fireworks.ai/inference",
+ apiKey: Resource.FIREWORKS_API_KEY.value,
+ model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
+ weight: 1,
+ },
+ },
},
"kimi-k2": {
id: "kimi-k2" as const,
auth: true,
- api: "https://inference.baseten.co",
- apiKey: Resource.BASETEN_API_KEY.value,
- model: "moonshotai/Kimi-K2-Instruct-0905",
cost: {
input: 0.0000006,
output: 0.0000025,
@@ -130,13 +178,24 @@ export async function handler(
cacheWrite1h: 0,
},
headerMappings: {},
+ providers: {
+ baseten: {
+ api: "https://inference.baseten.co",
+ apiKey: Resource.BASETEN_API_KEY.value,
+ model: "moonshotai/Kimi-K2-Instruct-0905",
+ weight: 4,
+ },
+ fireworks: {
+ api: "https://api.fireworks.ai/inference",
+ apiKey: Resource.FIREWORKS_API_KEY.value,
+ model: "accounts/fireworks/models/kimi-k2-instruct-0905",
+ weight: 1,
+ },
+ },
},
"grok-code": {
id: "grok-code" as const,
auth: false,
- api: "https://api.x.ai",
- apiKey: Resource.XAI_API_KEY.value,
- model: "grok-code",
cost: {
input: 0,
output: 0,
@@ -148,14 +207,18 @@ export async function handler(
"x-grok-conv-id": "x-opencode-session",
"x-grok-req-id": "x-opencode-request",
},
+ providers: {
+ xai: {
+ api: "https://api.x.ai",
+ apiKey: Resource.XAI_API_KEY.value,
+ model: "grok-code",
+ },
+ },
},
// deprecated
"qwen/qwen3-coder": {
id: "qwen/qwen3-coder" as const,
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,
@@ -164,6 +227,20 @@ export async function handler(
cacheWrite1h: 0,
},
headerMappings: {},
+ providers: {
+ baseten: {
+ api: "https://inference.baseten.co",
+ apiKey: Resource.BASETEN_API_KEY.value,
+ model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
+ weight: 5,
+ },
+ fireworks: {
+ api: "https://api.fireworks.ai/inference",
+ apiKey: Resource.FIREWORKS_API_KEY.value,
+ model: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
+ weight: 1,
+ },
+ },
},
}
@@ -195,15 +272,19 @@ export async function handler(
const apiKey = await authenticate()
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
await checkCredits()
+ const providerName = selectProvider()
+ const providerData = MODEL.providers[providerName]
+ logger.metric({ provider: providerName })
// Request to model provider
- const res = await fetch(new URL(url.pathname.replace(/^\/zen/, "") + url.search, MODEL.api), {
+ const startTimestamp = Date.now()
+ const res = await fetch(path.posix.join(providerData.api, url.pathname.replace(/^\/zen/, "") + url.search), {
method: "POST",
headers: (() => {
const headers = input.request.headers
headers.delete("host")
headers.delete("content-length")
- opts.setAuthHeader(headers, MODEL.apiKey)
+ opts.setAuthHeader(headers, providerData.apiKey)
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
headers.set(k, headers.get(v)!)
})
@@ -211,7 +292,7 @@ export async function handler(
})(),
body: JSON.stringify({
...(opts.modifyBody?.(body) ?? body),
- model: MODEL.model,
+ model: providerData.model,
}),
})
@@ -245,7 +326,6 @@ export async function handler(
const decoder = new TextDecoder()
let buffer = ""
let responseLength = 0
- let startTimestamp = Date.now()
function pump(): Promise<void> {
return (
@@ -262,7 +342,6 @@ export async function handler(
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
}
responseLength += value.length
-
buffer += decoder.decode(value, { stream: true })
const parts = buffer.split("\n\n")
@@ -311,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]),
)
@@ -344,6 +423,13 @@ export async function handler(
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
}
+ function selectProvider() {
+ const picks = Object.entries(MODEL.providers).flatMap(([name, provider]) =>
+ Array<string>(provider.weight ?? 1).fill(name),
+ )
+ return picks[Math.floor(Math.random() * picks.length)]
+ }
+
async function trackUsage(usage: any) {
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
opts.normalizeUsage(usage)
@@ -416,9 +502,25 @@ export async function handler(
"error.message": error.message,
})
+ // Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
- return new Response(JSON.stringify({ error: { message: error.message } }), { status: 401 })
+ return new Response(
+ JSON.stringify({
+ type: "error",
+ error: { type: error.constructor.name, message: error.message },
+ }),
+ { status: 401 },
+ )
- return new Response(JSON.stringify({ error: { message: error.message } }), { status: 500 })
+ return new Response(
+ JSON.stringify({
+ type: "error",
+ error: {
+ type: "error",
+ message: error.message,
+ },
+ }),
+ { status: 500 },
+ )
}
}
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..e832ebb00
--- /dev/null
+++ b/cloud/core/migrations/meta/0003_snapshot.json
@@ -0,0 +1,581 @@
+{
+ "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": {}
+ }
+}
diff --git a/cloud/core/migrations/meta/_journal.json b/cloud/core/migrations/meta/_journal.json
index 131e85613..c5ea50210 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
}
]
}
diff --git a/cloud/core/package.json b/cloud/core/package.json
index d949a66dc..48af18043 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": {
@@ -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<Actor.Info>(),
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),
+ ],
)
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/function/sst-env.d.ts b/cloud/function/sst-env.d.ts
index 11bc7adea..afa8c6fe7 100644
--- a/cloud/function/sst-env.d.ts
+++ b/cloud/function/sst-env.d.ts
@@ -30,6 +30,10 @@ declare module "sst" {
type: "sst.sst.Linkable"
username: string
}
+ FIREWORKS_API_KEY: {
+ type: "sst.sst.Secret"
+ value: string
+ }
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
diff --git a/cloud/resource/sst-env.d.ts b/cloud/resource/sst-env.d.ts
index 11bc7adea..afa8c6fe7 100644
--- a/cloud/resource/sst-env.d.ts
+++ b/cloud/resource/sst-env.d.ts
@@ -30,6 +30,10 @@ declare module "sst" {
type: "sst.sst.Linkable"
username: string
}
+ FIREWORKS_API_KEY: {
+ type: "sst.sst.Secret"
+ value: string
+ }
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
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/infra/cloud.ts b/infra/cloud.ts
index d1d8d2933..9c58150c3 100644
--- a/infra/cloud.ts
+++ b/infra/cloud.ts
@@ -103,6 +103,7 @@ const ANTHROPIC_API_KEY = new sst.Secret("ANTHROPIC_API_KEY")
const OPENAI_API_KEY = new sst.Secret("OPENAI_API_KEY")
const XAI_API_KEY = new sst.Secret("XAI_API_KEY")
const BASETEN_API_KEY = new sst.Secret("BASETEN_API_KEY")
+const FIREWORKS_API_KEY = new sst.Secret("FIREWORKS_API_KEY")
const STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
properties: { value: auth.url.apply((url) => url!) },
@@ -136,6 +137,7 @@ new sst.cloudflare.x.SolidStart("Console", {
OPENAI_API_KEY,
XAI_API_KEY,
BASETEN_API_KEY,
+ FIREWORKS_API_KEY,
],
environment: {
//VITE_DOCS_URL: web.url.apply((url) => url!),
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/function/sst-env.d.ts b/packages/function/sst-env.d.ts
index 11bc7adea..afa8c6fe7 100644
--- a/packages/function/sst-env.d.ts
+++ b/packages/function/sst-env.d.ts
@@ -30,6 +30,10 @@ declare module "sst" {
type: "sst.sst.Linkable"
username: string
}
+ FIREWORKS_API_KEY: {
+ type: "sst.sst.Secret"
+ value: string
+ }
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
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/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts
index 2011c26cb..86b9f31b8 100644
--- a/packages/opencode/src/cli/cmd/tui.ts
+++ b/packages/opencode/src/cli/cmd/tui.ts
@@ -16,6 +16,7 @@ import { Ide } from "../../ide"
import { Flag } from "../../flag/flag"
import { Session } from "../../session"
import { Instance } from "../../project/instance"
+import { $ } from "bun"
declare global {
const OPENCODE_TUI_PATH: string
@@ -111,8 +112,7 @@ export const TuiCommand = cmd({
hostname: args.hostname,
})
- let cmd = ["go", "run", "./main.go"]
- let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
+ let cmd = [] as string[]
const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
if (tui) {
let binaryName = tui.name
@@ -125,9 +125,13 @@ export const TuiCommand = cmd({
await Bun.write(file, tui, { mode: 0o755 })
await fs.chmod(binary, 0o755)
}
- cwd = process.cwd()
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")]
+ }
Log.Default.info("tui", {
cmd,
})
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/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts
index 4e3ba9d48..f2b961e45 100644
--- a/packages/opencode/src/id/id.ts
+++ b/packages/opencode/src/id/id.ts
@@ -30,7 +30,7 @@ export namespace Identifier {
function generateID(prefix: keyof typeof prefixes, descending: boolean, given?: string): string {
if (!given) {
- return generateNewID(prefix, descending)
+ return create(prefix, descending)
}
if (!given.startsWith(prefixes[prefix])) {
@@ -49,8 +49,8 @@ export namespace Identifier {
return result
}
- function generateNewID(prefix: keyof typeof prefixes, descending: boolean): string {
- const currentTimestamp = Date.now()
+ export function create(prefix: keyof typeof prefixes, descending: boolean, timestamp?: number): string {
+ const currentTimestamp = timestamp ?? Date.now()
if (currentTimestamp !== lastTimestamp) {
lastTimestamp = currentTimestamp
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<string, SDK>()
+ const sdk = new Map<number, SDK>()
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 })
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 6b93a1275..21eaa3be8 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -86,6 +86,7 @@ export namespace Session {
time: z.object({
created: z.number(),
updated: z.number(),
+ compacting: z.number().optional(),
}),
revert: z
.object({
@@ -137,12 +138,17 @@ 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<string, AbortController>()
- const autoCompacting = new Map<string, boolean>()
const queued = new Map<
string,
{
@@ -156,7 +162,6 @@ export namespace Session {
return {
pending,
- autoCompacting,
queued,
}
},
@@ -714,24 +719,8 @@ export namespace Session {
})().then((x) => Provider.getModel(x.providerID, x.modelID))
let msgs = await messages(input.sessionID)
- const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
- // auto summarize if too long
- if (previous && previous.tokens) {
- const tokens =
- previous.tokens.input + previous.tokens.cache.read + previous.tokens.cache.write + previous.tokens.output
- if (model.info.limit.context && tokens > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) {
- state().autoCompacting.set(input.sessionID, true)
-
- await summarize({
- sessionID: input.sessionID,
- providerID: model.providerID,
- modelID: model.info.id,
- })
- return prompt(input)
- }
- }
using abort = lock(input.sessionID)
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
@@ -999,7 +988,38 @@ export namespace Session {
error: e,
})
},
- async prepareStep({ messages }) {
+ 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),
+ }
+ }
+ }
+
+ // Add queued messages to the stream
const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed)
if (queue.length) {
for (const item of queue) {
@@ -1756,10 +1776,22 @@ export namespace Session {
}
export async function summarize(input: { sessionID: string; providerID: string; modelID: string }) {
- using abort = lock(input.sessionID)
+ await update(input.sessionID, (draft) => {
+ draft.time.compacting = Date.now()
+ })
+ await using _ = defer(async () => {
+ await update(input.sessionID, (draft) => {
+ draft.time.compacting = undefined
+ })
+ })
const msgs = await messages(input.sessionID)
- const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
- const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
+ const start = Math.max(
+ 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)
const model = await Provider.getModel(input.providerID, input.modelID)
const system = [
...SystemPrompt.summarize(model.providerID),
@@ -1767,36 +1799,8 @@ export namespace Session {
...(await SystemPrompt.custom()),
]
- const next: MessageV2.Info = {
- id: Identifier.ascending("message"),
- role: "assistant",
- sessionID: input.sessionID,
- system,
- mode: "build",
- path: {
- cwd: Instance.directory,
- root: Instance.worktree,
- },
- summary: true,
- cost: 0,
- modelID: input.modelID,
- providerID: model.providerID,
- tokens: {
- input: 0,
- output: 0,
- reasoning: 0,
- cache: { read: 0, write: 0 },
- },
- time: {
- created: Date.now(),
- },
- }
- await updateMessage(next)
-
- const processor = createProcessor(next, model.info)
- const stream = streamText({
+ const generated = await generateText({
maxRetries: 10,
- abortSignal: abort.signal,
model: model.language,
messages: [
...system.map(
@@ -1805,7 +1809,7 @@ export namespace Session {
content: x,
}),
),
- ...MessageV2.toModelMessage(filtered),
+ ...MessageV2.toModelMessage(toSummarize),
{
role: "user",
content: [
@@ -1817,9 +1821,45 @@ 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(),
+ },
+ }
+ await updateMessage(msg)
+ await updatePart({
+ type: "text",
+ sessionID: input.sessionID,
+ messageID: msg.id,
+ id: Identifier.ascending("part"),
+ text: generated.text,
+ time: {
+ start: Date.now(),
+ end: Date.now(),
+ },
+ })
- const result = await processor.process(stream)
- return result
+ Bus.publish(Event.Compacted, {
+ sessionID: input.sessionID,
+ })
+
+ return msg
}
function isLocked(sessionID: string) {
@@ -1837,12 +1877,6 @@ export namespace Session {
log.info("unlocking", { sessionID })
state().pending.delete(sessionID)
- const isAutoCompacting = state().autoCompacting.get(sessionID) ?? false
- if (isAutoCompacting) {
- state().autoCompacting.delete(sessionID)
- return
- }
-
const session = await get(sessionID)
if (session.parentID) return
diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts
index 64a0ea167..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<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
+ output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues
+ .filter((item) => item.severity === 1)
+ .map(LSP.Diagnostic.pretty)
+ .join("\n")}\n</file_diagnostics>\n`
continue
}
- output += `\n<project_diagnostics>\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</project_diagnostics>\n`
}
return {
@@ -599,7 +597,7 @@ export function replace(content: string, oldString: string, newString: string, r
for (const replacer of [
SimpleReplacer,
LineTrimmedReplacer,
- BlockAnchorReplacer,
+ // BlockAnchorReplacer,
WhitespaceNormalizedReplacer,
IndentationFlexibleReplacer,
EscapeNormalizedReplacer,
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/go/.release-please-manifest.json b/packages/sdk/go/.release-please-manifest.json
index 64f3cdd64..76d5538a4 100644
--- a/packages/sdk/go/.release-please-manifest.json
+++ b/packages/sdk/go/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.8.0"
+ ".": "0.9.0"
}
diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml
index 79ec5d889..db9407052 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-97b61518d8666ea7cb310af04248e00bcf8dc9753ba3c7e84471df72b3232004.yml
-openapi_spec_hash: a3500531973ad999c350b87c21aa3ab8
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-46826ba8640557721614b0c9a3f1860681d825ca8d8b12869652fa25aacb0b4c.yml
+openapi_spec_hash: 33b8db6fde3021579b21325ce910197d
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
diff --git a/packages/sdk/go/CHANGELOG.md b/packages/sdk/go/CHANGELOG.md
index c0ea4a073..fad7e6837 100644
--- a/packages/sdk/go/CHANGELOG.md
+++ b/packages/sdk/go/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 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)
+
+### Features
+
+- **api:** api update ([2d3a28d](https://github.com/sst/opencode-sdk-go/commit/2d3a28df5657845aa4d73087e1737d1fc8c3ce1c))
+
## 0.8.0 (2025-09-01)
Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare/v0.7.0...v0.8.0)
diff --git a/packages/sdk/go/README.md b/packages/sdk/go/README.md
index 780111828..0fe3d32bf 100644
--- a/packages/sdk/go/README.md
+++ b/packages/sdk/go/README.md
@@ -24,7 +24,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->
```sh
-go get -u 'github.com/sst/[email protected]'
+go get -u 'github.com/sst/[email protected]'
```
<!-- x-release-please-end -->
diff --git a/packages/sdk/go/app.go b/packages/sdk/go/app.go
index 53a8aeb4a..62d86f93f 100644
--- a/packages/sdk/go/app.go
+++ b/packages/sdk/go/app.go
@@ -50,33 +50,37 @@ func (r *AppService) Providers(ctx context.Context, query AppProvidersParams, op
}
type Model struct {
- ID string `json:"id,required"`
- Attachment bool `json:"attachment,required"`
- Cost ModelCost `json:"cost,required"`
- Limit ModelLimit `json:"limit,required"`
- Name string `json:"name,required"`
- Options map[string]interface{} `json:"options,required"`
- Reasoning bool `json:"reasoning,required"`
- ReleaseDate string `json:"release_date,required"`
- Temperature bool `json:"temperature,required"`
- ToolCall bool `json:"tool_call,required"`
- JSON modelJSON `json:"-"`
+ ID string `json:"id,required"`
+ Attachment bool `json:"attachment,required"`
+ Cost ModelCost `json:"cost,required"`
+ Limit ModelLimit `json:"limit,required"`
+ Name string `json:"name,required"`
+ Options map[string]interface{} `json:"options,required"`
+ Reasoning bool `json:"reasoning,required"`
+ ReleaseDate string `json:"release_date,required"`
+ Temperature bool `json:"temperature,required"`
+ ToolCall bool `json:"tool_call,required"`
+ Experimental bool `json:"experimental"`
+ Provider ModelProvider `json:"provider"`
+ JSON modelJSON `json:"-"`
}
// modelJSON contains the JSON metadata for the struct [Model]
type modelJSON struct {
- ID apijson.Field
- Attachment apijson.Field
- Cost apijson.Field
- Limit apijson.Field
- Name apijson.Field
- Options apijson.Field
- Reasoning apijson.Field
- ReleaseDate apijson.Field
- Temperature apijson.Field
- ToolCall apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
+ ID apijson.Field
+ Attachment apijson.Field
+ Cost apijson.Field
+ Limit apijson.Field
+ Name apijson.Field
+ Options apijson.Field
+ Reasoning apijson.Field
+ ReleaseDate apijson.Field
+ Temperature apijson.Field
+ ToolCall apijson.Field
+ Experimental apijson.Field
+ Provider apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
}
func (r *Model) UnmarshalJSON(data []byte) (err error) {
@@ -135,6 +139,26 @@ func (r modelLimitJSON) RawJSON() string {
return r.raw
}
+type ModelProvider struct {
+ Npm string `json:"npm,required"`
+ JSON modelProviderJSON `json:"-"`
+}
+
+// modelProviderJSON contains the JSON metadata for the struct [ModelProvider]
+type modelProviderJSON struct {
+ Npm apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ModelProvider) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r modelProviderJSON) RawJSON() string {
+ return r.raw
+}
+
type Provider struct {
ID string `json:"id,required"`
Env []string `json:"env,required"`
diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go
index 5469fb29e..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) {
@@ -1562,34 +1564,38 @@ func (r configProviderJSON) RawJSON() string {
}
type ConfigProviderModel struct {
- ID string `json:"id"`
- Attachment bool `json:"attachment"`
- Cost ConfigProviderModelsCost `json:"cost"`
- Limit ConfigProviderModelsLimit `json:"limit"`
- Name string `json:"name"`
- Options map[string]interface{} `json:"options"`
- Reasoning bool `json:"reasoning"`
- ReleaseDate string `json:"release_date"`
- Temperature bool `json:"temperature"`
- ToolCall bool `json:"tool_call"`
- JSON configProviderModelJSON `json:"-"`
+ ID string `json:"id"`
+ Attachment bool `json:"attachment"`
+ Cost ConfigProviderModelsCost `json:"cost"`
+ Experimental bool `json:"experimental"`
+ Limit ConfigProviderModelsLimit `json:"limit"`
+ Name string `json:"name"`
+ Options map[string]interface{} `json:"options"`
+ Provider ConfigProviderModelsProvider `json:"provider"`
+ Reasoning bool `json:"reasoning"`
+ ReleaseDate string `json:"release_date"`
+ Temperature bool `json:"temperature"`
+ ToolCall bool `json:"tool_call"`
+ JSON configProviderModelJSON `json:"-"`
}
// configProviderModelJSON contains the JSON metadata for the struct
// [ConfigProviderModel]
type configProviderModelJSON struct {
- ID apijson.Field
- Attachment apijson.Field
- Cost apijson.Field
- Limit apijson.Field
- Name apijson.Field
- Options apijson.Field
- Reasoning apijson.Field
- ReleaseDate apijson.Field
- Temperature apijson.Field
- ToolCall apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
+ ID apijson.Field
+ Attachment apijson.Field
+ Cost apijson.Field
+ Experimental apijson.Field
+ Limit apijson.Field
+ Name apijson.Field
+ Options apijson.Field
+ Provider apijson.Field
+ Reasoning apijson.Field
+ ReleaseDate apijson.Field
+ Temperature apijson.Field
+ ToolCall apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
}
func (r *ConfigProviderModel) UnmarshalJSON(data []byte) (err error) {
@@ -1650,6 +1656,27 @@ func (r configProviderModelsLimitJSON) RawJSON() string {
return r.raw
}
+type ConfigProviderModelsProvider struct {
+ Npm string `json:"npm,required"`
+ JSON configProviderModelsProviderJSON `json:"-"`
+}
+
+// configProviderModelsProviderJSON contains the JSON metadata for the struct
+// [ConfigProviderModelsProvider]
+type configProviderModelsProviderJSON struct {
+ Npm apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *ConfigProviderModelsProvider) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r configProviderModelsProviderJSON) RawJSON() string {
+ return r.raw
+}
+
type ConfigProviderOptions struct {
APIKey string `json:"apiKey"`
BaseURL string `json:"baseURL"`
@@ -1726,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/sdk/go/event.go b/packages/sdk/go/event.go
index 2f44f907e..00ba202cc 100644
--- a/packages/sdk/go/event.go
+++ b/packages/sdk/go/event.go
@@ -63,7 +63,8 @@ type EventListResponse struct {
// [EventListResponseEventSessionUpdatedProperties],
// [EventListResponseEventSessionDeletedProperties],
// [EventListResponseEventSessionIdleProperties],
- // [EventListResponseEventSessionErrorProperties], [interface{}].
+ // [EventListResponseEventSessionErrorProperties],
+ // [EventListResponseEventSessionCompactedProperties], [interface{}].
Properties interface{} `json:"properties,required"`
Type EventListResponseType `json:"type,required"`
JSON eventListResponseJSON `json:"-"`
@@ -105,6 +106,7 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventSessionCompacted],
// [EventListResponseEventServerConnected].
func (r EventListResponse) AsUnion() EventListResponseUnion {
return r.union
@@ -118,7 +120,8 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
// [EventListResponseEventPermissionUpdated],
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
-// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or
+// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
+// [EventListResponseEventSessionCompacted] or
// [EventListResponseEventServerConnected].
type EventListResponseUnion interface {
implementsEventListResponse()
@@ -195,6 +198,11 @@ func init() {
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(EventListResponseEventSessionCompacted{}),
+ DiscriminatorValue: "session.compacted",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
DiscriminatorValue: "server.connected",
},
@@ -1108,6 +1116,66 @@ 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"`
@@ -1163,12 +1231,13 @@ const (
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, EventListResponseTypeServerConnected:
+ case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeSessionCompacted, EventListResponseTypeServerConnected:
return true
}
return false
diff --git a/packages/sdk/go/file.go b/packages/sdk/go/file.go
index 3e1b2f42f..bc36075f1 100644
--- a/packages/sdk/go/file.go
+++ b/packages/sdk/go/file.go
@@ -100,15 +100,17 @@ func (r FileStatus) IsKnown() bool {
}
type FileNode struct {
- Ignored bool `json:"ignored,required"`
- Name string `json:"name,required"`
- Path string `json:"path,required"`
- Type FileNodeType `json:"type,required"`
- JSON fileNodeJSON `json:"-"`
+ Absolute string `json:"absolute,required"`
+ Ignored bool `json:"ignored,required"`
+ Name string `json:"name,required"`
+ Path string `json:"path,required"`
+ Type FileNodeType `json:"type,required"`
+ JSON fileNodeJSON `json:"-"`
}
// fileNodeJSON contains the JSON metadata for the struct [FileNode]
type fileNodeJSON struct {
+ Absolute apijson.Field
Ignored apijson.Field
Name apijson.Field
Path apijson.Field
@@ -141,16 +143,18 @@ func (r FileNodeType) IsKnown() bool {
}
type FileReadResponse struct {
- Content string `json:"content,required"`
- Type FileReadResponseType `json:"type,required"`
- JSON fileReadResponseJSON `json:"-"`
+ Content string `json:"content,required"`
+ Diff string `json:"diff"`
+ Patch FileReadResponsePatch `json:"patch"`
+ JSON fileReadResponseJSON `json:"-"`
}
// fileReadResponseJSON contains the JSON metadata for the struct
// [FileReadResponse]
type fileReadResponseJSON struct {
Content apijson.Field
- Type apijson.Field
+ Diff apijson.Field
+ Patch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -163,19 +167,64 @@ func (r fileReadResponseJSON) RawJSON() string {
return r.raw
}
-type FileReadResponseType string
+type FileReadResponsePatch struct {
+ Hunks []FileReadResponsePatchHunk `json:"hunks,required"`
+ NewFileName string `json:"newFileName,required"`
+ OldFileName string `json:"oldFileName,required"`
+ Index string `json:"index"`
+ NewHeader string `json:"newHeader"`
+ OldHeader string `json:"oldHeader"`
+ JSON fileReadResponsePatchJSON `json:"-"`
+}
-const (
- FileReadResponseTypeRaw FileReadResponseType = "raw"
- FileReadResponseTypePatch FileReadResponseType = "patch"
-)
+// fileReadResponsePatchJSON contains the JSON metadata for the struct
+// [FileReadResponsePatch]
+type fileReadResponsePatchJSON struct {
+ Hunks apijson.Field
+ NewFileName apijson.Field
+ OldFileName apijson.Field
+ Index apijson.Field
+ NewHeader apijson.Field
+ OldHeader apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
-func (r FileReadResponseType) IsKnown() bool {
- switch r {
- case FileReadResponseTypeRaw, FileReadResponseTypePatch:
- return true
- }
- return false
+func (r *FileReadResponsePatch) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r fileReadResponsePatchJSON) RawJSON() string {
+ return r.raw
+}
+
+type FileReadResponsePatchHunk struct {
+ Lines []string `json:"lines,required"`
+ NewLines float64 `json:"newLines,required"`
+ NewStart float64 `json:"newStart,required"`
+ OldLines float64 `json:"oldLines,required"`
+ OldStart float64 `json:"oldStart,required"`
+ JSON fileReadResponsePatchHunkJSON `json:"-"`
+}
+
+// fileReadResponsePatchHunkJSON contains the JSON metadata for the struct
+// [FileReadResponsePatchHunk]
+type fileReadResponsePatchHunkJSON struct {
+ Lines apijson.Field
+ NewLines apijson.Field
+ NewStart apijson.Field
+ OldLines apijson.Field
+ OldStart apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *FileReadResponsePatchHunk) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r fileReadResponsePatchHunkJSON) RawJSON() string {
+ return r.raw
}
type FileListParams struct {
diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go
index 3c8392e9d..0e818c5b2 100644
--- a/packages/sdk/go/internal/version.go
+++ b/packages/sdk/go/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.8.0" // x-release-please-version
+const PackageVersion = "0.9.0" // x-release-please-version
diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go
index 88d71b575..6696e0faf 100644
--- a/packages/sdk/go/session.go
+++ b/packages/sdk/go/session.go
@@ -1332,15 +1332,17 @@ func (r sessionJSON) RawJSON() string {
}
type SessionTime struct {
- Created float64 `json:"created,required"`
- Updated float64 `json:"updated,required"`
- JSON sessionTimeJSON `json:"-"`
+ Created float64 `json:"created,required"`
+ Updated float64 `json:"updated,required"`
+ Compacting float64 `json:"compacting"`
+ JSON sessionTimeJSON `json:"-"`
}
// sessionTimeJSON contains the JSON metadata for the struct [SessionTime]
type sessionTimeJSON struct {
Created apijson.Field
Updated apijson.Field
+ Compacting apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
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
@@ -51,6 +51,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/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 4b333a93c..4a891f282 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -7,6 +7,7 @@ import (
"path/filepath"
"slices"
"strings"
+ "time"
"log/slog"
@@ -656,6 +657,9 @@ func (a *App) IsBusy() bool {
if len(a.Messages) == 0 {
return false
}
+ if a.IsCompacting() {
+ return true
+ }
lastMessage := a.Messages[len(a.Messages)-1]
if casted, ok := lastMessage.Info.(opencode.AssistantMessage); ok {
return casted.Time.Completed == 0
@@ -663,6 +667,13 @@ func (a *App) IsBusy() bool {
return false
}
+func (a *App) IsCompacting() bool {
+ if time.Since(time.UnixMilli(int64(a.Session.Time.Compacting))) < time.Second*30 {
+ return true
+ }
+ return false
+}
+
func (a *App) HasAnimatingWork() bool {
for _, msg := range a.Messages {
switch casted := msg.Info.(type) {
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 907734a12..2841e2cc8 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -385,6 +385,9 @@ func (m *editorComponent) Content() string {
} else if m.app.IsBusy() {
keyText := m.getInterruptKeyText()
status := "working"
+ if m.app.IsCompacting() {
+ status = "compacting"
+ }
if m.app.CurrentPermission.ID != "" {
status = "waiting for permission"
}
@@ -504,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}),
@@ -662,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)
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 053d538a4..8e36d99c1 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -365,6 +365,9 @@ func (m *messagesComponent) renderView() tea.Cmd {
lastAssistantMessage := "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
for _, msg := range slices.Backward(m.app.Messages) {
if assistant, ok := msg.Info.(opencode.AssistantMessage); ok {
+ if assistant.Time.Completed > 0 {
+ break
+ }
lastAssistantMessage = assistant.ID
break
}
@@ -475,6 +478,9 @@ func (m *messagesComponent) renderView() tea.Cmd {
}
case opencode.AssistantMessage:
+ if casted.Summary {
+ continue
+ }
if casted.ID == m.app.Session.Revert.MessageID {
reverted = true
revertedMessageCount = 1
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 62a647a14..69d023315 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -592,10 +592,40 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
if matchIndex == -1 {
- a.app.Messages = append(a.app.Messages, app.Message{
+ // Extract the new message ID
+ var newMessageID string
+ switch casted := msg.Properties.Info.AsUnion().(type) {
+ case opencode.UserMessage:
+ newMessageID = casted.ID
+ case opencode.AssistantMessage:
+ newMessageID = casted.ID
+ }
+
+ // Find the correct insertion index by scanning backwards
+ // Most messages are added to the end, so start from the end
+ insertIndex := len(a.app.Messages)
+ for i := len(a.app.Messages) - 1; i >= 0; i-- {
+ var existingID string
+ switch casted := a.app.Messages[i].Info.(type) {
+ case opencode.UserMessage:
+ existingID = casted.ID
+ case opencode.AssistantMessage:
+ existingID = casted.ID
+ }
+ if existingID < newMessageID {
+ insertIndex = i + 1
+ break
+ }
+ }
+
+ // Create the new message
+ newMessage := app.Message{
Info: msg.Properties.Info.AsUnion(),
Parts: []opencode.PartUnion{},
- })
+ }
+
+ // Insert at the correct position
+ a.app.Messages = append(a.app.Messages[:insertIndex], append([]app.Message{newMessage}, a.app.Messages[insertIndex:]...)...)
}
}
case opencode.EventListResponseEventPermissionUpdated:
@@ -627,6 +657,10 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
slog.Error("Server error", "name", err.Name, "message", err.Data.Message)
return a, toast.NewErrorToast(err.Data.Message, toast.WithTitle(string(err.Name)))
}
+ case opencode.EventListResponseEventSessionCompacted:
+ if msg.Properties.SessionID == a.app.Session.ID {
+ return a, toast.NewSuccessToast("Session compacted successfully")
+ }
case tea.WindowSizeMsg:
msg.Height -= 2 // Make space for the status bar
a.width, a.height = msg.Width, msg.Height
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",
diff --git a/sst-env.d.ts b/sst-env.d.ts
index 7db87a85d..22caba969 100644
--- a/sst-env.d.ts
+++ b/sst-env.d.ts
@@ -44,6 +44,10 @@ declare module "sst" {
type: "sst.sst.Linkable"
username: string
}
+ FIREWORKS_API_KEY: {
+ type: "sst.sst.Secret"
+ value: string
+ }
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string