Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
491abd6b5b | ||
|
|
4518f96e3d | ||
|
|
a9dcbedf99 | ||
|
|
9231043eb4 | ||
|
|
04dcd87170 | ||
|
|
c31fd9ed79 | ||
|
|
2989d92794 | ||
|
|
256d074411 | ||
|
|
8b01676ec0 | ||
|
|
34c6c8494a | ||
|
|
522bed6b7d | ||
|
|
dda672284c | ||
|
|
6018364164 | ||
|
|
bc0d438cee | ||
|
|
abef91c223 | ||
|
|
1bbf6d38e5 | ||
|
|
c9c9db1e8d | ||
|
|
b11fe9fbc6 | ||
|
|
60f3d413de | ||
|
|
1df2d78b85 | ||
|
|
2286a872c1 | ||
|
|
8a83301e0d | ||
|
|
9bc40f00e3 | ||
|
|
c3c440948a | ||
|
|
aa10f8a7f6 | ||
|
|
a2db58f125 | ||
|
|
574be9febf | ||
|
|
5b05ede748 | ||
|
|
4032426185 | ||
|
|
8d8045ff95 | ||
|
|
b3c8bec019 | ||
|
|
25f43adaa0 | ||
|
|
4913ee6afd | ||
|
|
c59ded82b3 | ||
|
|
40bdbf92a3 | ||
|
|
ad76d7e57d | ||
|
|
863ae6fa7d | ||
|
|
8f230ad4b4 | ||
|
|
c0f90eb564 | ||
|
|
50fb337270 | ||
|
|
e08ec077b0 | ||
|
|
796245d146 |
@@ -36,10 +36,8 @@ jobs:
|
||||
|
||||
Issue number:
|
||||
${{ github.event.issue.number }}
|
||||
Issue body:
|
||||
${{ github.event.issue.body }}
|
||||
|
||||
Please search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
||||
Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
|
||||
Consider:
|
||||
1. Similar titles or descriptions
|
||||
2. Same error messages or symptoms
|
||||
|
||||
@@ -24,3 +24,6 @@ example, if the page title is "Models", avoid using a section title like "Add
|
||||
new models". This might be unavoidable in some cases, but try to avoid it.
|
||||
|
||||
Check out the /packages/web/src/content/docs/docs/index.mdx as an example.
|
||||
|
||||
For JS or TS code snippets remove trailing semicolons and any trailing commas
|
||||
that might not be needed.
|
||||
|
||||
@@ -52,3 +52,6 @@
|
||||
| 2025-08-16 | 198,118 (+4,510) | 184,558 (+2,766) | 382,676 (+7,276) |
|
||||
| 2025-08-17 | 201,299 (+3,181) | 186,269 (+1,711) | 387,568 (+4,892) |
|
||||
| 2025-08-18 | 204,559 (+3,260) | 187,399 (+1,130) | 391,958 (+4,390) |
|
||||
| 2025-08-19 | 209,814 (+5,255) | 189,668 (+2,269) | 399,482 (+7,524) |
|
||||
| 2025-08-20 | 214,497 (+4,683) | 191,481 (+1,813) | 405,978 (+6,496) |
|
||||
| 2025-08-21 | 220,465 (+5,968) | 194,784 (+3,303) | 415,249 (+9,271) |
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opencode/cloud-core": "workspace:*",
|
||||
"@solidjs/meta": "^0.29.4",
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
@@ -25,7 +26,7 @@
|
||||
},
|
||||
"cloud/core": {
|
||||
"name": "@opencode/cloud-core",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"drizzle-orm": "0.41.0",
|
||||
@@ -39,7 +40,7 @@
|
||||
},
|
||||
"cloud/function": {
|
||||
"name": "@opencode/cloud-function",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -59,7 +60,7 @@
|
||||
},
|
||||
"cloud/web": {
|
||||
"name": "@opencode/cloud-web",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "0.13.9",
|
||||
"@openauthjs/solid": "0.0.0-20250322224806",
|
||||
@@ -78,7 +79,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode/function",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
@@ -93,7 +94,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -143,7 +144,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
},
|
||||
@@ -155,7 +156,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.80.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -164,7 +165,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode/web",
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.12",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -186,7 +187,7 @@
|
||||
"sharp": "0.32.5",
|
||||
"shiki": "3.4.2",
|
||||
"solid-js": "1.9.7",
|
||||
"toolbeam-docs-theme": "0.4.3",
|
||||
"toolbeam-docs-theme": "0.4.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
@@ -2804,7 +2805,7 @@
|
||||
|
||||
"toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="],
|
||||
|
||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.3", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-3um/NsSq4xFeKbKrNGPHIzfTixwnEVvroqA8Q+lecnYHHJ5TtiYTggHDqewOW+I67t0J1IVBwVKUPjxiQfIcog=="],
|
||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.5", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-8yu1Q57pYujy1EnQuZ5JzrV6uzTWwUAcTkBkkujtFNNUBC8jTASWp5WypiViVMd59oFqzedy1PR0G9nBBhZvRQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"dev": "vinxi dev --host 0.0.0.0",
|
||||
"build": "vinxi build",
|
||||
"start": "vinxi start",
|
||||
"version": "0.5.7"
|
||||
"version": "0.5.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -14,7 +14,8 @@
|
||||
"@solidjs/router": "^0.15.0",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"solid-js": "^1.9.5",
|
||||
"vinxi": "^0.5.7"
|
||||
"vinxi": "^0.5.7",
|
||||
"@opencode/cloud-core": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MetaProvider, Title } from "@solidjs/meta";
|
||||
import { Router } from "@solidjs/router";
|
||||
import { FileRoutes } from "@solidjs/start/router";
|
||||
import { Suspense } from "solid-js";
|
||||
import { ErrorBoundary, Suspense } from "solid-js";
|
||||
import "@ibm/plex/css/ibm-plex.css";
|
||||
import "./app.css";
|
||||
|
||||
@@ -11,7 +11,9 @@ export default function App() {
|
||||
root={props => (
|
||||
<MetaProvider>
|
||||
<Title>SolidStart - Basic</Title>
|
||||
<Suspense>{props.children}</Suspense>
|
||||
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</ErrorBoundary>
|
||||
</MetaProvider>
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,90 @@
|
||||
|
||||
|
||||
import { useSession } from "vinxi/http"
|
||||
import { createClient } from "@openauthjs/openauth/client"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { and, Database, eq, inArray } from "@opencode/cloud-core/drizzle/index.js"
|
||||
import { WorkspaceTable } from "@opencode/cloud-core/schema/workspace.sql.js"
|
||||
import { UserTable } from "@opencode/cloud-core/schema/user.sql.js"
|
||||
import { query, redirect } from "@solidjs/router"
|
||||
import { AccountTable } from "@opencode/cloud-core/schema/account.sql.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
|
||||
export async function withActor<T>(fn: () => T) {
|
||||
const actor = await getActor()
|
||||
return Actor.provide(actor.type, actor.properties, fn)
|
||||
}
|
||||
|
||||
export const getActor = query(async (): Promise<Actor.Info> => {
|
||||
"use server"
|
||||
const evt = getRequestEvent()
|
||||
const url = new URL(evt!.request.headers.get("referer") ?? evt!.request.url)
|
||||
const auth = await useAuthSession()
|
||||
const [workspaceHint] = url.pathname.split("/").filter((x) => x.length > 0)
|
||||
if (!workspaceHint) {
|
||||
if (auth.data.current) {
|
||||
const current = auth.data.account[auth.data.current]
|
||||
return {
|
||||
type: "account",
|
||||
properties: {
|
||||
email: current.email,
|
||||
accountID: current.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (Object.keys(auth.data.account).length > 0) {
|
||||
const current = Object.values(auth.data.account)[0]
|
||||
await auth.update(val => ({
|
||||
...val,
|
||||
current: current.id,
|
||||
}))
|
||||
return {
|
||||
type: "account",
|
||||
properties: {
|
||||
email: current.email,
|
||||
accountID: current.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "public",
|
||||
properties: {},
|
||||
}
|
||||
}
|
||||
const accounts = Object.keys(auth.data.account)
|
||||
const result = await Database.transaction(async (tx) => {
|
||||
return await tx.select({
|
||||
user: UserTable
|
||||
})
|
||||
.from(AccountTable)
|
||||
.innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
|
||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
|
||||
.where(
|
||||
and(
|
||||
inArray(AccountTable.id, accounts),
|
||||
eq(WorkspaceTable.id, workspaceHint),
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.execute()
|
||||
.then((x) => x[0])
|
||||
})
|
||||
if (result) {
|
||||
return {
|
||||
type: "user",
|
||||
properties: {
|
||||
userID: result.user.id,
|
||||
workspaceID: result.user.workspaceID,
|
||||
},
|
||||
}
|
||||
}
|
||||
throw redirect("/auth/authorize")
|
||||
}, "actor")
|
||||
|
||||
|
||||
export const AuthClient = createClient({
|
||||
clientID: "app",
|
||||
issuer: "https://auth.dev.opencode.ai",
|
||||
issuer: import.meta.env.VITE_AUTH_URL,
|
||||
})
|
||||
|
||||
export interface AuthSession {
|
||||
@@ -15,7 +96,6 @@ export interface AuthSession {
|
||||
}
|
||||
|
||||
export function useAuthSession() {
|
||||
"use server"
|
||||
|
||||
return useSession<AuthSession>({
|
||||
password: "0".repeat(32),
|
||||
@@ -26,3 +106,4 @@ export function useAuthSession() {
|
||||
|
||||
export function AuthProvider() {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createAsync, query } from "@solidjs/router"
|
||||
import { getActor, withActor } from "~/context/auth"
|
||||
|
||||
const getPosts = query(async () => {
|
||||
"use server"
|
||||
return withActor(() => {
|
||||
return "ok"
|
||||
})
|
||||
}, "posts")
|
||||
|
||||
|
||||
export default function () {
|
||||
const actor = createAsync(async () => getActor())
|
||||
return <div>{JSON.stringify(actor())}</div>
|
||||
}
|
||||
@@ -5,11 +5,6 @@ export async function GET(input: APIEvent) {
|
||||
const url = new URL(input.request.url)
|
||||
const code = url.searchParams.get("code")
|
||||
if (!code) throw new Error("No code found")
|
||||
const redirectURI = `${url.origin}${url.pathname}`
|
||||
console.log({
|
||||
redirectURI,
|
||||
code,
|
||||
})
|
||||
const result = await AuthClient.exchange(code, `${url.origin}${url.pathname}`)
|
||||
if (result.err) {
|
||||
throw new Error(result.err.message)
|
||||
|
||||
@@ -6,6 +6,9 @@ import IMG_SPLASH from "../asset/screenshot-splash.webp"
|
||||
import IMG_VSCODE from "../asset/screenshot-vscode.webp"
|
||||
import IMG_GITHUB from "../asset/screenshot-github.webp"
|
||||
import { IconCopy, IconCheck } from "../component/icon"
|
||||
import { createAsync, query, redirect, RouteDefinition } from "@solidjs/router"
|
||||
import { getActor, withActor } from "~/context/auth"
|
||||
import { Account } from "@opencode/cloud-core/account.js"
|
||||
|
||||
function CopyStatus() {
|
||||
return (
|
||||
@@ -16,7 +19,22 @@ function CopyStatus() {
|
||||
)
|
||||
}
|
||||
|
||||
const isLoggedIn = query(async () => {
|
||||
"use server"
|
||||
const actor = await getActor()
|
||||
if (actor.type === "account") {
|
||||
const workspaces = await withActor(() => Account.workspaces())
|
||||
throw redirect("/" + workspaces[0].id)
|
||||
}
|
||||
return
|
||||
}, "isLoggedIn")
|
||||
|
||||
|
||||
|
||||
export default function Home() {
|
||||
createAsync(() => isLoggedIn(), {
|
||||
deferStream: true,
|
||||
})
|
||||
onMount(() => {
|
||||
const commands = document.querySelectorAll("[data-copy]")
|
||||
for (const button of commands) {
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cloud-core",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -20,7 +20,6 @@ export namespace Actor {
|
||||
properties: {
|
||||
userID: string
|
||||
workspaceID: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Resource } from "sst"
|
||||
export * from "drizzle-orm"
|
||||
import postgres from "postgres"
|
||||
|
||||
function createClient() {
|
||||
const createClient = memo(() => {
|
||||
const client = postgres({
|
||||
idle_timeout: 30000,
|
||||
connect_timeout: 30000,
|
||||
@@ -19,12 +19,13 @@ function createClient() {
|
||||
})
|
||||
|
||||
return drizzle(client, {})
|
||||
}
|
||||
})
|
||||
|
||||
import { PgTransaction, type PgTransactionConfig } from "drizzle-orm/pg-core"
|
||||
import type { ExtractTablesWithRelations } from "drizzle-orm"
|
||||
import type { PostgresJsQueryResultHKT } from "drizzle-orm/postgres-js"
|
||||
import { Context } from "../context"
|
||||
import { memo } from "../util/memo"
|
||||
|
||||
export namespace Database {
|
||||
export type Transaction = PgTransaction<
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export function memo<T>(fn: () => T) {
|
||||
let value: T | undefined
|
||||
let loaded = false
|
||||
|
||||
return (): T => {
|
||||
if (loaded) return value as T
|
||||
loaded = true
|
||||
value = fn()
|
||||
return value as T
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/cloud-function",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -2,11 +2,12 @@ import { Resource } from "sst"
|
||||
import { z } from "zod"
|
||||
import { issuer } from "@openauthjs/openauth"
|
||||
import { createSubjects } from "@openauthjs/openauth/subject"
|
||||
import { CodeProvider } from "@openauthjs/openauth/provider/code"
|
||||
import { GithubProvider } from "@openauthjs/openauth/provider/github"
|
||||
import { GoogleOidcProvider } from "@openauthjs/openauth/provider/google"
|
||||
import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
|
||||
import { Account } from "@opencode/cloud-core/account.js"
|
||||
import { Workspace } from "@opencode/cloud-core/workspace.js"
|
||||
import { Actor } from "@opencode/cloud-core/actor.js"
|
||||
|
||||
type Env = {
|
||||
AuthStorage: KVNamespace
|
||||
@@ -117,6 +118,12 @@ export default {
|
||||
email: email!,
|
||||
})
|
||||
}
|
||||
await Actor.provide("account", { accountID, email }, async () => {
|
||||
const workspaces = await Account.workspaces()
|
||||
if (workspaces.length === 0) {
|
||||
await Workspace.create()
|
||||
}
|
||||
})
|
||||
return ctx.subject("account", accountID, { accountID, email })
|
||||
},
|
||||
}).fetch(request, env, ctx)
|
||||
|
||||
Vendored
-4
@@ -14,10 +14,6 @@ declare module "sst" {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"DATABASE_PASSWORD": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/cloud-web",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"type": "module",
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
|
||||
/// <reference path="../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
+2
-2
@@ -25,8 +25,8 @@ export const api = new sst.cloudflare.Worker("Api", {
|
||||
])
|
||||
args.migrations = {
|
||||
// Note: when releasing the next tag, make sure all stages use tag v2
|
||||
oldTag: $app.stage === "production" ? "" : "v1",
|
||||
newTag: $app.stage === "production" ? "" : "v1",
|
||||
oldTag: $app.stage === "production" || $app.stage === "thdxr" ? "" : "v1",
|
||||
newTag: $app.stage === "production" || $app.stage === "thdxr" ? "" : "v1",
|
||||
//newSqliteClasses: ["SyncServer"],
|
||||
}
|
||||
},
|
||||
|
||||
+14
-1
@@ -10,7 +10,7 @@ const DATABASE_USERNAME = new sst.Secret("DATABASE_USERNAME")
|
||||
const DATABASE_PASSWORD = new sst.Secret("DATABASE_PASSWORD")
|
||||
export const database = new sst.Linkable("Database", {
|
||||
properties: {
|
||||
host: "aws-us-east-2-1.pg.psdb.cloud",
|
||||
host: `aws-us-east-2-${$app.stage === "thdxr" ? "2" : "1"}.pg.psdb.cloud`,
|
||||
database: "postgres",
|
||||
username: DATABASE_USERNAME.value,
|
||||
password: DATABASE_PASSWORD.value,
|
||||
@@ -106,6 +106,7 @@ export const gateway = new sst.cloudflare.Worker("GatewayApi", {
|
||||
// CONSOLE
|
||||
////////////////
|
||||
|
||||
/*
|
||||
export const console = new sst.cloudflare.x.StaticSite("Console", {
|
||||
domain: `console.${domain}`,
|
||||
path: "cloud/web",
|
||||
@@ -119,3 +120,15 @@ export const console = new sst.cloudflare.x.StaticSite("Console", {
|
||||
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
||||
},
|
||||
})
|
||||
*/
|
||||
|
||||
new sst.x.DevCommand("Solid", {
|
||||
link: [database],
|
||||
dev: {
|
||||
directory: "cloud/app",
|
||||
command: "bun dev",
|
||||
},
|
||||
environment: {
|
||||
VITE_AUTH_URL: auth.url.apply((url) => url!),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/function",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
Vendored
-4
@@ -14,10 +14,6 @@ declare module "sst" {
|
||||
"type": "sst.sst.Linkable"
|
||||
"value": string
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"DATABASE_PASSWORD": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
|
||||
@@ -97,7 +97,6 @@ if (!snapshot) {
|
||||
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||
|
||||
/* AUR package - commented out as AUR is down
|
||||
const pkgbuild = [
|
||||
"# Maintainer: dax",
|
||||
"# Maintainer: adam",
|
||||
@@ -136,7 +135,6 @@ if (!snapshot) {
|
||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
||||
if (!dry) await $`cd ./dist/aur-${pkg} && git push`
|
||||
}
|
||||
*/
|
||||
|
||||
// Homebrew formula
|
||||
const homebrewFormula = [
|
||||
|
||||
@@ -100,7 +100,7 @@ export namespace Agent {
|
||||
tools: {},
|
||||
builtIn: false,
|
||||
}
|
||||
const { model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||
const { name, model, prompt, tools, description, temperature, top_p, mode, permission, ...extra } = value
|
||||
item.options = {
|
||||
...item.options,
|
||||
...extra,
|
||||
@@ -120,6 +120,8 @@ export namespace Agent {
|
||||
if (temperature != undefined) item.temperature = temperature
|
||||
if (top_p != undefined) item.topP = top_p
|
||||
if (mode) item.mode = mode
|
||||
// just here for consistency & to prevent it from being added as an option
|
||||
if (name) item.name = name
|
||||
|
||||
if (permission ?? cfg.permission) {
|
||||
item.permission = mergeAgentPermissions(cfg.permission ?? {}, permission ?? {})
|
||||
|
||||
@@ -8,9 +8,9 @@ import { Snapshot } from "../snapshot"
|
||||
|
||||
export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Promise<T>) {
|
||||
return App.provide(input, async (app) => {
|
||||
await Plugin.init()
|
||||
Share.init()
|
||||
Format.init()
|
||||
Plugin.init()
|
||||
ConfigHooks.init()
|
||||
LSP.init()
|
||||
Snapshot.init()
|
||||
|
||||
@@ -46,7 +46,10 @@ const AgentCreateCommand = cmd({
|
||||
const spinner = prompts.spinner()
|
||||
|
||||
spinner.start("Generating agent configuration...")
|
||||
const generated = await Agent.generate({ description: query })
|
||||
const generated = await Agent.generate({ description: query }).catch((error) => {
|
||||
spinner.stop(`LLM failed to generate agent: ${error.message}`, 1)
|
||||
throw new UI.CancelledError()
|
||||
})
|
||||
spinner.stop(`Agent ${generated.identifier} generated`)
|
||||
|
||||
const availableTools = [
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Auth } from "../../auth"
|
||||
import { cmd } from "./cmd"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import open from "open"
|
||||
import { UI } from "../ui"
|
||||
import { ModelsDev } from "../../provider/models"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
@@ -159,9 +158,6 @@ export const AuthLoginCommand = cmd({
|
||||
const authorize = await method.authorize()
|
||||
|
||||
if (authorize.url) {
|
||||
try {
|
||||
await open(authorize.url)
|
||||
} catch (e) {}
|
||||
prompts.log.info("Go to: " + authorize.url)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,11 +67,17 @@ export const RunCommand = cmd({
|
||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||
const session = await (async () => {
|
||||
if (args.continue) {
|
||||
const list = Session.list()
|
||||
const first = await list.next()
|
||||
await list.return()
|
||||
if (first.done) return
|
||||
return first.value
|
||||
const it = Session.list()
|
||||
try {
|
||||
for await (const s of it) {
|
||||
if (s.parentID === undefined) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return
|
||||
} finally {
|
||||
await it.return()
|
||||
}
|
||||
}
|
||||
|
||||
if (args.session) return Session.get(args.session)
|
||||
|
||||
@@ -82,11 +82,17 @@ export const TuiCommand = cmd({
|
||||
const result = await bootstrap({ cwd }, async (app) => {
|
||||
const sessionID = await (async () => {
|
||||
if (args.continue) {
|
||||
const list = Session.list()
|
||||
const first = await list.next()
|
||||
await list.return()
|
||||
if (first.done) return
|
||||
return first.value.id
|
||||
const it = Session.list()
|
||||
try {
|
||||
for await (const s of it) {
|
||||
if (s.parentID === undefined) {
|
||||
return s.id
|
||||
}
|
||||
}
|
||||
return
|
||||
} finally {
|
||||
await it.return()
|
||||
}
|
||||
}
|
||||
if (args.session) {
|
||||
return args.session
|
||||
|
||||
@@ -45,7 +45,7 @@ export const UpgradeCommand = {
|
||||
spinner.start("Upgrading...")
|
||||
const err = await Installation.upgrade(method, target).catch((err) => err)
|
||||
if (err) {
|
||||
spinner.stop("Upgrade failed")
|
||||
spinner.stop("Upgrade failed", 1)
|
||||
if (err instanceof Installation.UpgradeFailedError) prompts.log.error(err.data.stderr)
|
||||
else if (err instanceof Error) prompts.log.error(err.message)
|
||||
prompts.outro("Done")
|
||||
|
||||
@@ -64,7 +64,7 @@ export namespace Config {
|
||||
if (agentFolderPath.includes("/")) {
|
||||
const relativePath = agentFolderPath.replace(".md", "")
|
||||
const pathParts = relativePath.split("/")
|
||||
agentName = pathParts.slice(0, -1).join("/").toUpperCase() + "/" + pathParts[pathParts.length - 1].toUpperCase()
|
||||
agentName = pathParts.slice(0, -1).join("/") + "/" + pathParts[pathParts.length - 1]
|
||||
}
|
||||
|
||||
const config = {
|
||||
|
||||
@@ -28,7 +28,7 @@ await Promise.all([
|
||||
fs.mkdir(Global.Path.bin, { recursive: true }),
|
||||
])
|
||||
|
||||
const CACHE_VERSION = "8"
|
||||
const CACHE_VERSION = "9"
|
||||
|
||||
const version = await Bun.file(path.join(Global.Path.cache, "version"))
|
||||
.text()
|
||||
|
||||
@@ -25,7 +25,7 @@ export namespace Plugin {
|
||||
}
|
||||
const plugins = [...(config.plugin ?? [])]
|
||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||
plugins.push("opencode-copilot-auth")
|
||||
plugins.push("opencode-copilot-auth@0.0.2")
|
||||
plugins.push("opencode-anthropic-auth@0.0.2")
|
||||
}
|
||||
for (let plugin of plugins) {
|
||||
@@ -68,7 +68,12 @@ export namespace Plugin {
|
||||
return state().then((x) => x.hooks)
|
||||
}
|
||||
|
||||
export function init() {
|
||||
export async function init() {
|
||||
const hooks = await state().then((x) => x.hooks)
|
||||
const config = await Config.get()
|
||||
for (const hook of hooks) {
|
||||
hook.config?.(config)
|
||||
}
|
||||
Bus.subscribeAll(async (input) => {
|
||||
const hooks = await state().then((x) => x.hooks)
|
||||
for (const hook of hooks) {
|
||||
|
||||
@@ -36,6 +36,12 @@ export namespace Provider {
|
||||
},
|
||||
}
|
||||
},
|
||||
async opencode() {
|
||||
return {
|
||||
autoload: true,
|
||||
options: {},
|
||||
}
|
||||
},
|
||||
openai: async () => {
|
||||
return {
|
||||
autoload: false,
|
||||
@@ -276,6 +282,15 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
for (const [providerID, provider] of Object.entries(providers)) {
|
||||
// Filter out blacklisted models
|
||||
const filteredModels = Object.fromEntries(
|
||||
Object.entries(provider.info.models).filter(
|
||||
([modelID]) =>
|
||||
modelID !== "gpt-5-chat-latest" && !(providerID === "openrouter" && modelID === "openai/gpt-5-chat"),
|
||||
),
|
||||
)
|
||||
provider.info.models = filteredModels
|
||||
|
||||
if (Object.keys(provider.info.models).length === 0) {
|
||||
delete providers[providerID]
|
||||
continue
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
|
||||
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
||||
import PROMPT_PLAN from "../session/prompt/plan.txt"
|
||||
import BUILD_SWITCH from "../session/prompt/build-switch.txt"
|
||||
|
||||
import { App } from "../app/app"
|
||||
import { Bus } from "../bus"
|
||||
@@ -726,6 +727,18 @@ export namespace Session {
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
|
||||
const lastAssistantMsg = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
|
||||
if (lastAssistantMsg?.mode === "plan" && agent.name === "build") {
|
||||
msgs.at(-1)?.parts.push({
|
||||
id: Identifier.ascending("part"),
|
||||
messageID: userMsg.id,
|
||||
sessionID: input.sessionID,
|
||||
type: "text",
|
||||
text: BUILD_SWITCH,
|
||||
synthetic: true,
|
||||
})
|
||||
}
|
||||
let system = SystemPrompt.header(input.providerID)
|
||||
system.push(
|
||||
...(() => {
|
||||
@@ -945,6 +958,13 @@ export namespace Session {
|
||||
toolName: "invalid",
|
||||
}
|
||||
},
|
||||
headers:
|
||||
input.providerID === "opencode"
|
||||
? {
|
||||
"x-opencode-session": input.sessionID,
|
||||
"x-opencode-request": userMsg.id,
|
||||
}
|
||||
: undefined,
|
||||
maxRetries: 3,
|
||||
activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
|
||||
maxOutputTokens: outputLimit,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
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.
|
||||
@@ -86,7 +86,7 @@ export namespace ToolRegistry {
|
||||
result["webfetch"] = false
|
||||
}
|
||||
|
||||
if (modelID.includes("qwen")) {
|
||||
if (modelID.toLowerCase().includes("qwen")) {
|
||||
result["todowrite"] = false
|
||||
result["todoread"] = false
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
UserMessage,
|
||||
Part,
|
||||
Auth,
|
||||
Config,
|
||||
} from "@opencode-ai/sdk"
|
||||
import type { BunShell } from "./shell"
|
||||
|
||||
@@ -20,6 +21,7 @@ export type Plugin = (input: PluginInput) => Promise<Hooks>
|
||||
|
||||
export interface Hooks {
|
||||
event?: (input: { event: Event }) => Promise<void>
|
||||
config?: (input: Config) => Promise<void>
|
||||
auth?: {
|
||||
provider: string
|
||||
loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -207,6 +207,9 @@ func New(
|
||||
|
||||
func (a *App) Keybind(commandName commands.CommandName) string {
|
||||
command := a.Commands[commandName]
|
||||
if len(command.Keybindings) == 0 {
|
||||
return ""
|
||||
}
|
||||
kb := command.Keybindings[0]
|
||||
key := kb.Key
|
||||
if kb.RequiresLeader {
|
||||
|
||||
@@ -382,11 +382,9 @@ func (m *editorComponent) Content() string {
|
||||
status = "waiting for permission"
|
||||
}
|
||||
if m.interruptKeyInDebounce && m.app.CurrentPermission.ID == "" {
|
||||
bright := t.Accent()
|
||||
if status == "waiting for permission" {
|
||||
bright = t.Warning()
|
||||
}
|
||||
hint = util.Shimmer(status, t.Background(), t.TextMuted(), bright) + m.spinner.View() + muted(
|
||||
hint = muted(
|
||||
status,
|
||||
) + m.spinner.View() + muted(
|
||||
" ",
|
||||
) + base(
|
||||
keyText+" again",
|
||||
@@ -394,11 +392,7 @@ func (m *editorComponent) Content() string {
|
||||
" interrupt",
|
||||
)
|
||||
} else {
|
||||
bright := t.Accent()
|
||||
if status == "waiting for permission" {
|
||||
bright = t.Warning()
|
||||
}
|
||||
hint = util.Shimmer(status, t.Background(), t.TextMuted(), bright) + m.spinner.View()
|
||||
hint = muted(status) + m.spinner.View()
|
||||
if m.app.CurrentPermission.ID == "" {
|
||||
hint += muted(" ") + base(keyText) + muted(" interrupt")
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ func renderText(
|
||||
extra string,
|
||||
isThinking bool,
|
||||
isQueued bool,
|
||||
shimmer bool,
|
||||
fileParts []opencode.FilePart,
|
||||
agentParts []opencode.AgentPart,
|
||||
toolCalls ...opencode.ToolPart,
|
||||
@@ -234,7 +235,12 @@ func renderText(
|
||||
}
|
||||
content = util.ToMarkdown(text, width, backgroundColor)
|
||||
if isThinking {
|
||||
label := util.Shimmer("Thinking...", backgroundColor, t.TextMuted(), t.Accent())
|
||||
var label string
|
||||
if shimmer {
|
||||
label = util.Shimmer("Thinking...", backgroundColor, t.TextMuted(), t.Accent())
|
||||
} else {
|
||||
label = styles.NewStyle().Background(backgroundColor).Foreground(t.TextMuted()).Render("Thinking...")
|
||||
}
|
||||
label = styles.NewStyle().Background(backgroundColor).Width(width - 6).Render(label)
|
||||
content = label + "\n\n" + content
|
||||
} else if strings.TrimSpace(text) == "Generating..." {
|
||||
@@ -665,10 +671,22 @@ func renderToolDetails(
|
||||
body = strings.Join(steps, "\n")
|
||||
|
||||
body += "\n\n"
|
||||
body += baseStyle(app.Keybind(commands.SessionChildCycleCommand)) +
|
||||
mutedStyle(", ") +
|
||||
baseStyle(app.Keybind(commands.SessionChildCycleReverseCommand)) +
|
||||
mutedStyle(" navigate child sessions")
|
||||
|
||||
// Build navigation hint with proper spacing
|
||||
cycleKeybind := app.Keybind(commands.SessionChildCycleCommand)
|
||||
cycleReverseKeybind := app.Keybind(commands.SessionChildCycleReverseCommand)
|
||||
|
||||
var navParts []string
|
||||
if cycleKeybind != "" {
|
||||
navParts = append(navParts, baseStyle(cycleKeybind))
|
||||
}
|
||||
if cycleReverseKeybind != "" {
|
||||
navParts = append(navParts, baseStyle(cycleReverseKeybind))
|
||||
}
|
||||
|
||||
if len(navParts) > 0 {
|
||||
body += strings.Join(navParts, mutedStyle(", ")) + mutedStyle(" navigate child sessions")
|
||||
}
|
||||
}
|
||||
body = defaultStyle(body)
|
||||
default:
|
||||
@@ -903,7 +921,9 @@ func renderArgs(args *map[string]any, titleKey string) string {
|
||||
continue
|
||||
}
|
||||
if key == "filePath" || key == "path" {
|
||||
value = util.Relative(value.(string))
|
||||
if strValue, ok := value.(string); ok {
|
||||
value = util.Relative(strValue)
|
||||
}
|
||||
}
|
||||
if key == titleKey {
|
||||
title = fmt.Sprintf("%s", value)
|
||||
|
||||
@@ -336,6 +336,25 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
|
||||
width := m.width // always use full width
|
||||
|
||||
// Find the last streaming ReasoningPart to only shimmer that one
|
||||
lastStreamingReasoningID := ""
|
||||
if m.showThinkingBlocks {
|
||||
for mi := len(m.app.Messages) - 1; mi >= 0 && lastStreamingReasoningID == ""; mi-- {
|
||||
if _, ok := m.app.Messages[mi].Info.(opencode.AssistantMessage); !ok {
|
||||
continue
|
||||
}
|
||||
parts := m.app.Messages[mi].Parts
|
||||
for pi := len(parts) - 1; pi >= 0; pi-- {
|
||||
if rp, ok := parts[pi].(opencode.ReasoningPart); ok {
|
||||
if strings.TrimSpace(rp.Text) != "" && rp.Time.End == 0 {
|
||||
lastStreamingReasoningID = rp.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reverted := false
|
||||
revertedMessageCount := 0
|
||||
revertedToolCount := 0
|
||||
@@ -437,6 +456,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
files,
|
||||
false,
|
||||
isQueued,
|
||||
false,
|
||||
fileParts,
|
||||
agentParts,
|
||||
)
|
||||
@@ -513,6 +533,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
toolCallParts...,
|
||||
@@ -530,6 +551,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
toolCallParts...,
|
||||
@@ -600,6 +622,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
}
|
||||
if part.Text != "" {
|
||||
text := part.Text
|
||||
shimmer := part.Time.End == 0 && part.ID == lastStreamingReasoningID
|
||||
content = renderText(
|
||||
m.app,
|
||||
message.Info,
|
||||
@@ -610,6 +633,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
true,
|
||||
false,
|
||||
shimmer,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
)
|
||||
@@ -644,6 +668,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
[]opencode.FilePart{},
|
||||
[]opencode.AgentPart{},
|
||||
)
|
||||
@@ -746,16 +771,18 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
} else {
|
||||
for _, part := range response.Parts {
|
||||
if part.CallID == m.app.CurrentPermission.CallID {
|
||||
content := renderToolDetails(
|
||||
m.app,
|
||||
part.AsUnion().(opencode.ToolPart),
|
||||
m.app.CurrentPermission,
|
||||
width,
|
||||
)
|
||||
if content != "" {
|
||||
partCount++
|
||||
lineCount += lipgloss.Height(content) + 1
|
||||
blocks = append(blocks, content)
|
||||
if toolPart, ok := part.AsUnion().(opencode.ToolPart); ok {
|
||||
content := renderToolDetails(
|
||||
m.app,
|
||||
toolPart,
|
||||
m.app.CurrentPermission,
|
||||
width,
|
||||
)
|
||||
if content != "" {
|
||||
partCount++
|
||||
lineCount += lipgloss.Height(content) + 1
|
||||
blocks = append(blocks, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,31 @@ package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
opencode "github.com/sst/opencode-sdk-go"
|
||||
)
|
||||
|
||||
func sanitizeValue(val any) any {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err, ok := val.(error); ok {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(val)
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
return fmt.Sprintf("%T", val)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
type APILogHandler struct {
|
||||
client *opencode.Client
|
||||
service string
|
||||
@@ -67,21 +86,13 @@ func (h *APILogHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
h.mu.Lock()
|
||||
for _, attr := range h.attrs {
|
||||
val := attr.Value.Any()
|
||||
if err, ok := val.(error); ok {
|
||||
extra[attr.Key] = err.Error()
|
||||
} else {
|
||||
extra[attr.Key] = val
|
||||
}
|
||||
extra[attr.Key] = sanitizeValue(val)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
r.Attrs(func(attr slog.Attr) bool {
|
||||
val := attr.Value.Any()
|
||||
if err, ok := val.(error); ok {
|
||||
extra[attr.Key] = err.Error()
|
||||
} else {
|
||||
extra[attr.Key] = val
|
||||
}
|
||||
extra[attr.Key] = sanitizeValue(val)
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import { rehypeHeadingIds } from "@astrojs/markdown-remark"
|
||||
import rehypeAutolinkHeadings from "rehype-autolink-headings"
|
||||
import { spawnSync } from "child_process"
|
||||
|
||||
const github = "https://github.com/sst/opencode"
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: config.url,
|
||||
@@ -49,7 +47,7 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
editLink: {
|
||||
baseUrl: `${github}/edit/dev/packages/web/`,
|
||||
baseUrl: `${config.github}/edit/dev/packages/web/`,
|
||||
},
|
||||
markdown: {
|
||||
headingLinks: false,
|
||||
@@ -69,7 +67,14 @@ export default defineConfig({
|
||||
|
||||
{
|
||||
label: "Usage",
|
||||
items: ["docs/tui", "docs/cli", "docs/ide", "docs/share", "docs/github"],
|
||||
items: [
|
||||
"docs/tui",
|
||||
"docs/cli",
|
||||
"docs/ide",
|
||||
"docs/share",
|
||||
"docs/github",
|
||||
"docs/gitlab"
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
@@ -79,7 +84,6 @@ export default defineConfig({
|
||||
"docs/agents",
|
||||
"docs/models",
|
||||
"docs/themes",
|
||||
"docs/plugins",
|
||||
"docs/keybinds",
|
||||
"docs/formatters",
|
||||
"docs/permissions",
|
||||
@@ -87,6 +91,11 @@ export default defineConfig({
|
||||
"docs/mcp-servers",
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
label: "Develop",
|
||||
items: ["docs/sdk", "docs/server", "docs/plugins"],
|
||||
},
|
||||
],
|
||||
components: {
|
||||
Hero: "./src/components/Hero.astro",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode/web",
|
||||
"type": "module",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
|
||||
@@ -31,7 +31,7 @@
|
||||
"sharp": "0.32.5",
|
||||
"shiki": "3.4.2",
|
||||
"solid-js": "1.9.7",
|
||||
"toolbeam-docs-theme": "0.4.3"
|
||||
"toolbeam-docs-theme": "0.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"opencode": "workspace:*",
|
||||
|
||||
@@ -176,7 +176,7 @@ opencode run Explain the use of context in Go
|
||||
|
||||
### serve
|
||||
|
||||
Start a headless opencode server for API access.
|
||||
Start a headless opencode server for API access. Check out the [server docs](/docs/server) for the full HTTP interface.
|
||||
|
||||
```bash
|
||||
opencode serve
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: GitLab
|
||||
description: Use opencode in GitLab issues and merge requests
|
||||
---
|
||||
|
||||
opencode integrates with your GitLab workflow.
|
||||
Mention `@opencode` in a comment, and opencode will execute tasks within your GitLab CI pipeline.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Triage issues**: Ask opencode to look into an issue and explain it to you.
|
||||
- **Fix and implement**: Ask opencode to fix an issue or implement a feature.
|
||||
It will work create a new branch and raised a merge request with the changes.
|
||||
- **Secure**: opencode runs on your GitLab runners.
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
opencode runs in your GitLab CI/CD pipeline, here's what you'll need to set it up:
|
||||
|
||||
:::tip
|
||||
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:
|
||||
|
||||
<details>
|
||||
<summary>Flow configuration</summary>
|
||||
|
||||
```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.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples of how you can use opencode in GitLab.
|
||||
|
||||
:::tip
|
||||
You can configure to use a different trigger phrase than `@opencode`.
|
||||
:::
|
||||
|
||||
- **Explain an issue**
|
||||
|
||||
Add this comment in a GitLab issue.
|
||||
|
||||
```
|
||||
@opencode explain this issue
|
||||
```
|
||||
|
||||
opencode will read the issue and reply with a clear explanation.
|
||||
|
||||
- **Fix an issue**
|
||||
|
||||
In a GitLab issue, say:
|
||||
|
||||
```
|
||||
@opencode fix this
|
||||
```
|
||||
|
||||
opencode will create a new branch, implement the changes, and open a merge request with the changes.
|
||||
|
||||
- **Review merge requests**
|
||||
|
||||
Leave the following comment on a GitLab merge request.
|
||||
|
||||
```
|
||||
@opencode review this merge request
|
||||
```
|
||||
|
||||
opencode will review the merge request and provide feedback.
|
||||
@@ -20,7 +20,6 @@ opencode has a list of keybinds that you can customize through the opencode conf
|
||||
"session_export": "<leader>x",
|
||||
"session_new": "<leader>n",
|
||||
"session_list": "<leader>l",
|
||||
"session_navigation": "<leader>g",
|
||||
"session_share": "<leader>s",
|
||||
"session_unshare": "none",
|
||||
"session_interrupt": "esc",
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
---
|
||||
title: SDK
|
||||
description: JS SDK for the opencode server.
|
||||
---
|
||||
|
||||
import config from "../../../../config.mjs"
|
||||
export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts`
|
||||
|
||||
The opencode [JS/TS SDK](https://www.npmjs.com/package/@opencode-ai/sdk) provides a type-safe client for interacting with the opencode server. You can use it to build custom integrations and control opencode programmatically.
|
||||
|
||||
[Learn more](/docs/server) about how the opencode server works.
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
Install the SDK from npm:
|
||||
|
||||
```bash
|
||||
npm install @opencode-ai/sdk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create client
|
||||
|
||||
Create a client instance to connect to your opencode server:
|
||||
|
||||
```javascript
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
})
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
| --------- | ---------- | --------------------------- | ----------------------- |
|
||||
| `baseUrl` | `string` | URL of the opencode server | `http://localhost:4096` |
|
||||
| `fetch` | `function` | Custom fetch implementation | `globalThis.fetch` |
|
||||
|
||||
---
|
||||
|
||||
## Start server
|
||||
|
||||
You can also programmatically start an opencode server:
|
||||
|
||||
```javascript
|
||||
import { createOpencodeServer } from "@opencode-ai/sdk"
|
||||
|
||||
const server = await createOpencodeServer({
|
||||
host: "127.0.0.1",
|
||||
port: 4096,
|
||||
})
|
||||
|
||||
console.log(`Server running at ${server.url}`)
|
||||
|
||||
server.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
The SDK includes TypeScript definitions for all API types. Import them directly:
|
||||
|
||||
```typescript
|
||||
import type { Session, Message, Part } from "@opencode-ai/sdk"
|
||||
```
|
||||
|
||||
All types are generated from the server's OpenAPI specification and available in the <a href={typesUrl}>types file</a>.
|
||||
|
||||
---
|
||||
|
||||
## Errors
|
||||
|
||||
The SDK throws typed errors that you can catch and handle:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const session = await client.session.get({ id: "invalid-id" })
|
||||
} catch (error) {
|
||||
console.error("Failed to get session:", error.message)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## APIs
|
||||
|
||||
The SDK exposes all server APIs through a type-safe client interface.
|
||||
|
||||
---
|
||||
|
||||
### App
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------ | ------------------ | --------------------------------------- |
|
||||
| `app.get()` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||
| `app.init()` | Initialize the app | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const app = await client.app.get()
|
||||
await client.app.init()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Config
|
||||
|
||||
| Method | Description | Response |
|
||||
| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| `config.get()` | Get config info | <a href={typesUrl}><code>Config</code></a> |
|
||||
| `config.providers()` | List providers and default models | `{ providers: `<a href={typesUrl}><code>Provider[]</code></a>`, default: { [key: string]: string } }` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const config = await client.config.get()
|
||||
const { providers, default: defaults } = await client.config.providers()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sessions
|
||||
|
||||
| Method | Description | Notes |
|
||||
| ------------------------------------------------------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `session.list()` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `session.get({ id })` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.children({ id })` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `session.create({ parentID?, title? })` | Create session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.delete({ id })` | Delete session | Returns `boolean` |
|
||||
| `session.update({ id, title? })` | Update session properties | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.init({ id, messageID, providerID, modelID })` | Analyze app and create `AGENTS.md` | Returns `boolean` |
|
||||
| `session.abort({ id })` | Abort a running session | Returns `boolean` |
|
||||
| `session.share({ id })` | Share session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.unshare({ id })` | Unshare session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.summarize({ id, providerID, modelID })` | Summarize session | Returns `boolean` |
|
||||
| `session.messages({ id })` | List messages in a session | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}[]` |
|
||||
| `session.message({ id, messageID })` | Get message details | Returns `{ info: `<a href={typesUrl}><code>Message</code></a>`, parts: `<a href={typesUrl}><code>Part[]</code></a>`}` |
|
||||
| `session.chat({ id, ...chatInput })` | Send chat message | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.shell({ id, agent, command })` | Run a shell command | Returns <a href={typesUrl}><code>Message</code></a> |
|
||||
| `session.revert({ id, messageID, partID? })` | Revert a message | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.unrevert({ id })` | Restore reverted messages | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `session.permissions.respond({ id, permissionID, response })` | Respond to a permission request | Returns `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Create and manage sessions
|
||||
const session = await client.session.create({ title: "My session" })
|
||||
const sessions = await client.session.list()
|
||||
|
||||
// Send messages
|
||||
const message = await client.session.chat({
|
||||
id: session.id,
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-3-5-sonnet-20241022",
|
||||
parts: [{ type: "text", text: "Hello!" }]
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Files
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `find.text({ pattern })` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
|
||||
| `find.files({ query })` | Find files by name | `string[]` (file paths) |
|
||||
| `find.symbols({ query })` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> |
|
||||
| `file.read({ path })` | Read a file | `{ type: "raw" \| "patch", content: string }` |
|
||||
| `file.status()` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Search and read files
|
||||
const textResults = await client.find.text({ pattern: "function.*opencode" })
|
||||
const files = await client.find.files({ query: "*.ts" })
|
||||
const content = await client.file.read({ path: "src/index.ts" })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Logging
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------------------------------------ | --------------- | --------- |
|
||||
| `log.write({ service, level, message, extra? })` | Write log entry | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
await client.log.write({
|
||||
service: "my-app",
|
||||
level: "info",
|
||||
message: "Operation completed"
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Agents
|
||||
|
||||
| Method | Description | Response |
|
||||
| -------------- | ------------------------- | ------------------------------------------- |
|
||||
| `agent.list()` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
const agents = await client.agent.list()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TUI
|
||||
|
||||
| Method | Description | Response |
|
||||
| --------------------------------------------- | --------------------------------- | ---------------------- |
|
||||
| `tui.appendPrompt({ text })` | Append text to the prompt | `boolean` |
|
||||
| `tui.openHelp()` | Open the help dialog | `boolean` |
|
||||
| `tui.openSessions()` | Open the session selector | `boolean` |
|
||||
| `tui.openThemes()` | Open the theme selector | `boolean` |
|
||||
| `tui.openModels()` | Open the model selector | `boolean` |
|
||||
| `tui.submitPrompt()` | Submit the current prompt | `boolean` |
|
||||
| `tui.clearPrompt()` | Clear the prompt | `boolean` |
|
||||
| `tui.executeCommand({ command })` | Execute a command | `boolean` |
|
||||
| `tui.showToast({ title?, message, variant })` | Show toast notification | `boolean` |
|
||||
| `tui.control.next()` | Wait for the next control request | Control request object |
|
||||
| `tui.control.response({ body })` | Respond to a control request | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Control TUI interface
|
||||
await client.tui.appendPrompt({ text: "Add this to prompt" })
|
||||
await client.tui.showToast({
|
||||
message: "Task completed",
|
||||
variant: "success"
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Auth
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------------------- | ------------------------------ | --------- |
|
||||
| `auth.set({ id, ...authData })` | Set authentication credentials | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
await client.auth.set({
|
||||
id: "anthropic",
|
||||
type: "api",
|
||||
key: "your-api-key"
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Events
|
||||
|
||||
| Method | Description | Response |
|
||||
| ------------------- | ------------------------- | ------------------------- |
|
||||
| `event.subscribe()` | Server-sent events stream | Server-sent events stream |
|
||||
|
||||
---
|
||||
|
||||
#### Examples
|
||||
|
||||
```javascript
|
||||
// Listen to real-time events
|
||||
const eventStream = await client.event.subscribe()
|
||||
for await (const event of eventStream) {
|
||||
console.log("Event:", event.type, event.properties)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: Server
|
||||
description: Interact with opencode server over HTTP.
|
||||
---
|
||||
|
||||
import config from "../../../../config.mjs"
|
||||
export const typesUrl = `${config.github}/blob/dev/packages/sdk/js/src/gen/types.gen.ts`
|
||||
|
||||
The `opencode serve` command runs a headless HTTP server that exposes an OpenAPI endpoint that an opencode client can use.
|
||||
|
||||
---
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
opencode serve [--port <number>] [--hostname <string>]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Short | Description | Default |
|
||||
| ------------ | ----- | --------------------- | ----------- |
|
||||
| `--port` | `-p` | Port to listen on | `4096` |
|
||||
| `--hostname` | `-h` | Hostname to listen on | `127.0.0.1` |
|
||||
|
||||
---
|
||||
|
||||
### How it works
|
||||
|
||||
When you run `opencode` it starts a TUI and a server. Where the TUI is the
|
||||
client that talks to the server. The server exposes an OpenAPI 3.1 spec
|
||||
endpoint. This endpoint is also used to generate an [SDK](/docs/sdk).
|
||||
|
||||
:::tip
|
||||
Use the opencode server to interact with opencode programmatically.
|
||||
:::
|
||||
|
||||
This architecture lets opencode support multiple clients and allows you to interact with opencode programmatically.
|
||||
|
||||
You can run `opencode serve` to start a standalone server. If you have the
|
||||
opencode TUI running, `opencode serve` will start a new server.
|
||||
|
||||
---
|
||||
|
||||
#### Connect to an existing server
|
||||
|
||||
When you start the TUI it randomly assigns a port and hostname. You can instead pass in the `--hostname` and `--port` [flags](/docs/cli). Then use this to connect to its server.
|
||||
|
||||
The [`/tui`](#tui) endpoint can be used to drive the TUI through the server. For example, you can prefill or run a prompt. This setup is used by the opencode [IDE](/docs/ide) plugins.
|
||||
|
||||
---
|
||||
|
||||
## Spec
|
||||
|
||||
The server publishes an OpenAPI 3.1 spec that can be viewed at:
|
||||
|
||||
```
|
||||
http://<hostname>:<port>/doc
|
||||
```
|
||||
|
||||
For example, `http://localhost:4096/doc`. Use the spec to generate clients or inspect request and response types. Or view it in a Swagger explorer.
|
||||
|
||||
---
|
||||
|
||||
## APIs
|
||||
|
||||
The opencode server exposes the following APIs.
|
||||
|
||||
---
|
||||
|
||||
### App
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ----------- | ------------------ | --------------------------------------- |
|
||||
| `GET` | `/app` | Get app info | <a href={typesUrl}><code>App</code></a> |
|
||||
| `POST` | `/app/init` | Initialize the app | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Config
|
||||
|
||||
| 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 } }` |
|
||||
|
||||
---
|
||||
|
||||
### Sessions
|
||||
|
||||
| Method | Path | Description | Notes |
|
||||
| -------- | ---------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `GET` | `/session` | List sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `GET` | `/session/:id` | Get session | Returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `GET` | `/session/:id/children` | List child sessions | Returns <a href={typesUrl}><code>Session[]</code></a> |
|
||||
| `POST` | `/session` | Create session | body: `{ parentID?, title? }`, returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `DELETE` | `/session/:id` | Delete session | |
|
||||
| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns <a href={typesUrl}><code>Session</code></a> |
|
||||
| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }` |
|
||||
| `POST` | `/session/:id/abort` | Abort a running session | |
|
||||
| `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>`}` |
|
||||
| `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 }` |
|
||||
| `POST` | `/session/:id/unrevert` | Restore reverted messages | |
|
||||
| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response }` |
|
||||
|
||||
---
|
||||
|
||||
### Files
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `GET` | `/find?pattern=<pat>` | Search for text in files | Array of match objects with `path`, `lines`, `line_number`, `absolute_offset`, `submatches` |
|
||||
| `GET` | `/find/file?query=<q>` | Find files by name | `string[]` (file paths) |
|
||||
| `GET` | `/find/symbol?query=<q>` | Find workspace symbols | <a href={typesUrl}><code>Symbol[]</code></a> |
|
||||
| `GET` | `/file?path=<path>` | Read a file | `{ type: "raw" \| "patch", content: string }` |
|
||||
| `GET` | `/file/status` | Get status for tracked files | <a href={typesUrl}><code>File[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
### Logging
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------ | ------------------------------------------------------------ | --------- |
|
||||
| `POST` | `/log` | Write log entry. Body: `{ service, level, message, extra? }` | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Agents
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | -------- | ------------------------- | ------------------------------------------- |
|
||||
| `GET` | `/agent` | List all available agents | <a href={typesUrl}><code>Agent[]</code></a> |
|
||||
|
||||
---
|
||||
|
||||
### TUI
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ----------------------- | ------------------------------------------- | ---------------------- |
|
||||
| `POST` | `/tui/append-prompt` | Append text to the prompt | `boolean` |
|
||||
| `POST` | `/tui/open-help` | Open the help dialog | `boolean` |
|
||||
| `POST` | `/tui/open-sessions` | Open the session selector | `boolean` |
|
||||
| `POST` | `/tui/open-themes` | Open the theme selector | `boolean` |
|
||||
| `POST` | `/tui/open-models` | Open the model selector | `boolean` |
|
||||
| `POST` | `/tui/submit-prompt` | Submit the current prompt | `boolean` |
|
||||
| `POST` | `/tui/clear-prompt` | Clear the prompt | `boolean` |
|
||||
| `POST` | `/tui/execute-command` | Execute a command (`{ command }`) | `boolean` |
|
||||
| `POST` | `/tui/show-toast` | Show toast (`{ title?, message, variant }`) | `boolean` |
|
||||
| `GET` | `/tui/control/next` | Wait for the next control request | Control request object |
|
||||
| `POST` | `/tui/control/response` | Respond to a control request (`{ body }`) | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Auth
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ----------- | --------------------------------------------------------------- | --------- |
|
||||
| `PUT` | `/auth/:id` | Set authentication credentials. Body must match provider schema | `boolean` |
|
||||
|
||||
---
|
||||
|
||||
### Events
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | -------- | ----------------------------------------------------------------------------- | ------------------------- |
|
||||
| `GET` | `/event` | Server-sent events stream. First event is `server.connected`, then bus events | Server-sent events stream |
|
||||
|
||||
---
|
||||
|
||||
### Docs
|
||||
|
||||
| Method | Path | Description | Response |
|
||||
| ------ | ------ | -------------------------------------- | ------------------------------------------ |
|
||||
| `GET` | `/doc` | OpenAPI 3.1 specification | HTML page with OpenAPI spec |
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "0.5.7",
|
||||
"version": "0.5.13",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Vendored
-4
@@ -27,10 +27,6 @@ declare module "sst" {
|
||||
"Bucket": {
|
||||
"type": "sst.cloudflare.Bucket"
|
||||
}
|
||||
"Console": {
|
||||
"type": "sst.cloudflare.StaticSite"
|
||||
"url": string
|
||||
}
|
||||
"DATABASE_PASSWORD": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
|
||||
Reference in New Issue
Block a user