mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 01:06:16 -04:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 535230dce4 | |||
| 555fb53505 | |||
| b1e0a23351 | |||
| 2b69bcccdf | |||
| e03f27381f | |||
| aebd50da7e | |||
| c02f58c2af | |||
| c8f4d54f7f | |||
| 4983d255dd | |||
| f2b4891ff0 | |||
| efcb5abbf7 | |||
| d37e58719e | |||
| c6c153de95 | |||
| 417e8f619c | |||
| f2094b7bb3 | |||
| 176dc51b2e | |||
| f7d9a031e6 | |||
| 3e2478ebf9 | |||
| 1f4e8b4954 | |||
| 9a346a00fb | |||
| 0a13820927 | |||
| c5fa3ee9f8 | |||
| c294a18155 | |||
| c3dc6d6df6 | |||
| ef3425a177 | |||
| 0290b4aaf0 | |||
| 4ceee53480 | |||
| 469dc9095f | |||
| 661d50f95f | |||
| 3978a8e636 | |||
| 983e3b2ee3 | |||
| 1bd198eb34 | |||
| 3c502861a7 | |||
| a52b352b24 | |||
| 79c73267cf | |||
| 54f7fb5019 | |||
| dd97d784b6 | |||
| 91832bd5d7 | |||
| 3abca8fd4b | |||
| f5b3992479 | |||
| 53f1f16122 | |||
| 4614e4983e | |||
| 84f0c63fa1 | |||
| 3e9b451fb4 | |||
| 4ccf683527 | |||
| b236ca9047 | |||
| aa9ebe5d7c | |||
| 4c94753eda | |||
| c3a55c35bb | |||
| d5275010d5 | |||
| dedfa563c2 | |||
| 37b6a55eb1 | |||
| 7aa57accf5 | |||
| c2fa28c1be | |||
| 30aae66320 | |||
| 7b95190df3 | |||
| fa3e7bb9b0 | |||
| 5b56848c3d | |||
| 780e532094 | |||
| 29310957c8 | |||
| 2b0577c725 | |||
| bcd656ffae | |||
| 0e0c5a9b68 | |||
| d36fcc4f8e | |||
| ea82b60d7d | |||
| ea0285a96c | |||
| 6960408ca2 | |||
| fa36195492 | |||
| a6265ea3d2 | |||
| bb3f02b8bb | |||
| bdc0f7c86d | |||
| c8ca036834 | |||
| 8c7fee7840 | |||
| e53fb7f8ed | |||
| b05cbc9101 | |||
| 38e8c42cf0 | |||
| 58fe884327 | |||
| e69d10b6c9 | |||
| 10aee9755c | |||
| 63384bc214 | |||
| 2508e06c58 | |||
| 6487d0607b | |||
| a3513244f1 | |||
| 32b47fcc1e | |||
| fde03d3c93 | |||
| 9045f13acc | |||
| 74f0edc7a8 | |||
| dcabafcdce | |||
| 02e8242c3b | |||
| 57e26bd2fe | |||
| 0f263bfefe | |||
| 34a33dfc16 | |||
| 162a789fa2 | |||
| 198a753b62 | |||
| ab3c22b77a | |||
| f0f6e9cad7 | |||
| bbaae459c6 | |||
| eb3c820fb8 | |||
| 3468808fc6 | |||
| cd42503e2c | |||
| 1cea8b9e77 | |||
| d8fd7b155f | |||
| 248a644fb0 | |||
| c8ff81bae4 | |||
| 74469a0d3d | |||
| 4d481dea7e | |||
| 48328bec6e |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -24,4 +24,4 @@ jobs:
|
||||
env:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
with:
|
||||
model: opencode/sonic
|
||||
model: opencode/sonic
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -72,3 +72,8 @@
|
||||
| 2025-09-05 | 283,769 (+3,339) | 223,793 (+1,690) | 507,562 (+5,029) |
|
||||
| 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) |
|
||||
|
||||
@@ -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=="],
|
||||
|
||||
|
||||
@@ -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.5"
|
||||
"version": "0.7.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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")!)
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -113,7 +117,7 @@ export default function Home() {
|
||||
<h3 data-component="title">npm</h3>
|
||||
<button data-copy data-slot="button">
|
||||
<span>
|
||||
npm install -g <strong>opencode-ai</strong>
|
||||
npm install -g <strong>opencode-ai</strong>
|
||||
</span>
|
||||
<CopyStatus />
|
||||
</button>
|
||||
@@ -122,7 +126,7 @@ export default function Home() {
|
||||
<h3 data-component="title">bun</h3>
|
||||
<button data-copy data-slot="button">
|
||||
<span>
|
||||
bun install -g <strong>opencode-ai</strong>
|
||||
bun install -g <strong>opencode-ai</strong>
|
||||
</span>
|
||||
<CopyStatus />
|
||||
</button>
|
||||
@@ -131,7 +135,7 @@ export default function Home() {
|
||||
<h3 data-component="title">homebrew</h3>
|
||||
<button data-copy data-slot="button">
|
||||
<span>
|
||||
brew install <strong>sst/tap/opencode</strong>
|
||||
brew install <strong>sst/tap/opencode</strong>
|
||||
</span>
|
||||
<CopyStatus />
|
||||
</button>
|
||||
@@ -140,7 +144,7 @@ export default function Home() {
|
||||
<h3 data-component="title">paru</h3>
|
||||
<button data-copy data-slot="button">
|
||||
<span>
|
||||
paru -S <strong>opencode-bin</strong>
|
||||
paru -S <strong>opencode-bin</strong>
|
||||
</span>
|
||||
<CopyStatus />
|
||||
</button>
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -1,310 +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",
|
||||
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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const FREE_WORKSPACES = [
|
||||
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
|
||||
]
|
||||
|
||||
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()
|
||||
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 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 || 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) {
|
||||
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 = isFree ? 0 : 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,
|
||||
})
|
||||
type Usage = {
|
||||
prompt_tokens?: number
|
||||
completion_tokens?: number
|
||||
total_tokens?: number
|
||||
prompt_tokens_details?: {
|
||||
text_tokens?: number
|
||||
audio_tokens?: number
|
||||
image_tokens?: number
|
||||
cached_tokens?: number
|
||||
}
|
||||
completion_tokens_details?: {
|
||||
reasoning_tokens?: number
|
||||
audio_tokens?: number
|
||||
accepted_prediction_tokens?: number
|
||||
rejected_prediction_tokens?: number
|
||||
}
|
||||
}
|
||||
|
||||
export function POST(input: APIEvent) {
|
||||
let usage: 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
|
||||
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(chunk.slice(6)) as { usage?: Usage }
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
@@ -12,14 +12,10 @@
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"types": [
|
||||
"vinxi/types/client"
|
||||
],
|
||||
"types": ["vinxi/types/client"],
|
||||
"isolatedModules": true,
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `key` ADD `old_name` varchar(255);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `key` ADD CONSTRAINT `name` UNIQUE(`workspace_id`,`name`);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `usage` ADD `provider` varchar(255);
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `usage` MODIFY COLUMN `provider` varchar(255) NOT NULL;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `usage` ADD `cache_write_5m_tokens` int;--> statement-breakpoint
|
||||
ALTER TABLE `usage` ADD `cache_write_1h_tokens` int;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `usage` DROP COLUMN `cache_write_tokens`;
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cloud-core",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.9",
|
||||
"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": {
|
||||
|
||||
+26
-6
@@ -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)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)],
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/cloud-function",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.9",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Resource } from "@opencode/cloud-resource"
|
||||
import type { TraceItem } from "@cloudflare/workers-types"
|
||||
|
||||
export default {
|
||||
async tail(events: TraceItem[]) {
|
||||
for (const event of events) {
|
||||
if (!event.event) continue
|
||||
if (!("request" in event.event)) continue
|
||||
if (event.event.request.method !== "POST") continue
|
||||
|
||||
const url = new URL(event.event.request.url)
|
||||
if (url.pathname !== "/zen/v1/chat/completions") return
|
||||
|
||||
let metrics = {
|
||||
event_type: "completions",
|
||||
"cf.continent": event.event.request.cf?.continent,
|
||||
"cf.country": event.event.request.cf?.country,
|
||||
"cf.city": event.event.request.cf?.city,
|
||||
"cf.region": event.event.request.cf?.region,
|
||||
"cf.latitude": event.event.request.cf?.latitude,
|
||||
"cf.longitude": event.event.request.cf?.longitude,
|
||||
"cf.timezone": event.event.request.cf?.timezone,
|
||||
duration: event.wallTime,
|
||||
request_length: parseInt(event.event.request.headers["content-length"] ?? "0"),
|
||||
status: event.event.response?.status ?? 0,
|
||||
ip: event.event.request.headers["x-real-ip"],
|
||||
}
|
||||
for (const log of event.logs) {
|
||||
for (const message of log.message) {
|
||||
if (!message.startsWith("_metric:")) continue
|
||||
metrics = { ...metrics, ...JSON.parse(message.slice(8)) }
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(metrics, null, 2))
|
||||
|
||||
const ret = await fetch("https://api.honeycomb.io/1/events/zen", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Honeycomb-Event-Time": (event.eventTimestamp ?? Date.now()).toString(),
|
||||
"X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value,
|
||||
},
|
||||
body: JSON.stringify(metrics),
|
||||
})
|
||||
console.log(ret.status)
|
||||
console.log(await ret.text())
|
||||
}
|
||||
},
|
||||
}
|
||||
Vendored
+66
-53
@@ -6,78 +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
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
OPENAI_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"XAI_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
STRIPE_SECRET_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
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
@@ -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)`)
|
||||
},
|
||||
|
||||
Vendored
+66
-53
@@ -6,78 +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
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
OPENAI_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"XAI_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
STRIPE_SECRET_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
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": [
|
||||
"@cloudflare/workers-types",
|
||||
"node"
|
||||
]
|
||||
"types": ["@cloudflare/workers-types", "node"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "@opencode/cloud-scripts",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.9",
|
||||
"$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:*",
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -1 +0,0 @@
|
||||
// placeholder
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
+16
-4
@@ -7,7 +7,7 @@ import { domain } from "./stage"
|
||||
|
||||
const cluster = planetscale.getDatabaseOutput({
|
||||
name: "opencode",
|
||||
organization: "sst",
|
||||
organization: "anomalyco",
|
||||
})
|
||||
|
||||
const branch =
|
||||
@@ -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!) },
|
||||
@@ -114,6 +116,15 @@ const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
|
||||
// CONSOLE
|
||||
////////////////
|
||||
|
||||
let logProcessor
|
||||
if ($app.stage === "production" || $app.stage === "frank") {
|
||||
const HONEYCOMB_API_KEY = new sst.Secret("HONEYCOMB_API_KEY")
|
||||
logProcessor = new sst.cloudflare.Worker("LogProcessor", {
|
||||
handler: "cloud/function/src/log-processor.ts",
|
||||
link: [HONEYCOMB_API_KEY],
|
||||
})
|
||||
}
|
||||
|
||||
new sst.cloudflare.x.SolidStart("Console", {
|
||||
domain,
|
||||
path: "cloud/app",
|
||||
@@ -123,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!),
|
||||
@@ -135,9 +148,8 @@ new sst.cloudflare.x.SolidStart("Console", {
|
||||
server: {
|
||||
transform: {
|
||||
worker: {
|
||||
placement: {
|
||||
mode: "smart",
|
||||
},
|
||||
placement: { mode: "smart" },
|
||||
tailConsumers: logProcessor ? [{ service: logProcessor.nodes.worker.scriptName }] : [],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+3
-2
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.9",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
Vendored
+66
-53
@@ -6,78 +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
|
||||
}
|
||||
"STRIPE_SECRET_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
GOOGLE_CLIENT_ID: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"STRIPE_WEBHOOK_SECRET": {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
HONEYCOMB_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"Web": {
|
||||
"type": "sst.cloudflare.Astro"
|
||||
"url": string
|
||||
OPENAI_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
"XAI_API_KEY": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
STRIPE_SECRET_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
|
||||
Api: cloudflare.Service
|
||||
AuthApi: cloudflare.Service
|
||||
AuthStorage: cloudflare.KVNamespace
|
||||
Bucket: cloudflare.R2Bucket
|
||||
LogProcessor: cloudflare.Service
|
||||
}
|
||||
}
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
@@ -52,5 +52,7 @@ echo It seems that your package manager failed to install the right version of t
|
||||
exit /b 1
|
||||
|
||||
:execute
|
||||
rem Execute the binary with all arguments
|
||||
"%resolved%" %*
|
||||
rem Execute the binary with all arguments in the same console window
|
||||
rem Use start /b /wait to ensure it runs in the current shell context for all shells
|
||||
start /b /wait "" "%resolved%" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.9",
|
||||
"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:",
|
||||
|
||||
@@ -70,6 +70,11 @@ function findBinary() {
|
||||
|
||||
function main() {
|
||||
try {
|
||||
if (os.platform() === "win32") {
|
||||
console.log("Windows detected, skipping postinstall")
|
||||
return
|
||||
}
|
||||
|
||||
const binaryPath = findBinary()
|
||||
const binScript = path.join(__dirname, "bin", "opencode")
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
function main() {
|
||||
if (os.platform() !== "win32") {
|
||||
console.log("Non-Windows platform detected, skipping preinstall")
|
||||
return
|
||||
}
|
||||
|
||||
const binDir = path.join(__dirname, "bin")
|
||||
const unixScript = path.join(binDir, "opencode")
|
||||
|
||||
console.log("Windows detected: Configuring bin scripts for Windows")
|
||||
|
||||
if (fs.existsSync(unixScript)) {
|
||||
console.log("Removing Unix shell script from bin/")
|
||||
fs.unlinkSync(unixScript)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
main()
|
||||
} catch (error) {
|
||||
console.error("Preinstall script error:", error.message)
|
||||
process.exit(0)
|
||||
}
|
||||
@@ -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) &&
|
||||
@@ -66,6 +79,7 @@ for (const [os, arch] of targets) {
|
||||
|
||||
await $`mkdir -p ./dist/${pkg.name}`
|
||||
await $`cp -r ./bin ./dist/${pkg.name}/bin`
|
||||
await $`cp ./script/preinstall.mjs ./dist/${pkg.name}/preinstall.mjs`
|
||||
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
|
||||
await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||
JSON.stringify(
|
||||
@@ -75,6 +89,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||
[pkg.name]: `./bin/${pkg.name}`,
|
||||
},
|
||||
scripts: {
|
||||
preinstall: "node ./preinstall.mjs",
|
||||
postinstall: "node ./postinstall.mjs",
|
||||
},
|
||||
version,
|
||||
|
||||
@@ -50,7 +50,7 @@ export namespace Agent {
|
||||
|
||||
const planPermission = mergeAgentPermissions(
|
||||
{
|
||||
edit: "ask",
|
||||
edit: "deny",
|
||||
bash: "ask",
|
||||
webfetch: "allow",
|
||||
},
|
||||
|
||||
@@ -71,7 +71,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 +79,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 +104,7 @@ export const RunCommand = cmd({
|
||||
|
||||
if (!session) {
|
||||
UI.error("Session not found")
|
||||
return
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const cfg = await Config.get()
|
||||
@@ -221,6 +221,7 @@ export const RunCommand = cmd({
|
||||
if (errorMsg) process.stdout.write(errorMsg)
|
||||
}
|
||||
UI.empty()
|
||||
if (errorMsg) process.exit(1)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Ide } from "../../ide"
|
||||
import { Flag } from "../../flag/flag"
|
||||
import { Session } from "../../session"
|
||||
import { Instance } from "../../project/instance"
|
||||
import { $ } from "bun"
|
||||
|
||||
declare global {
|
||||
const OPENCODE_TUI_PATH: string
|
||||
@@ -111,8 +112,7 @@ export const TuiCommand = cmd({
|
||||
hostname: args.hostname,
|
||||
})
|
||||
|
||||
let cmd = ["go", "run", "./main.go"]
|
||||
let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
|
||||
let cmd = [] as string[]
|
||||
const tui = Bun.embeddedFiles.find((item) => (item as File).name.includes("tui")) as File
|
||||
if (tui) {
|
||||
let binaryName = tui.name
|
||||
@@ -125,9 +125,13 @@ export const TuiCommand = cmd({
|
||||
await Bun.write(file, tui, { mode: 0o755 })
|
||||
await fs.chmod(binary, 0o755)
|
||||
}
|
||||
cwd = process.cwd()
|
||||
cmd = [binary]
|
||||
}
|
||||
if (!tui) {
|
||||
const dir = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
|
||||
await $`go build -o ./dist/tui ./main.go`.cwd(dir)
|
||||
cmd = [path.join(dir, "dist/tui")]
|
||||
}
|
||||
Log.Default.info("tui", {
|
||||
cmd,
|
||||
})
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
@@ -488,6 +499,7 @@ export namespace Config {
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
disable_paste_summary: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -7,6 +7,7 @@ export namespace Flag {
|
||||
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
|
||||
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")
|
||||
|
||||
function truthy(key: string) {
|
||||
const value = process.env[key]?.toLowerCase()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -84,7 +84,11 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(ExportCommand)
|
||||
.command(GithubCommand)
|
||||
.fail((msg) => {
|
||||
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||
if (
|
||||
msg.startsWith("Unknown argument") ||
|
||||
msg.startsWith("Not enough non-option arguments") ||
|
||||
msg.startsWith("Invalid values:")
|
||||
) {
|
||||
cli.showHelp("log")
|
||||
}
|
||||
process.exit(1)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: {},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Server } from "../server/server"
|
||||
import { BunProc } from "../bun"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Flag } from "../flag/flag"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
|
||||
export namespace Plugin {
|
||||
const log = Log.create({ service: "plugin" })
|
||||
@@ -24,6 +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),
|
||||
}
|
||||
const plugins = [...(config.plugin ?? [])]
|
||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||
@@ -75,6 +78,14 @@ export namespace Plugin {
|
||||
const config = await Config.get()
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
Bus.subscribeAll(async (input) => {
|
||||
const hooks = await state().then((x) => x.hooks)
|
||||
|
||||
@@ -28,7 +28,9 @@ export namespace ModelsDev {
|
||||
context: z.number(),
|
||||
output: z.number(),
|
||||
}),
|
||||
experimental: z.boolean().optional(),
|
||||
options: z.record(z.any()),
|
||||
provider: z.object({ npm: z.string() }).optional(),
|
||||
})
|
||||
.openapi({
|
||||
ref: "Model",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { NamedError } from "../util/error"
|
||||
import { Auth } from "../auth"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Global } from "../global"
|
||||
import { Flag } from "../flag/flag"
|
||||
|
||||
export namespace Provider {
|
||||
const log = Log.create({ service: "provider" })
|
||||
@@ -81,7 +82,8 @@ export namespace Provider {
|
||||
switch (regionPrefix) {
|
||||
case "us": {
|
||||
const modelRequiresPrefix = ["claude", "deepseek"].some((m) => modelID.includes(m))
|
||||
if (modelRequiresPrefix) {
|
||||
const isGovCloud = region.startsWith("us-gov")
|
||||
if (modelRequiresPrefix && !isGovCloud) {
|
||||
modelID = `${regionPrefix}.${modelID}`
|
||||
}
|
||||
break
|
||||
@@ -159,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")
|
||||
|
||||
@@ -233,6 +235,7 @@ export namespace Provider {
|
||||
context: 0,
|
||||
output: 0,
|
||||
},
|
||||
provider: model.provider ?? existing?.provider,
|
||||
}
|
||||
parsed.models[modelID] = parsedModel
|
||||
}
|
||||
@@ -287,12 +290,15 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
for (const [providerID, provider] of Object.entries(providers)) {
|
||||
// Filter out blacklisted models
|
||||
const filteredModels = Object.fromEntries(
|
||||
Object.entries(provider.info.models).filter(
|
||||
([modelID]) =>
|
||||
modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
|
||||
),
|
||||
Object.entries(provider.info.models)
|
||||
// Filter out blacklisted models
|
||||
.filter(
|
||||
([modelID]) =>
|
||||
modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
|
||||
)
|
||||
// Filter out experimental models
|
||||
.filter(([, model]) => !model.experimental || Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS),
|
||||
)
|
||||
provider.info.models = filteredModels
|
||||
|
||||
@@ -314,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 })
|
||||
@@ -361,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)
|
||||
|
||||
@@ -23,6 +23,8 @@ import { Auth } from "../auth"
|
||||
import { Command } from "../command"
|
||||
import { Global } from "../global"
|
||||
import { ProjectRoute } from "./project"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
import { zodToJsonSchema } from "zod-to-json-schema"
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
@@ -46,6 +48,29 @@ const ERRORS = {
|
||||
export namespace Server {
|
||||
const log = Log.create({ service: "server" })
|
||||
|
||||
// Schemas for HTTP tool registration
|
||||
const HttpParamSpec = z
|
||||
.object({
|
||||
type: z.enum(["string", "number", "boolean", "array"]),
|
||||
description: z.string().optional(),
|
||||
optional: z.boolean().optional(),
|
||||
items: z.enum(["string", "number", "boolean"]).optional(),
|
||||
})
|
||||
.openapi({ ref: "HttpParamSpec" })
|
||||
|
||||
const HttpToolRegistration = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
description: z.string(),
|
||||
parameters: z.object({
|
||||
type: z.literal("object"),
|
||||
properties: z.record(HttpParamSpec),
|
||||
}),
|
||||
callbackUrl: z.string(),
|
||||
headers: z.record(z.string(), z.string()).optional(),
|
||||
})
|
||||
.openapi({ ref: "HttpToolRegistration" })
|
||||
|
||||
export const Event = {
|
||||
Connected: Bus.event("server.connected", z.object({})),
|
||||
}
|
||||
@@ -166,6 +191,99 @@ export namespace Server {
|
||||
return c.json(await Config.get())
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/experimental/tool/register",
|
||||
describeRoute({
|
||||
description: "Register a new HTTP callback tool",
|
||||
operationId: "tool.register",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tool registered successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
zValidator("json", HttpToolRegistration),
|
||||
async (c) => {
|
||||
ToolRegistry.registerHTTP(c.req.valid("json"))
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/experimental/tool/ids",
|
||||
describeRoute({
|
||||
description: "List all tool IDs (including built-in and dynamically registered)",
|
||||
operationId: "tool.ids",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tool IDs",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.array(z.string()).openapi({ ref: "ToolIDs" })),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(ToolRegistry.ids())
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/experimental/tool",
|
||||
describeRoute({
|
||||
description: "List tools with JSON schema parameters for a provider/model",
|
||||
operationId: "tool.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tools",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
id: z.string(),
|
||||
description: z.string(),
|
||||
parameters: z.any(),
|
||||
})
|
||||
.openapi({ ref: "ToolListItem" }),
|
||||
)
|
||||
.openapi({ ref: "ToolList" }),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
zValidator(
|
||||
"query",
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
model: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { provider, model } = c.req.valid("query")
|
||||
const tools = await ToolRegistry.tools(provider, model)
|
||||
return c.json(
|
||||
tools.map((t) => ({
|
||||
id: t.id,
|
||||
description: t.description,
|
||||
// Handle both Zod schemas and plain JSON schemas
|
||||
parameters: (t.parameters as any)?._def ? zodToJsonSchema(t.parameters as any) : t.parameters,
|
||||
})),
|
||||
)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/path",
|
||||
describeRoute({
|
||||
|
||||
@@ -52,6 +52,8 @@ import { ulid } from "ulid"
|
||||
import { defer } from "../util/defer"
|
||||
import { Command } from "../command"
|
||||
import { $ } from "bun"
|
||||
import { ListTool } from "../tool/ls"
|
||||
import { Token } from "../util/token"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -85,6 +87,7 @@ export namespace Session {
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
updated: z.number(),
|
||||
compacting: z.number().optional(),
|
||||
}),
|
||||
revert: z
|
||||
.object({
|
||||
@@ -136,12 +139,17 @@ export namespace Session {
|
||||
error: MessageV2.Assistant.shape.error,
|
||||
}),
|
||||
),
|
||||
Compacted: Bus.event(
|
||||
"session.compacted",
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
const state = Instance.state(
|
||||
() => {
|
||||
const pending = new Map<string, AbortController>()
|
||||
const autoCompacting = new Map<string, boolean>()
|
||||
const queued = new Map<
|
||||
string,
|
||||
{
|
||||
@@ -155,7 +163,6 @@ export namespace Session {
|
||||
|
||||
return {
|
||||
pending,
|
||||
autoCompacting,
|
||||
queued,
|
||||
}
|
||||
},
|
||||
@@ -355,6 +362,7 @@ export namespace Session {
|
||||
Bus.publish(MessageV2.Event.Updated, {
|
||||
info: msg,
|
||||
})
|
||||
return msg
|
||||
}
|
||||
|
||||
async function updatePart(part: MessageV2.Part) {
|
||||
@@ -365,6 +373,36 @@ export namespace Session {
|
||||
return part
|
||||
}
|
||||
|
||||
async function cleanupRevert(session: Info) {
|
||||
if (!session.revert) return
|
||||
const sessionID = session.id
|
||||
let msgs = await messages(sessionID)
|
||||
const messageID = session.revert.messageID
|
||||
const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
|
||||
msgs = preserve
|
||||
for (const msg of remove) {
|
||||
await Storage.remove(["message", sessionID, msg.info.id])
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: sessionID, messageID: msg.info.id })
|
||||
}
|
||||
const last = preserve.at(-1)
|
||||
if (session.revert.partID && last) {
|
||||
const partID = session.revert.partID
|
||||
const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
|
||||
last.parts = preserveParts
|
||||
for (const part of removeParts) {
|
||||
await Storage.remove(["part", last.info.id, part.id])
|
||||
await Bus.publish(MessageV2.Event.PartRemoved, {
|
||||
sessionID: sessionID,
|
||||
messageID: last.info.id,
|
||||
partID: part.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
await update(sessionID, (draft) => {
|
||||
draft.revert = undefined
|
||||
})
|
||||
}
|
||||
|
||||
export const PromptInput = z.object({
|
||||
sessionID: Identifier.schema("session"),
|
||||
messageID: Identifier.schema("message").optional(),
|
||||
@@ -425,31 +463,7 @@ export namespace Session {
|
||||
// Process revert cleanup first, before creating new messages
|
||||
const session = await get(input.sessionID)
|
||||
if (session.revert) {
|
||||
let msgs = await messages(input.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", input.sessionID, msg.info.id])
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.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: input.sessionID,
|
||||
messageID: last.info.id,
|
||||
partID: part.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
await update(input.sessionID, (draft) => {
|
||||
draft.revert = undefined
|
||||
})
|
||||
await cleanupRevert(session)
|
||||
}
|
||||
const userMsg: MessageV2.Info = {
|
||||
id: input.messageID ?? Identifier.ascending("message"),
|
||||
@@ -570,7 +584,45 @@ export namespace Session {
|
||||
]
|
||||
}
|
||||
|
||||
let file = Bun.file(filePath)
|
||||
if (part.mime === "application/x-directory") {
|
||||
const args = { path: filePath }
|
||||
const result = await ListTool.init().then((t) =>
|
||||
t.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: new AbortController().signal,
|
||||
agent: input.agent!,
|
||||
messageID: userMsg.id,
|
||||
extra: { bypassCwdCheck: true },
|
||||
metadata: async () => {},
|
||||
}),
|
||||
)
|
||||
return [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: `Called the list tool with the following input: ${JSON.stringify(args)}`,
|
||||
},
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
synthetic: true,
|
||||
text: result.output,
|
||||
},
|
||||
{
|
||||
...part,
|
||||
id: part.id ?? Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const file = Bun.file(filePath)
|
||||
FileTime.read(input.sessionID, filePath)
|
||||
return [
|
||||
{
|
||||
@@ -667,30 +719,29 @@ export namespace Session {
|
||||
}
|
||||
return Provider.defaultModel()
|
||||
})().then((x) => Provider.getModel(x.providerID, x.modelID))
|
||||
let msgs = await messages(input.sessionID)
|
||||
|
||||
const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
let msgs = await messages(input.sessionID).then((x) => sinceSummary(x))
|
||||
|
||||
const lastAssistant = msgs.findLast((msg) => msg.info.role === "assistant")
|
||||
if (
|
||||
lastAssistant?.info.role === "assistant" &&
|
||||
needsCompaction({
|
||||
tokens: lastAssistant.info.tokens,
|
||||
model: model.info,
|
||||
})
|
||||
) {
|
||||
const msg = await summarize({
|
||||
sessionID: input.sessionID,
|
||||
providerID: model.providerID,
|
||||
modelID: model.info.id,
|
||||
})
|
||||
msgs = [msg, { info: userMsg, parts: userParts }]
|
||||
}
|
||||
|
||||
const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
|
||||
|
||||
// auto summarize if too long
|
||||
if (previous && previous.tokens) {
|
||||
const tokens =
|
||||
previous.tokens.input + previous.tokens.cache.read + previous.tokens.cache.write + previous.tokens.output
|
||||
if (model.info.limit.context && tokens > Math.max((model.info.limit.context - outputLimit) * 0.9, 0)) {
|
||||
state().autoCompacting.set(input.sessionID, true)
|
||||
|
||||
await summarize({
|
||||
sessionID: input.sessionID,
|
||||
providerID: model.providerID,
|
||||
modelID: model.info.id,
|
||||
})
|
||||
return prompt(input)
|
||||
}
|
||||
}
|
||||
using abort = lock(input.sessionID)
|
||||
|
||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
|
||||
const numRealUserMsgs = msgs.filter(
|
||||
(m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic),
|
||||
).length
|
||||
@@ -760,8 +811,8 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
const lastAssistantMsg = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
if (lastAssistantMsg?.mode === "plan" && agent.name === "build") {
|
||||
const wasPlan = msgs.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan")
|
||||
if (wasPlan && agent.name === "build") {
|
||||
msgs.at(-1)?.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
@@ -785,39 +836,21 @@ export namespace Session {
|
||||
const [first, ...rest] = system
|
||||
system = [first, rest.join("\n")]
|
||||
|
||||
const assistantMsg: MessageV2.Info = {
|
||||
id: Identifier.ascending("message"),
|
||||
role: "assistant",
|
||||
system,
|
||||
mode: inputAgent,
|
||||
path: {
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
const processor = await createProcessor({
|
||||
sessionID: input.sessionID,
|
||||
}
|
||||
await updateMessage(assistantMsg)
|
||||
model: model.info,
|
||||
providerID: model.providerID,
|
||||
agent: inputAgent,
|
||||
system,
|
||||
})
|
||||
|
||||
await using _ = defer(async () => {
|
||||
if (assistantMsg.time.completed) return
|
||||
await Storage.remove(["session", "message", input.sessionID, assistantMsg.id])
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: assistantMsg.id })
|
||||
if (processor.message.time.completed) return
|
||||
await Storage.remove(["session", "message", input.sessionID, processor.message.id])
|
||||
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: processor.message.id })
|
||||
})
|
||||
const tools: Record<string, AITool> = {}
|
||||
|
||||
const processor = createProcessor(assistantMsg, model.info)
|
||||
|
||||
const enabledTools = pipe(
|
||||
agent.tools,
|
||||
mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)),
|
||||
@@ -844,7 +877,7 @@ export namespace Session {
|
||||
const result = await item.execute(args, {
|
||||
sessionID: input.sessionID,
|
||||
abort: options.abortSignal!,
|
||||
messageID: assistantMsg.id,
|
||||
messageID: processor.message.id,
|
||||
callID: options.toolCallId,
|
||||
agent: agent.name,
|
||||
metadata: async (val) => {
|
||||
@@ -948,15 +981,39 @@ export namespace Session {
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
let pointer = 0
|
||||
const stream = streamText({
|
||||
onError(e) {
|
||||
log.error("streamText error", {
|
||||
error: e,
|
||||
})
|
||||
},
|
||||
async prepareStep({ messages }) {
|
||||
async prepareStep({ messages, steps }) {
|
||||
const step = steps.at(-1)
|
||||
if (
|
||||
step &&
|
||||
needsCompaction({
|
||||
tokens: getUsage(model.info, step.usage, step.providerMetadata).tokens,
|
||||
model: model.info,
|
||||
}) &&
|
||||
false
|
||||
) {
|
||||
await processor.end()
|
||||
const msg = await Session.summarize({
|
||||
sessionID: input.sessionID,
|
||||
providerID: model.providerID,
|
||||
modelID: model.info.id,
|
||||
})
|
||||
await processor.next()
|
||||
pointer = messages.length
|
||||
messages.push(...MessageV2.toModelMessage([msg]))
|
||||
}
|
||||
|
||||
// Add queued messages to the stream
|
||||
const queue = (state().queued.get(input.sessionID) ?? []).filter((x) => !x.processed)
|
||||
if (queue.length) {
|
||||
await processor.end()
|
||||
for (const item of queue) {
|
||||
if (item.processed) continue
|
||||
messages.push(
|
||||
@@ -969,38 +1026,24 @@ export namespace Session {
|
||||
)
|
||||
item.processed = true
|
||||
}
|
||||
assistantMsg.time.completed = Date.now()
|
||||
await updateMessage(assistantMsg)
|
||||
Object.assign(assistantMsg, {
|
||||
id: Identifier.ascending("message"),
|
||||
role: "assistant",
|
||||
system,
|
||||
path: {
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: model.modelID,
|
||||
providerID: model.providerID,
|
||||
mode: inputAgent,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
sessionID: input.sessionID,
|
||||
})
|
||||
await updateMessage(assistantMsg)
|
||||
await processor.next()
|
||||
}
|
||||
return {
|
||||
messages,
|
||||
messages: messages.slice(pointer),
|
||||
}
|
||||
},
|
||||
async experimental_repairToolCall(input) {
|
||||
const lower = input.toolCall.toolName.toLowerCase()
|
||||
if (lower !== input.toolCall.toolName && tools[lower]) {
|
||||
log.info("repairing tool call", {
|
||||
tool: input.toolCall.toolName,
|
||||
repaired: lower,
|
||||
})
|
||||
return {
|
||||
...input.toolCall,
|
||||
toolName: lower,
|
||||
}
|
||||
}
|
||||
return {
|
||||
...input.toolCall,
|
||||
input: JSON.stringify({
|
||||
@@ -1074,6 +1117,7 @@ export namespace Session {
|
||||
item.callback(result)
|
||||
}
|
||||
state().queued.delete(input.sessionID)
|
||||
// Session.prune(input)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1085,6 +1129,10 @@ export namespace Session {
|
||||
export type ShellInput = z.infer<typeof ShellInput>
|
||||
export async function shell(input: ShellInput) {
|
||||
using abort = lock(input.sessionID)
|
||||
const session = await get(input.sessionID)
|
||||
if (session.revert) {
|
||||
await cleanupRevert(session)
|
||||
}
|
||||
const userMsg: MessageV2.User = {
|
||||
id: Identifier.ascending("message"),
|
||||
sessionID: input.sessionID,
|
||||
@@ -1297,7 +1345,15 @@ export namespace Session {
|
||||
return
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) return
|
||||
if (stats.isDirectory()) {
|
||||
parts.push({
|
||||
type: "file",
|
||||
url: `file://${filepath}`,
|
||||
filename: name,
|
||||
mime: "application/x-directory",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
parts.push({
|
||||
type: "file",
|
||||
@@ -1333,11 +1389,60 @@ export namespace Session {
|
||||
})
|
||||
}
|
||||
|
||||
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
|
||||
async function createProcessor(input: {
|
||||
sessionID: string
|
||||
providerID: string
|
||||
model: ModelsDev.Model
|
||||
system: string[]
|
||||
agent: string
|
||||
}) {
|
||||
const toolcalls: Record<string, MessageV2.ToolPart> = {}
|
||||
let snapshot: string | undefined
|
||||
let shouldStop = false
|
||||
return {
|
||||
|
||||
async function createMessage() {
|
||||
const msg: MessageV2.Info = {
|
||||
id: Identifier.ascending("message"),
|
||||
role: "assistant",
|
||||
system: input.system,
|
||||
mode: input.agent,
|
||||
path: {
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
cost: 0,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: input.model.id,
|
||||
providerID: input.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
sessionID: input.sessionID,
|
||||
}
|
||||
await updateMessage(msg)
|
||||
return msg
|
||||
}
|
||||
|
||||
let assistantMsg = await createMessage()
|
||||
|
||||
const result = {
|
||||
async end() {
|
||||
if (assistantMsg) {
|
||||
assistantMsg.time.completed = Date.now()
|
||||
await updateMessage(assistantMsg)
|
||||
}
|
||||
},
|
||||
async next() {
|
||||
assistantMsg = await createMessage()
|
||||
},
|
||||
get message() {
|
||||
return assistantMsg
|
||||
},
|
||||
partFromToolCall(toolCallID: string) {
|
||||
return toolcalls[toolCallID]
|
||||
},
|
||||
@@ -1493,7 +1598,7 @@ export namespace Session {
|
||||
break
|
||||
|
||||
case "finish-step":
|
||||
const usage = getUsage(model, value.usage, value.providerMetadata)
|
||||
const usage = getUsage(input.model, value.usage, value.providerMetadata)
|
||||
assistantMsg.cost += usage.cost
|
||||
assistantMsg.tokens = usage.tokens
|
||||
await updatePart({
|
||||
@@ -1584,7 +1689,7 @@ export namespace Session {
|
||||
case LoadAPIKeyError.isInstance(e):
|
||||
assistantMsg.error = new MessageV2.AuthError(
|
||||
{
|
||||
providerID: model.id,
|
||||
providerID: input.providerID,
|
||||
message: e.message,
|
||||
},
|
||||
{ cause: e },
|
||||
@@ -1623,6 +1728,7 @@ export namespace Session {
|
||||
return { info: assistantMsg, parts: p }
|
||||
},
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const RevertInput = z.object({
|
||||
@@ -1688,10 +1794,15 @@ export namespace Session {
|
||||
}
|
||||
|
||||
export async function summarize(input: { sessionID: string; providerID: string; modelID: string }) {
|
||||
using abort = lock(input.sessionID)
|
||||
const msgs = await messages(input.sessionID)
|
||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
|
||||
await update(input.sessionID, (draft) => {
|
||||
draft.time.compacting = Date.now()
|
||||
})
|
||||
await using _ = defer(async () => {
|
||||
await update(input.sessionID, (draft) => {
|
||||
draft.time.compacting = undefined
|
||||
})
|
||||
})
|
||||
const toSummarize = await messages(input.sessionID).then((x) => sinceSummary(x))
|
||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||
const system = [
|
||||
...SystemPrompt.summarize(model.providerID),
|
||||
@@ -1699,7 +1810,7 @@ export namespace Session {
|
||||
...(await SystemPrompt.custom()),
|
||||
]
|
||||
|
||||
const next: MessageV2.Info = {
|
||||
const msg = (await updateMessage({
|
||||
id: Identifier.ascending("message"),
|
||||
role: "assistant",
|
||||
sessionID: input.sessionID,
|
||||
@@ -1709,26 +1820,21 @@ export namespace Session {
|
||||
cwd: Instance.directory,
|
||||
root: Instance.worktree,
|
||||
},
|
||||
summary: true,
|
||||
cost: 0,
|
||||
modelID: input.modelID,
|
||||
providerID: model.providerID,
|
||||
tokens: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
input: 0,
|
||||
reasoning: 0,
|
||||
cache: { read: 0, write: 0 },
|
||||
},
|
||||
modelID: input.modelID,
|
||||
providerID: model.providerID,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
await updateMessage(next)
|
||||
|
||||
const processor = createProcessor(next, model.info)
|
||||
const stream = streamText({
|
||||
})) as MessageV2.Assistant
|
||||
const generated = await generateText({
|
||||
maxRetries: 10,
|
||||
abortSignal: abort.signal,
|
||||
model: model.language,
|
||||
messages: [
|
||||
...system.map(
|
||||
@@ -1737,7 +1843,7 @@ export namespace Session {
|
||||
content: x,
|
||||
}),
|
||||
),
|
||||
...MessageV2.toModelMessage(filtered),
|
||||
...MessageV2.toModelMessage(toSummarize),
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
@@ -1749,9 +1855,77 @@ export namespace Session {
|
||||
},
|
||||
],
|
||||
})
|
||||
const usage = getUsage(model.info, generated.usage, generated.providerMetadata)
|
||||
msg.cost += usage.cost
|
||||
msg.tokens = usage.tokens
|
||||
msg.summary = true
|
||||
msg.time.completed = Date.now()
|
||||
await updateMessage(msg)
|
||||
const part = await updatePart({
|
||||
type: "text",
|
||||
sessionID: input.sessionID,
|
||||
messageID: msg.id,
|
||||
id: Identifier.ascending("part"),
|
||||
text: generated.text,
|
||||
time: {
|
||||
start: Date.now(),
|
||||
end: Date.now(),
|
||||
},
|
||||
})
|
||||
|
||||
const result = await processor.process(stream)
|
||||
return result
|
||||
Bus.publish(Event.Compacted, {
|
||||
sessionID: input.sessionID,
|
||||
})
|
||||
|
||||
return {
|
||||
info: msg,
|
||||
parts: [part],
|
||||
}
|
||||
}
|
||||
|
||||
function sinceSummary(msgs: { info: MessageV2.Info; parts: MessageV2.Part[] }[]) {
|
||||
const result = []
|
||||
for (let i = msgs.length - 1; i >= 0; i--) {
|
||||
const msg = msgs[i]
|
||||
result.push(msg)
|
||||
if (msg.info.role === "assistant" && msg.info.summary) break
|
||||
}
|
||||
return result.toReversed()
|
||||
}
|
||||
|
||||
function needsCompaction(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
|
||||
const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
|
||||
const output = Math.min(input.model.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
|
||||
const usable = input.model.limit.context - output
|
||||
return count > usable
|
||||
}
|
||||
|
||||
// goes backwards through parts until there are 40_000 tokens worth of tool
|
||||
// calls. then erases output of previous tool calls. idea is to throw away old
|
||||
// tool calls that are no longer relevant.
|
||||
export async function prune(input: { sessionID: string }) {
|
||||
const msgs = await messages(input.sessionID)
|
||||
let sum = 0
|
||||
for (let msgIndex = msgs.length - 2; msgIndex >= 0; msgIndex--) {
|
||||
const msg = msgs[msgIndex]
|
||||
if (msg.info.role === "assistant" && msg.info.summary) return
|
||||
for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
|
||||
const part = msg.parts[partIndex]
|
||||
if (part.type === "tool")
|
||||
if (part.state.status === "completed") {
|
||||
if (part.state.time.compacted) return
|
||||
sum += Token.estimate(part.state.output)
|
||||
if (sum > 40_000) {
|
||||
log.info("pruning", {
|
||||
sum,
|
||||
id: part.id,
|
||||
})
|
||||
part.state.time.compacted = Date.now()
|
||||
await updatePart(part)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isLocked(sessionID: string) {
|
||||
@@ -1769,12 +1943,6 @@ export namespace Session {
|
||||
log.info("unlocking", { sessionID })
|
||||
state().pending.delete(sessionID)
|
||||
|
||||
const isAutoCompacting = state().autoCompacting.get(sessionID) ?? false
|
||||
if (isAutoCompacting) {
|
||||
state().autoCompacting.delete(sessionID)
|
||||
return
|
||||
}
|
||||
|
||||
const session = await get(sessionID)
|
||||
if (session.parentID) return
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export namespace MessageV2 {
|
||||
time: z.object({
|
||||
start: z.number(),
|
||||
end: z.number(),
|
||||
compacted: z.number().optional(),
|
||||
}),
|
||||
})
|
||||
.openapi({
|
||||
@@ -487,8 +488,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 +529,7 @@ export namespace MessageV2 {
|
||||
state: "output-available",
|
||||
toolCallId: part.callID,
|
||||
input: part.state.input,
|
||||
output: part.state.output,
|
||||
output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output,
|
||||
},
|
||||
]
|
||||
if (part.state.status === "error")
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ import { WebFetchTool } from "./webfetch"
|
||||
import { WriteTool } from "./write"
|
||||
import { InvalidTool } from "./invalid"
|
||||
import type { Agent } from "../agent/agent"
|
||||
import { Tool } from "./tool"
|
||||
|
||||
export namespace ToolRegistry {
|
||||
const ALL = [
|
||||
// Built-in tools that ship with opencode
|
||||
const BUILTIN = [
|
||||
InvalidTool,
|
||||
BashTool,
|
||||
EditTool,
|
||||
@@ -30,13 +32,101 @@ export namespace ToolRegistry {
|
||||
TaskTool,
|
||||
]
|
||||
|
||||
// Extra tools registered at runtime (via plugins)
|
||||
const EXTRA: Tool.Info[] = []
|
||||
|
||||
// Tools registered via HTTP callback (via SDK/API)
|
||||
const HTTP: Tool.Info[] = []
|
||||
|
||||
export type HttpParamSpec = {
|
||||
type: "string" | "number" | "boolean" | "array"
|
||||
description?: string
|
||||
optional?: boolean
|
||||
items?: "string" | "number" | "boolean"
|
||||
}
|
||||
export type HttpToolRegistration = {
|
||||
id: string
|
||||
description: string
|
||||
parameters: {
|
||||
type: "object"
|
||||
properties: Record<string, HttpParamSpec>
|
||||
}
|
||||
callbackUrl: string
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
function buildZodFromHttpSpec(spec: HttpToolRegistration["parameters"]) {
|
||||
const shape: Record<string, z.ZodTypeAny> = {}
|
||||
for (const [key, val] of Object.entries(spec.properties)) {
|
||||
let base: z.ZodTypeAny
|
||||
switch (val.type) {
|
||||
case "string":
|
||||
base = z.string()
|
||||
break
|
||||
case "number":
|
||||
base = z.number()
|
||||
break
|
||||
case "boolean":
|
||||
base = z.boolean()
|
||||
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())
|
||||
break
|
||||
default:
|
||||
base = z.any()
|
||||
}
|
||||
if (val.description) base = base.describe(val.description)
|
||||
shape[key] = val.optional ? base.optional() : base
|
||||
}
|
||||
return z.object(shape)
|
||||
}
|
||||
|
||||
export function register(tool: Tool.Info) {
|
||||
// Prevent duplicates by id (replace existing)
|
||||
const idx = EXTRA.findIndex((t) => t.id === tool.id)
|
||||
if (idx >= 0) EXTRA.splice(idx, 1, tool)
|
||||
else EXTRA.push(tool)
|
||||
}
|
||||
|
||||
export function registerHTTP(input: HttpToolRegistration) {
|
||||
const parameters = buildZodFromHttpSpec(input.parameters)
|
||||
const info = Tool.define(input.id, {
|
||||
description: input.description,
|
||||
parameters,
|
||||
async execute(args) {
|
||||
const res = await fetch(input.callbackUrl, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", ...(input.headers ?? {}) },
|
||||
body: JSON.stringify({ args }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP tool callback failed: ${res.status} ${await res.text()}`)
|
||||
}
|
||||
const json = (await res.json()) as { title?: string; output: string; metadata?: Record<string, any> }
|
||||
return {
|
||||
title: json.title ?? input.id,
|
||||
output: json.output ?? "",
|
||||
metadata: (json.metadata ?? {}) as any,
|
||||
}
|
||||
},
|
||||
})
|
||||
const idx = HTTP.findIndex((t) => t.id === info.id)
|
||||
if (idx >= 0) HTTP.splice(idx, 1, info)
|
||||
else HTTP.push(info)
|
||||
}
|
||||
|
||||
function allTools(): Tool.Info[] {
|
||||
return [...BUILTIN, ...EXTRA, ...HTTP]
|
||||
}
|
||||
|
||||
export function ids() {
|
||||
return ALL.map((t) => t.id)
|
||||
return allTools().map((t) => t.id)
|
||||
}
|
||||
|
||||
export async function tools(providerID: string, _modelID: string) {
|
||||
const result = await Promise.all(
|
||||
ALL.map(async (t) => ({
|
||||
allTools().map(async (t) => ({
|
||||
id: t.id,
|
||||
...(await t.init()),
|
||||
})),
|
||||
@@ -45,21 +135,21 @@ export namespace ToolRegistry {
|
||||
if (providerID === "openai") {
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
parameters: optionalToNullable(t.parameters as unknown as z.ZodTypeAny),
|
||||
}))
|
||||
}
|
||||
|
||||
if (providerID === "azure") {
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: optionalToNullable(t.parameters),
|
||||
parameters: optionalToNullable(t.parameters as unknown as z.ZodTypeAny),
|
||||
}))
|
||||
}
|
||||
|
||||
if (providerID === "google") {
|
||||
return result.map((t) => ({
|
||||
...t,
|
||||
parameters: sanitizeGeminiParameters(t.parameters),
|
||||
parameters: sanitizeGeminiParameters(t.parameters as unknown as z.ZodTypeAny),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -68,7 +158,7 @@ export namespace ToolRegistry {
|
||||
|
||||
export async function enabled(
|
||||
_providerID: string,
|
||||
modelID: string,
|
||||
_modelID: string,
|
||||
agent: Agent.Info,
|
||||
): Promise<Record<string, boolean>> {
|
||||
const result: Record<string, boolean> = {}
|
||||
@@ -86,11 +176,6 @@ export namespace ToolRegistry {
|
||||
result["webfetch"] = false
|
||||
}
|
||||
|
||||
if (modelID.toLowerCase().includes("qwen")) {
|
||||
result["todowrite"] = false
|
||||
result["todoread"] = false
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -6,4 +6,4 @@
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
export {}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
import "zod-openapi/extend"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
|
||||
// Helper to create a Request targeting the in-memory Hono app
|
||||
function makeRequest(method: string, url: string, body?: any) {
|
||||
const headers: Record<string, string> = { "content-type": "application/json" }
|
||||
const init: RequestInit = { method, headers }
|
||||
if (body !== undefined) init.body = JSON.stringify(body)
|
||||
return new Request(url, init)
|
||||
}
|
||||
|
||||
describe("HTTP tool registration API", () => {
|
||||
test("POST /tool/register then list via /tool/ids and /tool", async () => {
|
||||
const projectRoot = path.join(__dirname, "../..")
|
||||
await Instance.provide(projectRoot, async () => {
|
||||
const { Server } = await import("../../src/server/server")
|
||||
|
||||
const toolSpec = {
|
||||
id: "http-echo",
|
||||
description: "Simple echo tool (test-only)",
|
||||
parameters: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
foo: { type: "string" as const, optional: true },
|
||||
bar: { type: "number" as const },
|
||||
},
|
||||
},
|
||||
callbackUrl: "http://localhost:9999/echo",
|
||||
}
|
||||
|
||||
// Register
|
||||
const registerRes = await Server.App.fetch(
|
||||
makeRequest("POST", "http://localhost:4096/experimental/tool/register", toolSpec),
|
||||
)
|
||||
expect(registerRes.status).toBe(200)
|
||||
const ok = await registerRes.json()
|
||||
expect(ok).toBe(true)
|
||||
|
||||
// IDs should include the new tool
|
||||
const idsRes = await Server.App.fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids"))
|
||||
expect(idsRes.status).toBe(200)
|
||||
const ids = (await idsRes.json()) as string[]
|
||||
expect(ids).toContain("http-echo")
|
||||
|
||||
// List tools for a provider/model and check JSON Schema shape
|
||||
const listRes = await Server.App.fetch(
|
||||
makeRequest("GET", "http://localhost:4096/experimental/tool?provider=openai&model=gpt-4o"),
|
||||
)
|
||||
expect(listRes.status).toBe(200)
|
||||
const list = (await listRes.json()) as Array<{ id: string; description: string; parameters: any }>
|
||||
const found = list.find((t) => t.id === "http-echo")
|
||||
expect(found).toBeTruthy()
|
||||
expect(found!.description).toBe("Simple echo tool (test-only)")
|
||||
|
||||
// Basic JSON Schema checks
|
||||
expect(found!.parameters?.type).toBe("object")
|
||||
expect(found!.parameters?.properties?.bar?.type).toBe("number")
|
||||
|
||||
const foo = found!.parameters?.properties?.foo
|
||||
// optional -> nullable for OpenAI/Azure providers; accept either type array including null or nullable: true
|
||||
const fooIsNullable = Array.isArray(foo?.type) ? foo.type.includes("null") : foo?.nullable === true
|
||||
expect(fooIsNullable).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Plugin tool.register hook", () => {
|
||||
test("Plugin registers tool during Plugin.init()", async () => {
|
||||
// Create a temporary project directory with opencode.json that points to our plugin
|
||||
const tmpDir = path.join(os.tmpdir(), `opencode-test-project-${Date.now()}`)
|
||||
await Bun.$`mkdir -p ${tmpDir}`
|
||||
|
||||
const tmpPluginPath = path.join(tmpDir, `test-plugin-${Date.now()}.ts`)
|
||||
const pluginCode = `
|
||||
export async function TestPlugin() {
|
||||
return {
|
||||
async ["tool.register"](_input, { registerHTTP }) {
|
||||
registerHTTP({
|
||||
id: "from-plugin",
|
||||
description: "Registered from test plugin",
|
||||
parameters: { type: "object", properties: { name: { type: "string", optional: true } } },
|
||||
callbackUrl: "http://localhost:9999/echo"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
await Bun.write(tmpPluginPath, pluginCode)
|
||||
|
||||
const configPath = path.join(tmpDir, "opencode.json")
|
||||
await Bun.write(configPath, JSON.stringify({ plugin: ["file://" + tmpPluginPath] }, null, 2))
|
||||
|
||||
await Instance.provide(tmpDir, async () => {
|
||||
const { Plugin } = await import("../../src/plugin")
|
||||
const { ToolRegistry } = await import("../../src/tool/registry")
|
||||
const { Server } = await import("../../src/server/server")
|
||||
|
||||
// Initialize plugins (will invoke our tool.register hook)
|
||||
await Plugin.init()
|
||||
|
||||
// Confirm the tool is registered
|
||||
const allIDs = ToolRegistry.ids()
|
||||
expect(allIDs).toContain("from-plugin")
|
||||
|
||||
// Also verify via the HTTP surface
|
||||
const idsRes = await Server.App.fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids"))
|
||||
expect(idsRes.status).toBe(200)
|
||||
const ids = (await idsRes.json()) as string[]
|
||||
expect(ids).toContain("from-plugin")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test("Multiple plugins can each register tools", async () => {
|
||||
const tmpDir = path.join(os.tmpdir(), `opencode-test-project-multi-${Date.now()}`)
|
||||
await Bun.$`mkdir -p ${tmpDir}`
|
||||
|
||||
// Create two plugin files
|
||||
const pluginAPath = path.join(tmpDir, `plugin-a-${Date.now()}.ts`)
|
||||
const pluginBPath = path.join(tmpDir, `plugin-b-${Date.now()}.ts`)
|
||||
const pluginA = `
|
||||
export async function PluginA() {
|
||||
return {
|
||||
async ["tool.register"](_input, { registerHTTP }) {
|
||||
registerHTTP({
|
||||
id: "alpha-tool",
|
||||
description: "Alpha tool",
|
||||
parameters: { type: "object", properties: { a: { type: "string", optional: true } } },
|
||||
callbackUrl: "http://localhost:9999/echo"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const pluginB = `
|
||||
export async function PluginB() {
|
||||
return {
|
||||
async ["tool.register"](_input, { registerHTTP }) {
|
||||
registerHTTP({
|
||||
id: "beta-tool",
|
||||
description: "Beta tool",
|
||||
parameters: { type: "object", properties: { b: { type: "number", optional: true } } },
|
||||
callbackUrl: "http://localhost:9999/echo"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
await Bun.write(pluginAPath, pluginA)
|
||||
await Bun.write(pluginBPath, pluginB)
|
||||
|
||||
// Config with both plugins
|
||||
await Bun.write(
|
||||
path.join(tmpDir, "opencode.json"),
|
||||
JSON.stringify({ plugin: ["file://" + pluginAPath, "file://" + pluginBPath] }, null, 2),
|
||||
)
|
||||
|
||||
await Instance.provide(tmpDir, async () => {
|
||||
const { Plugin } = await import("../../src/plugin")
|
||||
const { ToolRegistry } = await import("../../src/tool/registry")
|
||||
const { Server } = await import("../../src/server/server")
|
||||
|
||||
await Plugin.init()
|
||||
|
||||
const ids = ToolRegistry.ids()
|
||||
expect(ids).toContain("alpha-tool")
|
||||
expect(ids).toContain("beta-tool")
|
||||
|
||||
const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
expect(res.status).toBe(200)
|
||||
const httpIds = (await res.json()) as string[]
|
||||
expect(httpIds).toContain("alpha-tool")
|
||||
expect(httpIds).toContain("beta-tool")
|
||||
})
|
||||
})
|
||||
|
||||
test("Plugin registers native/local tool with function execution", async () => {
|
||||
const tmpDir = path.join(os.tmpdir(), `opencode-test-project-native-${Date.now()}`)
|
||||
await Bun.$`mkdir -p ${tmpDir}`
|
||||
|
||||
const pluginPath = path.join(tmpDir, `plugin-native-${Date.now()}.ts`)
|
||||
const pluginCode = `
|
||||
export async function NativeToolPlugin({ $, Tool, z }) {
|
||||
// Use z (zod) provided by the plugin system
|
||||
|
||||
// Define a native tool using Tool.define from plugin input
|
||||
const MyNativeTool = Tool.define("my-native-tool", {
|
||||
description: "A native tool that runs local code",
|
||||
parameters: z.object({
|
||||
message: z.string().describe("Message to process"),
|
||||
count: z.number().optional().describe("Repeat count").default(1)
|
||||
}),
|
||||
async execute(args, ctx) {
|
||||
// This runs locally in the plugin process, not via HTTP!
|
||||
const result = args.message.repeat(args.count)
|
||||
const output = \`Processed: \${result}\`
|
||||
|
||||
// Can also run shell commands directly
|
||||
const hostname = await $\`hostname\`.text()
|
||||
|
||||
return {
|
||||
title: "Native Tool Result",
|
||||
output: output + " on " + hostname.trim(),
|
||||
metadata: { processedAt: new Date().toISOString() }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
async ["tool.register"](_input, { register, registerHTTP }) {
|
||||
// Register our native tool
|
||||
register(MyNativeTool)
|
||||
|
||||
// Can also register HTTP tools in the same plugin
|
||||
registerHTTP({
|
||||
id: "http-tool-from-same-plugin",
|
||||
description: "HTTP tool alongside native tool",
|
||||
parameters: { type: "object", properties: {} },
|
||||
callbackUrl: "http://localhost:9999/echo"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
await Bun.write(pluginPath, pluginCode)
|
||||
|
||||
await Bun.write(path.join(tmpDir, "opencode.json"), JSON.stringify({ plugin: ["file://" + pluginPath] }, null, 2))
|
||||
|
||||
await Instance.provide(tmpDir, async () => {
|
||||
const { Plugin } = await import("../../src/plugin")
|
||||
const { ToolRegistry } = await import("../../src/tool/registry")
|
||||
const { Server } = await import("../../src/server/server")
|
||||
|
||||
await Plugin.init()
|
||||
|
||||
// Both tools should be registered
|
||||
const ids = ToolRegistry.ids()
|
||||
expect(ids).toContain("my-native-tool")
|
||||
expect(ids).toContain("http-tool-from-same-plugin")
|
||||
|
||||
// Verify via HTTP endpoint
|
||||
const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
expect(res.status).toBe(200)
|
||||
const httpIds = (await res.json()) as string[]
|
||||
expect(httpIds).toContain("my-native-tool")
|
||||
expect(httpIds).toContain("http-tool-from-same-plugin")
|
||||
|
||||
// Get tool details to verify native tool has proper structure
|
||||
const toolsRes = await Server.App.fetch(
|
||||
new Request("http://localhost:4096/experimental/tool?provider=anthropic&model=claude"),
|
||||
)
|
||||
expect(toolsRes.status).toBe(200)
|
||||
const tools = (await toolsRes.json()) as any[]
|
||||
const nativeTool = tools.find((t) => t.id === "my-native-tool")
|
||||
expect(nativeTool).toBeTruthy()
|
||||
expect(nativeTool.description).toBe("A native tool that runs local code")
|
||||
expect(nativeTool.parameters.properties.message).toBeTruthy()
|
||||
expect(nativeTool.parameters.properties.count).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
// Malformed plugin (no tool.register) should not throw and should not register anything
|
||||
test("Plugin without tool.register is handled gracefully", async () => {
|
||||
const tmpDir = path.join(os.tmpdir(), `opencode-test-project-noreg-${Date.now()}`)
|
||||
await Bun.$`mkdir -p ${tmpDir}`
|
||||
|
||||
const pluginPath = path.join(tmpDir, `plugin-noreg-${Date.now()}.ts`)
|
||||
const pluginSrc = `
|
||||
export async function NoRegisterPlugin() {
|
||||
return {
|
||||
// no tool.register hook provided
|
||||
async config(_cfg) { /* noop */ }
|
||||
}
|
||||
}
|
||||
`
|
||||
await Bun.write(pluginPath, pluginSrc)
|
||||
|
||||
await Bun.write(path.join(tmpDir, "opencode.json"), JSON.stringify({ plugin: ["file://" + pluginPath] }, null, 2))
|
||||
|
||||
await Instance.provide(tmpDir, async () => {
|
||||
const { Plugin } = await import("../../src/plugin")
|
||||
const { ToolRegistry } = await import("../../src/tool/registry")
|
||||
const { Server } = await import("../../src/server/server")
|
||||
|
||||
await Plugin.init()
|
||||
|
||||
// Ensure our specific id isn't present
|
||||
const ids = ToolRegistry.ids()
|
||||
expect(ids).not.toContain("malformed-tool")
|
||||
|
||||
const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
expect(res.status).toBe(200)
|
||||
const httpIds = (await res.json()) as string[]
|
||||
expect(httpIds).not.toContain("malformed-tool")
|
||||
})
|
||||
})
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user