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.ts28
-rw-r--r--cloud/app/src/routes/zen/v1/messages.ts6
-rw-r--r--cloud/app/src/routes/zen/v1/responses.ts26
-rw-r--r--cloud/app/src/util/zen.ts51
-rw-r--r--cloud/core/migrations/0005_jazzy_skrulls.sql1
-rw-r--r--cloud/core/migrations/0006_parallel_gauntlet.sql2
-rw-r--r--cloud/core/migrations/meta/0005_snapshot.json588
-rw-r--r--cloud/core/migrations/meta/0006_snapshot.json602
-rw-r--r--cloud/core/migrations/meta/_journal.json14
-rw-r--r--cloud/core/package.json2
-rw-r--r--cloud/core/src/schema/billing.sql.ts6
-rw-r--r--cloud/function/package.json2
-rw-r--r--cloud/scripts/package.json7
-rw-r--r--cloud/scripts/src/backfill-usage-provider.ts10
-rw-r--r--cloud/scripts/src/placeholder.ts1
-rw-r--r--packages/function/package.json2
-rw-r--r--packages/opencode/package.json2
-rw-r--r--packages/opencode/src/session/index.ts307
-rw-r--r--packages/opencode/src/session/message-v2.ts3
-rw-r--r--packages/opencode/src/util/token.ts7
-rw-r--r--packages/plugin/package.json2
-rw-r--r--packages/sdk/js/package.json2
-rw-r--r--packages/sdk/js/src/gen/types.gen.ts1
-rw-r--r--packages/web/package.json2
-rw-r--r--sdks/vscode/package.json2
27 files changed, 1504 insertions, 175 deletions
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) |
diff --git a/cloud/app/package.json b/cloud/app/package.json
index c5507fa42..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.3"
+ "version": "0.7.6"
},
"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 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..12246a6d6
--- /dev/null
+++ b/cloud/core/migrations/meta/0005_snapshot.json
@@ -0,0 +1,588 @@
+{
+ "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": {}
+ }
+}
diff --git a/cloud/core/migrations/meta/0006_snapshot.json b/cloud/core/migrations/meta/0006_snapshot.json
new file mode 100644
index 000000000..d726b6f67
--- /dev/null
+++ b/cloud/core/migrations/meta/0006_snapshot.json
@@ -0,0 +1,602 @@
+{
+ "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": {}
+ }
+}
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/package.json b/cloud/core/package.json
index fa401cea8..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.3",
+ "version": "0.7.6",
"private": true,
"type": "module",
"dependencies": {
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)],
diff --git a/cloud/function/package.json b/cloud/function/package.json
index 9b9728c7e..09a1be0f0 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.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 fc9491148..0b8a5c5fd 100644
--- a/cloud/scripts/package.json
+++ b/cloud/scripts/package.json
@@ -1,12 +1,13 @@
{
"name": "@opencode/cloud-scripts",
- "version": "0.7.3",
+ "version": "0.7.6",
"$schema": "https://json.schemastore.org/package.json",
"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
diff --git a/packages/function/package.json b/packages/function/package.json
index 8bb1b22f5..91662a0fe 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.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 c012c7506..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.3",
+ "version": "0.7.6",
"name": "opencode",
"type": "module",
"private": true,
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 21eaa3be8..d333828b8 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" })
@@ -361,6 +362,7 @@ export namespace Session {
Bus.publish(MessageV2.Event.Updated, {
info: msg,
})
+ return msg
}
async function updatePart(part: MessageV2.Part) {
@@ -717,14 +719,29 @@ export namespace Session {
}
return Provider.defaultModel()
})().then((x) => Provider.getModel(x.providerID, x.modelID))
- let msgs = await messages(input.sessionID)
+
+ 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]
+ }
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 +836,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<string, AITool> = {}
- const processor = createProcessor(assistantMsg, model.info)
-
const enabledTools = pipe(
agent.tools,
mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)),
@@ -878,7 +877,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 +981,8 @@ export namespace Session {
},
},
)
+
+ let pointer = 0
const stream = streamText({
onError(e) {
log.error("streamText error", {
@@ -989,39 +990,29 @@ 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),
- }
- }
+ 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 +1025,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) {
@@ -1150,6 +1116,7 @@ export namespace Session {
item.callback(result)
}
state().queued.delete(input.sessionID)
+ Session.prune(input)
return result
}
@@ -1421,11 +1388,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<string, MessageV2.ToolPart> = {}
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 +1597,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 +1688,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 +1727,7 @@ export namespace Session {
return { info: assistantMsg, parts: p }
},
}
+ return result
}
export const RevertInput = z.object({
@@ -1784,14 +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),
- )
- const split = start + Math.floor((msgs.length - start) / 2)
- log.info("summarizing", { start, split })
- const toSummarize = msgs.slice(start, split)
+ 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),
@@ -1799,6 +1809,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 +1855,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 +1876,55 @@ export namespace Session {
sessionID: input.sessionID,
})
- return msg
+ 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.
+ 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--) {
+ 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("pruning", {
+ sum,
+ id: part.id,
+ })
+ part.state.time.compacted = Date.now()
+ await updatePart(part)
+ }
+ }
+ }
+ }
}
function isLocked(sessionID: string) {
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")
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))
+ }
+}
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index fad117b06..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.3",
+ "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 ff50048e0..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.3",
+ "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 adf9d3f2c..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
}
}
diff --git a/packages/web/package.json b/packages/web/package.json
index 7acf09a78..6f24c4082 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.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 1de6c560a..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.3",
+ "version": "0.7.6",
"publisher": "sst-dev",
"repository": {
"type": "git",