Compare commits

...

26 Commits

Author SHA1 Message Date
opencode
d36fcc4f8e release: v0.7.0 2025-09-10 08:42:45 +00:00
Dax Raad
ea82b60d7d ci: stuff 2025-09-10 04:35:49 -04:00
Dax Raad
ea0285a96c ci: stuff 2025-09-10 04:27:23 -04:00
Dax Raad
6960408ca2 ci: bump version 2025-09-10 04:23:57 -04:00
GitHub Action
fa36195492 chore: format code 2025-09-10 07:40:01 +00:00
Dax Raad
a6265ea3d2 upgrade to latest bun 2025-09-10 03:36:42 -04:00
Dax Raad
bb3f02b8bb wip: ignore 2025-09-10 03:13:42 -04:00
Aiden Cline
bdc0f7c86d tweak: wrap build-switch w/ system-reminder (#2525) 2025-09-09 23:57:13 -05:00
GitHub Action
c8ca036834 chore: format code 2025-09-10 03:49:07 +00:00
Dax Raad
8c7fee7840 ci: fix 2025-09-09 23:48:35 -04:00
Dax Raad
e53fb7f8ed ci: format 2025-09-09 23:47:47 -04:00
Dax Raad
b05cbc9101 ci: format 2025-09-09 23:44:04 -04:00
Dax Raad
38e8c42cf0 ci: format 2025-09-09 23:44:04 -04:00
opencode
58fe884327 release: v0.6.10 2025-09-10 03:32:47 +00:00
Dax Raad
e69d10b6c9 repair tool calls when casing is wrong 2025-09-09 23:25:27 -04:00
opencode
10aee9755c release: v0.6.9 2025-09-09 21:17:41 +00:00
Frank
63384bc214 wip: zen 2025-09-09 16:40:12 -04:00
Frank
2508e06c58 wip: zen 2025-09-09 16:32:56 -04:00
Frank
6487d0607b wip: zen 2025-09-09 16:15:35 -04:00
Frank
a3513244f1 wip: zen 2025-09-09 15:47:28 -04:00
madflow
32b47fcc1e feat: svelte lsp (#2508) 2025-09-09 13:59:58 -05:00
Aiden Cline
fde03d3c93 fix: exit code being non zero when using run cmd (#2523) 2025-09-09 12:00:55 -05:00
GitHub Action
9045f13acc ignore: update download stats 2025-09-09 2025-09-09 12:04:32 +00:00
Frank
74f0edc7a8 wip: zen 2025-09-09 05:42:15 -04:00
opencode
dcabafcdce release: v0.6.8 2025-09-09 07:40:23 +00:00
Frank
02e8242c3b Remove debug logging 2025-09-09 03:35:09 -04:00
104 changed files with 1629 additions and 1495 deletions

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -73,3 +73,4 @@
| 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) |

View File

@@ -26,7 +26,7 @@
},
"cloud/core": {
"name": "@opencode/cloud-core",
"version": "0.6.4",
"version": "0.6.10",
"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.6.10",
"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.6.10",
"dependencies": {
"@opencode/cloud-core": "workspace:*",
"tsx": "4.20.5",
@@ -81,7 +81,7 @@
},
"packages/function": {
"name": "@opencode/function",
"version": "0.6.4",
"version": "0.6.10",
"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.6.10",
"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.6.10",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
},
@@ -157,7 +157,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "0.6.4",
"version": "0.6.10",
"dependencies": {
"@hey-api/openapi-ts": "0.81.0",
},
@@ -169,7 +169,7 @@
},
"packages/web": {
"name": "@opencode/web",
"version": "0.6.4",
"version": "0.6.10",
"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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,9 +64,7 @@ export default function Home() {
<section data-component="cta">
<div data-slot="left">
<a href="/docs">
Get Started
</a>
<a href="/docs">Get Started</a>
</div>
<div data-slot="right">
<button data-copy data-slot="command">
@@ -90,7 +88,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

View File

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

View File

@@ -1,15 +1,7 @@
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 { json, query, action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createMemo, createSignal, For, Show } from "solid-js"
import { withActor } from "~/context/auth.withActor"
import { IconCopy, IconCheck } from "~/component/icon"
@@ -432,50 +424,57 @@ function PaymentsSection() {
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() {
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>

View File

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

View File

@@ -0,0 +1,34 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/util/zen"
export function POST(input: APIEvent) {
return handler(input, {
parseUsageChunk: (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))
} catch (e) {
return
}
return json.response?.usage
},
buildUsage: (usage: any) => {
const inputTokens = usage.input_tokens ?? 0
const outputTokens = usage.output_tokens ?? 0
const reasoningTokens = usage.output_tokens_details?.reasoning_tokens ?? 0
const cacheReadTokens = usage.input_tokens_details?.cached_tokens ?? 0
return {
inputTokens: inputTokens - cacheReadTokens,
outputTokens: outputTokens - reasoningTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens: 0,
}
},
})
}

View File

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

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

@@ -0,0 +1,367 @@
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 { Resource } from "@opencode/cloud-resource"
export async function handler(
input: APIEvent,
opts: {
transformBody?: (body: any) => any
parseUsageChunk: (chunk: string) => string | undefined
buildUsage: (body: any) => {
inputTokens: number
outputTokens: number
reasoningTokens: number
cacheReadTokens: number
cacheWriteTokens: number
}
},
) {
class AuthError extends Error {}
class CreditsError extends Error {}
class ModelError extends Error {}
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: {},
// },
"gpt-5": {
id: "gpt-5" as const,
auth: true,
api: "https://api.openai.com",
apiKey: Resource.OPENAI_API_KEY.value,
model: "gpt-5",
cost: {
input: 0.00000125,
output: 0.00001,
reasoning: 0.00001,
cacheRead: 0.000000125,
cacheWrite: 0,
},
headerMappings: {},
},
"qwen3-coder": {
id: "qwen3-coder" as const,
auth: true,
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
cost: {
input: 0.00000038,
output: 0.00000153,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {},
},
"kimi-k2": {
id: "kimi-k2" as const,
auth: true,
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "moonshotai/Kimi-K2-Instruct-0905",
cost: {
input: 0.0000006,
output: 0.0000025,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {},
},
"grok-code": {
id: "grok-code" as const,
auth: false,
api: "https://api.x.ai",
apiKey: Resource.XAI_API_KEY.value,
model: "grok-code",
cost: {
input: 0,
output: 0,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {
"x-grok-conv-id": "x-opencode-session",
"x-grok-req-id": "x-opencode-request",
},
},
// deprecated
"qwen/qwen3-coder": {
id: "qwen/qwen3-coder" as const,
auth: true,
api: "https://inference.baseten.co",
apiKey: Resource.BASETEN_API_KEY.value,
model: "Qwen/Qwen3-Coder-480B-A35B-Instruct",
cost: {
input: 0.00000038,
output: 0.00000153,
reasoning: 0,
cacheRead: 0,
cacheWrite: 0,
},
headerMappings: {},
},
}
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()
// 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({
...(opts.transformBody?.(body) ?? body),
model: MODEL.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
let startTimestamp = Date.now()
let receivedFirstByte = false
function pump(): Promise<void> {
return (
reader?.read().then(async ({ done, value }) => {
if (done) {
logger.metric({ response_length: responseLength })
c.close()
return
}
if (!receivedFirstByte) {
receivedFirstByte = true
logger.metric({ time_to_first_byte: Date.now() - startTimestamp })
}
buffer += decoder.decode(value, { stream: true })
responseLength += value.length
const parts = buffer.split("\n\n")
buffer = parts.pop() ?? ""
for (const part of parts) {
logger.debug(part)
const usage = opts.parseUsageChunk(part.trim())
if (usage) await trackUsage(usage)
}
c.enqueue(value)
return pump()
}) || Promise.resolve()
)
}
return pump()
},
})
return new Response(stream, {
status: res.status,
statusText: res.statusText,
headers: resHeaders,
})
function validateModel() {
if (!(body.model in MODELS)) {
throw new ModelError(`Model ${body.model} not supported`)
}
const model = MODELS[body.model as keyof typeof MODELS]
logger.metric({ model: model.id })
return model
}
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.")
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")
}
async function trackUsage(usage: any) {
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWriteTokens } = opts.buildUsage(usage)
const inputCost = MODEL.cost.input * inputTokens * 100
const outputCost = MODEL.cost.output * outputTokens * 100
const reasoningCost = MODEL.cost.reasoning * reasoningTokens * 100
const cacheReadCost = MODEL.cost.cacheRead * cacheReadTokens * 100
const cacheWriteCost = MODEL.cost.cacheWrite * cacheWriteTokens * 100
const totalCostInCent = inputCost + outputCost + reasoningCost + cacheReadCost + cacheWriteCost
logger.metric({
"tokens.input": inputTokens,
"tokens.output": outputTokens,
"tokens.reasoning": reasoningTokens,
"tokens.cache_read": cacheReadTokens,
"tokens.cache_write": cacheWriteTokens,
"cost.input": Math.round(inputCost),
"cost.output": Math.round(outputCost),
"cost.reasoning": Math.round(reasoningCost),
"cost.cache_read": Math.round(cacheReadCost),
"cost.cache_write": Math.round(cacheWriteCost),
"cost.total": Math.round(totalCostInCent),
})
if (!apiKey) return
const cost = isFree ? 0 : centsToMicroCents(totalCostInCent)
await Database.transaction(async (tx) => {
await tx.insert(UsageTable).values({
workspaceID: apiKey.workspaceID,
id: Identifier.create("usage"),
model: MODEL.id,
inputTokens,
outputTokens,
reasoningTokens,
cacheReadTokens,
cacheWriteTokens,
cost,
})
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${cost}`,
})
.where(eq(BillingTable.workspaceID, apiKey.workspaceID))
})
await Database.use((tx) =>
tx
.update(KeyTable)
.set({ timeUsed: sql`now()` })
.where(eq(KeyTable.id, apiKey.id)),
)
}
} catch (error: any) {
logger.metric({
"error.type": error.constructor.name,
"error.message": error.message,
})
if (error instanceof AuthError || error instanceof CreditsError || error instanceof ModelError)
return new Response(JSON.stringify({ error: { message: error.message } }), { status: 401 })
return new Response(JSON.stringify({ error: { message: error.message } }), { status: 500 })
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,4 +17,4 @@
"breakpoints": true
}
]
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.6.7",
"version": "0.7.0",
"private": true,
"type": "module",
"dependencies": {

View File

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

View File

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

View File

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

View File

@@ -6,83 +6,87 @@
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
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
OPENAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
"XAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
Web: {
type: "sst.cloudflare.Astro"
url: string
}
XAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}

View File

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

View File

@@ -6,83 +6,87 @@
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
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
OPENAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
"XAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
Web: {
type: "sst.cloudflare.Astro"
url: string
}
XAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -100,6 +100,7 @@ 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 STRIPE_SECRET_KEY = new sst.Secret("STRIPE_SECRET_KEY")
@@ -132,6 +133,7 @@ new sst.cloudflare.x.SolidStart("Console", {
STRIPE_WEBHOOK_SECRET,
STRIPE_SECRET_KEY,
ANTHROPIC_API_KEY,
OPENAI_API_KEY,
XAI_API_KEY,
BASETEN_API_KEY,
],

View File

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

View File

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

View File

@@ -6,83 +6,87 @@
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
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
OPENAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
"XAI_API_KEY": {
"type": "sst.sst.Secret"
"value": string
Web: {
type: "sst.cloudflare.Astro"
url: string
}
XAI_API_KEY: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -766,8 +766,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,
@@ -1007,6 +1007,17 @@ export namespace Session {
}
},
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({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,16 +6,8 @@
"module": "preserve",
"declaration": true,
"moduleResolution": "bundler",
"lib": [
"es2022",
"dom",
"dom.iterable"
],
"customConditions": [
"development"
]
"lib": ["es2022", "dom", "dom.iterable"],
"customConditions": ["development"]
},
"include": [
"src"
]
"include": ["src"]
}

View File

@@ -2,15 +2,15 @@ name: CI
on:
push:
branches-ignore:
- 'generated'
- 'codegen/**'
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
- "generated"
- "codegen/**"
- "integrated/**"
- "stl-preview-head/**"
- "stl-preview-base/**"
pull_request:
branches-ignore:
- 'stl-preview-head/**'
- 'stl-preview-base/**'
- "stl-preview-head/**"
- "stl-preview-base/**"
jobs:
lint:

View File

@@ -1,3 +1,3 @@
{
".": "0.8.0"
}
}

View File

@@ -6,7 +6,7 @@ Full Changelog: [v0.7.0...v0.8.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
- **api:** api update ([ae87a71](https://github.com/sst/opencode-sdk-go/commit/ae87a71949994590ace8285a39f0991ef34b664d))
## 0.7.0 (2025-09-01)
@@ -14,7 +14,7 @@ Full Changelog: [v0.6.0...v0.7.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
- **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4))
## 0.6.0 (2025-09-01)
@@ -22,7 +22,7 @@ Full Changelog: [v0.5.0...v0.6.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
- **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8))
## 0.5.0 (2025-08-31)
@@ -30,7 +30,7 @@ Full Changelog: [v0.4.0...v0.5.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([44b281d](https://github.com/sst/opencode-sdk-go/commit/44b281d0bb39c5022a984ac9d0fca1529ccc0604))
- **api:** api update ([44b281d](https://github.com/sst/opencode-sdk-go/commit/44b281d0bb39c5022a984ac9d0fca1529ccc0604))
## 0.4.0 (2025-08-31)
@@ -38,7 +38,7 @@ Full Changelog: [v0.3.0...v0.4.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([fa9d6ec](https://github.com/sst/opencode-sdk-go/commit/fa9d6ec6472e62f4f6605d0a71a7aa8bf8a24559))
- **api:** api update ([fa9d6ec](https://github.com/sst/opencode-sdk-go/commit/fa9d6ec6472e62f4f6605d0a71a7aa8bf8a24559))
## 0.3.0 (2025-08-31)
@@ -46,7 +46,7 @@ Full Changelog: [v0.2.0...v0.3.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([aae1c06](https://github.com/sst/opencode-sdk-go/commit/aae1c06bb5a93a1cd9c589846a84b3f16246f5da))
- **api:** api update ([aae1c06](https://github.com/sst/opencode-sdk-go/commit/aae1c06bb5a93a1cd9c589846a84b3f16246f5da))
## 0.2.0 (2025-08-31)
@@ -54,7 +54,7 @@ Full Changelog: [v0.1.0...v0.2.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([1472790](https://github.com/sst/opencode-sdk-go/commit/1472790542515f47bd46e2a9e28d8afea024cf9c))
- **api:** api update ([1472790](https://github.com/sst/opencode-sdk-go/commit/1472790542515f47bd46e2a9e28d8afea024cf9c))
## 0.1.0 (2025-08-31)
@@ -62,61 +62,59 @@ Full Changelog: [v0.0.1...v0.1.0](https://github.com/sst/opencode-sdk-go/compare
### Features
* **api:** api update ([3f03ddd](https://github.com/sst/opencode-sdk-go/commit/3f03dddd5ec0de98f99ce48679077dcae9ceffd6))
* **api:** api update ([e9f79c4](https://github.com/sst/opencode-sdk-go/commit/e9f79c4792b21ef64ab0431ffd76f5a71e04d182))
* **api:** api update ([139a686](https://github.com/sst/opencode-sdk-go/commit/139a6862d2f0ab0c8ea791663d736868be3e96e6))
* **api:** api update ([2ed0800](https://github.com/sst/opencode-sdk-go/commit/2ed0800b2c5b99877e9f7fde669a6c005fad6b77))
* **api:** api update ([88a87a4](https://github.com/sst/opencode-sdk-go/commit/88a87a458f56ce0c18b502c73da933f614f56e8b))
* **api:** api update ([0e5d65b](https://github.com/sst/opencode-sdk-go/commit/0e5d65b571e7b30dc6347e6730098878ebba3a42))
* **api:** api update ([ba381f1](https://github.com/sst/opencode-sdk-go/commit/ba381f1e07aad24e9824df7d53befae2a644f69f))
* **api:** api update ([3f429f5](https://github.com/sst/opencode-sdk-go/commit/3f429f5b4be5607433ef5fdc0d5bf67fe590d039))
* **api:** api update ([9f34787](https://github.com/sst/opencode-sdk-go/commit/9f347876b35b7f898060c1a5f71c322e95978e3e))
* **api:** api update ([379c8e0](https://github.com/sst/opencode-sdk-go/commit/379c8e00197e13aebaf2f2d61277b125f1f90011))
* **api:** api update ([550511c](https://github.com/sst/opencode-sdk-go/commit/550511c4c5b5055ac8ff22b7b11731331bd9d088))
* **api:** api update ([547f0c2](https://github.com/sst/opencode-sdk-go/commit/547f0c262f2df1ce83eaa7267d68be64bb29b841))
* **api:** api update ([b7b0720](https://github.com/sst/opencode-sdk-go/commit/b7b07204bff314da24b1819c128835a43ef64065))
* **api:** api update ([7250ffc](https://github.com/sst/opencode-sdk-go/commit/7250ffcba262b916c958ddecc2a42927982db39f))
* **api:** api update ([17fbab7](https://github.com/sst/opencode-sdk-go/commit/17fbab73111a3eae488737c69b12370bc69c65f7))
* **api:** api update ([1270b5c](https://github.com/sst/opencode-sdk-go/commit/1270b5cd81e6ac769dcd92ade6d877891bf51bd5))
* **api:** api update ([a238d4a](https://github.com/sst/opencode-sdk-go/commit/a238d4abd6ed7d15f3547d27a4b6ecf4aec8431e))
* **api:** api update ([7475655](https://github.com/sst/opencode-sdk-go/commit/7475655aca577fe4f807c2f02f92171f6a358e9c))
* **api:** api update ([429d258](https://github.com/sst/opencode-sdk-go/commit/429d258bb56e9cdeb1528be3944bf5537ac26a96))
* **api:** api update ([f250915](https://github.com/sst/opencode-sdk-go/commit/f2509157eaf1b453e741ee9482127cad2e3ace25))
* **api:** api update ([5efc987](https://github.com/sst/opencode-sdk-go/commit/5efc987353801d1e772c20edf162b1c75da32743))
* **api:** api update ([98a8350](https://github.com/sst/opencode-sdk-go/commit/98a83504f7cfc361e83314c3e79a4e9ff53f0560))
* **api:** api update ([6da8bf8](https://github.com/sst/opencode-sdk-go/commit/6da8bf8bfe91d45991fb580753d77c5534fc0b1b))
* **api:** api update ([f8c7148](https://github.com/sst/opencode-sdk-go/commit/f8c7148ae56143823186e2675a78e82676154956))
* **api:** manual updates ([7cf038f](https://github.com/sst/opencode-sdk-go/commit/7cf038ffae5da1b77e1cef11b5fa166a53b467f2))
* **api:** update via SDK Studio ([068a0eb](https://github.com/sst/opencode-sdk-go/commit/068a0eb025010da0c8d86fa1bb496a39dbedcef9))
* **api:** update via SDK Studio ([ca651ed](https://github.com/sst/opencode-sdk-go/commit/ca651edaf71d1f3678f929287474f5bc4f1aad10))
* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
* **api:** update via SDK Studio ([51315fa](https://github.com/sst/opencode-sdk-go/commit/51315fa2eae424743ea79701e67d44447c44144d))
* **api:** update via SDK Studio ([af07955](https://github.com/sst/opencode-sdk-go/commit/af0795543240aefaf04fc7663a348825541c79ed))
* **api:** update via SDK Studio ([5e3468a](https://github.com/sst/opencode-sdk-go/commit/5e3468a0aaa5ed3b13e019c3a24e0ba9147d1675))
* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
* **client:** expand max streaming buffer size ([76303e5](https://github.com/sst/opencode-sdk-go/commit/76303e51067e78e732af26ced9d83b8bad7655c3))
* **client:** support optional json html escaping ([449748f](https://github.com/sst/opencode-sdk-go/commit/449748f35a1d8cb6f91dc36d25bf9489f4f371bd))
- **api:** api update ([3f03ddd](https://github.com/sst/opencode-sdk-go/commit/3f03dddd5ec0de98f99ce48679077dcae9ceffd6))
- **api:** api update ([e9f79c4](https://github.com/sst/opencode-sdk-go/commit/e9f79c4792b21ef64ab0431ffd76f5a71e04d182))
- **api:** api update ([139a686](https://github.com/sst/opencode-sdk-go/commit/139a6862d2f0ab0c8ea791663d736868be3e96e6))
- **api:** api update ([2ed0800](https://github.com/sst/opencode-sdk-go/commit/2ed0800b2c5b99877e9f7fde669a6c005fad6b77))
- **api:** api update ([88a87a4](https://github.com/sst/opencode-sdk-go/commit/88a87a458f56ce0c18b502c73da933f614f56e8b))
- **api:** api update ([0e5d65b](https://github.com/sst/opencode-sdk-go/commit/0e5d65b571e7b30dc6347e6730098878ebba3a42))
- **api:** api update ([ba381f1](https://github.com/sst/opencode-sdk-go/commit/ba381f1e07aad24e9824df7d53befae2a644f69f))
- **api:** api update ([3f429f5](https://github.com/sst/opencode-sdk-go/commit/3f429f5b4be5607433ef5fdc0d5bf67fe590d039))
- **api:** api update ([9f34787](https://github.com/sst/opencode-sdk-go/commit/9f347876b35b7f898060c1a5f71c322e95978e3e))
- **api:** api update ([379c8e0](https://github.com/sst/opencode-sdk-go/commit/379c8e00197e13aebaf2f2d61277b125f1f90011))
- **api:** api update ([550511c](https://github.com/sst/opencode-sdk-go/commit/550511c4c5b5055ac8ff22b7b11731331bd9d088))
- **api:** api update ([547f0c2](https://github.com/sst/opencode-sdk-go/commit/547f0c262f2df1ce83eaa7267d68be64bb29b841))
- **api:** api update ([b7b0720](https://github.com/sst/opencode-sdk-go/commit/b7b07204bff314da24b1819c128835a43ef64065))
- **api:** api update ([7250ffc](https://github.com/sst/opencode-sdk-go/commit/7250ffcba262b916c958ddecc2a42927982db39f))
- **api:** api update ([17fbab7](https://github.com/sst/opencode-sdk-go/commit/17fbab73111a3eae488737c69b12370bc69c65f7))
- **api:** api update ([1270b5c](https://github.com/sst/opencode-sdk-go/commit/1270b5cd81e6ac769dcd92ade6d877891bf51bd5))
- **api:** api update ([a238d4a](https://github.com/sst/opencode-sdk-go/commit/a238d4abd6ed7d15f3547d27a4b6ecf4aec8431e))
- **api:** api update ([7475655](https://github.com/sst/opencode-sdk-go/commit/7475655aca577fe4f807c2f02f92171f6a358e9c))
- **api:** api update ([429d258](https://github.com/sst/opencode-sdk-go/commit/429d258bb56e9cdeb1528be3944bf5537ac26a96))
- **api:** api update ([f250915](https://github.com/sst/opencode-sdk-go/commit/f2509157eaf1b453e741ee9482127cad2e3ace25))
- **api:** api update ([5efc987](https://github.com/sst/opencode-sdk-go/commit/5efc987353801d1e772c20edf162b1c75da32743))
- **api:** api update ([98a8350](https://github.com/sst/opencode-sdk-go/commit/98a83504f7cfc361e83314c3e79a4e9ff53f0560))
- **api:** api update ([6da8bf8](https://github.com/sst/opencode-sdk-go/commit/6da8bf8bfe91d45991fb580753d77c5534fc0b1b))
- **api:** api update ([f8c7148](https://github.com/sst/opencode-sdk-go/commit/f8c7148ae56143823186e2675a78e82676154956))
- **api:** manual updates ([7cf038f](https://github.com/sst/opencode-sdk-go/commit/7cf038ffae5da1b77e1cef11b5fa166a53b467f2))
- **api:** update via SDK Studio ([068a0eb](https://github.com/sst/opencode-sdk-go/commit/068a0eb025010da0c8d86fa1bb496a39dbedcef9))
- **api:** update via SDK Studio ([ca651ed](https://github.com/sst/opencode-sdk-go/commit/ca651edaf71d1f3678f929287474f5bc4f1aad10))
- **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
- **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
- **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
- **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
- **api:** update via SDK Studio ([51315fa](https://github.com/sst/opencode-sdk-go/commit/51315fa2eae424743ea79701e67d44447c44144d))
- **api:** update via SDK Studio ([af07955](https://github.com/sst/opencode-sdk-go/commit/af0795543240aefaf04fc7663a348825541c79ed))
- **api:** update via SDK Studio ([5e3468a](https://github.com/sst/opencode-sdk-go/commit/5e3468a0aaa5ed3b13e019c3a24e0ba9147d1675))
- **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
- **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
- **client:** expand max streaming buffer size ([76303e5](https://github.com/sst/opencode-sdk-go/commit/76303e51067e78e732af26ced9d83b8bad7655c3))
- **client:** support optional json html escaping ([449748f](https://github.com/sst/opencode-sdk-go/commit/449748f35a1d8cb6f91dc36d25bf9489f4f371bd))
### Bug Fixes
* **client:** process custom base url ahead of time ([9b360d6](https://github.com/sst/opencode-sdk-go/commit/9b360d642cf6f302104308af5622e17099899e5f))
* **client:** resolve lint errors in streaming tests ([4d36cb0](https://github.com/sst/opencode-sdk-go/commit/4d36cb09fc9d436734d5dab1c499acaa88568ff7))
* close body before retrying ([4da3f7f](https://github.com/sst/opencode-sdk-go/commit/4da3f7f372bad222a189ba3eabcfde3373166ae5))
* don't try to deserialize as json when ResponseBodyInto is []byte ([595291f](https://github.com/sst/opencode-sdk-go/commit/595291f6dba6af472f160b9f8e3d145002f43a4a))
- **client:** process custom base url ahead of time ([9b360d6](https://github.com/sst/opencode-sdk-go/commit/9b360d642cf6f302104308af5622e17099899e5f))
- **client:** resolve lint errors in streaming tests ([4d36cb0](https://github.com/sst/opencode-sdk-go/commit/4d36cb09fc9d436734d5dab1c499acaa88568ff7))
- close body before retrying ([4da3f7f](https://github.com/sst/opencode-sdk-go/commit/4da3f7f372bad222a189ba3eabcfde3373166ae5))
- don't try to deserialize as json when ResponseBodyInto is []byte ([595291f](https://github.com/sst/opencode-sdk-go/commit/595291f6dba6af472f160b9f8e3d145002f43a4a))
### Chores
* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
* **internal:** codegen related update ([6a22ce6](https://github.com/sst/opencode-sdk-go/commit/6a22ce6df155f5003e80b8a75686a9e513a5568a))
* **internal:** fix lint script for tests ([391c482](https://github.com/sst/opencode-sdk-go/commit/391c482148ed0a77c4ad52807abeb2d540b56797))
* **internal:** update comment in script ([b7f1c3e](https://github.com/sst/opencode-sdk-go/commit/b7f1c3e16935c71e243004b8f321d661cd8e9474))
* lint tests ([616796b](https://github.com/sst/opencode-sdk-go/commit/616796b761704bde6be5c6c2428f28c79c7f05ff))
* lint tests in subpackages ([50c82ff](https://github.com/sst/opencode-sdk-go/commit/50c82ff0757c973834b68adc22566b70f767b611))
* sync repo ([2f34d5d](https://github.com/sst/opencode-sdk-go/commit/2f34d5d53e56e9cdc3df99be7ee7efc83dd977a3))
* update @stainless-api/prism-cli to v5.15.0 ([2f24852](https://github.com/sst/opencode-sdk-go/commit/2f2485216d4f4891d1fbfbc23ff8410c2f35152a))
- **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
- **internal:** codegen related update ([6a22ce6](https://github.com/sst/opencode-sdk-go/commit/6a22ce6df155f5003e80b8a75686a9e513a5568a))
- **internal:** fix lint script for tests ([391c482](https://github.com/sst/opencode-sdk-go/commit/391c482148ed0a77c4ad52807abeb2d540b56797))
- **internal:** update comment in script ([b7f1c3e](https://github.com/sst/opencode-sdk-go/commit/b7f1c3e16935c71e243004b8f321d661cd8e9474))
- lint tests ([616796b](https://github.com/sst/opencode-sdk-go/commit/616796b761704bde6be5c6c2428f28c79c7f05ff))
- lint tests in subpackages ([50c82ff](https://github.com/sst/opencode-sdk-go/commit/50c82ff0757c973834b68adc22566b70f767b611))
- sync repo ([2f34d5d](https://github.com/sst/opencode-sdk-go/commit/2f34d5d53e56e9cdc3df99be7ee7efc83dd977a3))
- update @stainless-api/prism-cli to v5.15.0 ([2f24852](https://github.com/sst/opencode-sdk-go/commit/2f2485216d4f4891d1fbfbc23ff8410c2f35152a))

View File

@@ -60,8 +60,5 @@
}
],
"release-type": "go",
"extra-files": [
"internal/version.go",
"README.md"
]
}
"extra-files": ["internal/version.go", "README.md"]
}

View File

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

View File

@@ -14,7 +14,7 @@ await createClient({
input: "./openapi.json",
output: {
path: "./src/gen",
tsConfigPath: path.join(dir, 'tsconfig.json')
tsConfigPath: path.join(dir, "tsconfig.json"),
},
plugins: [
{

View File

@@ -641,6 +641,9 @@ export type Config = {
options?: {
[key: string]: unknown
}
provider?: {
npm: string
}
}
}
options?: {
@@ -1009,6 +1012,9 @@ export type Model = {
options: {
[key: string]: unknown
}
provider?: {
npm: string
}
}
export type McpLocalConfig = {

View File

@@ -105,7 +105,7 @@ export function createOpencodeTui(options?: TuiOptions) {
const proc = spawn(`opencode`, args, {
signal: options?.signal,
stdio: 'inherit',
stdio: "inherit",
env: {
...process.env,
OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}),

View File

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

View File

@@ -6,16 +6,8 @@
"module": "nodenext",
"declaration": true,
"moduleResolution": "nodenext",
"lib": [
"es2022",
"dom",
"dom.iterable"
],
"customConditions": [
"development"
]
"lib": ["es2022", "dom", "dom.iterable"],
"customConditions": ["development"]
},
"include": [
"src"
]
"include": ["src"]
}

View File

@@ -1,219 +1,218 @@
{
"$schema": "https://opencode.ai/theme.json",
"defs": {
"vesperBg": "#101010",
"vesperFg": "#FFF",
"vesperComment": "#8b8b8b94",
"vesperKeyword": "#A0A0A0",
"vesperFunction": "#FFC799",
"vesperString": "#99FFE4",
"vesperNumber": "#FFC799",
"vesperError": "#FF8080",
"vesperWarning": "#FFC799",
"vesperSuccess": "#99FFE4",
"vesperMuted": "#A0A0A0"
"$schema": "https://opencode.ai/theme.json",
"defs": {
"vesperBg": "#101010",
"vesperFg": "#FFF",
"vesperComment": "#8b8b8b94",
"vesperKeyword": "#A0A0A0",
"vesperFunction": "#FFC799",
"vesperString": "#99FFE4",
"vesperNumber": "#FFC799",
"vesperError": "#FF8080",
"vesperWarning": "#FFC799",
"vesperSuccess": "#99FFE4",
"vesperMuted": "#A0A0A0"
},
"theme": {
"primary": {
"dark": "#FFC799",
"light": "#FFC799"
},
"theme": {
"primary": {
"dark": "#FFC799",
"light": "#FFC799"
},
"secondary": {
"dark": "#99FFE4",
"light": "#99FFE4"
},
"accent": {
"dark": "#FFC799",
"light": "#FFC799"
},
"error": {
"dark": "vesperError",
"light": "vesperError"
},
"warning": {
"dark": "vesperWarning",
"light": "vesperWarning"
},
"success": {
"dark": "vesperSuccess",
"light": "vesperSuccess"
},
"info": {
"dark": "#FFC799",
"light": "#FFC799"
},
"text": {
"dark": "vesperFg",
"light": "vesperBg"
},
"textMuted": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"background": {
"dark": "vesperBg",
"light": "#FFF"
},
"backgroundPanel": {
"dark": "vesperBg",
"light": "#F0F0F0"
},
"backgroundElement": {
"dark": "vesperBg",
"light": "#E0E0E0"
},
"border": {
"dark": "#282828",
"light": "#D0D0D0"
},
"borderActive": {
"dark": "#FFC799",
"light": "#FFC799"
},
"borderSubtle": {
"dark": "#1C1C1C",
"light": "#E8E8E8"
},
"diffAdded": {
"dark": "vesperSuccess",
"light": "vesperSuccess"
},
"diffRemoved": {
"dark": "vesperError",
"light": "vesperError"
},
"diffContext": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"diffHunkHeader": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"diffHighlightAdded": {
"dark": "vesperSuccess",
"light": "vesperSuccess"
},
"diffHighlightRemoved": {
"dark": "vesperError",
"light": "vesperError"
},
"diffAddedBg": {
"dark": "#0d2818",
"light": "#e8f5e8"
},
"diffRemovedBg": {
"dark": "#281a1a",
"light": "#f5e8e8"
},
"diffContextBg": {
"dark": "vesperBg",
"light": "#F8F8F8"
},
"diffLineNumber": {
"dark": "#505050",
"light": "#808080"
},
"diffAddedLineNumberBg": {
"dark": "#0d2818",
"light": "#e8f5e8"
},
"diffRemovedLineNumberBg": {
"dark": "#281a1a",
"light": "#f5e8e8"
},
"markdownText": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownHeading": {
"dark": "#FFC799",
"light": "#FFC799"
},
"markdownLink": {
"dark": "#FFC799",
"light": "#FFC799"
},
"markdownLinkText": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"markdownCode": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"markdownBlockQuote": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownEmph": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownStrong": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownHorizontalRule": {
"dark": "#65737E",
"light": "#65737E"
},
"markdownListItem": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownListEnumeration": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownImage": {
"dark": "#FFC799",
"light": "#FFC799"
},
"markdownImageText": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"markdownCodeBlock": {
"dark": "vesperFg",
"light": "vesperBg"
},
"syntaxComment": {
"dark": "vesperComment",
"light": "vesperComment"
},
"syntaxKeyword": {
"dark": "vesperKeyword",
"light": "vesperKeyword"
},
"syntaxFunction": {
"dark": "vesperFunction",
"light": "vesperFunction"
},
"syntaxVariable": {
"dark": "vesperFg",
"light": "vesperBg"
},
"syntaxString": {
"dark": "vesperString",
"light": "vesperString"
},
"syntaxNumber": {
"dark": "vesperNumber",
"light": "vesperNumber"
},
"syntaxType": {
"dark": "vesperFunction",
"light": "vesperFunction"
},
"syntaxOperator": {
"dark": "vesperKeyword",
"light": "vesperKeyword"
},
"syntaxPunctuation": {
"dark": "vesperFg",
"light": "vesperBg"
}
"secondary": {
"dark": "#99FFE4",
"light": "#99FFE4"
},
"accent": {
"dark": "#FFC799",
"light": "#FFC799"
},
"error": {
"dark": "vesperError",
"light": "vesperError"
},
"warning": {
"dark": "vesperWarning",
"light": "vesperWarning"
},
"success": {
"dark": "vesperSuccess",
"light": "vesperSuccess"
},
"info": {
"dark": "#FFC799",
"light": "#FFC799"
},
"text": {
"dark": "vesperFg",
"light": "vesperBg"
},
"textMuted": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"background": {
"dark": "vesperBg",
"light": "#FFF"
},
"backgroundPanel": {
"dark": "vesperBg",
"light": "#F0F0F0"
},
"backgroundElement": {
"dark": "vesperBg",
"light": "#E0E0E0"
},
"border": {
"dark": "#282828",
"light": "#D0D0D0"
},
"borderActive": {
"dark": "#FFC799",
"light": "#FFC799"
},
"borderSubtle": {
"dark": "#1C1C1C",
"light": "#E8E8E8"
},
"diffAdded": {
"dark": "vesperSuccess",
"light": "vesperSuccess"
},
"diffRemoved": {
"dark": "vesperError",
"light": "vesperError"
},
"diffContext": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"diffHunkHeader": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"diffHighlightAdded": {
"dark": "vesperSuccess",
"light": "vesperSuccess"
},
"diffHighlightRemoved": {
"dark": "vesperError",
"light": "vesperError"
},
"diffAddedBg": {
"dark": "#0d2818",
"light": "#e8f5e8"
},
"diffRemovedBg": {
"dark": "#281a1a",
"light": "#f5e8e8"
},
"diffContextBg": {
"dark": "vesperBg",
"light": "#F8F8F8"
},
"diffLineNumber": {
"dark": "#505050",
"light": "#808080"
},
"diffAddedLineNumberBg": {
"dark": "#0d2818",
"light": "#e8f5e8"
},
"diffRemovedLineNumberBg": {
"dark": "#281a1a",
"light": "#f5e8e8"
},
"markdownText": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownHeading": {
"dark": "#FFC799",
"light": "#FFC799"
},
"markdownLink": {
"dark": "#FFC799",
"light": "#FFC799"
},
"markdownLinkText": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"markdownCode": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"markdownBlockQuote": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownEmph": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownStrong": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownHorizontalRule": {
"dark": "#65737E",
"light": "#65737E"
},
"markdownListItem": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownListEnumeration": {
"dark": "vesperFg",
"light": "vesperBg"
},
"markdownImage": {
"dark": "#FFC799",
"light": "#FFC799"
},
"markdownImageText": {
"dark": "vesperMuted",
"light": "vesperMuted"
},
"markdownCodeBlock": {
"dark": "vesperFg",
"light": "vesperBg"
},
"syntaxComment": {
"dark": "vesperComment",
"light": "vesperComment"
},
"syntaxKeyword": {
"dark": "vesperKeyword",
"light": "vesperKeyword"
},
"syntaxFunction": {
"dark": "vesperFunction",
"light": "vesperFunction"
},
"syntaxVariable": {
"dark": "vesperFg",
"light": "vesperBg"
},
"syntaxString": {
"dark": "vesperString",
"light": "vesperString"
},
"syntaxNumber": {
"dark": "vesperNumber",
"light": "vesperNumber"
},
"syntaxType": {
"dark": "vesperFunction",
"light": "vesperFunction"
},
"syntaxOperator": {
"dark": "vesperKeyword",
"light": "vesperKeyword"
},
"syntaxPunctuation": {
"dark": "vesperFg",
"light": "vesperBg"
}
}
}

View File

@@ -1,12 +1,8 @@
const stage = process.env.SST_STAGE || "dev"
export default {
url: stage === "production"
? "https://opencode.ai"
: `https://${stage}.opencode.ai`,
console: stage === "production"
? "https://opencode.ai/auth"
: `https://${stage}.opencode.ai/auth`,
url: stage === "production" ? "https://opencode.ai" : `https://${stage}.opencode.ai`,
console: stage === "production" ? "https://opencode.ai/auth" : `https://${stage}.opencode.ai/auth`,
email: "contact@anoma.ly",
socialCard: "https://social-cards.sst.dev",
github: "https://github.com/sst/opencode",

View File

@@ -1,7 +1,7 @@
{
"name": "@opencode/web",
"type": "module",
"version": "0.6.7",
"version": "0.7.0",
"scripts": {
"dev": "astro dev",
"dev:remote": "sst shell --stage=dev --target=Web astro dev",

View File

@@ -37,11 +37,7 @@ function getStatusText(status: [Status, string?]): string {
}
}
export default function Share(props: {
id: string
api: string
info: Session.Info
}) {
export default function Share(props: { id: string; api: string; info: Session.Info }) {
let lastScrollY = 0
let hasScrolledToAnchor = false
let scrollTimeout: number | undefined
@@ -67,7 +63,8 @@ export default function Share(props: {
created: props.info.time.created,
updated: props.info.time.updated,
},
}, messages: {}
},
messages: {},
})
const messages = createMemo(() => Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)))
const [connectionStatus, setConnectionStatus] = createSignal<[Status, string?]>(["disconnected", "Disconnected"])
@@ -413,7 +410,11 @@ export default function Share(props: {
</li>
<li>
<span data-element-label>Output Tokens</span>
{data().tokens.output ? <span>{data().tokens.output}</span> : <span data-placeholder>&mdash;</span>}
{data().tokens.output ? (
<span>{data().tokens.output}</span>
) : (
<span data-placeholder>&mdash;</span>
)}
</li>
<li>
<span data-element-label>Reasoning Tokens</span>

View File

@@ -90,7 +90,6 @@
}
[data-component="mobile"] {
& > [data-component="diff-block"]:first-child > div {
padding-top: 0.25rem;
}

View File

@@ -61,5 +61,4 @@
padding: 2px 0;
font-size: 0.75rem;
}
}

View File

@@ -10,19 +10,12 @@ export function ContentError(props: Props) {
const overflow = createOverflow()
return (
<div
class={style.root}
data-expanded={expanded() || props.expand === true ? true : undefined}
>
<div class={style.root} data-expanded={expanded() || props.expand === true ? true : undefined}>
<div data-section="content" ref={overflow.ref}>
{props.children}
</div>
{((!props.expand && overflow.status) || expanded()) && (
<button
type="button"
data-element-button-text
onClick={() => setExpanded((e) => !e)}
>
<button type="button" data-element-button-text onClick={() => setExpanded((e) => !e)}>
{expanded() ? "Show less" : "Show more"}
</button>
)}

View File

@@ -11,8 +11,7 @@ export function CopyButton(props: CopyButtonProps) {
function handleCopyClick() {
if (props.text) {
navigator.clipboard.writeText(props.text)
.catch((err) => console.error("Copy failed", err))
navigator.clipboard.writeText(props.text).catch((err) => console.error("Copy failed", err))
setCopied(true)
setTimeout(() => setCopied(false), 2000)
@@ -21,15 +20,8 @@ export function CopyButton(props: CopyButtonProps) {
return (
<div data-component="copy-button" class={styles.root}>
<button
type="button"
onClick={handleCopyClick}
data-copied={copied() ? true : undefined}
>
{copied()
? <IconCheckCircle width={16} height={16} />
: <IconClipboard width={16} height={16} />
}
<button type="button" onClick={handleCopyClick} data-copied={copied() ? true : undefined}>
{copied() ? <IconCheckCircle width={16} height={16} /> : <IconClipboard width={16} height={16} />}
</button>
</div>
)

View File

@@ -168,32 +168,25 @@ export function Part(props: PartProps) {
</Show>
</div>
)}
{
props.message.role === "user" && props.part.type === "file" && (
<div data-component="attachment">
<div data-slot="copy">Attachment</div>
<div data-slot="filename">{props.part.filename}</div>
</div>
)
}
{
props.part.type === "step-start" && props.message.role === "assistant" && (
<div data-component="step-start">
<div data-slot="provider">{props.message.providerID}</div>
<div data-slot="model">{props.message.modelID}</div>
</div>
)
}
{
props.part.type === "tool" && props.part.state.status === "error" && (
<div data-component="tool" data-tool="error">
<ContentError>{formatErrorString(props.part.state.error)}</ContentError>
<Spacer />
</div>
)
}
{
props.part.type === "tool" &&
{props.message.role === "user" && props.part.type === "file" && (
<div data-component="attachment">
<div data-slot="copy">Attachment</div>
<div data-slot="filename">{props.part.filename}</div>
</div>
)}
{props.part.type === "step-start" && props.message.role === "assistant" && (
<div data-component="step-start">
<div data-slot="provider">{props.message.providerID}</div>
<div data-slot="model">{props.message.modelID}</div>
</div>
)}
{props.part.type === "tool" && props.part.state.status === "error" && (
<div data-component="tool" data-tool="error">
<ContentError>{formatErrorString(props.part.state.error)}</ContentError>
<Spacer />
</div>
)}
{props.part.type === "tool" &&
props.part.state.status === "completed" &&
props.message.role === "assistant" && (
<>
@@ -295,10 +288,9 @@ export function Part(props: PartProps) {
.toMillis()}
/>
</>
)
}
</div >
</div >
)}
</div>
</div>
)
}

View File

@@ -17,8 +17,6 @@ But it also accepts commands as documented on this page. This allows you to inte
opencode run "Explain how closures work in JavaScript"
```
---
## Commands

View File

@@ -62,7 +62,7 @@ Use the `command` option in your opencode [config](/docs/config):
"description": "Run tests with coverage",
"agent": "build",
"model": "anthropic/claude-3-5-sonnet-20241022"
},
}
}
}
```

View File

@@ -164,13 +164,13 @@ You can configure custom commands for repetitive tasks through the `command` opt
"template": "Run the full test suite with coverage report and show any failures.\nFocus on the failing tests and suggest fixes.",
"description": "Run tests with coverage",
"agent": "build",
"model": "anthropic/claude-3-5-sonnet-20241022"
"model": "anthropic/claude-3-5-sonnet-20241022",
},
"component": {
"template": "Create a new React component named $ARGUMENTS with TypeScript support.\nInclude proper typing and basic structure.",
"description": "Create a new component"
}
}
"description": "Create a new component",
},
},
}
```

View File

@@ -11,19 +11,19 @@ opencode automatically formats files after they are written or edited using lang
opencode comes with several built-in formatters for popular languages and frameworks. Below is a list of the formatters, supported file extensions, and commands or config options it needs.
| Formatter | Extensions | Requirements |
| -------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| gofmt | .go | `gofmt` command available |
| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available |
| Formatter | Extensions | Requirements |
| -------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| gofmt | .go | `gofmt` command available |
| mix | .ex, .exs, .eex, .heex, .leex, .neex, .sface | `mix` command available |
| prettier | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://prettier.io/docs/en/index.html) | `prettier` dependency in `package.json` |
| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file |
| zig | .zig, .zon | `zig` command available |
| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file |
| ktlint | .kt, .kts | `ktlint` command available |
| ruff | .py, .pyi | `ruff` command available with config |
| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available |
| biome | .js, .jsx, .ts, .tsx, .html, .css, .md, .json, .yaml, and [more](https://biomejs.dev/) | `biome.json(c)` config file |
| zig | .zig, .zon | `zig` command available |
| clang-format | .c, .cpp, .h, .hpp, .ino, and [more](https://clang.llvm.org/docs/ClangFormat.html) | `.clang-format` config file |
| ktlint | .kt, .kts | `ktlint` command available |
| ruff | .py, .pyi | `ruff` command available with config |
| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
| htmlbeautifier | .erb, .html.erb | `htmlbeautifier` command available |
So if your project has `prettier` in your `package.json`, opencode will automatically use it.
@@ -48,7 +48,7 @@ You can customize formatters through the `formatter` section in your opencode co
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"formatter": { }
"formatter": {}
}
```
@@ -56,7 +56,7 @@ Each formatter configuration supports the following:
| Property | Type | Description |
| ------------- | -------- | ------------------------------------------------------- |
| `disabled` | boolean | Set this to `true` to disable the formatter |
| `disabled` | boolean | Set this to `true` to disable the formatter |
| `command` | string[] | The command to run for formatting |
| `environment` | object | Environment variables to set when running the formatter |
| `extensions` | string[] | File extensions this formatter should handle |

View File

@@ -25,88 +25,89 @@ opencode runs in your GitLab CI/CD pipeline, here's what you'll need to set it u
Check out the [**GitLab docs**](https://docs.gitlab.com/user/duo_agent_platform/agent_assistant/) for up to date instructions.
:::
1. Configure your GitLab environment
2. Set up CI/CD
3. Get an AI model provider API key
4. Create a service account
5. Configure CI/CD variables
6. Create a flow config file, here's an example:
1. Configure your GitLab environment
2. Set up CI/CD
3. Get an AI model provider API key
4. Create a service account
5. Configure CI/CD variables
6. Create a flow config file, here's an example:
<details>
<summary>Flow configuration</summary>
<details>
```yaml
image: node:22-slim
commands:
- echo "Installing opencode"
- npm install --global opencode-ai
- echo "Installing glab"
- export GITLAB_TOKEN=$GITLAB_TOKEN_OPENCODE
- apt-get update --quiet && apt-get install --yes curl wget gpg git && rm --recursive --force /var/lib/apt/lists/*
- curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash
- apt-get install --yes glab
- echo "Configuring glab"
- echo $GITLAB_HOST
- echo "Creating opencode auth configuration"
- mkdir --parents ~/.local/share/opencode
- |
cat > ~/.local/share/opencode/auth.json << EOF
{
"anthropic": {
"type": "api",
"key": "$ANTHROPIC_API_KEY"
}
}
EOF
- echo "Configuring git"
- git config --global user.email "opencode@gitlab.com"
- git config --global user.name "Opencode"
- echo "Testing glab"
- glab issue list
- echo "Running Opencode"
- |
opencode run "
You are an AI assistant helping with GitLab operations.
Context: $AI_FLOW_CONTEXT
Task: $AI_FLOW_INPUT
Event: $AI_FLOW_EVENT
Please execute the requested task using the available GitLab tools.
Be thorough in your analysis and provide clear explanations.
<important>
Please use the glab CLI to access data from GitLab. The glab CLI has already been authenticated. You can run the corresponding commands.
If you are asked to summarise an MR or issue or asked to provide more information then please post back a note to the MR/Issue so that the user can see it.
You don't need to commit or push up changes, those will be done automatically based on the file changes you make.
</important>
"
- git checkout --branch $CI_WORKLOAD_REF origin/$CI_WORKLOAD_REF
- echo "Checking for git changes and pushing if any exist"
- |
if ! git diff --quiet || ! git diff --cached --quiet || [ --not --zero "$(git ls-files --others --exclude-standard)" ]; then
echo "Git changes detected, adding and pushing..."
git add .
if git diff --cached --quiet; then
echo "No staged changes to commit"
else
echo "Committing changes to branch: $CI_WORKLOAD_REF"
git commit --message "Codex changes"
echo "Pushing changes up to $CI_WORKLOAD_REF"
git push https://gitlab-ci-token:$GITLAB_TOKEN@$GITLAB_HOST/gl-demo-ultimate-dev-ai-epic-17570/test-java-project.git $CI_WORKLOAD_REF
echo "Changes successfully pushed"
fi
else
echo "No git changes detected, skipping push"
fi
variables:
- ANTHROPIC_API_KEY
- GITLAB_TOKEN_OPENCODE
- GITLAB_HOST
```
<summary>Flow configuration</summary>
</details>
```yaml
image: node:22-slim
commands:
- echo "Installing opencode"
- npm install --global opencode-ai
- echo "Installing glab"
- export GITLAB_TOKEN=$GITLAB_TOKEN_OPENCODE
- apt-get update --quiet && apt-get install --yes curl wget gpg git && rm --recursive --force /var/lib/apt/lists/*
- curl --silent --show-error --location "https://raw.githubusercontent.com/upciti/wakemeops/main/assets/install_repository" | bash
- apt-get install --yes glab
- echo "Configuring glab"
- echo $GITLAB_HOST
- echo "Creating opencode auth configuration"
- mkdir --parents ~/.local/share/opencode
- |
cat > ~/.local/share/opencode/auth.json << EOF
{
"anthropic": {
"type": "api",
"key": "$ANTHROPIC_API_KEY"
}
}
EOF
- echo "Configuring git"
- git config --global user.email "opencode@gitlab.com"
- git config --global user.name "Opencode"
- echo "Testing glab"
- glab issue list
- echo "Running Opencode"
- |
opencode run "
You are an AI assistant helping with GitLab operations.
Context: $AI_FLOW_CONTEXT
Task: $AI_FLOW_INPUT
Event: $AI_FLOW_EVENT
Please execute the requested task using the available GitLab tools.
Be thorough in your analysis and provide clear explanations.
<important>
Please use the glab CLI to access data from GitLab. The glab CLI has already been authenticated. You can run the corresponding commands.
If you are asked to summarise an MR or issue or asked to provide more information then please post back a note to the MR/Issue so that the user can see it.
You don't need to commit or push up changes, those will be done automatically based on the file changes you make.
</important>
"
- git checkout --branch $CI_WORKLOAD_REF origin/$CI_WORKLOAD_REF
- echo "Checking for git changes and pushing if any exist"
- |
if ! git diff --quiet || ! git diff --cached --quiet || [ --not --zero "$(git ls-files --others --exclude-standard)" ]; then
echo "Git changes detected, adding and pushing..."
git add .
if git diff --cached --quiet; then
echo "No staged changes to commit"
else
echo "Committing changes to branch: $CI_WORKLOAD_REF"
git commit --message "Codex changes"
echo "Pushing changes up to $CI_WORKLOAD_REF"
git push https://gitlab-ci-token:$GITLAB_TOKEN@$GITLAB_HOST/gl-demo-ultimate-dev-ai-epic-17570/test-java-project.git $CI_WORKLOAD_REF
echo "Changes successfully pushed"
fi
else
echo "No git changes detected, skipping push"
fi
variables:
- ANTHROPIC_API_KEY
- GITLAB_TOKEN_OPENCODE
- GITLAB_HOST
```
</details>
You can refer to the [GitLab CLI agents docs](https://docs.gitlab.com/user/duo_agent_platform/agent_assistant/) for detailed instructions.

View File

@@ -41,26 +41,10 @@ You can also install it with the following:
- **Using Node.js**
<Tabs>
<TabItem label="npm">
```bash
npm install -g opencode-ai
```
</TabItem>
<TabItem label="Bun">
```bash
bun install -g opencode-ai
```
</TabItem>
<TabItem label="pnpm">
```bash
pnpm install -g opencode-ai
```
</TabItem>
<TabItem label="Yarn">
```bash
yarn global add opencode-ai
```
</TabItem>
<TabItem label="npm">```bash npm install -g opencode-ai ```</TabItem>
<TabItem label="Bun">```bash bun install -g opencode-ai ```</TabItem>
<TabItem label="pnpm">```bash pnpm install -g opencode-ai ```</TabItem>
<TabItem label="Yarn">```bash yarn global add opencode-ai ```</TabItem>
</Tabs>
- **Using Homebrew on macOS and Linux**

View File

@@ -24,6 +24,7 @@ opencode comes with several built-in LSP servers for popular languages:
| vue | .vue | Auto-installs for Vue projects |
| rust | .rs | `rust-analyzer` command available |
| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects |
| svelte | .svelte | Auto-installs for Svelte projects |
LSP servers are automatically enabled when one of the above file extensions are detected and the requirements are met.

View File

@@ -79,9 +79,9 @@ The opencode server exposes the following APIs.
### Config
| Method | Path | Description | Response |
| ------ | ------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `GET` | `/config` | Get config info | <a href={typesUrl}><code>Config</code></a> |
| Method | Path | Description | Response |
| ------ | ------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- |
| `GET` | `/config` | Get config info | <a href={typesUrl}><code>Config</code></a> |
| `GET` | `/config/providers` | List providers and default models | `{ providers: `<a href={typesUrl}>Provider[]</a>`, default: { [key: string]: string } }` |
---
@@ -101,8 +101,8 @@ The opencode server exposes the following APIs.
| `POST` | `/session/:id/share` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
| `DELETE` | `/session/:id/share` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
| `POST` | `/session/:id/summarize` | Summarize session | |
| `GET` | `/session/:id/message` | List messages in a session | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}[]` |
| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` |
| `GET` | `/session/:id/message` | List messages in a session | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}[]` |
| `GET` | `/session/:id/message/:messageID` | Get message details | Returns `{ info: `<a href={typesUrl}>Message</a>`, parts: `<a href={typesUrl}>Part[]</a>`}` |
| `POST` | `/session/:id/message` | Send chat message | body matches [`ChatInput`](https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts#L358), returns <a href={typesUrl}><code>Message</code></a> |
| `POST` | `/session/:id/shell` | Run a shell command | body matches [`CommandInput`](https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts#L1007), returns <a href={typesUrl}><code>Message</code></a> |
| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID }` |
@@ -175,6 +175,6 @@ The opencode server exposes the following APIs.
### Docs
| Method | Path | Description | Response |
| ------ | ------ | -------------------------------------- | ------------------------------------------ |
| Method | Path | Description | Response |
| ------ | ------ | ------------------------- | --------------------------- |
| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec |

View File

@@ -272,6 +272,7 @@ Both the `/editor` and `/export` commands use the editor specified in your `EDIT
To make it permanent, add this to your shell profile;
`~/.bashrc`, `~/.zshrc`, etc.
</TabItem>
<TabItem label="Windows (CMD)">
@@ -284,6 +285,7 @@ Both the `/editor` and `/export` commands use the editor specified in your `EDIT
To make it permanent, use **System Properties** > **Environment
Variables**.
</TabItem>
<TabItem label="Windows (PowerShell)">
@@ -295,6 +297,7 @@ Both the `/editor` and `/export` commands use the editor specified in your `EDIT
```
To make it permanent, add this to your PowerShell profile.
</TabItem>
</Tabs>

View File

@@ -73,11 +73,11 @@ We created opencode zen to:
Below are the models that we currently support and their prices **per 1M
tokens**.
| Model | Input | Output |
| ----- | ----- | ------ |
| Qwen3 Coder 480B | $0.38 | $0.50 |
| Grok Code Fast 1 | Free | Free |
| Sonnet 4 | Soon | Soon |
| Model | Input | Output |
| ---------------- | ----- | ------ |
| Qwen3 Coder 480B | $0.38 | $0.50 |
| Grok Code Fast 1 | Free | Free |
| Sonnet 4 | Soon | Soon |
:::note
We add a small markup to cover our costs.
@@ -86,7 +86,7 @@ We add a small markup to cover our costs.
A couple of notes:
- These are the raw prices based on the provider we are using internally. We
charge a small markup on top of this to cover our processing fees.
charge a small markup on top of this to cover our processing fees.
- Grok Code Fast 1 is currently free on opencode till Sep 10th. The xAI team is
using this time to collect feedback and improve Grok Code.

View File

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

13
script/format.ts Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bun
import { $ } from "bun"
await $`bun run prettier --ignore-unknown --write $(git ls-files)`
if (process.env["CI"] && (await $`git status --porcelain`.text())) {
await $`git config --local user.email "action@github.com"`
await $`git config --local user.name "GitHub Action"`
await $`git add -A`
await $`git commit -m "chore: format code"`
await $`git push --no-verify`
}

View File

@@ -2,15 +2,25 @@
import { $ } from "bun"
if (process.versions.bun !== "1.2.21") {
throw new Error("This script requires bun@1.2.21")
}
console.log("=== publishing ===\n")
const snapshot = process.env["OPENCODE_SNAPSHOT"] === "true"
const version = snapshot
? `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
: process.env["OPENCODE_VERSION"]
if (!version) {
throw new Error("OPENCODE_VERSION is required")
}
const version = await (async () => {
if (snapshot) return `0.0.0-${new Date().toISOString().slice(0, 16).replace(/[-:T]/g, "")}`
const [major, minor, patch] = (await $`gh release list --limit 1 --json tagName --jq '.[0].tagName'`.text())
.trim()
.replace(/^v/, "")
.split(".")
.map((x) => Number(x) || 0)
const t = process.env["OPENCODE_BUMP"]?.toLowerCase()
if (t === "major") return `${major + 1}.0.0`
if (t === "minor") return `${major}.${minor + 1}.0`
return `${major}.${minor}.${patch + 1}`
})()
process.env["OPENCODE_VERSION"] = version
console.log("version:", version)

View File

@@ -1,42 +1,3 @@
#!/usr/bin/env bash
# Parse command line arguments
minor=false
while [ "$#" -gt 0 ]; do
case "$1" in
--minor) minor=true; shift 1;;
*) echo "Unknown parameter: $1"; exit 1;;
esac
done
# Get the latest release from GitHub
latest_tag=$(gh release list --limit 1 --json tagName --jq '.[0].tagName')
# If there is no tag, exit the script
if [ -z "$latest_tag" ]; then
echo "No tags found"
exit 1
fi
echo "Latest tag: $latest_tag"
# Remove the 'v' prefix and split into major, minor, and patch numbers
version_without_v=${latest_tag#v}
IFS='.' read -ra VERSION <<< "$version_without_v"
if [ "$minor" = true ]; then
# Increment the minor version and reset patch to 0
minor_number=${VERSION[1]}
let "minor_number++"
new_version="${VERSION[0]}.$minor_number.0"
else
# Increment the patch version
patch_number=${VERSION[2]}
let "patch_number++"
new_version="${VERSION[0]}.${VERSION[1]}.$patch_number"
fi
echo "New version: $new_version"
gh workflow run publish.yml -f version="$new_version"
gh workflow run publish.yml -f bump="patch"

View File

@@ -1,5 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
import { defineConfig } from "@vscode/test-cli"
export default defineConfig({
files: 'out/test/**/*.test.js',
});
files: "out/test/**/*.test.js",
})

View File

@@ -1,56 +1,54 @@
const esbuild = require("esbuild");
const esbuild = require("esbuild")
const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');
const production = process.argv.includes("--production")
const watch = process.argv.includes("--watch")
/**
* @type {import('esbuild').Plugin}
*/
const esbuildProblemMatcherPlugin = {
name: 'esbuild-problem-matcher',
name: "esbuild-problem-matcher",
setup(build) {
build.onStart(() => {
console.log('[watch] build started');
});
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`);
console.error(` ${location.file}:${location.line}:${location.column}:`);
});
console.log('[watch] build finished');
});
},
};
async function main() {
const ctx = await esbuild.context({
entryPoints: [
'src/extension.ts'
],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'node',
outfile: 'dist/extension.js',
external: ['vscode'],
logLevel: 'silent',
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin,
],
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
setup(build) {
build.onStart(() => {
console.log("[watch] build started")
})
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`)
console.error(` ${location.file}:${location.line}:${location.column}:`)
})
console.log("[watch] build finished")
})
},
}
main().catch(e => {
console.error(e);
process.exit(1);
});
async function main() {
const ctx = await esbuild.context({
entryPoints: ["src/extension.ts"],
bundle: true,
format: "cjs",
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: "node",
outfile: "dist/extension.js",
external: ["vscode"],
logLevel: "silent",
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin,
],
})
if (watch) {
await ctx.watch()
} else {
await ctx.rebuild()
await ctx.dispose()
}
}
main().catch((e) => {
console.error(e)
process.exit(1)
})

View File

@@ -1,28 +1,34 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import typescriptEslint from "@typescript-eslint/eslint-plugin"
import tsParser from "@typescript-eslint/parser"
export default [{
export default [
{
files: ["**/*.ts"],
}, {
},
{
plugins: {
"@typescript-eslint": typescriptEslint,
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
"@typescript-eslint/naming-convention": [
"warn",
{
selector: "import",
format: ["camelCase", "PascalCase"],
},
],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
}];
},
]

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