Compare commits

...

99 Commits

Author SHA1 Message Date
opencode
4b30705c42 release: v0.8.0 2025-09-14 06:07:43 +00:00
Mani Sundararajan
1f8d396b76 fix(dev): build tui with correct file ext for windows (#2590) 2025-09-14 01:59:25 -04:00
Aiden Cline
3752bb9717 fix: token counting visual bug (#2587) 2025-09-13 19:46:24 -05:00
Aiden Cline
16d66c209d respect subagent in command, add subtask flag (#2569) 2025-09-13 12:47:18 -05:00
Aiden Cline
6506e48c54 tweak: keep aborted msgs in context (#2583) 2025-09-13 12:25:30 -05:00
GitHub Action
f0e8b7c29b ignore: update download stats 2025-09-13 2025-09-13 12:03:56 +00:00
Dax Raad
a00b49d65b disable autocompact if context is 0 2025-09-13 05:59:18 -04:00
Dax Raad
b1589be4ba add disable OPENCODE_DISABLE_AUTOCOMPACT 2025-09-13 05:55:04 -04:00
Dax Raad
eb24d2f847 ignore: fix 2025-09-13 05:53:03 -04:00
Dax
9bb25a9260 Session management and prompt handling improvements (#2577)
Co-authored-by: GitHub Action <action@github.com>
2025-09-13 05:46:14 -04:00
opencode
535230dce4 release: v0.7.9 2025-09-13 05:29:37 +00:00
Dax Raad
555fb53505 nudge llm to continue properly after compaction 2025-09-13 01:23:54 -04:00
Tommy D. Rossi
b1e0a23351 fix: ShellError: exit code 1 errors (#2568)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-13 00:06:07 -05:00
Nicholas Hamilton
2b69bcccdf docs: typo in web agents.mdx (#2574) 2025-09-12 23:26:52 -05:00
Trillium Smith
e03f27381f docs: add tip block for finding available models (#2501)
Co-authored-by: GitHub Action <action@github.com>
2025-09-12 21:22:54 -04:00
Aiden Cline
aebd50da7e fix: make permission always behavior match expectation (#2573) 2025-09-12 18:59:38 -05:00
Stephen Murray
c02f58c2af fix: await cleanupRevert() to prevent dupe msgs after undo (#2572) 2025-09-12 18:42:39 -05:00
Dax Raad
c8f4d54f7f wip: zen 2025-09-12 14:53:00 -04:00
GitHub Action
4983d255dd chore: format code 2025-09-12 18:46:43 +00:00
Dax Raad
f2b4891ff0 wip: zen 2025-09-12 14:46:08 -04:00
GitHub Action
efcb5abbf7 chore: format code 2025-09-12 18:33:14 +00:00
Jay V
d37e58719e ignore: zen 2025-09-12 14:32:43 -04:00
Frank
c6c153de95 wip: zen 2025-09-12 14:22:42 -04:00
opencode
417e8f619c release: v0.7.8 2025-09-12 18:09:55 +00:00
Dax Raad
f2094b7bb3 temporarily disable midstream compaction 2025-09-12 14:00:54 -04:00
Dax Raad
176dc51b2e ci: exclude production branch from format workflow 2025-09-12 13:41:38 -04:00
opencode
f7d9a031e6 release: v0.7.7 2025-09-12 17:28:35 +00:00
Dax Raad
3e2478ebf9 undo session pruning 2025-09-12 13:20:13 -04:00
GitHub Action
1f4e8b4954 chore: format code 2025-09-12 16:19:09 +00:00
Frank
9a346a00fb wip: zen 2025-09-12 12:18:32 -04:00
Frank
0a13820927 Merge branch 'production' into dev 2025-09-12 12:04:27 -04:00
GitHub Action
c5fa3ee9f8 chore: format code 2025-09-12 15:57:50 +00:00
Frank
c294a18155 wip: zen 2025-09-12 11:57:14 -04:00
Frank
c3dc6d6df6 wip: zen 2025-09-12 11:57:14 -04:00
GitHub Action
ef3425a177 ignore: update download stats 2025-09-12 2025-09-12 12:04:14 +00:00
Dax Raad
0290b4aaf0 ignore: internal 2025-09-12 10:45:44 +00:00
opencode
4ceee53480 release: v0.7.6 2025-09-12 10:45:44 +00:00
Dax Raad
469dc9095f add microcompact 2025-09-12 06:38:47 -04:00
opencode
661d50f95f release: v0.7.5 2025-09-12 10:25:57 +00:00
opencode
3978a8e636 release: v0.7.4 2025-09-12 10:08:33 +00:00
Dax Raad
983e3b2ee3 fix compaction issues 2025-09-12 06:01:11 -04:00
GitHub Action
1bd198eb34 chore: format code 2025-09-11 22:34:21 +00:00
GitHub Action
3c502861a7 chore: format code 2025-09-11 22:30:52 +00:00
Frank
a52b352b24 wip: zen 2025-09-11 18:30:13 -04:00
Dax Raad
79c73267cf wip: zen 2025-09-11 18:20:37 -04:00
opencode
54f7fb5019 release: v0.7.3 2025-09-11 21:38:17 +00:00
Frank
dd97d784b6 Merge branch 'production' into dev 2025-09-11 17:28:51 -04:00
GitHub Action
91832bd5d7 chore: format code 2025-09-11 21:22:56 +00:00
Frank
3abca8fd4b wip: zen 2025-09-11 17:22:05 -04:00
Dax Raad
f5b3992479 properly support model level npm definition 2025-09-11 16:22:44 -04:00
Chris Covington
53f1f16122 feat: Add an experimental option to disable paste summaries (#2552)
Co-authored-by: rekram1-node <aidenpcline@gmail.com>
2025-09-11 14:21:08 -05:00
Aiden Cline
4614e4983e fix: command being passed as arg when no args present (#2553) 2025-09-11 13:03:12 -05:00
opencode
84f0c63fa1 release: v0.7.2 2025-09-11 17:02:59 +00:00
Dax Raad
3e9b451fb4 reduce LSP verbosity 2025-09-11 12:54:12 -04:00
Dax Raad
4ccf683527 remove block anchor edit 2025-09-11 12:53:10 -04:00
GitHub Action
b236ca9047 ignore: update download stats 2025-09-11 2025-09-11 12:04:26 +00:00
Dax Raad
aa9ebe5d7c ignore: compacting 2025-09-11 02:31:28 -04:00
Dax Raad
4c94753eda compaction improvements 2025-09-11 02:22:51 -04:00
GitHub Action
c3a55c35bb chore: format code 2025-09-11 05:33:59 +00:00
Frank
d5275010d5 wip: zen 2025-09-11 01:33:23 -04:00
Frank
dedfa563c2 wip: zen 2025-09-11 01:32:06 -04:00
GitHub Action
37b6a55eb1 chore: format code 2025-09-11 04:00:17 +00:00
GitHub Action
7aa57accf5 chore: format code 2025-09-11 03:59:39 +00:00
Jay V
c2fa28c1be ignore: zen 2025-09-10 17:59:03 -10:00
Jay V
30aae66320 docs: lander 2025-09-10 16:42:32 -10:00
Jay V
7b95190df3 docs: add twitter 2025-09-10 11:47:41 -10:00
Frank
fa3e7bb9b0 wip: zen 2025-09-10 17:39:28 -04:00
Emmanuel LOUISY-GABRIEL
5b56848c3d Update providers.mdx because of small typo (#2539) 2025-09-10 15:49:25 -05:00
Aiden Cline
780e532094 resolve nested commands (#2537) 2025-09-10 14:05:26 -05:00
Aiden Cline
29310957c8 fix: handle @dir in command (#2533) 2025-09-10 13:27:44 -05:00
opencode
2b0577c725 release: v0.7.1 2025-09-10 15:40:31 +00:00
Dax Raad
bcd656ffae fix issue with flags being parsed incorrectly 2025-09-10 11:34:39 -04:00
GitHub Action
0e0c5a9b68 ignore: update download stats 2025-09-10 2025-09-10 12:04:18 +00:00
opencode
d36fcc4f8e release: v0.7.0 2025-09-10 08:42:45 +00:00
Dax Raad
ea82b60d7d ci: stuff 2025-09-10 04:35:49 -04:00
Dax Raad
ea0285a96c ci: stuff 2025-09-10 04:27:23 -04:00
Dax Raad
6960408ca2 ci: bump version 2025-09-10 04:23:57 -04:00
GitHub Action
fa36195492 chore: format code 2025-09-10 07:40:01 +00:00
Dax Raad
a6265ea3d2 upgrade to latest bun 2025-09-10 03:36:42 -04:00
Dax Raad
bb3f02b8bb wip: ignore 2025-09-10 03:13:42 -04:00
Aiden Cline
bdc0f7c86d tweak: wrap build-switch w/ system-reminder (#2525) 2025-09-09 23:57:13 -05:00
GitHub Action
c8ca036834 chore: format code 2025-09-10 03:49:07 +00:00
Dax Raad
8c7fee7840 ci: fix 2025-09-09 23:48:35 -04:00
Dax Raad
e53fb7f8ed ci: format 2025-09-09 23:47:47 -04:00
Dax Raad
b05cbc9101 ci: format 2025-09-09 23:44:04 -04:00
Dax Raad
38e8c42cf0 ci: format 2025-09-09 23:44:04 -04:00
opencode
58fe884327 release: v0.6.10 2025-09-10 03:32:47 +00:00
Dax Raad
e69d10b6c9 repair tool calls when casing is wrong 2025-09-09 23:25:27 -04:00
opencode
10aee9755c release: v0.6.9 2025-09-09 21:17:41 +00:00
Frank
63384bc214 wip: zen 2025-09-09 16:40:12 -04:00
Frank
2508e06c58 wip: zen 2025-09-09 16:32:56 -04:00
Frank
6487d0607b wip: zen 2025-09-09 16:15:35 -04:00
Frank
a3513244f1 wip: zen 2025-09-09 15:47:28 -04:00
madflow
32b47fcc1e feat: svelte lsp (#2508) 2025-09-09 13:59:58 -05:00
Aiden Cline
fde03d3c93 fix: exit code being non zero when using run cmd (#2523) 2025-09-09 12:00:55 -05:00
GitHub Action
9045f13acc ignore: update download stats 2025-09-09 2025-09-09 12:04:32 +00:00
Frank
74f0edc7a8 wip: zen 2025-09-09 05:42:15 -04:00
opencode
dcabafcdce release: v0.6.8 2025-09-09 07:40:23 +00:00
Frank
02e8242c3b Remove debug logging 2025-09-09 03:35:09 -04:00
156 changed files with 8392 additions and 3765 deletions

View File

@@ -17,7 +17,7 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.19
bun-version: 1.2.21
- run: bun install

32
.github/workflows/format.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Format
on:
push:
branches-ignore:
- production
pull_request:
branches-ignore:
- production
workflow_dispatch:
jobs:
format:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.21
- name: run
run: |
bun install
./script/format.ts
env:
CI: true

View File

@@ -2,7 +2,7 @@ name: discord
on:
release:
types: [published] # fires only when a release is published
types: [published] # fires only when a release is published
jobs:
notify:

View File

@@ -24,4 +24,4 @@ jobs:
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/sonic
model: opencode/sonic

View File

@@ -21,7 +21,7 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.19
bun-version: 1.2.21
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce

View File

@@ -1,17 +1,17 @@
name: publish
run-name: "${{ format('v{0}', inputs.version) }}"
run-name: "${{ format('release {0}', inputs.bump) }}"
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish"
bump:
description: "Bump major, minor, or patch"
required: true
type: string
title:
description: "Custom title for this run"
required: false
type: string
type: choice
options:
- major
- minor
- patch
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -37,16 +37,16 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.19
bun-version: 1.2.21
- name: Cache ~/.bun
id: cache-bun
uses: actions/cache@v3
with:
path: ~/.bun
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
key: ${{ runner.os }}-bun-1-2-21-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
${{ runner.os }}-bun-1-2-21-
- name: Install makepkg
run: |
@@ -65,8 +65,9 @@ jobs:
- name: Publish
run: |
OPENCODE_VERSION=${{ inputs.version }} ./script/publish.ts
./script/publish.ts
env:
OPENCODE_BUMP: ${{ inputs.bump }}
GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -15,7 +15,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.19
bun-version: 1.2.21
- name: Install dependencies
run: bun install

View File

@@ -107,4 +107,4 @@ The other confusingly named repo has no relation to this one. You can [read the
---
**Join our community** [Discord](https://discord.gg/opencode) | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/anomaly_inv)
**Join our community** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)

View File

@@ -73,3 +73,8 @@
| 2025-09-06 | 286,245 (+2,476) | 225,036 (+1,243) | 511,281 (+3,719) |
| 2025-09-07 | 288,623 (+2,378) | 225,866 (+830) | 514,489 (+3,208) |
| 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) |
| 2025-09-12 | 321,046 (+6,963) | 240,728 (+3,372) | 561,774 (+10,335) |
| 2025-09-13 | 324,894 (+3,848) | 245,539 (+4,811) | 570,433 (+8,659) |

View File

@@ -8,7 +8,7 @@
},
"devDependencies": {
"prettier": "3.5.3",
"sst": "3.17.12",
"sst": "3.17.13",
},
},
"cloud/app": {
@@ -26,7 +26,7 @@
},
"cloud/core": {
"name": "@opencode/cloud-core",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@opencode/cloud-resource": "workspace:*",
@@ -43,7 +43,7 @@
},
"cloud/function": {
"name": "@opencode/cloud-function",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -69,7 +69,7 @@
},
"cloud/scripts": {
"name": "@opencode/cloud-scripts",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@opencode/cloud-core": "workspace:*",
"tsx": "4.20.5",
@@ -81,7 +81,7 @@
},
"packages/function": {
"name": "@opencode/function",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -96,7 +96,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "0.6.4",
"version": "0.7.6",
"bin": {
"opencode": "./bin/opencode",
},
@@ -136,7 +136,7 @@
"@octokit/webhooks-types": "7.6.1",
"@standard-schema/spec": "1.0.0",
"@tsconfig/bun": "1.0.7",
"@types/bun": "latest",
"@types/bun": "catalog:",
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"typescript": "catalog:",
@@ -146,7 +146,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
},
@@ -157,7 +157,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@hey-api/openapi-ts": "0.81.0",
},
@@ -169,7 +169,7 @@
},
"packages/web": {
"name": "@opencode/web",
"version": "0.6.4",
"version": "0.7.6",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -217,6 +217,7 @@
"catalog": {
"@hono/zod-validator": "0.4.2",
"@tsconfig/node22": "22.0.2",
"@types/bun": "1.2.21",
"@types/node": "22.13.9",
"ai": "5.0.8",
"hono": "4.7.10",
@@ -2646,23 +2647,23 @@
"ssri": ["ssri@10.0.6", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ=="],
"sst": ["sst@3.17.12", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.12", "sst-darwin-x64": "3.17.12", "sst-linux-arm64": "3.17.12", "sst-linux-x64": "3.17.12", "sst-linux-x86": "3.17.12", "sst-win32-arm64": "3.17.12", "sst-win32-x64": "3.17.12", "sst-win32-x86": "3.17.12" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-UwUbucNZRLp9GHgPAwkat1sBsNGaJfHsLXZHCMKsolCW7CEuugJfvBl2vOyJrhKP4N+Xnv1QFh0BGsOmN0kQeA=="],
"sst": ["sst@3.17.13", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.13", "sst-darwin-x64": "3.17.13", "sst-linux-arm64": "3.17.13", "sst-linux-x64": "3.17.13", "sst-linux-x86": "3.17.13", "sst-win32-arm64": "3.17.13", "sst-win32-x64": "3.17.13", "sst-win32-x86": "3.17.13" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-NaNTZL7uk2AsXzPBySQy7aqXlStXorR+bA785NxvCbskwkc44nVSQcEsvX5tdsD6/jrWpw9tDy4sStv2ycLAng=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9Oky2ZmJoeEN97ALWtFRt3kvSIZLjYoQoOtJvTaNQJTFi/9OsUE/6I5zdedf5GhMKCT1JvY+Ngpv3U3Y6SEYOg=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HZaDReT/c+2CcEnFkYjMty63II2ckQrUniiSdoEH6eAWyU1Iy7UwKK4I2GYm+5dy9xeSBaOKga6FMdLqFxIiUg=="],
"sst-darwin-x64": ["sst-darwin-x64@3.17.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-n6tWCjFF9Pb+QzxXJmuTGfQ4GW96Nf6ATtb7Wpa+9RDLRHrEBdOjXAp7osr7MB9djPRkt4942nwUZ7wX/EULpg=="],
"sst-darwin-x64": ["sst-darwin-x64@3.17.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-1DlYMrmrI5RY3/Ob039JatgvDKZ5QNtyRkVu0WcnsOvcxFE4dzrCiYKYHg2A+FMDl+H1qcwy2gGA3BTwC9in1w=="],
"sst-linux-arm64": ["sst-linux-arm64@3.17.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-iMflBzQWhF5kmRdXu402dwVpQI9LfFR3yFok3HUTV0ema5Pq2kPphEatEEw1dyG2ZXCBLeKN+T3Ujjfer+ddRA=="],
"sst-linux-arm64": ["sst-linux-arm64@3.17.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-A4+ZamchUdaX0pqvYWZ+r7OP1bruwEj9qgWT5kU7Q5pqaotIsEitYQi0q9nZFKH+5mXYesUwSy5FA+Q8T3X/Rg=="],
"sst-linux-x64": ["sst-linux-x64@3.17.12", "", { "os": "linux", "cpu": "x64" }, "sha512-89rZXs3IfGrY9yiDNuLfcJvHnAUX1gRVeB+lqQ1M2sbJD2iMpN+fx93owcApAndtZYzYNfQYEZ/xYwI6HFfu4w=="],
"sst-linux-x64": ["sst-linux-x64@3.17.13", "", { "os": "linux", "cpu": "x64" }, "sha512-yhKZc5CojqjB2DnyeVka5jeRb4oc3lBx8Qf6or0w4cs47SBIqyvO0iR/3IeKvRRL1hiEUeUF8r/q83rQo9jZMw=="],
"sst-linux-x86": ["sst-linux-x86@3.17.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zc2nd2syaq/DfNxtDcn4NOh8RBCaCZ1qsjLFpvGGfMMRnGiWjofuE6eFX3fhchGL3uvaqwlENvtzj4UC/MF5wQ=="],
"sst-linux-x86": ["sst-linux-x86@3.17.13", "", { "os": "linux", "cpu": "none" }, "sha512-G1FIUmpUaECB/3CV5EO/y1QmV5mQ8RUkFeZq64oyILEEaMzSWWKz0glHzQ3+p316VE74MzbktiWRqsCKQy8GeA=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-Bb7M6PoImmGeyzJu75QbNHBs0mDp21DsKFyMucn2dwxYwahuFPjjMbG+tlziWtxcNgdZMdEcy9jR8ot1jAIh0Q=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-9uCiIXmsGoxGeNWgM81x/d6V/vecjoiHXhBUPDGlQ1Dct1SbtHAgaf/g2ec5XwSQb9B/tne4qk81eMlTl83Z1A=="],
"sst-win32-x64": ["sst-win32-x64@3.17.12", "", { "os": "win32", "cpu": "x64" }, "sha512-7f41o1WhxdcuLhHijoavkX5O3L/Pnma6zCoL3kG6f9Njc6Zyj8Oha2fQz6Tesb/Qt8deG04WU4bL3FmxgNHU6g=="],
"sst-win32-x64": ["sst-win32-x64@3.17.13", "", { "os": "win32", "cpu": "x64" }, "sha512-hTuj4rFSCI/9tX4NMUpNJ69Q9td/giekELDRNv03ys8TpJGoGvPT8D6VD1eX7j1CQnOZIgeEphfW9WmCXkLaIA=="],
"sst-win32-x86": ["sst-win32-x86@3.17.12", "", { "os": "win32", "cpu": "none" }, "sha512-AfsNJQMTlefHitaVRWh5Uf3AaICIaomFbSo5qDbibgkvhbppCxgMFpW0IxiWySjWrCN5hMMkxdxlZP9IHqqxjQ=="],
"sst-win32-x86": ["sst-win32-x86@3.17.13", "", { "os": "win32", "cpu": "none" }, "sha512-AuMDGux+H1kPckKJ7Szgi04EpBoOKh/v5zFNAPjvWSkcWcGZ+hsBUx3h/FO/AkGK3RnlLMRj4CQQLoa10RSSIg=="],
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],

View File

@@ -7,7 +7,7 @@
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "vinxi build && ../../packages/opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "0.6.7"
"version": "0.8.0"
},
"dependencies": {
"@ibm/plex": "6.4.1",

View File

@@ -1,15 +1,15 @@
import { MetaProvider, Title, Meta } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { ErrorBoundary, Suspense } from "solid-js";
import "@ibm/plex/css/ibm-plex.css";
import "./app.css";
import { MetaProvider, Title, Meta } from "@solidjs/meta"
import { Router } from "@solidjs/router"
import { FileRoutes } from "@solidjs/start/router"
import { ErrorBoundary, Suspense } from "solid-js"
import "@ibm/plex/css/ibm-plex.css"
import "./app.css"
export default function App() {
return (
<Router
explicitLinks={true}
root={props => (
root={(props) => (
<MetaProvider>
<Title>opencode</Title>
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
@@ -19,5 +19,5 @@ export default function App() {
>
<FileRoutes />
</Router>
);
)
}

View File

@@ -6,34 +6,66 @@ export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
<path d="M264.5 0H288.5V8.5H272.5V16.5H288.5V25H272.5V33H288.5V41.5H264.5V0Z" fill="currentColor" />
<path d="M248.5 0H224.5V41.5H248.5V33H232.5V8.5H248.5V0Z" fill="currentColor" />
<path d="M256.5 8.5H248.5V33H256.5V8.5Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M184.5 0H216.5V41.5H184.5V0ZM208.5 8.5H192.5V33H208.5V8.5Z"
fill="currentColor"
/>
<path d="M144.5 8.5H136.5V41.5H144.5V8.5Z" fill="currentColor" />
<path d="M136.5 0H112.5V41.5H120.5V8.5H136.5V0Z" fill="currentColor" />
<path d="M80.5 0H104.5V8.5H88.5V16.5H104.5V25H88.5V33H104.5V41.5H80.5V0Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z" fill="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M40.5 0H72.5V41.5H48.5V49.5H40.5V0ZM64.5 8.5H48.5V33H64.5V8.5Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.5 0H32.5V41.5955H0.5V0ZM24.5 8.5H8.5V33H24.5V8.5Z"
fill="currentColor"
/>
<path d="M152.5 0H176.5V8.5H160.5V33H176.5V41.5H152.5V0Z" fill="currentColor" />
</svg>
);
)
}
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg
{...props}
viewBox="0 0 512 512" >
<rect width="336" height="336" x="128" y="128" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="57" ry="57"></rect>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"></path>
<svg {...props} viewBox="0 0 512 512">
<rect
width="336"
height="336"
x="128"
y="128"
fill="none"
stroke="currentColor"
stroke-linejoin="round"
stroke-width="32"
rx="57"
ry="57"
></rect>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
d="m383.5 128l.5-24a56.16 56.16 0 0 0-56-56H112a64.19 64.19 0 0 0-64 64v216a56.16 56.16 0 0 0 56 56h24"
></path>
</svg>
)
}
export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg
{...props}
viewBox="0 0 24 24" >
<path fill="currentColor" d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"></path>
<svg {...props} viewBox="0 0 24 24">
<path
fill="currentColor"
d="M9 16.17L5.53 12.7a.996.996 0 1 0-1.41 1.41l4.18 4.18c.39.39 1.02.39 1.41 0L20.29 7.71a.996.996 0 1 0-1.41-1.41z"
></path>
</svg>
)
}

View File

@@ -1,4 +1,4 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";
import { mount, StartClient } from "@solidjs/start/client"
mount(() => <StartClient />, document.getElementById("app")!);
mount(() => <StartClient />, document.getElementById("app")!)

View File

@@ -1,26 +1,28 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server"
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.svg" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
), {
mode: "async",
})
export default createHandler(
() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.svg" />
<meta property="og:image" content="/social-share.png" />
<meta property="twitter:image" content="/social-share.png" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
),
{
mode: "async",
},
)

View File

@@ -1,308 +0,0 @@
/**
* @deprecated Use zen/v1/chat/completions instead
*/
import { Resource } from "@opencode/cloud-resource"
import type { APIEvent } from "@solidjs/start/server"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Identifier } from "@opencode/cloud-core/identifier.js"
const MODELS = {
// "anthropic/claude-sonnet-4": {
// auth: true,
// api: "https://api.anthropic.com",
// apiKey: Resource.ANTHROPIC_API_KEY.value,
// model: "claude-sonnet-4-20250514",
// cost: {
// input: 0.0000015,
// output: 0.000006,
// reasoning: 0.0000015,
// cacheRead: 0.0000001,
// cacheWrite: 0.0000001,
// },
// headerMappings: {},
// },
"qwen/qwen3-coder": {
id: "qwen/qwen3-coder",
auth: true,
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
cost: {
input: 0.00000038,
output: 0.00000153,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {},
},
"grok-code": {
id: "x-ai/grok-code-fast-1",
auth: false,
api: "https://api.x.ai",
apiKey: Resource.XAI_API_KEY.value,
model: "grok-code",
cost: {
input: 0,
output: 0,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {
"x-grok-conv-id": "x-opencode-session",
"x-grok-req-id": "x-opencode-request",
},
},
}
class AuthError extends Error {}
class CreditsError extends Error {}
class ModelError extends Error {}
export async function POST(input: APIEvent) {
try {
const url = new URL(input.request.url)
const body = await input.request.json()
const MODEL = validateModel()
const apiKey = await authenticate()
await checkCredits()
// Request to model provider
const res = await fetch(new URL(url.pathname.replace(/^\/gateway/, "") + url.search, MODEL.api), {
method: "POST",
headers: (() => {
const headers = input.request.headers
headers.delete("host")
headers.delete("content-length")
headers.set("authorization", `Bearer ${MODEL.apiKey}`)
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
headers.set(k, headers.get(v)!)
})
return headers
})(),
body: JSON.stringify({
...body,
model: MODEL.model,
stream_options: {
include_usage: true,
},
}),
})
// Scrub response headers
const resHeaders = new Headers()
const keepHeaders = ["content-type", "cache-control"]
for (const [k, v] of res.headers.entries()) {
if (keepHeaders.includes(k.toLowerCase())) {
resHeaders.set(k, v)
}
}
// Handle non-streaming response
if (!body.stream) {
const body = await res.json()
await trackUsage(body)
return new Response(JSON.stringify(body), {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
}
// Handle streaming response
const stream = new ReadableStream({
start(c) {
const reader = res.body?.getReader()
const decoder = new TextDecoder()
let buffer = ""
function pump(): Promise<void> {
return (
reader?.read().then(async ({ done, value }) => {
if (done) {
c.close()
return
}
buffer += decoder.decode(value, { stream: true })
const parts = buffer.split("\n\n")
buffer = parts.pop() ?? ""
const usage = parts
.map((part) => part.trim())
.filter((part) => part.startsWith("data: "))
.map((part) => {
try {
return JSON.parse(part.slice(6))
} catch (e) {
return {}
}
})
.find((part) => part.usage)
if (usage) await trackUsage(usage)
c.enqueue(value)
return pump()
}) || Promise.resolve()
)
}
return pump()
},
})
return new Response(stream, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
function validateModel() {
if (!(body.model in MODELS)) {
throw new ModelError(`Model ${body.model} not supported`)
}
return MODELS[body.model as keyof typeof MODELS]
}
async function authenticate() {
try {
const authHeader = input.request.headers.get("authorization")
if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.")
const apiKey = authHeader.split(" ")[1]
const key = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.where(eq(KeyTable.key, apiKey))
.then((rows) => rows[0]),
)
if (!key) throw new AuthError("Invalid API key.")
return key
} catch (e) {
console.log(e)
// ignore error if model does not require authentication
if (!MODEL.auth) return
throw e
}
}
async function checkCredits() {
if (!apiKey || !MODEL.auth) return
const billing = await Database.use((tx) =>
tx
.select({
balance: BillingTable.balance,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
.then((rows) => rows[0]),
)
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
}
async function trackUsage(chunk: any) {
console.log(`trackUsage ${apiKey}`)
if (!apiKey) return
const usage = chunk.usage
const inputTokens = usage.prompt_tokens ?? 0
const outputTokens = usage.completion_tokens ?? 0
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
//const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0
const cacheWriteTokens = 0
const inputCost = MODEL.cost.input * inputTokens
const outputCost = MODEL.cost.output * outputTokens
const reasoningCost = MODEL.cost.reasoning * reasoningTokens
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens
const costInCents = (inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost) * 100
const cost = centsToMicroCents(costInCents)
await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID: apiKey.workspaceID,
id: Identifier.create("usage"),
model: MODEL.id,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens,
cost,
})
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, apiKey.id)),
)
}
} catch (error: any) {
if (error instanceof AuthError) {
return new Response(
JSON.stringify({
error: {
message: error.message,
type: "invalid_request_error",
param: null,
code: "unauthorized",
},
}),
{
status: 401,
},
)
}
if (error instanceof CreditsError) {
return new Response(
JSON.stringify({
error: {
message: error.message,
type: "insufficient_quota",
param: null,
code: "insufficient_quota",
},
}),
{
status: 401,
},
)
}
if (error instanceof ModelError) {
return new Response(JSON.stringify({ error: { message: error.message } }), {
status: 401,
})
}
console.log(error)
return new Response(JSON.stringify({ error: { message: error.message } }), {
status: 500,
})
}
}

View File

@@ -29,6 +29,9 @@
--heading-font-size: 1rem;
}
display: flex;
gap: var(--vertical-padding);
flex-direction: column;
font-family: var(--font-mono);
color: var(--color-text);
padding: calc(var(--padding) + 1rem);
@@ -47,7 +50,8 @@
}
[data-component="top"] {
padding: var(--padding);
padding: calc(var(--padding) * 1.5) var(--padding) var(--padding);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
@@ -79,6 +83,25 @@
color: var(--color-text-secondary);
text-transform: uppercase;
}
[data-slot="login"] {
position: absolute;
top: 0;
right: 0;
border-width: 0 0 1px 1px;
border-style: solid;
border-color: var(--color-border);
background-color: var(--color-bg);
@media (max-width: 30rem) {
display: none;
}
a {
display: block;
padding: 0.5rem 1rem calc(0.5rem + 4px);
}
}
}
[data-component="cta"] {
@@ -108,6 +131,19 @@
}
}
[data-slot="center"] {
display: none;
@media (max-width: 30rem) {
display: block;
flex: 1;
text-align: center;
padding: var(--vertical-padding) 0.5rem;
border-top: 1px solid var(--color-border);
border-left: none;
}
}
[data-slot="right"] {
flex: 1;
padding: var(--vertical-padding) 1rem;
@@ -440,22 +476,8 @@
border-left: 1px solid var(--color-border);
}
/* Small desktop: first two columns shrink to content, third expands */
@media (max-width: 57rem) {
[data-slot="cell"]:nth-child(1),
[data-slot="cell"]:nth-child(2) {
flex: 0 0 auto;
padding-left: calc(var(--padding) / 2);
padding-right: calc(var(--padding) / 2);
}
[data-slot="cell"]:nth-child(3) {
flex: 1;
}
}
/* Mobile: third column on its own row */
@media (max-width: 40rem) {
@media (max-width: 30rem) {
flex-wrap: wrap;
[data-slot="cell"]:nth-child(1),
@@ -470,4 +492,13 @@
}
}
}
[data-component="legal"] {
color: var(--color-text-dimmed);
text-align: center;
a {
color: var(--color-text-dimmed);
}
}
}

View File

@@ -1,13 +1,11 @@
import "./index.css"
import { Title } from "@solidjs/meta"
import { Match, onCleanup, onMount, Switch } from "solid-js"
import { onCleanup, onMount } from "solid-js"
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
import IMG_VSCODE from "../asset/lander/screenshot-vscode.png"
import IMG_GITHUB from "../asset/lander/screenshot-github.png"
import { IconCopy, IconCheck } from "../component/icon"
import { createAsync, query, redirect, A } from "@solidjs/router"
import { createAsync, query } from "@solidjs/router"
import { getActor } from "~/context/auth"
import { withActor } from "~/context/auth.withActor"
import { Account } from "@opencode/cloud-core/account.js"
@@ -55,18 +53,23 @@ export default function Home() {
return (
<main data-page="home">
<Title>opencode | AI coding agent built for the terminal</Title>
<div data-component="content">
<section data-component="top">
<img data-slot="logo light" src={logoLight} alt="opencode logo light" />
<img data-slot="logo dark" src={logoDark} alt="opencode logo dark" />
<h1 data-slot="title">The AI coding agent built for the terminal</h1>
<div data-slot="login">
<a href="/auth">opencode zen</a>
</div>
</section>
<section data-component="cta">
<div data-slot="left">
<a href="/docs">
Get Started
</a>
<a href="/docs">Get Started</a>
</div>
<div data-slot="center">
<a href="/auth">opencode zen</a>
</div>
<div data-slot="right">
<button data-copy data-slot="command">
@@ -90,7 +93,8 @@ export default function Home() {
<strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
</li>
<li>
<strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode <label>New</label>
<strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode{" "}
<label>New</label>
</li>
<li>
<strong>Multi-session</strong> Start multiple agents in parallel on the same project
@@ -157,19 +161,23 @@ export default function Home() {
</section>
<footer data-component="footer">
<div data-slot="cell">
<a href="https://x.com/opencode">X.com</a>
</div>
<div data-slot="cell">
<a href="https://github.com/sst/opencode">GitHub</a>
</div>
<div data-slot="cell">
<a href="https://opencode.ai/discord">Discord</a>
</div>
<div data-slot="cell">
<span>
©2025 <a href="https://anoma.ly">Anomaly Innovations</a>
</span>
</div>
</footer>
</div>
<div data-component="legal">
<span>
©2025 <a href="https://anoma.ly">Anomaly Innovations</a>
</span>
</div>
</main>
)
}

View File

@@ -15,7 +15,7 @@
cursor: pointer;
transition: all 0.15s ease;
&:hover {
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-accent);
}
@@ -26,13 +26,7 @@
&:disabled {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background-color: var(--color-bg);
border-color: var(--color-border);
transform: none;
}
transform: none;
}
&[data-color="primary"] {
@@ -40,7 +34,7 @@
border-color: var(--color-primary);
color: var(--color-primary-text);
&:hover {
&:hover:not(:disabled) {
background-color: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
@@ -51,7 +45,7 @@
border-color: transparent;
color: var(--color-text-muted);
&:hover {
&:hover:not(:disabled) {
background-color: var(--color-surface-hover);
border-color: var(--color-border);
color: var(--color-text);

View File

@@ -2,7 +2,17 @@ import "./workspace.css"
import { useAuthSession } from "~/context/auth.session"
import { IconLogo } from "../component/icon"
import { withActor } from "~/context/auth.withActor"
import { query, action, redirect, createAsync, RouteSectionProps, Navigate, useNavigate, useParams, A } from "@solidjs/router"
import {
query,
action,
redirect,
createAsync,
RouteSectionProps,
Navigate,
useNavigate,
useParams,
A,
} from "@solidjs/router"
import { User } from "@opencode/cloud-core/user.js"
import { Actor } from "@opencode/cloud-core/actor.js"
import { getRequestEvent } from "solid-js/web"

View File

@@ -47,12 +47,6 @@
font-size: var(--font-size-md);
}
}
p {
line-height: 1.4;
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
}
}
section:not(:last-child) {
@@ -121,11 +115,18 @@
[data-component="api-keys-section"] {
[data-slot="create-form"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
[data-slot="input-container"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
@media (max-width: 30rem) {
gap: var(--space-2);
}
@@ -154,6 +155,13 @@
display: flex;
gap: var(--space-2);
}
[data-slot="form-error"] {
color: var(--color-danger);
font-size: var(--font-size-sm);
margin-top: var(--space-1);
line-height: 1.4;
}
}
[data-slot="api-keys-table"] {
@@ -192,11 +200,35 @@
&[data-slot="key-value"] {
font-family: var(--font-mono);
div {
cursor: pointer;
button {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-3);
font-size: var(--font-size-sm);
font-weight: 400;
border: none;
background-color: transparent;
color: var(--color-text-muted);
font-family: var(--font-mono);
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: all 0.15s ease;
text-transform: none;
&:hover:not(:disabled) {
background-color: var(--color-bg-surface);
color: var(--color-text);
}
&:disabled {
cursor: default;
color: var(--color-text);
}
span {
font-family: inherit;
}
}
}
@@ -262,6 +294,9 @@
[data-slot="value"] {
color: var(--color-danger);
}
[data-slot="currency"] {
color: var(--color-danger);
}
}
[data-slot="currency"] {
@@ -428,4 +463,168 @@
}
}
}
[data-slot="new-user-sections"] {
display: flex;
flex-direction: column;
gap: var(--space-8);
padding: var(--space-6);
background-color: var(--color-bg-surface);
border: 1px dashed var(--color-border);
border-radius: var(--border-radius-sm);
@media (max-width: 30rem) {
gap: var(--space-8);
padding: var(--space-4);
}
[data-component="feature-grid"] {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-6);
@media (max-width: 30rem) {
grid-template-columns: 1fr;
gap: var(--space-4);
}
[data-slot="feature"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
h3 {
font-size: var(--font-size-sm);
font-weight: 600;
margin: 0;
color: var(--color-text);
text-transform: uppercase;
letter-spacing: -0.025rem;
}
p {
font-size: var(--font-size-sm);
line-height: 1.5;
margin: 0;
color: var(--color-text-muted);
}
}
}
[data-component="api-key-highlight"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
[data-slot="section-title"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
h2 {
font-size: var(--font-size-md);
font-weight: 600;
line-height: 1.2;
letter-spacing: -0.03125rem;
margin: 0;
color: var(--color-text-secondary);
text-transform: uppercase;
@media (max-width: 30rem) {
font-size: var(--font-size-md);
}
}
}
[data-slot="key-display"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
[data-slot="key-container"] {
display: flex;
gap: var(--space-3);
padding: var(--space-4);
border: 2px solid var(--color-accent);
border-radius: var(--border-radius-sm);
align-items: center;
@media (max-width: 40rem) {
flex-direction: column;
gap: var(--space-3);
align-items: stretch;
}
[data-slot="key-value"] {
flex: 1;
font-family: var(--font-mono);
font-size: var(--font-size-sm);
color: var(--color-text);
background-color: var(--color-bg);
padding: var(--space-3);
border-radius: var(--border-radius-sm);
border: 1px solid var(--color-border);
word-break: break-all;
line-height: 1.4;
@media (max-width: 40rem) {
font-size: var(--font-size-xs);
padding: var(--space-2-5);
}
}
button {
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
font-size: var(--font-size-sm);
font-weight: 500;
white-space: nowrap;
min-width: 130px;
@media (max-width: 40rem) {
justify-content: center;
padding: var(--space-2-5) var(--space-3);
font-size: var(--font-size-xs);
min-width: 96px;
}
}
}
}
}
[data-component="next-steps"] {
display: flex;
flex-direction: column;
gap: var(--space-6);
ol {
margin: 0;
padding-left: 0;
display: flex;
flex-direction: column;
gap: var(--space-2);
list-style-position: inside;
li {
font-size: var(--font-size-md);
line-height: 1.5;
color: var(--color-text-secondary);
code {
font-family: var(--font-mono);
font-size: var(--font-size-sm);
padding: var(--space-1) var(--space-2);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
color: var(--color-text);
}
}
}
}
}
}

View File

@@ -1,18 +1,11 @@
import "./[id].css"
import { Billing } from "@opencode/cloud-core/billing.js"
import { Key } from "@opencode/cloud-core/key.js"
import {
json,
query,
action,
useParams,
useAction,
createAsync,
useSubmission,
} from "@solidjs/router"
import { createMemo, createSignal, For, Show } from "solid-js"
import { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createEffect, createMemo, createSignal, For, Show } from "solid-js"
import { withActor } from "~/context/auth.withActor"
import { IconCopy, IconCheck } from "~/component/icon"
import { createStore } from "solid-js/store"
function formatDateForTable(date: Date) {
const options: Intl.DateTimeFormatOptions = {
@@ -49,20 +42,31 @@ const listKeys = query(async (workspaceID: string) => {
return withActor(() => Key.list(), workspaceID)
}, "key.list")
const createKey = action(async (workspaceID: string, name: string) => {
const createKey = action(async (form: FormData) => {
"use server"
const name = form.get("name")?.toString().trim()
if (!name) return { error: "Name is required" }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(
withActor(() => Key.create({ name }), workspaceID),
await withActor(
() =>
Key.create({ name })
.then((data) => ({ error: undefined, data }))
.catch((e) => ({ error: e.message as string })),
workspaceID,
),
{ revalidate: listKeys.key },
)
}, "key.create")
const removeKey = action(async (workspaceID: string, id: string) => {
const removeKey = action(async (form: FormData) => {
"use server"
return json(
withActor(() => Key.remove({ id }), workspaceID),
{ revalidate: listKeys.key },
)
const id = form.get("id")?.toString()
if (!id) return { error: "ID is required" }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
}, "key.remove")
/////////////////////////////////////
@@ -100,127 +104,22 @@ const createCheckoutUrl = action(async (workspaceID: string, successUrl: string,
// return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID)
// }, "portalUrl")
function KeysSection() {
// Dummy data for testing
const dummyKeys = [
{
id: "key_1",
name: "Development API Key",
key: "oc_dev_1234567890abcdef1234567890abcdef12345678",
timeCreated: new Date("2024-01-15T10:30:00Z"),
},
{
id: "key_2",
name: "Production API Key",
key: "oc_prod_abcdef1234567890abcdef1234567890abcdef12",
timeCreated: new Date("2024-02-01T14:22:00Z"),
},
{
id: "key_3",
name: "Testing Environment",
key: "oc_test_9876543210fedcba9876543210fedcba98765432",
timeCreated: new Date("2024-02-10T09:15:00Z"),
},
]
function KeySection() {
const params = useParams()
const keys = createAsync(() => listKeys(params.id))
// const keys = () => dummyKeys
const [showForm, setShowForm] = createSignal(false)
const [name, setName] = createSignal("")
const removeAction = useAction(removeKey)
const createAction = useAction(createKey)
const createSubmission = useSubmission(createKey)
const [copiedId, setCopiedId] = createSignal<string | null>(null)
function formatKey(key: string) {
if (key.length <= 11) return key
return `${key.slice(0, 7)}...${key.slice(-4)}`
}
async function handleCreateKey() {
if (!name().trim()) return
try {
await createAction(params.id, name().trim())
setName("")
setShowForm(false)
} catch (error) {
console.error("Failed to create API key:", error)
}
}
async function copyKeyToClipboard(text: string, keyId: string) {
try {
await navigator.clipboard.writeText(text)
setCopiedId(keyId)
setTimeout(() => setCopiedId(null), 1500)
} catch (error) {
console.error("Failed to copy to clipboard:", error)
}
}
async function handleDeleteKey(keyId: string) {
if (!confirm("Are you sure you want to delete this API key?")) {
return
}
try {
await removeAction(params.id, keyId)
} catch (error) {
console.error("Failed to delete API key:", error)
}
}
return (
<section data-component="api-keys-section">
<div data-slot="section-title">
<h2>API Keys</h2>
<p>Manage your API keys for accessing opencode services.</p>
</div>
<Show
when={!showForm()}
fallback={
<div data-slot="create-form">
<input
data-component="input"
type="text"
placeholder="Enter key name"
value={name()}
onInput={(e) => setName(e.currentTarget.value)}
onKeyPress={(e) => e.key === "Enter" && handleCreateKey()}
/>
<div data-slot="form-actions">
<button
data-color="ghost"
onClick={() => {
setShowForm(false)
setName("")
}}
>
Cancel
</button>
<button
data-color="primary"
disabled={createSubmission.pending || !name().trim()}
onClick={handleCreateKey}
>
{createSubmission.pending ? "Creating..." : "Create"}
</button>
</div>
</div>
}
>
<button
data-color="primary"
onClick={() => {
console.log("clicked")
setShowForm(true)
}}
>
Create API Key
</button>
</Show>
<KeyCreateForm />
<div data-slot="api-keys-table">
<Show
when={keys()?.length}
@@ -241,30 +140,42 @@ function KeysSection() {
</thead>
<tbody>
<For each={keys()!}>
{(key) => (
<tr>
<td data-slot="key-name">{key.name}</td>
<td data-slot="key-value">
<div onClick={() => copyKeyToClipboard(key.key, key.id)} title="Click to copy API key">
<span>{formatKey(key.key)}</span>
<Show
when={copiedId() === key.id}
fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
{(key) => {
const [copied, setCopied] = createSignal(false)
// const submission = useSubmission(removeKey, ([fd]) => fd.get("id")?.toString() === key.id)
return (
<tr>
<td data-slot="key-name">{key.name}</td>
<td data-slot="key-value">
<button
data-color="ghost"
disabled={copied()}
onClick={async () => {
await navigator.clipboard.writeText(key.key)
setCopied(true)
setTimeout(() => setCopied(false), 1000)
}}
title="Copy API key"
>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</div>
</td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)}
</td>
<td data-slot="key-actions">
<button data-color="ghost" onClick={() => handleDeleteKey(key.id)} title="Delete API key">
Delete
</button>
</td>
</tr>
)}
<span>{formatKey(key.key)}</span>
<Show when={copied()} fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</button>
</td>
<td data-slot="key-date" title={formatDateUTC(key.timeCreated)}>
{formatDateForTable(key.timeCreated)}
</td>
<td data-slot="key-actions">
<form action={removeKey} method="post">
<input type="hidden" name="id" value={key.id} />
<input type="hidden" name="workspaceID" value={params.id} />
<button data-color="ghost">Delete</button>
</form>
</td>
</tr>
)
}}
</For>
</tbody>
</table>
@@ -274,27 +185,73 @@ function KeysSection() {
)
}
function KeyCreateForm() {
const params = useParams()
const submission = useSubmission(createKey)
const [store, setStore] = createStore({ show: false })
let input: HTMLInputElement
createEffect(() => {
if (!submission.pending && submission.result && !submission.result.error) {
hide()
}
})
function show() {
// submission.clear() does not clear the result in some cases, ie.
// 1. Create key with empty name => error shows
// 2. Put in a key name and creates the key => form hides
// 3. Click add key button again => form shows with the same error if
// submission.clear() is called only once
while (true) {
submission.clear()
if (!submission.result) break
}
setStore("show", true)
input.focus()
}
function hide() {
setStore("show", false)
}
return (
<Show
when={store.show}
fallback={
<button data-color="primary" onClick={() => show()}>
Create API Key
</button>
}
>
<form action={createKey} method="post" data-slot="create-form">
<div data-slot="input-container">
<input ref={(r) => (input = r)} data-component="input" name="name" type="text" placeholder="Enter key name" />
<Show when={submission.result && submission.result.error}>
{(err) => <div data-slot="form-error">{err()}</div>}
</Show>
</div>
<input type="hidden" name="workspaceID" value={params.id} />
<div data-slot="form-actions">
<button type="reset" data-color="ghost" onClick={() => hide()}>
Cancel
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Creating..." : "Create"}
</button>
</div>
</form>
</Show>
)
}
function BalanceSection() {
const params = useParams()
const dummyBalanceInfo = { balance: 2500000000 } // $25.00 in cents
const balanceInfo = createAsync(() => getBalanceInfo(params.id))
// const balanceInfo = () => dummyBalanceInfo
const createCheckoutUrlAction = useAction(createCheckoutUrl)
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
async function handleBuyCredits() {
try {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
} catch (error) {
console.error("Failed to get checkout URL:", error)
}
}
return (
<section data-component="balance-section">
<div data-slot="section-title">
@@ -317,7 +274,17 @@ function BalanceSection() {
})()}
</span>
</div>
<button data-color="primary" disabled={createCheckoutUrlSubmission.pending} onClick={handleBuyCredits}>
<button
data-color="primary"
disabled={createCheckoutUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
}}
>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Buy Credits"}
</button>
</div>
@@ -327,43 +294,8 @@ function BalanceSection() {
function UsageSection() {
const params = useParams()
const dummyUsage = [
{
id: "usage_1",
model: "claude-3-sonnet-20240229",
inputTokens: 1250,
outputTokens: 890,
cost: 125000000, // $1.25 in cents
timeCreated: "2024-02-10T15:30:00Z",
},
{
id: "usage_2",
model: "gpt-4-turbo-preview",
inputTokens: 2100,
outputTokens: 1456,
cost: 340000000, // $3.40 in cents
timeCreated: "2024-02-09T09:45:00Z",
},
{
id: "usage_3",
model: "claude-3-haiku-20240307",
inputTokens: 850,
outputTokens: 620,
cost: 45000000, // $0.45 in cents
timeCreated: "2024-02-08T13:22:00Z",
},
{
id: "usage_4",
model: "gpt-3.5-turbo",
inputTokens: 1800,
outputTokens: 1200,
cost: 85000000, // $0.85 in cents
timeCreated: "2024-02-07T11:15:00Z",
},
]
const usage = createAsync(() => getUsageInfo(params.id))
// const usage = () => dummyUsage
return (
<section data-component="usage-section">
<div data-slot="section-title">
@@ -414,76 +346,148 @@ function UsageSection() {
)
}
function PaymentsSection() {
function PaymentSection() {
const params = useParams()
const dummyPayments = [
{
id: "pi_1234567890",
amount: 5000000000, // $50.00 in cents
timeCreated: "2024-02-01T10:00:00Z",
},
{
id: "pi_0987654321",
amount: 2500000000, // $25.00 in cents
timeCreated: "2024-01-15T14:30:00Z",
},
]
const payments = createAsync(() => getPaymentsInfo(params.id))
// const payments = () => dummyPayments
return payments() && payments()!.length > 0 && (
<section data-component="payments-section">
<div data-slot="section-title">
<h2>Payments History</h2>
<p>Recent payment transactions.</p>
</div>
<div data-slot="payments-table">
<table data-slot="payments-table-element">
<thead>
<tr>
<th>Date</th>
<th>Payment ID</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<For each={payments()!}>
{(payment) => {
const date = new Date(payment.timeCreated)
return (
<tr>
<td data-slot="payment-date" title={formatDateUTC(date)}>
{formatDateForTable(date)}
</td>
<td data-slot="payment-id">{payment.id}</td>
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
</tr>
)
}}
</For>
</tbody>
</table>
</div>
</section>
return (
payments() &&
payments()!.length > 0 && (
<section data-component="payments-section">
<div data-slot="section-title">
<h2>Payments History</h2>
<p>Recent payment transactions.</p>
</div>
<div data-slot="payments-table">
<table data-slot="payments-table-element">
<thead>
<tr>
<th>Date</th>
<th>Payment ID</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<For each={payments()!}>
{(payment) => {
const date = new Date(payment.timeCreated)
return (
<tr>
<td data-slot="payment-date" title={formatDateUTC(date)}>
{formatDateForTable(date)}
</td>
<td data-slot="payment-id">{payment.id}</td>
<td data-slot="payment-amount">${((payment.amount ?? 0) / 100000000).toFixed(2)}</td>
</tr>
)
}}
</For>
</tbody>
</table>
</div>
</section>
)
)
}
export default function() {
function NewUserSection() {
const params = useParams()
const [copiedKey, setCopiedKey] = createSignal(false)
const keys = createAsync(() => listKeys(params.id))
const usage = createAsync(() => getUsageInfo(params.id))
const isNew = createMemo(() => {
const keysList = keys()
const usageList = usage()
return keysList?.length === 1 && (!usageList || usageList.length === 0)
})
const defaultKey = createMemo(() => keys()?.at(-1)?.key)
return (
<Show when={isNew()}>
<div data-slot="new-user-sections">
<div data-component="feature-grid">
<div data-slot="feature">
<h3>Tested & Verified Models</h3>
<p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
</div>
<div data-slot="feature">
<h3>Highest Quality</h3>
<p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
</div>
<div data-slot="feature">
<h3>No Lock-in</h3>
<p>Use Zen with any coding agent, and continue using other providers with opencode whenever you want.</p>
</div>
</div>
<div data-component="api-key-highlight">
<Show when={defaultKey()}>
<div data-slot="key-display">
<div data-slot="key-container">
<code data-slot="key-value">{defaultKey()}</code>
<button
data-color="primary"
disabled={copiedKey()}
onClick={async () => {
await navigator.clipboard.writeText(defaultKey() ?? "")
setCopiedKey(true)
setTimeout(() => setCopiedKey(false), 2000)
}}
title="Copy API key"
>
<Show
when={copiedKey()}
fallback={
<>
<IconCopy style={{ width: "16px", height: "16px" }} /> Copy Key
</>
}
>
<IconCheck style={{ width: "16px", height: "16px" }} /> Copied!
</Show>
</button>
</div>
</div>
</Show>
</div>
<div data-component="next-steps">
<ol>
<li>
Run <code>opencode auth login</code> and select opencode
</li>
<li>Paste your API key</li>
<li>Start opencode</li>
<li>
Run <code>/models</code> to see available models
</li>
</ol>
</div>
</div>
</Show>
)
}
export default function () {
return (
<div data-page="workspace-[id]">
<section data-component="title-section">
<h1>Zen</h1>
<p>
Curated list of models provided by opencode. <a target="_blank" href="/docs/zen">Learn more</a>.
Curated list of models provided by opencode.{" "}
<a target="_blank" href="/docs/zen">
Learn more
</a>
.
</p>
</section>
<div data-slot="sections">
<KeysSection />
<NewUserSection />
<KeySection />
<BalanceSection />
<UsageSection />
<PaymentsSection />
<PaymentSection />
</div>
</div>
)

View File

@@ -1,330 +1,54 @@
import { Resource } from "@opencode/cloud-resource"
import type { APIEvent } from "@solidjs/start/server"
import { Database, eq, sql } from "@opencode/cloud-core/drizzle/index.js"
import { KeyTable } from "@opencode/cloud-core/schema/key.sql.js"
import { BillingTable, UsageTable } from "@opencode/cloud-core/schema/billing.sql.js"
import { centsToMicroCents } from "@opencode/cloud-core/util/price.js"
import { Identifier } from "@opencode/cloud-core/identifier.js"
import { handler } from "~/util/zen"
const MODELS = {
// "anthropic/claude-sonnet-4": {
// auth: true,
// api: "https://api.anthropic.com",
// apiKey: Resource.ANTHROPIC_API_KEY.value,
// model: "claude-sonnet-4-20250514",
// cost: {
// input: 0.0000015,
// output: 0.000006,
// reasoning: 0.0000015,
// cacheRead: 0.0000001,
// cacheWrite: 0.0000001,
// },
// headerMappings: {},
// },
"qwen/qwen3-coder": {
id: "qwen/qwen3-coder" 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,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {},
},
"moonshotai/kimi-k2": {
id: "moonshotai/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,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {},
},
"grok-code": {
id: "x-ai/grok-code-fast-1" as const,
auth: false,
api: "https://api.x.ai",
apiKey: Resource.XAI_API_KEY.value,
model: "grok-code",
cost: {
input: 0,
output: 0,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {
"x-grok-conv-id": "x-opencode-session",
"x-grok-req-id": "x-opencode-request",
},
},
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
}
}
const FREE_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
]
export function POST(input: APIEvent) {
let usage: Usage
return handler(input, {
modifyBody: (body: any) => ({
...body,
...(body.stream ? { stream_options: { include_usage: true } } : {}),
}),
setAuthHeader: (headers: Headers, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`)
},
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
onStreamPart: (chunk: string) => {
if (!chunk.startsWith("data: ")) return
class AuthError extends Error {}
class CreditsError extends Error {}
class ModelError extends Error {}
export async function POST(input: APIEvent) {
try {
const url = new URL(input.request.url)
const body = await input.request.json()
logMetric({
is_tream: !!body.stream,
session: input.request.headers.get("x-opencode-session"),
request: input.request.headers.get("x-opencode-request"),
})
const MODEL = validateModel()
const apiKey = await authenticate()
const isFree = FREE_WORKSPACES.includes(apiKey?.workspaceID ?? "")
await checkCredits()
// Request to model provider
const res = await fetch(new URL(url.pathname.replace(/^\/zen/, "") + url.search, MODEL.api), {
method: "POST",
headers: (() => {
const headers = input.request.headers
headers.delete("host")
headers.delete("content-length")
headers.set("authorization", `Bearer ${MODEL.apiKey}`)
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
headers.set(k, headers.get(v)!)
})
return headers
})(),
body: JSON.stringify({
...body,
model: MODEL.model,
stream_options: {
include_usage: true,
},
}),
})
// Scrub response headers
const resHeaders = new Headers()
const keepHeaders = ["content-type", "cache-control"]
for (const [k, v] of res.headers.entries()) {
if (keepHeaders.includes(k.toLowerCase())) {
resHeaders.set(k, v)
}
}
// Handle non-streaming response
if (!body.stream) {
const json = await res.json()
const body = JSON.stringify(json)
logMetric({ response_length: body.length })
await trackUsage(json)
return new Response(body, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
}
// Handle streaming response
const stream = new ReadableStream({
start(c) {
const reader = res.body?.getReader()
const decoder = new TextDecoder()
let buffer = ""
let responseLength = 0
let startTimestamp = Date.now()
let receivedFirstByte = false
function pump(): Promise<void> {
return (
reader?.read().then(async ({ done, value }) => {
if (done) {
logMetric({ response_length: responseLength })
c.close()
return
}
if (!receivedFirstByte) {
receivedFirstByte = true
logMetric({ time_to_first_byte: Date.now() - startTimestamp })
}
buffer += decoder.decode(value, { stream: true })
responseLength += value.length
const parts = buffer.split("\n\n")
buffer = parts.pop() ?? ""
const usage = parts
.map((part) => part.trim())
.filter((part) => part.startsWith("data: "))
.map((part) => {
try {
return JSON.parse(part.slice(6))
} catch (e) {
return {}
}
})
.find((part) => part.usage)
if (usage) await trackUsage(usage)
c.enqueue(value)
return pump()
}) || Promise.resolve()
)
}
return pump()
},
})
return new Response(stream, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
function validateModel() {
if (!(body.model in MODELS)) {
throw new ModelError(`Model ${body.model} not supported`)
}
const model = MODELS[body.model as keyof typeof MODELS]
logMetric({ model: model.id })
return model
}
async function authenticate() {
let json
try {
const authHeader = input.request.headers.get("authorization")
if (!authHeader || !authHeader.startsWith("Bearer ")) throw new AuthError("Missing API key.")
const apiKey = authHeader.split(" ")[1]
const key = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.where(eq(KeyTable.key, apiKey))
.then((rows) => rows[0]),
)
if (!key) throw new AuthError("Invalid API key.")
logMetric({
api_key: key.id,
workspace: key.workspaceID,
})
return key
json = JSON.parse(chunk.slice(6)) as { usage?: Usage }
} catch (e) {
// ignore error if model does not require authentication
if (!MODEL.auth) return
throw e
return
}
}
async function checkCredits() {
if (!apiKey || !MODEL.auth || isFree) return
const billing = await Database.use((tx) =>
tx
.select({
balance: BillingTable.balance,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
.then((rows) => rows[0]),
)
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
}
async function trackUsage(chunk: any) {
const usage = chunk.usage
const inputTokens = usage.prompt_tokens ?? 0
const outputTokens = usage.completion_tokens ?? 0
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? 0
const cacheReadTokens = usage.prompt_tokens_details?.cached_tokens ?? 0
//const cacheWriteTokens = providerMetadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? 0
const cacheWriteTokens = 0
const inputCost = MODEL.cost.input * inputTokens * 100
const outputCost = MODEL.cost.output * outputTokens * 100
const reasoningCost = MODEL.cost.reasoning * reasoningTokens * 100
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens * 100
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens * 100
const totalCostInCent = inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost
logMetric({
"tokens.input": inputTokens,
"tokens.output": outputTokens,
"tokens.reasoning": reasoningTokens,
"tokens.cache_read": cacheReadTokens,
"tokens.cache_write": cacheWriteTokens,
"cost.input": Math.round(inputCost),
"cost.output": Math.round(outputCost),
"cost.reasoning": Math.round(reasoningCost),
"cost.cache_read": Math.round(cacheReadCost),
"cost.cache_write": Math.round(cacheWriteCost),
"cost.total": Math.round(totalCostInCent),
})
if (!apiKey) return
const cost = isFree ? 0 : centsToMicroCents(totalCostInCent)
await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID: apiKey.workspaceID,
id: Identifier.create("usage"),
model: MODEL.id,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens,
cost,
})
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, apiKey.id)),
)
}
} catch (error: any) {
logMetric({
"error.type": error.constructor.name,
"error.message": 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({ error: { message: error.message } }), { status: 500 })
}
function logMetric(values: Record<string, any>) {
console.log(`_metric:${JSON.stringify(values)}`)
}
if (!json.usage) return
usage = json.usage
},
getStreamUsage: () => usage,
normalizeUsage: (usage: Usage) => ({
inputTokens: usage.prompt_tokens ?? 0,
outputTokens: usage.completion_tokens ?? 0,
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? undefined,
cacheReadTokens: usage.prompt_tokens_details?.cached_tokens ?? undefined,
}),
})
}

View File

@@ -0,0 +1,61 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/util/zen"
type Usage = {
cache_creation?: {
ephemeral_5m_input_tokens?: number
ephemeral_1h_input_tokens?: number
}
cache_creation_input_tokens?: number
cache_read_input_tokens?: number
input_tokens?: number
output_tokens?: number
server_tool_use?: {
web_search_requests?: number
}
}
export function POST(input: APIEvent) {
let usage: Usage
return handler(input, {
modifyBody: (body: any) => ({
...body,
service_tier: "standard_only",
}),
setAuthHeader: (headers: Headers, apiKey: string) => headers.set("x-api-key", apiKey),
parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
onStreamPart: (chunk: string) => {
const data = chunk.split("\n")[1]
if (!data.startsWith("data: ")) return
let json
try {
json = JSON.parse(data.slice(6)) as { usage?: Usage }
} catch (e) {
return
}
if (!json.usage) return
usage = {
...usage,
...json.usage,
cache_creation: {
...usage?.cache_creation,
...json.usage.cache_creation,
},
server_tool_use: {
...usage?.server_tool_use,
...json.usage.server_tool_use,
},
}
},
getStreamUsage: () => usage,
normalizeUsage: (usage: Usage) => ({
inputTokens: usage.input_tokens ?? 0,
outputTokens: usage.output_tokens ?? 0,
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,
}),
})
}

View File

@@ -0,0 +1,52 @@
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: Usage
return handler(input, {
setAuthHeader: (headers: Headers, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`)
},
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
onStreamPart: (chunk: string) => {
const [event, data] = chunk.split("\n")
if (event !== "event: response.completed") return
if (!data.startsWith("data: ")) return
let json
try {
json = JSON.parse(data.slice(6)) as { response?: { usage?: Usage } }
} catch (e) {
return
}
if (!json.response?.usage) return
usage = json.response.usage
},
getStreamUsage: () => usage,
normalizeUsage: (usage: Usage) => {
const inputTokens = usage.input_tokens ?? 0
const outputTokens = usage.output_tokens ?? 0
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? undefined
const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? undefined
return {
inputTokens: inputTokens - (cacheReadTokens ?? 0),
outputTokens: outputTokens - (reasoningTokens ?? 0),
reasoningTokens,
cacheReadTokens,
}
},
})
}

View File

@@ -14,6 +14,7 @@ body {
--font-size-8xl: 6rem;
--font-size-9xl: 8rem;
--font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-mono:
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-sans: var(--font-mono);
}

531
cloud/app/src/util/zen.ts Normal file
View File

@@ -0,0 +1,531 @@
import type { APIEvent } from "@solidjs/start/server"
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: {
modifyBody?: (body: any) => any
setAuthHeader: (headers: Headers, apiKey: string) => void
parseApiKey: (headers: Headers) => string | undefined
onStreamPart: (chunk: string) => void
getStreamUsage: () => any
normalizeUsage: (body: any) => {
inputTokens: number
outputTokens: number
reasoningTokens?: number
cacheReadTokens?: number
cacheWrite5mTokens?: number
cacheWrite1hTokens?: number
}
},
) {
class AuthError extends Error {}
class CreditsError extends Error {}
class ModelError extends Error {}
const MODELS: Record<string, Model> = {
"claude-opus-4-1": {
id: "claude-opus-4-1" as const,
auth: true,
cost: {
input: 0.000015,
output: 0.000075,
cacheRead: 0.0000015,
cacheWrite5m: 0.00001875,
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,
cost: (usage: any) => {
const totalInputTokens =
usage.inputTokens + usage.cacheReadTokens + usage.cacheWrite5mTokens + usage.cacheWrite1hTokens
return totalInputTokens <= 200_000
? {
input: 0.000003,
output: 0.000015,
cacheRead: 0.0000003,
cacheWrite5m: 0.00000375,
cacheWrite1h: 0.000006,
}
: {
input: 0.000006,
output: 0.0000225,
cacheRead: 0.0000006,
cacheWrite5m: 0.0000075,
cacheWrite1h: 0.000012,
}
},
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,
cost: {
input: 0.0000008,
output: 0.000004,
cacheRead: 0.00000008,
cacheWrite5m: 0.000001,
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,
cost: {
input: 0.00000125,
output: 0.00001,
cacheRead: 0.000000125,
},
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,
cost: {
input: 0.00000045,
output: 0.0000018,
},
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,
cost: {
input: 0.0000006,
output: 0.0000025,
},
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,
cost: {
input: 0,
output: 0,
cacheRead: 0,
},
headerMappings: {
"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,
cost: {
input: 0.00000038,
output: 0.00000153,
},
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,
},
},
},
}
const FREE_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
]
const logger = {
metric: (values: Record<string, any>) => {
console.log(`_metric:${JSON.stringify(values)}`)
},
log: console.log,
debug: (message: string) => {
if (Resource.App.stage === "production") return
console.debug(message)
},
}
try {
const url = new URL(input.request.url)
const body = await input.request.json()
logger.debug(JSON.stringify(body))
logger.metric({
is_tream: !!body.stream,
session: input.request.headers.get("x-opencode-session"),
request: input.request.headers.get("x-opencode-request"),
})
const MODEL = validateModel()
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 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, providerData.apiKey)
Object.entries(MODEL.headerMappings ?? {}).forEach(([k, v]) => {
headers.set(k, headers.get(v)!)
})
return headers
})(),
body: JSON.stringify({
...(opts.modifyBody?.(body) ?? body),
model: providerData.model,
}),
})
// Scrub response headers
const resHeaders = new Headers()
const keepHeaders = ["content-type", "cache-control"]
for (const [k, v] of res.headers.entries()) {
if (keepHeaders.includes(k.toLowerCase())) {
resHeaders.set(k, v)
}
}
// Handle non-streaming response
if (!body.stream) {
const json = await res.json()
const body = JSON.stringify(json)
logger.metric({ response_length: body.length })
logger.debug(body)
await trackUsage(json.usage)
return new Response(body, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
}
// Handle streaming response
const stream = new ReadableStream({
start(c) {
const reader = res.body?.getReader()
const decoder = new TextDecoder()
let buffer = ""
let responseLength = 0
function pump(): Promise<void> {
return (
reader?.read().then(async ({ done, value }) => {
if (done) {
logger.metric({ response_length: responseLength })
const usage = opts.getStreamUsage()
if (usage) await trackUsage(usage)
c.close()
return
}
if (responseLength === 0) {
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
}
responseLength += value.length
buffer += decoder.decode(value, { stream: true })
const parts = buffer.split("\n\n")
buffer = parts.pop() ?? ""
for (const part of parts) {
logger.debug(part)
opts.onStreamPart(part.trim())
}
c.enqueue(value)
return pump()
}) || Promise.resolve()
)
}
return pump()
},
})
return new Response(stream, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
function validateModel() {
if (!(body.model in MODELS)) {
throw new ModelError(`Model ${body.model} not supported`)
}
const model = MODELS[body.model as keyof typeof MODELS]
logger.metric({ model: model.id })
return model
}
async function authenticate() {
try {
const apiKey = opts.parseApiKey(input.request.headers)
if (!apiKey) throw new AuthError("Missing API key.")
const key = await Database.use((tx) =>
tx
.select({
id: KeyTable.id,
workspaceID: KeyTable.workspaceID,
})
.from(KeyTable)
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
.then((rows) => rows[0]),
)
if (!key) throw new AuthError("Invalid API key.")
logger.metric({
api_key: key.id,
workspace: key.workspaceID,
})
return key
} catch (e) {
// ignore error if model does not require authentication
if (!MODEL.auth) return
throw e
}
}
async function checkCredits() {
if (!apiKey || !MODEL.auth || isFree) return
const billing = await Database.use((tx) =>
tx
.select({
balance: BillingTable.balance,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
.then((rows) => rows[0]),
)
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
}
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)
const modelCost = typeof MODEL.cost === "function" ? MODEL.cost(usage) : MODEL.cost
const inputCost = modelCost.input * inputTokens * 100
const outputCost = modelCost.output * outputTokens * 100
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 ?? 0) +
(cacheWrite5mCost ?? 0) +
(cacheWrite1hCost ?? 0)
logger.metric({
"tokens.input": inputTokens,
"tokens.output": outputTokens,
"tokens.reasoning": reasoningTokens,
"tokens.cache_read": cacheReadTokens,
"tokens.cache_write_5m": cacheWrite5mTokens,
"tokens.cache_write_1h": cacheWrite1hTokens,
"cost.input": Math.round(inputCost),
"cost.output": Math.round(outputCost),
"cost.reasoning": reasoningCost ? Math.round(reasoningCost) : undefined,
"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),
})
if (!apiKey) return
const cost = isFree ? 0 : centsToMicroCents(totalCostInCent)
await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID: apiKey.workspaceID,
id: Identifier.create("usage"),
model: MODEL.id,
provider: providerName,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWrite5mTokens,
cacheWrite1hTokens,
cost,
})
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, apiKey.id)),
)
}
} catch (error: any) {
logger.metric({
"error.type": error.constructor.name,
"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({
type: "error",
error: { type: error.constructor.name, message: error.message },
}),
{ status: 401 },
)
return new Response(
JSON.stringify({
type: "error",
error: {
type: "error",
message: error.message,
},
}),
{ status: 500 },
)
}
}

View File

@@ -6,4 +6,4 @@
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
export {}

View File

@@ -12,14 +12,10 @@
"allowJs": true,
"strict": true,
"noEmit": true,
"types": [
"vinxi/types/client"
],
"types": ["vinxi/types/client"],
"isolatedModules": true,
"paths": {
"~/*": [
"./src/*"
]
"~/*": ["./src/*"]
}
}
}

View File

@@ -0,0 +1 @@
ALTER TABLE `key` ADD `old_name` varchar(255);

View File

@@ -0,0 +1 @@
ALTER TABLE `key` ADD CONSTRAINT `name` UNIQUE(`workspace_id`,`name`);

View File

@@ -0,0 +1 @@
ALTER TABLE `usage` ADD `provider` varchar(255);

View File

@@ -0,0 +1 @@
ALTER TABLE `usage` MODIFY COLUMN `provider` varchar(255) NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE `usage` ADD `cache_write_5m_tokens` int;--> statement-breakpoint
ALTER TABLE `usage` ADD `cache_write_1h_tokens` int;

View File

@@ -0,0 +1 @@
ALTER TABLE `usage` DROP COLUMN `cache_write_tokens`;

View File

@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -140,10 +138,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -216,10 +211,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -320,10 +312,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -401,9 +390,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -411,10 +398,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -492,10 +476,7 @@
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -503,10 +484,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -563,9 +541,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -573,9 +549,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -592,4 +566,4 @@
"tables": {},
"indexes": {}
}
}
}

View File

@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -140,10 +138,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -216,10 +211,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -320,10 +312,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -401,9 +390,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -411,10 +398,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -492,10 +476,7 @@
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -503,10 +484,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -563,9 +541,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -573,9 +549,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -592,4 +566,4 @@
"tables": {},
"indexes": {}
}
}
}

View File

@@ -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": {}
}
}

View File

@@ -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": {}
}
}

View File

@@ -0,0 +1,588 @@
{
"version": "5",
"dialect": "mysql",
"id": "06dc6226-bfbb-4ccc-b4bc-f26070c3bed5",
"prevId": "26cebd59-f553-441c-a2b2-2f9578a0ad2b",
"tables": {
"account": {
"name": "account",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
},
"billing": {
"name": "billing",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"payment": {
"name": "payment",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"usage": {
"name": "usage",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider": {
"name": "provider",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_tokens": {
"name": "cache_write_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"key": {
"name": "key",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"actor": {
"name": "actor",
"type": "json",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"old_name": {
"name": "old_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"key": {
"name": "key",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_used": {
"name": "time_used",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"global_key": {
"name": "global_key",
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"color": {
"name": "color",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"workspace": {
"name": "workspace",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": ["slug"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": ["id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View File

@@ -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": {}
}
}

View File

@@ -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": {}
}
}

View File

@@ -0,0 +1,595 @@
{
"version": "5",
"dialect": "mysql",
"id": "91067cc9-d492-47b3-932a-42dcc0920b3c",
"prevId": "b0ad4b11-b607-46c7-8e2d-3b9823cdc5f7",
"tables": {
"account": {
"name": "account",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"email": {
"name": "email",
"columns": ["email"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraint": {}
},
"billing": {
"name": "billing",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_id": {
"name": "payment_method_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_method_last4": {
"name": "payment_method_last4",
"type": "varchar(4)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"balance": {
"name": "balance",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reload": {
"name": "reload",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"payment": {
"name": "payment",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"customer_id": {
"name": "customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payment_id": {
"name": "payment_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"amount": {
"name": "amount",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"usage": {
"name": "usage",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model": {
"name": "model",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider": {
"name": "provider",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"output_tokens": {
"name": "output_tokens",
"type": "int",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"reasoning_tokens": {
"name": "reasoning_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_read_tokens": {
"name": "cache_read_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_5m_tokens": {
"name": "cache_write_5m_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cache_write_1h_tokens": {
"name": "cache_write_1h_tokens",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"cost": {
"name": "cost",
"type": "bigint",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"key": {
"name": "key",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"actor": {
"name": "actor",
"type": "json",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"old_name": {
"name": "old_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"key": {
"name": "key",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_used": {
"name": "time_used",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"global_key": {
"name": "global_key",
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workspace_id": {
"name": "workspace_id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"color": {
"name": "color",
"type": "int",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
},
"workspace": {
"name": "workspace",
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"time_created": {
"name": "time_created",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "(now())"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp(3)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": ["slug"],
"isUnique": true
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": ["id"]
}
},
"uniqueConstraints": {},
"checkConstraint": {}
}
},
"views": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"tables": {},
"indexes": {}
}
}

View File

@@ -15,6 +15,48 @@
"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
},
{
"idx": 4,
"version": "5",
"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
},
{
"idx": 7,
"version": "5",
"when": 1757693869142,
"tag": "0007_familiar_nightshade",
"breakpoints": true
}
]
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.6.7",
"version": "0.8.0",
"private": true,
"type": "module",
"dependencies": {
@@ -18,6 +18,8 @@
},
"scripts": {
"db": "sst shell drizzle-kit",
"db-dev": "sst shell --stage dev -- drizzle-kit",
"db-prod": "sst shell --stage production -- drizzle-kit",
"typecheck": "tsc --noEmit"
},
"devDependencies": {

View File

@@ -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
@@ -41,15 +41,35 @@ export namespace Key {
key: secretKey,
timeUsed: null,
}),
)
).catch((e: any) => {
if (e.message.match(/Duplicate entry '.*' for key 'key.name'/))
throw new Error("A key with this name already exists. Please choose a different name.")
throw e
})
return keyID
})
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)))
})
})
}

View File

@@ -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,11 +34,13 @@ export const UsageTable = mysqlTable(
...workspaceColumns,
...timestamps,
model: varchar("model", { length: 255 }).notNull(),
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)],

View File

@@ -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),
],
)

View File

@@ -1,9 +1,6 @@
import { z } from "zod"
export function fn<T extends z.ZodType, Result>(
schema: T,
cb: (input: z.output<T>) => Result,
) {
export function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.output<T>) => Result) {
const result = (input: z.input<T>) => {
const parsed = schema.parse(input)
return cb(parsed)

View File

@@ -6,4 +6,4 @@
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
export {}

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode/cloud-function",
"version": "0.6.7",
"version": "0.8.0",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View File

@@ -6,83 +6,91 @@
import "sst"
declare module "sst" {
export interface Resource {
"ANTHROPIC_API_KEY": {
"type": "sst.sst.Secret"
"value": string
ANTHROPIC_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
AUTH_API_URL: {
type: "sst.sst.Linkable"
value: string
}
"BASETEN_API_KEY": {
"type": "sst.sst.Secret"
"value": string
BASETEN_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
Console: {
type: "sst.cloudflare.SolidStart"
url: string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
Database: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
username: string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
FIREWORKS_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
OPENAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"XAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
Web: {
type: "sst.cloudflare.Astro"
url: string
}
XAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}

View File

@@ -8,6 +8,9 @@ export const Resource = new Proxy(
// @ts-expect-error
const value = env[prop]
return typeof value === "string" ? JSON.parse(value) : value
} else if (prop === "App") {
// @ts-expect-error
return JSON.parse(env.SST_RESOURCE_App)
}
throw new Error(`"${prop}" is not linked in your sst.config.ts (cloudflare)`)
},

View File

@@ -6,83 +6,91 @@
import "sst"
declare module "sst" {
export interface Resource {
"ANTHROPIC_API_KEY": {
"type": "sst.sst.Secret"
"value": string
ANTHROPIC_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
AUTH_API_URL: {
type: "sst.sst.Linkable"
value: string
}
"BASETEN_API_KEY": {
"type": "sst.sst.Secret"
"value": string
BASETEN_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
Console: {
type: "sst.cloudflare.SolidStart"
url: string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
Database: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
username: string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
FIREWORKS_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
OPENAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"XAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
Web: {
type: "sst.cloudflare.Astro"
url: string
}
XAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}

View File

@@ -4,9 +4,6 @@
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"types": [
"@cloudflare/workers-types",
"node"
]
"types": ["@cloudflare/workers-types", "node"]
}
}

View File

@@ -1,12 +1,13 @@
{
"name": "@opencode/cloud-scripts",
"version": "0.6.7",
"version": "0.8.0",
"$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:*",

View File

@@ -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)
})

View File

@@ -1 +0,0 @@
// placeholder

View File

@@ -6,4 +6,4 @@
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
export {}

View File

@@ -4,7 +4,7 @@
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
"@types/bun": "catalog:"
},
"peerDependencies": {
"typescript": "^5"

2
github/sst-env.d.ts vendored
View File

@@ -6,4 +6,4 @@
/// <reference path="../sst-env.d.ts" />
import "sst"
export {}
export {}

View File

@@ -100,8 +100,10 @@ export const stripeWebhook = new WebhookEndpoint("StripeWebhookEndpoint", {
})
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!) },
@@ -132,8 +134,10 @@ new sst.cloudflare.x.SolidStart("Console", {
STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY,
ANTHROPIC_API_KEY,
OPENAI_API_KEY,
XAI_API_KEY,
BASETEN_API_KEY,
FIREWORKS_API_KEY,
],
environment: {
//VITE_DOCS_URL: web.url.apply((url) => url!),

View File

@@ -3,7 +3,7 @@
"name": "opencode",
"private": true,
"type": "module",
"packageManager": "bun@1.2.19",
"packageManager": "bun@1.2.21",
"scripts": {
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
"typecheck": "bun run --filter='*' typecheck",
@@ -17,6 +17,7 @@
"packages/sdk/js"
],
"catalog": {
"@types/bun": "1.2.21",
"@hono/zod-validator": "0.4.2",
"@types/node": "22.13.9",
"@tsconfig/node22": "22.0.2",
@@ -33,7 +34,7 @@
},
"devDependencies": {
"prettier": "3.5.3",
"sst": "3.17.12"
"sst": "3.17.13"
},
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@opencode/function",
"version": "0.6.7",
"version": "0.8.0",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",

View File

@@ -6,83 +6,91 @@
import "sst"
declare module "sst" {
export interface Resource {
"ANTHROPIC_API_KEY": {
"type": "sst.sst.Secret"
"value": string
ANTHROPIC_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
AUTH_API_URL: {
type: "sst.sst.Linkable"
value: string
}
"BASETEN_API_KEY": {
"type": "sst.sst.Secret"
"value": string
BASETEN_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
Console: {
type: "sst.cloudflare.SolidStart"
url: string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
Database: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
username: string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
FIREWORKS_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
OPENAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"XAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
Web: {
type: "sst.cloudflare.Astro"
url: string
}
XAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.6.7",
"version": "0.8.0",
"name": "opencode",
"type": "module",
"private": true,
@@ -19,7 +19,7 @@
"@octokit/webhooks-types": "7.6.1",
"@standard-schema/spec": "1.0.0",
"@tsconfig/bun": "1.0.7",
"@types/bun": "latest",
"@types/bun": "catalog:",
"@types/turndown": "5.0.5",
"@types/yargs": "17.0.33",
"typescript": "catalog:",

View File

@@ -38,7 +38,20 @@ for (const [os, arch] of targets) {
await $`CGO_ENABLED=0 GOOS=${os} GOARCH=${GOARCH[arch]} go build -ldflags="-s -w -X main.Version=${version}" -o ../opencode/dist/${name}/bin/tui ../tui/cmd/opencode/main.go`.cwd(
"../tui",
)
await $`bun build --define OPENCODE_TUI_PATH="'../../../dist/${name}/bin/tui'" --define OPENCODE_VERSION="'${version}'" --compile --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts`
await Bun.build({
compile: {
target: `bun-${os}-${arch}` as any,
outfile: `dist/${name}/bin/opencode`,
execArgv: [`--user-agent=opencode/${version}`, `--`],
windows: {},
},
entrypoints: ["./src/index.ts"],
define: {
OPENCODE_VERSION: `'${version}'`,
OPENCODE_TUI_PATH: `'../../../dist/${name}/bin/tui'`,
},
})
// await $`bun build --define OPENCODE_TUI_PATH="'../../../dist/${name}/bin/tui'" --define OPENCODE_VERSION="'${version}'" --compile --target=bun-${os}-${arch} --outfile=dist/${name}/bin/opencode ./src/index.ts`
// Run the binary only if it matches current OS/arch
if (
process.platform === (os === "windows" ? "win32" : os) &&

View File

@@ -11,6 +11,7 @@ import { MessageV2 } from "../../session/message-v2"
import { Identifier } from "../../id/id"
import { Agent } from "../../agent/agent"
import { Command } from "../../command"
import { SessionPrompt } from "../../session/prompt"
const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
@@ -71,7 +72,7 @@ export const RunCommand = cmd({
if (message.trim().length === 0 && !args.command) {
UI.error("You must provide a message or a command")
return
process.exit(1)
}
await bootstrap(process.cwd(), async () => {
@@ -79,7 +80,7 @@ export const RunCommand = cmd({
const exists = await Command.get(args.command)
if (!exists) {
UI.error(`Command "${args.command}" not found`)
return
process.exit(1)
}
}
const session = await (async () => {
@@ -104,7 +105,7 @@ export const RunCommand = cmd({
if (!session) {
UI.error("Session not found")
return
process.exit(1)
}
const cfg = await Config.get()
@@ -185,7 +186,7 @@ export const RunCommand = cmd({
})
if (args.command) {
await Session.command({
await SessionPrompt.command({
messageID: Identifier.ascending("message"),
sessionID: session.id,
agent: agent.name,
@@ -197,7 +198,7 @@ export const RunCommand = cmd({
}
const messageID = Identifier.ascending("message")
const result = await Session.prompt({
const result = await SessionPrompt.prompt({
sessionID: session.id,
messageID,
model: {
@@ -221,6 +222,7 @@ export const RunCommand = cmd({
if (errorMsg) process.stdout.write(errorMsg)
}
UI.empty()
if (errorMsg) process.exit(1)
})
},
})

View File

@@ -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
@@ -123,11 +123,16 @@ export const TuiCommand = cmd({
const file = Bun.file(binary)
if (!(await file.exists())) {
await Bun.write(file, tui, { mode: 0o755 })
await fs.chmod(binary, 0o755)
if (process.platform !== "win32") await fs.chmod(binary, 0o755)
}
cwd = process.cwd()
cmd = [binary]
}
if (!tui) {
const dir = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
let binaryName = `./dist/tui${process.platform === "win32" ? ".exe" : ""}`
await $`go build -o ${binaryName} ./main.go`.cwd(dir)
cmd = [path.join(dir, binaryName)]
}
Log.Default.info("tui", {
cmd,
})

View File

@@ -10,6 +10,7 @@ export namespace Command {
agent: z.string().optional(),
model: z.string().optional(),
template: z.string(),
subtask: z.boolean().optional(),
})
.openapi({
ref: "Command",
@@ -28,6 +29,7 @@ export namespace Command {
model: command.model,
description: command.description,
template: command.template,
subtask: command.subtask,
}
}

View File

@@ -118,16 +118,27 @@ export namespace Config {
// Load command markdown files
result.command = result.command || {}
const markdownCommands = [
...(await Filesystem.globUp("command/*.md", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/command/*.md", Instance.directory, Instance.worktree)),
...(await Filesystem.globUp("command/**/*.md", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/command/**/*.md", Instance.directory, Instance.worktree)),
]
for (const item of markdownCommands) {
const content = await Bun.file(item).text()
const md = matter(content)
if (!md.data) continue
const name = (() => {
const patterns = ["/.opencode/command/", "/command/"]
const pattern = patterns.find((p) => item.includes(p))
if (pattern) {
const index = item.indexOf(pattern)
return item.slice(index + pattern.length, -3)
}
return path.basename(item, ".md")
})()
const config = {
name: path.basename(item, ".md"),
name,
...md.data,
template: md.content.trim(),
}
@@ -233,6 +244,7 @@ export namespace Config {
description: z.string().optional(),
agent: z.string().optional(),
model: z.string().optional(),
subtask: z.boolean().optional(),
})
export type Command = z.infer<typeof Command>
@@ -488,6 +500,7 @@ export namespace Config {
.optional(),
})
.optional(),
disable_paste_summary: z.boolean().optional(),
})
.optional(),
})

View File

@@ -46,10 +46,7 @@ export namespace Fzf {
log.info("found", { filepath })
return { filepath }
}
filepath = path.join(
Global.Path.bin,
"fzf" + (process.platform === "win32" ? ".exe" : ""),
)
filepath = path.join(Global.Path.bin, "fzf" + (process.platform === "win32" ? ".exe" : ""))
const file = Bun.file(filepath)
if (!(await file.exists())) {
@@ -57,18 +54,15 @@ export namespace Fzf {
const arch = archMap[process.arch as keyof typeof archMap] ?? "amd64"
const config = PLATFORM[process.platform as keyof typeof PLATFORM]
if (!config)
throw new UnsupportedPlatformError({ platform: process.platform })
if (!config) throw new UnsupportedPlatformError({ platform: process.platform })
const version = VERSION
const platformName =
process.platform === "win32" ? "windows" : process.platform
const platformName = process.platform === "win32" ? "windows" : process.platform
const filename = `fzf-${version}-${platformName}_${arch}.${config.extension}`
const url = `https://github.com/junegunn/fzf/releases/download/v${version}/${filename}`
const response = await fetch(url)
if (!response.ok)
throw new DownloadFailedError({ url, status: response.status })
if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
const buffer = await response.arrayBuffer()
const archivePath = path.join(Global.Path.bin, filename)
@@ -87,13 +81,13 @@ export namespace Fzf {
})
}
if (config.extension === "zip") {
const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])));
const entries = await zipFileReader.getEntries();
let fzfEntry: any;
const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()])))
const entries = await zipFileReader.getEntries()
let fzfEntry: any
for (const entry of entries) {
if (entry.filename === "fzf.exe") {
fzfEntry = entry;
break;
fzfEntry = entry
break
}
}
@@ -101,18 +95,18 @@ export namespace Fzf {
throw new ExtractionFailedError({
filepath: archivePath,
stderr: "fzf.exe not found in zip archive",
});
})
}
const fzfBlob = await fzfEntry.getData(new BlobWriter());
const fzfBlob = await fzfEntry.getData(new BlobWriter())
if (!fzfBlob) {
throw new ExtractionFailedError({
filepath: archivePath,
stderr: "Failed to extract fzf.exe from zip archive",
});
})
}
await Bun.write(filepath, await fzfBlob.arrayBuffer());
await zipFileReader.close();
await Bun.write(filepath, await fzfBlob.arrayBuffer())
await zipFileReader.close()
}
await fs.unlink(archivePath)
if (process.platform !== "win32") await fs.chmod(filepath, 0o755)
@@ -127,4 +121,4 @@ export namespace Fzf {
const { filepath } = await state()
return filepath
}
}
}

View File

@@ -3,18 +3,16 @@ import { Log } from "../util/log"
export namespace FileTime {
const log = Log.create({ service: "file.time" })
export const state = Instance.state(
() => {
const read: {
[sessionID: string]: {
[path: string]: Date | undefined
}
} = {}
return {
read,
export const state = Instance.state(() => {
const read: {
[sessionID: string]: {
[path: string]: Date | undefined
}
},
)
} = {}
return {
read,
}
})
export function read(sessionID: string, file: string) {
log.info("read", { sessionID, file })

View File

@@ -8,6 +8,7 @@ export namespace Flag {
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()

View File

@@ -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

View File

@@ -81,6 +81,7 @@ export const LANGUAGE_EXTENSIONS: Record<string, string> = {
".zsh": "shellscript",
".ksh": "shellscript",
".sql": "sql",
".svelte": "svelte",
".swift": "swift",
".ts": "typescript",
".tsx": "typescriptreact",

View File

@@ -654,4 +654,56 @@ export namespace LSPServer {
}
},
}
export const Svelte: Info = {
id: "svelte",
extensions: [".svelte"],
root: NearestRoot([
"tsconfig.json",
"jsconfig.json",
"package.json",
"pnpm-lock.yaml",
"yarn.lock",
"bun.lockb",
"bun.lock",
"vite.config.ts",
"vite.config.js",
"svelte.config.ts",
"svelte.config.js",
]),
async spawn(root) {
let binary = Bun.which("svelteserver")
const args: string[] = []
if (!binary) {
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
if (!(await Bun.file(js).exists())) {
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], {
cwd: Global.Path.bin,
env: {
...process.env,
BUN_BE_BUN: "1",
},
stdout: "pipe",
stderr: "pipe",
stdin: "pipe",
}).exited
}
binary = BunProc.which()
args.push("run", js)
}
args.push("--stdio")
const proc = spawn(binary, args, {
cwd: root,
env: {
...process.env,
BUN_BE_BUN: "1",
},
})
return {
process: proc,
initialization: {},
}
},
}
}

View File

@@ -83,7 +83,7 @@ export namespace Permission {
toolCallID: input.callID,
pattern: input.pattern,
})
if (approved[input.sessionID]?.[input.pattern ?? input.type]) return
if (approved[input.sessionID]?.[input.type]) return
const info: Info = {
id: Identifier.ascending("permission"),
type: input.type,
@@ -141,9 +141,9 @@ export namespace Permission {
})
if (input.response === "always") {
approved[input.sessionID] = approved[input.sessionID] || {}
approved[input.sessionID][match.info.pattern ?? match.info.type] = true
approved[input.sessionID][match.info.type] = true
for (const item of Object.values(pending[input.sessionID])) {
if ((item.info.pattern ?? item.info.type) === (match.info.pattern ?? match.info.type)) {
if (item.info.type === match.info.type) {
respond({ sessionID: item.info.sessionID, permissionID: item.info.id, response: input.response })
}
}

View File

@@ -25,8 +25,8 @@ export namespace Plugin {
worktree: Instance.worktree,
directory: Instance.directory,
$: Bun.$,
Tool: await import("../tool/tool").then(m => m.Tool),
z: await import("zod").then(m => m.z),
Tool: await import("../tool/tool").then((m) => m.Tool),
z: await import("zod").then((m) => m.z),
}
const plugins = [...(config.plugin ?? [])]
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
@@ -79,10 +79,13 @@ export namespace Plugin {
for (const hook of hooks) {
await hook.config?.(config)
// Let plugins register tools at startup
await hook["tool.register"]?.({}, {
registerHTTP: ToolRegistry.registerHTTP,
register: ToolRegistry.register
})
await hook["tool.register"]?.(
{},
{
registerHTTP: ToolRegistry.registerHTTP,
register: ToolRegistry.register,
},
)
}
Bus.subscribeAll(async (input) => {
const hooks = await state().then((x) => x.hooks)

View File

@@ -30,6 +30,7 @@ export namespace ModelsDev {
}),
experimental: z.boolean().optional(),
options: z.record(z.any()),
provider: z.object({ npm: z.string() }).optional(),
})
.openapi({
ref: "Model",

View File

@@ -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")
@@ -235,6 +235,7 @@ export namespace Provider {
context: 0,
output: 0,
},
provider: model.provider ?? existing?.provider,
}
parsed.models[modelID] = parsedModel
}
@@ -287,7 +288,6 @@ export namespace Provider {
for (const [providerID, provider] of configProviders) {
mergeProvider(providerID, provider.options ?? {}, "config")
}
console.log("!@#!@#", Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS)
for (const [providerID, provider] of Object.entries(providers)) {
const filteredModels = Object.fromEntries(
@@ -320,29 +320,30 @@ export namespace Provider {
return state().then((state) => state.providers)
}
async function getSDK(provider: ModelsDev.Provider) {
async function getSDK(provider: ModelsDev.Provider, model: ModelsDev.Model) {
return (async () => {
using _ = log.time("getSDK", {
providerID: provider.id,
})
const s = await state()
const existing = s.sdk.get(provider.id)
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 pkg = provider.npm ?? provider.id
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 })
@@ -367,7 +368,7 @@ export namespace Provider {
if (!provider) throw new ModelNotFoundError({ providerID, modelID })
const info = provider.info.models[modelID]
if (!info) throw new ModelNotFoundError({ providerID, modelID })
const sdk = await getSDK(provider.info)
const sdk = await getSDK(provider.info, info)
try {
const language = provider.getModel ? await provider.getModel(sdk, modelID) : sdk.languageModel(modelID)

View File

@@ -25,6 +25,9 @@ import { Global } from "../global"
import { ProjectRoute } from "./project"
import { ToolRegistry } from "../tool/registry"
import { zodToJsonSchema } from "zod-to-json-schema"
import { SessionPrompt } from "../session/prompt"
import { SessionCompaction } from "../session/compaction"
import { SessionRevert } from "../session/revert"
const ERRORS = {
400: {
@@ -558,7 +561,7 @@ export namespace Server {
}),
),
async (c) => {
return c.json(Session.abort(c.req.valid("param").id))
return c.json(SessionPrompt.abort(c.req.valid("param").id))
},
)
.post(
@@ -651,7 +654,7 @@ export namespace Server {
async (c) => {
const id = c.req.valid("param").id
const body = c.req.valid("json")
await Session.summarize({ ...body, sessionID: id })
await SessionCompaction.run({ ...body, sessionID: id })
return c.json(true)
},
)
@@ -665,14 +668,7 @@ export namespace Server {
description: "List of messages",
content: {
"application/json": {
schema: resolver(
z
.object({
info: MessageV2.Info,
parts: MessageV2.Part.array(),
})
.array(),
),
schema: resolver(MessageV2.WithParts.array()),
},
},
},
@@ -750,11 +746,11 @@ export namespace Server {
id: z.string().openapi({ description: "Session ID" }),
}),
),
zValidator("json", Session.PromptInput.omit({ sessionID: true })),
zValidator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.prompt({ ...body, sessionID })
const msg = await SessionPrompt.prompt({ ...body, sessionID })
return c.json(msg)
},
)
@@ -785,11 +781,11 @@ export namespace Server {
id: z.string().openapi({ description: "Session ID" }),
}),
),
zValidator("json", Session.CommandInput.omit({ sessionID: true })),
zValidator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.command({ ...body, sessionID })
const msg = await SessionPrompt.command({ ...body, sessionID })
return c.json(msg)
},
)
@@ -815,11 +811,11 @@ export namespace Server {
id: z.string().openapi({ description: "Session ID" }),
}),
),
zValidator("json", Session.ShellInput.omit({ sessionID: true })),
zValidator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.shell({ ...body, sessionID })
const msg = await SessionPrompt.shell({ ...body, sessionID })
return c.json(msg)
},
)
@@ -845,11 +841,11 @@ export namespace Server {
id: z.string(),
}),
),
zValidator("json", Session.RevertInput.omit({ sessionID: true })),
zValidator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
async (c) => {
const id = c.req.valid("param").id
log.info("revert", c.req.valid("json"))
const session = await Session.revert({ sessionID: id, ...c.req.valid("json") })
const session = await SessionRevert.revert({ sessionID: id, ...c.req.valid("json") })
return c.json(session)
},
)
@@ -877,7 +873,7 @@ export namespace Server {
),
async (c) => {
const id = c.req.valid("param").id
const session = await Session.unrevert({ sessionID: id })
const session = await SessionRevert.unrevert({ sessionID: id })
return c.json(session)
},
)

View File

@@ -0,0 +1,124 @@
import { generateText, type ModelMessage } from "ai"
import { Session } from "."
import { Identifier } from "../id/id"
import { Instance } from "../project/instance"
import { Provider } from "../provider/provider"
import { defer } from "../util/defer"
import { MessageV2 } from "./message-v2"
import { SystemPrompt } from "./system"
import { Bus } from "../bus"
import z from "zod"
import type { ModelsDev } from "../provider/models"
import { SessionPrompt } from "./prompt"
import { Flag } from "../flag/flag"
export namespace SessionCompaction {
export const Event = {
Compacted: Bus.event(
"session.compacted",
z.object({
sessionID: z.string(),
}),
),
}
export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false
const context = input.model.limit.context
if (context === 0) return false
const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
const output = Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) || SessionPrompt.OUTPUT_TOKEN_MAX
const usable = context - output
return count > usable
}
export async function run(input: { sessionID: string; providerID: string; modelID: string }) {
await Session.update(input.sessionID, (draft) => {
draft.time.compacting = Date.now()
})
await using _ = defer(async () => {
await Session.update(input.sessionID, (draft) => {
draft.time.compacting = undefined
})
})
const toSummarize = await Session.messages(input.sessionID).then(MessageV2.filterSummarized)
const model = await Provider.getModel(input.providerID, input.modelID)
const system = [
...SystemPrompt.summarize(model.providerID),
...(await SystemPrompt.environment()),
...(await SystemPrompt.custom()),
]
const msg = (await Session.updateMessage({
id: Identifier.ascending("message"),
role: "assistant",
sessionID: input.sessionID,
system,
mode: "build",
path: {
cwd: Instance.directory,
root: Instance.worktree,
},
cost: 0,
tokens: {
output: 0,
input: 0,
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: input.modelID,
providerID: model.providerID,
time: {
created: Date.now(),
},
})) as MessageV2.Assistant
const generated = await generateText({
maxRetries: 10,
model: model.language,
messages: [
...system.map(
(x): ModelMessage => ({
role: "system",
content: x,
}),
),
...MessageV2.toModelMessage(toSummarize),
{
role: "user",
content: [
{
type: "text",
text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
},
],
},
],
})
const usage = Session.getUsage(model.info, generated.usage, generated.providerMetadata)
msg.cost += usage.cost
msg.tokens = usage.tokens
msg.summary = true
msg.time.completed = Date.now()
await Session.updateMessage(msg)
const part = await Session.updatePart({
type: "text",
sessionID: input.sessionID,
messageID: msg.id,
id: Identifier.ascending("part"),
text: generated.text,
time: {
start: Date.now(),
end: Date.now(),
},
})
Bus.publish(Event.Compacted, {
sessionID: input.sessionID,
})
return {
info: msg,
parts: [part],
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -52,6 +52,7 @@ export namespace MessageV2 {
time: z.object({
start: z.number(),
end: z.number(),
compacted: z.number().optional(),
}),
})
.openapi({
@@ -330,6 +331,12 @@ export namespace MessageV2 {
),
}
export const WithParts = z.object({
info: Info,
parts: z.array(Part),
})
export type WithParts = z.infer<typeof WithParts>
export function fromV1(v1: Message.Info) {
if (v1.role === "assistant") {
const info: Assistant = {
@@ -487,8 +494,8 @@ export namespace MessageV2 {
text: part.text,
},
]
// text/plain files are converted into text parts, ignore them
if (part.type === "file" && part.mime !== "text/plain")
// text/plain and directory files are converted into text parts, ignore them
if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory")
return [
{
type: "file",
@@ -528,7 +535,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")
@@ -551,4 +558,10 @@ export namespace MessageV2 {
return convertToModelMessages(result)
}
export function filterSummarized(msgs: { info: MessageV2.Info; parts: MessageV2.Part[] }[]) {
const i = msgs.findLastIndex((m) => m.info.role === "assistant" && !!m.info.summary)
if (i === -1) return msgs.slice()
return msgs.slice(i)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,5 @@
Your operational mode has changed from plan to build. You are no longer in read-only mode. You are permitted to make file changes as necessary and utilize your arsenal of tools as needed.
<system-reminder>
Your operational mode has changed from plan to build.
You are no longer in read-only mode.
You are permitted to make file changes, run shell commands, and utilize your arsenal of tools as needed.
</system-reminder>

View File

@@ -0,0 +1,105 @@
import z from "zod"
import { Identifier } from "../id/id"
import { Snapshot } from "../snapshot"
import { MessageV2 } from "./message-v2"
import { Session } from "."
import { Log } from "../util/log"
import { splitWhen } from "remeda"
import { Storage } from "../storage/storage"
import { Bus } from "../bus"
export namespace SessionRevert {
const log = Log.create({ service: "session.revert" })
export const RevertInput = z.object({
sessionID: Identifier.schema("session"),
messageID: Identifier.schema("message"),
partID: Identifier.schema("part").optional(),
})
export type RevertInput = z.infer<typeof RevertInput>
export async function revert(input: RevertInput) {
const all = await Session.messages(input.sessionID)
let lastUser: MessageV2.User | undefined
const session = await Session.get(input.sessionID)
let revert: Session.Info["revert"]
const patches: Snapshot.Patch[] = []
for (const msg of all) {
if (msg.info.role === "user") lastUser = msg.info
const remaining = []
for (const part of msg.parts) {
if (revert) {
if (part.type === "patch") {
patches.push(part)
}
continue
}
if (!revert) {
if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
// if no useful parts left in message, same as reverting whole message
const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
revert = {
messageID: !partID && lastUser ? lastUser.id : msg.info.id,
partID,
}
}
remaining.push(part)
}
}
}
if (revert) {
const session = await Session.get(input.sessionID)
revert.snapshot = session.revert?.snapshot ?? (await Snapshot.track())
await Snapshot.revert(patches)
if (revert.snapshot) revert.diff = await Snapshot.diff(revert.snapshot)
return Session.update(input.sessionID, (draft) => {
draft.revert = revert
})
}
return session
}
export async function unrevert(input: { sessionID: string }) {
log.info("unreverting", input)
const session = await Session.get(input.sessionID)
if (!session.revert) return session
if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot)
const next = await Session.update(input.sessionID, (draft) => {
draft.revert = undefined
})
return next
}
export async function cleanup(session: Session.Info) {
if (!session.revert) return
const sessionID = session.id
let msgs = await Session.messages(sessionID)
const messageID = session.revert.messageID
const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
msgs = preserve
for (const msg of remove) {
await Storage.remove(["message", sessionID, msg.info.id])
await Bus.publish(MessageV2.Event.Removed, { sessionID: sessionID, messageID: msg.info.id })
}
const last = preserve.at(-1)
if (session.revert.partID && last) {
const partID = session.revert.partID
const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
last.parts = preserveParts
for (const part of removeParts) {
await Storage.remove(["part", last.info.id, part.id])
await Bus.publish(MessageV2.Event.PartRemoved, {
sessionID: sessionID,
messageID: last.info.id,
partID: part.id,
})
}
}
await Session.update(sessionID, (draft) => {
draft.revert = undefined
})
}
}

View File

@@ -55,7 +55,15 @@ export namespace Snapshot {
export async function patch(hash: string): Promise<Patch> {
const git = gitdir()
await $`git --git-dir ${git} add .`.quiet().cwd(Instance.directory).nothrow()
const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(Instance.directory).text()
const result = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.quiet().cwd(Instance.directory).nothrow()
// If git diff fails, return empty patch
if (result.exitCode !== 0) {
log.warn("failed to get diff", { hash, exitCode: result.exitCode })
return { hash, files: [] }
}
const files = result.text()
return {
hash,
files: files
@@ -70,9 +78,19 @@ export namespace Snapshot {
export async function restore(snapshot: string) {
log.info("restore", { commit: snapshot })
const git = gitdir()
await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f`
const result = await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f`
.quiet()
.cwd(Instance.worktree)
.nothrow()
if (result.exitCode !== 0) {
log.error("failed to restore snapshot", {
snapshot,
exitCode: result.exitCode,
stderr: result.stderr.toString(),
stdout: result.stdout.toString(),
})
}
}
export async function revert(patches: Patch[]) {
@@ -97,8 +115,19 @@ export namespace Snapshot {
export async function diff(hash: string) {
const git = gitdir()
const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Instance.worktree).text()
return result.trim()
const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Instance.worktree).nothrow()
if (result.exitCode !== 0) {
log.warn("failed to get diff", {
hash,
exitCode: result.exitCode,
stderr: result.stderr.toString(),
stdout: result.stdout.toString(),
})
return ""
}
return result.text().trim()
}
function gitdir() {

View File

@@ -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,
@@ -623,5 +621,7 @@ export function replace(content: string, oldString: string, newString: string, r
if (notFound) {
throw new Error("oldString not found in content")
}
throw new Error("oldString found multiple times and requires more code context to uniquely identify the intended match")
throw new Error(
"oldString found multiple times and requires more code context to uniquely identify the intended match",
)
}

View File

@@ -71,9 +71,7 @@ export namespace ToolRegistry {
break
case "array":
if (!val.items) throw new Error(`array spec for ${key} requires 'items'`)
base = z.array(
val.items === "string" ? z.string() : val.items === "number" ? z.number() : z.boolean(),
)
base = z.array(val.items === "string" ? z.string() : val.items === "number" ? z.number() : z.boolean())
break
default:
base = z.any()

View File

@@ -6,6 +6,7 @@ import { Bus } from "../bus"
import { MessageV2 } from "../session/message-v2"
import { Identifier } from "../id/id"
import { Agent } from "../agent/agent"
import { SessionPrompt } from "../session/prompt"
export const TaskTool = Tool.define("task", async () => {
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
@@ -49,9 +50,9 @@ export const TaskTool = Tool.define("task", async () => {
}
ctx.abort.addEventListener("abort", () => {
Session.abort(session.id)
SessionPrompt.abort(session.id)
})
const result = await Session.prompt({
const result = await SessionPrompt.prompt({
messageID,
sessionID: session.id,
model: {

View File

@@ -11,14 +11,12 @@ const TodoInfo = z.object({
})
type TodoInfo = z.infer<typeof TodoInfo>
const state = Instance.state(
() => {
const todos: {
[sessionId: string]: TodoInfo[]
} = {}
return todos
},
)
const state = Instance.state(() => {
const todos: {
[sessionId: string]: TodoInfo[]
} = {}
return todos
})
export const TodoWriteTool = Tool.define("todowrite", {
description: DESCRIPTION_WRITE,

View File

@@ -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))
}
}

View File

@@ -6,4 +6,4 @@
/// <reference path="../../sst-env.d.ts" />
import "sst"
export {}
export {}

View File

@@ -7,7 +7,7 @@ describe("BunProc registry configuration", () => {
// Read the bun/index.ts file
const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
const content = await fs.readFile(bunIndexPath, "utf-8")
// Verify that no hardcoded registry is present
expect(content).not.toContain("--registry=")
expect(content).not.toContain("hasNpmRcConfig")
@@ -18,7 +18,7 @@ describe("BunProc registry configuration", () => {
// Read the bun/index.ts file
const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
const content = await fs.readFile(bunIndexPath, "utf-8")
// Verify that it uses Bun's default resolution
expect(content).toContain("Bun's default registry resolution")
expect(content).toContain("Bun will use them automatically")
@@ -29,22 +29,22 @@ describe("BunProc registry configuration", () => {
// Read the bun/index.ts file
const bunIndexPath = path.join(__dirname, "../src/bun/index.ts")
const content = await fs.readFile(bunIndexPath, "utf-8")
// Extract the install function
const installFunctionMatch = content.match(/export async function install[\s\S]*?^ }/m)
expect(installFunctionMatch).toBeTruthy()
if (installFunctionMatch) {
const installFunction = installFunctionMatch[0]
// Verify expected arguments are present
expect(installFunction).toContain('"add"')
expect(installFunction).toContain('"--force"')
expect(installFunction).toContain('"--exact"')
expect(installFunction).toContain('"--cwd"')
expect(installFunction).toContain('Global.Path.cache')
expect(installFunction).toContain("Global.Path.cache")
expect(installFunction).toContain('pkg + "@" + version')
// Verify no registry argument is added
expect(installFunction).not.toContain('"--registry"')
expect(installFunction).not.toContain('args.push("--registry')

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { Session } from "../../src/session/index"
import { SessionPrompt } from "../../src/session/prompt"
describe("fileRegex", () => {
const template = `This is a @valid/path/to/a/file and it should also match at
@@ -23,7 +23,7 @@ as well as @~/home-files and @~/paths/under/home.txt.
If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
const matches = Array.from(template.matchAll(Session.fileRegex))
const matches = Array.from(template.matchAll(SessionPrompt.fileRegex))
test("should extract exactly 12 file references", () => {
expect(matches.length).toBe(12)
@@ -79,13 +79,13 @@ If the reference is \`@quoted/in/backticks\` then it shouldn't match at all.`
test("should not match when preceded by backtick", () => {
const backtickTest = "This `@should/not/match` should be ignored"
const backtickMatches = Array.from(backtickTest.matchAll(Session.fileRegex))
const backtickMatches = Array.from(backtickTest.matchAll(SessionPrompt.fileRegex))
expect(backtickMatches.length).toBe(0)
})
test("should not match email addresses", () => {
const emailTest = "Contact user@example.com for help"
const emailMatches = Array.from(emailTest.matchAll(Session.fileRegex))
const emailMatches = Array.from(emailTest.matchAll(SessionPrompt.fileRegex))
expect(emailMatches.length).toBe(0)
})
})

View File

@@ -2,13 +2,7 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {
"lib": [
"ESNext",
"DOM",
"DOM.Iterable"
],
"customConditions": [
"development"
]
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"customConditions": ["development"]
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "0.6.7",
"version": "0.8.0",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"

View File

@@ -19,10 +19,7 @@ export type PluginInput = {
worktree: string
$: BunShell
Tool: {
define(
id: string,
init: any | (() => Promise<any>)
): any
define(id: string, init: any | (() => Promise<any>)): any
}
z: any // Zod instance for creating schemas
}
@@ -133,7 +130,7 @@ export interface Hooks {
input: {},
output: {
registerHTTP: (tool: HttpToolRegistration) => void | Promise<void>
register: (tool: any) => void | Promise<void> // Tool.Info type from opencode
register: (tool: any) => void | Promise<void> // Tool.Info type from opencode
},
) => Promise<void>
}

Some files were not shown because too many files have changed in this diff Show More