Compare commits

..

34 Commits

Author SHA1 Message Date
opencode ecafa40bcf release: v0.5.1 2025-08-14 20:47:45 +00:00
Dax Raad 25f4721c71 ci: aur is down 2025-08-14 16:42:39 -04:00
Dax Raad a433766a31 allow plugins to create custom auth providers 2025-08-14 16:25:08 -04:00
Jay V c93d50e8c7 ignore: lander tweaks 2025-08-14 15:53:41 -04:00
Jay V 3f879859d7 ignore: lander styles 2025-08-14 15:53:41 -04:00
Dax Raad ee62dc0745 wip: sdk 2025-08-14 12:22:27 -04:00
Dax Raad b7aefa715a ci: tweak 2025-08-14 12:20:22 -04:00
Dax Raad 796bc390db fix for session stuck in "Working..." 2025-08-14 12:20:22 -04:00
Lubos 703ae49675 chore: declare OpenAPI version 3.1.1 (#1931) 2025-08-14 12:10:32 -04:00
GitHub Action 37c0570a9f ignore: update download stats 2025-08-14 2025-08-14 12:04:42 +00:00
Aiden Cline 4dea0209bb fix: support fish shell (#1911) 2025-08-13 20:04:04 -05:00
Aiden Cline bb4b24a05f docs: fix bad example (#1913) 2025-08-13 20:03:44 -05:00
opencode e789abec79 release: v0.4.45 2025-08-13 22:32:26 +00:00
Aiden Cline 118617473e fix: bash should hide stdout from zshrc (#1909) 2025-08-13 17:04:32 -04:00
adamdotdevin a4beb60e19 chore: rename bash -> shell 2025-08-13 15:11:30 -05:00
Yuu Toriyama 3f0f910f7b Fix: Error [ERR_DLOPEN_FAILED] (#1546) 2025-08-13 19:49:14 +00:00
opencode 5bf841ab7a release: v0.4.44 2025-08-13 19:49:14 +00:00
Dax Raad 49727e3eab re-enable aur 2025-08-13 15:44:11 -04:00
Dax Raad 592c6ef97f ci: issue 2025-08-13 15:43:06 -04:00
envolution 00579f0ec1 Fix incorrect AUR namespace (#1907) 2025-08-13 15:37:15 -04:00
adamdotdevin 69d516c7fa fix: default scroll speed should be slower 2025-08-13 14:35:18 -05:00
Dax Raad bedeb626b2 docs: fix 2025-08-13 19:33:38 +00:00
Dominik Engelhardt a4c14dbb2d feat: convert attachments to text on delete (#1863)
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Dax <mail@thdxr.com>
2025-08-13 19:33:38 +00:00
opencode 036b24791d release: v0.4.43 2025-08-13 19:33:38 +00:00
Dax Raad 93b71477e6 support !shell commands 2025-08-13 15:26:13 -04:00
adamdotdevin 1357319f6f feat: bash commands 2025-08-13 13:28:22 -05:00
Dax Raad e729eed34d wip: bash 2025-08-13 14:14:27 -04:00
Jay V 2e5fdd8cef docs: global model options 2025-08-13 14:07:10 -04:00
Dax Raad 21f15f15c1 docs(cli): document ! bash commands and session persistence in CLI docs 2025-08-13 13:37:19 -04:00
Dax Raad c6344c5714 wip: bash 2025-08-13 13:31:29 -04:00
Dax Raad 7505fa61b9 wip: bash commands 2025-08-13 13:29:06 -04:00
Matt Cook 77bb5af092 fix: grammatical error in agent launch example (by Opencode) (#1897) 2025-08-13 12:25:38 -05:00
Aiden Cline 0c4fe73cbf fix: js plugin support as per documentation (#1896) 2025-08-13 12:25:04 -05:00
Aiden Cline e132f6183d fix: duplicates bot prompt (#1901) 2025-08-13 11:54:37 -05:00
56 changed files with 2415 additions and 849 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
- uses: oven-sh/setup-bun@v1
with:
bun-version: 1.2.17
bun-version: 1.2.19
- run: bun install
+14 -4
View File
@@ -23,14 +23,24 @@ jobs:
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCODE_PERMISSION: '{ "bash": { "gh*": "allow", "*": "deny" }, "webfetch": "deny" }'
OPENCODE_PERMISSION: |
{
"bash": {
"gh issue*": "allow",
"*": "deny"
},
"webfetch": "deny"
}
run: |
opencode run -m anthropic/claude-sonnet-4-20250514 "A new issue has been created: '${{ github.event.issue.title }}'
Issue number:
${{ github.event.issue.number }}
Issue body:
${{ github.event.issue.body }}
Please search through existing issues in this repository to find any potential duplicates of this new issue. Consider:
Please 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
3. Related functionality or components
@@ -42,9 +52,9 @@ jobs:
- A suggestion to check those issues first
Use this format for the comment:
'👋 This issue might be a duplicate of existing issues. Please check:
'This issue might be a duplicate of existing issues. Please check:
- #[issue_number]: [brief description of similarity]
If none of these address your specific case, please let us know how this issue differs.'
Feel free to ignore if none of these address your specific case.'
If no clear duplicates are found, do not comment."
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.17
bun-version: 1.2.19
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
+5 -5
View File
@@ -48,10 +48,10 @@ jobs:
restore-keys: |
${{ runner.os }}-bun-
# - name: Install makepkg
# run: |
# sudo apt-get update
# sudo apt-get install -y pacman-package-manager
- name: Install makepkg
run: |
sudo apt-get update
sudo apt-get install -y pacman-package-manager
- name: Setup SSH for AUR
run: |
mkdir -p ~/.ssh
@@ -59,7 +59,7 @@ jobs:
chmod 600 ~/.ssh/id_rsa
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
# ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- name: Install dependencies
run: bun install
+1
View File
@@ -47,3 +47,4 @@
| 2025-08-11 | 169,297 (+3,602) | 167,953 (+844) | 337,250 (+4,446) |
| 2025-08-12 | 176,307 (+7,010) | 171,876 (+3,923) | 348,183 (+10,933) |
| 2025-08-13 | 182,997 (+6,690) | 177,182 (+5,306) | 360,179 (+11,996) |
| 2025-08-14 | 189,063 (+6,066) | 179,741 (+2,559) | 368,804 (+8,625) |
+6
View File
@@ -113,6 +113,7 @@
"tree-sitter-bash": "0.23.3",
"turndown": "7.2.0",
"vscode-jsonrpc": "8.2.1",
"web-tree-sitter": "0.22.6",
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
@@ -189,6 +190,9 @@
"sharp",
"esbuild",
"protobufjs",
"tree-sitter",
"web-tree-sitter",
"tree-sitter-bash",
],
"catalog": {
"@hono/zod-validator": "0.4.2",
@@ -2481,6 +2485,8 @@
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"web-tree-sitter": ["web-tree-sitter@0.22.6", "", {}, "sha512-hS87TH71Zd6mGAmYCvlgxeGDjqd9GTeqXNqTT+u0Gs51uIozNIaaq/kUAbV/Zf56jb2ZOyG8BxZs2GG9wbLi6Q=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode/cloud-core",
"version": "0.4.42",
"version": "0.5.1",
"private": true,
"type": "module",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/cloud-function",
"version": "0.4.42",
"version": "0.5.1",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/cloud-web",
"version": "0.4.42",
"version": "0.5.1",
"private": true,
"description": "",
"type": "module",
+5 -2
View File
@@ -3,7 +3,7 @@
"name": "opencode",
"private": true,
"type": "module",
"packageManager": "bun@1.2.14",
"packageManager": "bun@1.2.19",
"scripts": {
"dev": "bun run --conditions=development packages/opencode/src/index.ts",
"typecheck": "bun run --filter='*' typecheck",
@@ -46,7 +46,10 @@
"trustedDependencies": [
"esbuild",
"protobufjs",
"sharp"
"sharp",
"tree-sitter",
"tree-sitter-bash",
"web-tree-sitter"
],
"patchedDependencies": {}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode/function",
"version": "0.4.42",
"version": "0.5.1",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.4.42",
"version": "0.5.1",
"name": "opencode",
"type": "module",
"private": true,
@@ -52,6 +52,7 @@
"remeda": "catalog:",
"tree-sitter": "0.22.4",
"tree-sitter-bash": "0.23.3",
"web-tree-sitter": "0.22.6",
"turndown": "7.2.0",
"vscode-jsonrpc": "8.2.1",
"xdg-basedir": "5.1.0",
+39 -38
View File
@@ -97,45 +97,46 @@ 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
// const pkgbuild = [
// "# Maintainer: dax",
// "# Maintainer: adam",
// "",
// "pkgname='${pkg}'",
// `pkgver=${version.split("-")[0]}`,
// "options=('!debug' '!strip')",
// "pkgrel=1",
// "pkgdesc='The AI coding agent built for the terminal.'",
// "url='https://github.com/sst/opencode'",
// "arch=('aarch64' 'x86_64')",
// "license=('MIT')",
// "provides=('opencode')",
// "conflicts=('opencode')",
// "depends=('fzf' 'ripgrep')",
// "",
// `source_aarch64=("\${pkgname}_\${pkgver}_aarch64.zip::https://github.com/sst/opencode/releases/download/v${version}/opencode-linux-arm64.zip")`,
// `sha256sums_aarch64=('${arm64Sha}')`,
// "",
// `source_x86_64=("\${pkgname}_\${pkgver}_x86_64.zip::https://github.com/sst/opencode/releases/download/v${version}/opencode-linux-x64.zip")`,
// `sha256sums_x86_64=('${x64Sha}')`,
// "",
// "package() {",
// ' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
// "}",
// "",
// ].join("\n")
/* AUR package - commented out as AUR is down
const pkgbuild = [
"# Maintainer: dax",
"# Maintainer: adam",
"",
"pkgname='${pkg}'",
`pkgver=${version.split("-")[0]}`,
"options=('!debug' '!strip')",
"pkgrel=1",
"pkgdesc='The AI coding agent built for the terminal.'",
"url='https://github.com/sst/opencode'",
"arch=('aarch64' 'x86_64')",
"license=('MIT')",
"provides=('opencode')",
"conflicts=('opencode')",
"depends=('fzf' 'ripgrep')",
"",
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.zip::https://github.com/sst/opencode/releases/download/v${version}/opencode-linux-arm64.zip")`,
`sha256sums_aarch64=('${arm64Sha}')`,
"",
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.zip::https://github.com/sst/opencode/releases/download/v${version}/opencode-linux-x64.zip")`,
`sha256sums_x86_64=('${x64Sha}')`,
"",
"package() {",
' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
"}",
"",
].join("\n")
// for (const pkg of ["opencode"]) {
// await $`rm -rf ./dist/aur-${pkg}`
// await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
// await $`cd ./dist/aur-${pkg} && git checkout master`
// await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
// await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
// await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
// await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
// if (!dry) await $`cd ./dist/aur-${pkg} && git push`
// }
for (const pkg of ["opencode-bin"]) {
await $`rm -rf ./dist/aur-${pkg}`
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
await $`cd ./dist/aur-${pkg} && git checkout master`
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
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 = [
-84
View File
@@ -1,84 +0,0 @@
import { generatePKCE } from "@openauthjs/openauth/pkce"
import { Auth } from "./index"
export namespace AuthAnthropic {
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
export async function authorize(mode: "max" | "console") {
const pkce = await generatePKCE()
const url = new URL(
`https://${mode === "console" ? "console.anthropic.com" : "claude.ai"}/oauth/authorize`,
import.meta.url,
)
url.searchParams.set("code", "true")
url.searchParams.set("client_id", CLIENT_ID)
url.searchParams.set("response_type", "code")
url.searchParams.set("redirect_uri", "https://console.anthropic.com/oauth/code/callback")
url.searchParams.set("scope", "org:create_api_key user:profile user:inference")
url.searchParams.set("code_challenge", pkce.challenge)
url.searchParams.set("code_challenge_method", "S256")
url.searchParams.set("state", pkce.verifier)
return {
url: url.toString(),
verifier: pkce.verifier,
}
}
export async function exchange(code: string, verifier: string) {
const splits = code.split("#")
const result = await fetch("https://console.anthropic.com/v1/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
code: splits[0],
state: splits[1],
grant_type: "authorization_code",
client_id: CLIENT_ID,
redirect_uri: "https://console.anthropic.com/oauth/code/callback",
code_verifier: verifier,
}),
})
if (!result.ok) throw new ExchangeFailed()
const json = await result.json()
return {
refresh: json.refresh_token as string,
access: json.access_token as string,
expires: Date.now() + json.expires_in * 1000,
}
}
export async function access() {
const info = await Auth.get("anthropic")
if (!info || info.type !== "oauth") return
if (info.access && info.expires > Date.now()) return info.access
const response = await fetch("https://console.anthropic.com/v1/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
grant_type: "refresh_token",
refresh_token: info.refresh,
client_id: CLIENT_ID,
}),
})
if (!response.ok) return
const json = await response.json()
await Auth.set("anthropic", {
type: "oauth",
refresh: json.refresh_token as string,
access: json.access_token as string,
expires: Date.now() + json.expires_in * 1000,
})
return json.access_token as string
}
export class ExchangeFailed extends Error {
constructor() {
super("Exchange failed")
}
}
}
-19
View File
@@ -1,19 +0,0 @@
import { Global } from "../global"
import { lazy } from "../util/lazy"
import path from "path"
export const AuthCopilot = lazy(async () => {
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
const exists = await file.exists()
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
.then((x) => Bun.write(file, x))
.catch(() => {})
if (!exists) {
const worked = await response
if (!worked) return
}
const result = await import(file.name!).catch(() => {})
if (!result) return
return result.AuthCopilot
})
+22 -16
View File
@@ -4,25 +4,31 @@ import fs from "fs/promises"
import { z } from "zod"
export namespace Auth {
export const Oauth = z.object({
type: z.literal("oauth"),
refresh: z.string(),
access: z.string(),
expires: z.number(),
})
export const Oauth = z
.object({
type: z.literal("oauth"),
refresh: z.string(),
access: z.string(),
expires: z.number(),
})
.openapi({ ref: "OAuth" })
export const Api = z.object({
type: z.literal("api"),
key: z.string(),
})
export const Api = z
.object({
type: z.literal("api"),
key: z.string(),
})
.openapi({ ref: "ApiAuth" })
export const WellKnown = z.object({
type: z.literal("wellknown"),
key: z.string(),
token: z.string(),
})
export const WellKnown = z
.object({
type: z.literal("wellknown"),
key: z.string(),
token: z.string(),
})
.openapi({ ref: "WellKnownAuth" })
export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown])
export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown]).openapi({ ref: "Auth" })
export type Info = z.infer<typeof Info>
const filepath = path.join(Global.Path.data, "auth.json")
+151 -214
View File
@@ -1,5 +1,3 @@
import { AuthAnthropic } from "../../auth/anthropic"
import { AuthCopilot } from "../../auth/copilot"
import { Auth } from "../../auth"
import { cmd } from "./cmd"
import * as prompts from "@clack/prompts"
@@ -10,6 +8,8 @@ import { map, pipe, sortBy, values } from "remeda"
import path from "path"
import os from "os"
import { Global } from "../../global"
import { Plugin } from "../../plugin"
import { App } from "../../app/app"
export const AuthCommand = cmd({
command: "auth",
@@ -75,242 +75,179 @@ export const AuthLoginCommand = cmd({
type: "string",
}),
async handler(args) {
UI.empty()
prompts.intro("Add credential")
if (args.url) {
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
const proc = Bun.spawn({
cmd: wellknown.auth.command,
stdout: "pipe",
})
const exit = await proc.exited
if (exit !== 0) {
prompts.log.error("Failed")
await App.provide({ cwd: process.cwd() }, async () => {
UI.empty()
prompts.intro("Add credential")
if (args.url) {
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
const proc = Bun.spawn({
cmd: wellknown.auth.command,
stdout: "pipe",
})
const exit = await proc.exited
if (exit !== 0) {
prompts.log.error("Failed")
prompts.outro("Done")
return
}
const token = await new Response(proc.stdout).text()
await Auth.set(args.url, {
type: "wellknown",
key: wellknown.auth.env,
token: token.trim(),
})
prompts.log.success("Logged into " + args.url)
prompts.outro("Done")
return
}
const token = await new Response(proc.stdout).text()
await Auth.set(args.url, {
type: "wellknown",
key: wellknown.auth.env,
token: token.trim(),
})
prompts.log.success("Logged into " + args.url)
prompts.outro("Done")
return
}
await ModelsDev.refresh().catch(() => {})
const providers = await ModelsDev.get()
const priority: Record<string, number> = {
anthropic: 0,
"github-copilot": 1,
openai: 2,
google: 3,
openrouter: 4,
vercel: 5,
}
let provider = await prompts.autocomplete({
message: "Select provider",
maxItems: 8,
options: [
...pipe(
providers,
values(),
sortBy(
(x) => priority[x.id] ?? 99,
(x) => x.name ?? x.id,
),
map((x) => ({
label: x.name,
value: x.id,
hint: priority[x.id] === 0 ? "recommended" : undefined,
})),
),
{
value: "other",
label: "Other",
},
],
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
if (provider === "other") {
provider = await prompts.text({
message: "Enter provider id",
validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
provider = provider.replace(/^@ai-sdk\//, "")
if (prompts.isCancel(provider)) throw new UI.CancelledError()
prompts.log.warn(
`This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
)
}
if (provider === "amazon-bedrock") {
prompts.log.info(
"Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
)
prompts.outro("Done")
return
}
if (provider === "anthropic") {
const method = await prompts.select({
message: "Login method",
await ModelsDev.refresh().catch(() => {})
const providers = await ModelsDev.get()
const priority: Record<string, number> = {
anthropic: 0,
"github-copilot": 1,
openai: 2,
google: 3,
openrouter: 4,
vercel: 5,
}
let provider = await prompts.autocomplete({
message: "Select provider",
maxItems: 8,
options: [
...pipe(
providers,
values(),
sortBy(
(x) => priority[x.id] ?? 99,
(x) => x.name ?? x.id,
),
map((x) => ({
label: x.name,
value: x.id,
hint: priority[x.id] === 0 ? "recommended" : undefined,
})),
),
{
label: "Claude Pro/Max",
value: "max",
},
{
label: "Create API Key",
value: "console",
},
{
label: "Manually enter API Key",
value: "api",
value: "other",
label: "Other",
},
],
})
if (prompts.isCancel(method)) throw new UI.CancelledError()
if (method === "max") {
// some weird bug where program exits without this
await new Promise((resolve) => setTimeout(resolve, 10))
const { url, verifier } = await AuthAnthropic.authorize("max")
prompts.note("Trying to open browser...")
try {
await open(url)
} catch (e) {
prompts.log.error(
"Failed to open browser perhaps you are running without a display or X server, please open the following URL in your browser:",
)
}
prompts.log.info(url)
if (prompts.isCancel(provider)) throw new UI.CancelledError()
const code = await prompts.text({
message: "Paste the authorization code here: ",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(code)) throw new UI.CancelledError()
try {
const credentials = await AuthAnthropic.exchange(code, verifier)
await Auth.set("anthropic", {
type: "oauth",
refresh: credentials.refresh,
access: credentials.access,
expires: credentials.expires,
const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
if (plugin && plugin.auth) {
let index = 0
if (plugin.auth.methods.length > 1) {
const method = await prompts.select({
message: "Login method",
options: [
...plugin.auth.methods.map((x, index) => ({
label: x.label,
value: index.toString(),
})),
],
})
prompts.log.success("Login successful")
} catch {
prompts.log.error("Invalid code")
if (prompts.isCancel(method)) throw new UI.CancelledError()
index = parseInt(method)
}
prompts.outro("Done")
return
}
const method = plugin.auth.methods[index]
if (method.type === "oauth") {
await new Promise((resolve) => setTimeout(resolve, 10))
const authorize = await method.authorize()
if (method === "console") {
// some weird bug where program exits without this
await new Promise((resolve) => setTimeout(resolve, 10))
const { url, verifier } = await AuthAnthropic.authorize("console")
prompts.note("Trying to open browser...")
try {
await open(url)
} catch (e) {
prompts.log.error(
"Failed to open browser perhaps you are running without a display or X server, please open the following URL in your browser:",
)
}
prompts.log.info(url)
const code = await prompts.text({
message: "Paste the authorization code here: ",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(code)) throw new UI.CancelledError()
try {
const credentials = await AuthAnthropic.exchange(code, verifier)
const accessToken = credentials.access
const response = await fetch("https://api.anthropic.com/api/oauth/claude_cli/create_api_key", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json, text/plain, */*",
},
})
if (!response.ok) {
throw new Error("Failed to create API key")
if (authorize.url) {
try {
await open(authorize.url)
} catch (e) {}
prompts.log.info("Go to: " + authorize.url)
}
const json = await response.json()
await Auth.set("anthropic", {
type: "api",
key: json.raw_key,
})
prompts.log.success("Login successful - API key created and saved")
} catch (error) {
prompts.log.error("Invalid code or failed to create API key")
if (authorize.method === "auto") {
if (authorize.instructions) {
prompts.log.info(authorize.instructions)
}
const spinner = prompts.spinner()
spinner.start("Waiting for authorization...")
const result = await authorize.callback()
if (result.type === "failed") {
spinner.stop("Failed to authorize", 1)
}
if (result.type === "success") {
await Auth.set(provider, {
type: "oauth",
refresh: result.refresh,
access: result.access,
expires: result.expires,
})
spinner.stop("Login successful")
}
}
if (authorize.method === "code") {
const code = await prompts.text({
message: "Paste the authorization code here: ",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(code)) throw new UI.CancelledError()
const result = await authorize.callback(code)
if (result.type === "failed") {
prompts.log.error("Failed to authorize")
}
if (result.type === "success") {
await Auth.set(provider, {
type: "oauth",
refresh: result.refresh,
access: result.access,
expires: result.expires,
})
prompts.log.success("Login successful")
}
}
prompts.outro("Done")
return
}
}
if (provider === "other") {
provider = await prompts.text({
message: "Enter provider id",
validate: (x) => (x && x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
provider = provider.replace(/^@ai-sdk\//, "")
if (prompts.isCancel(provider)) throw new UI.CancelledError()
prompts.log.warn(
`This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
)
}
if (provider === "amazon-bedrock") {
prompts.log.info(
"Amazon bedrock can be configured with standard AWS environment variables like AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE or AWS_ACCESS_KEY_ID",
)
prompts.outro("Done")
return
}
}
const copilot = await AuthCopilot()
if (provider === "github-copilot" && copilot) {
await new Promise((resolve) => setTimeout(resolve, 10))
const deviceInfo = await copilot.authorize()
prompts.note(`Please visit: ${deviceInfo.verification}\nEnter code: ${deviceInfo.user}`)
const spinner = prompts.spinner()
spinner.start("Waiting for authorization...")
while (true) {
await new Promise((resolve) => setTimeout(resolve, deviceInfo.interval * 1000))
const response = await copilot.poll(deviceInfo.device)
if (response.status === "pending") continue
if (response.status === "success") {
await Auth.set("github-copilot", {
type: "oauth",
refresh: response.refresh,
access: response.access,
expires: response.expires,
})
spinner.stop("Login successful")
break
}
if (response.status === "failed") {
spinner.stop("Failed to authorize", 1)
break
}
if (provider === "vercel") {
prompts.log.info("You can create an api key in the dashboard")
}
const key = await prompts.password({
message: "Enter your API key",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(key)) throw new UI.CancelledError()
await Auth.set(provider, {
type: "api",
key,
})
prompts.outro("Done")
return
}
if (provider === "vercel") {
prompts.log.info("You can create an api key in the dashboard")
}
const key = await prompts.password({
message: "Enter your API key",
validate: (x) => (x && x.length > 0 ? undefined : "Required"),
})
if (prompts.isCancel(key)) throw new UI.CancelledError()
await Auth.set(provider, {
type: "api",
key,
})
prompts.outro("Done")
},
})
+2 -2
View File
@@ -105,8 +105,8 @@ export namespace Config {
result.plugin = result.plugin || []
result.plugin.push(
...[
...(await Filesystem.globUp("plugin/*.ts", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/plugin/*.ts", app.path.cwd, app.path.root)),
...(await Filesystem.globUp("plugin/*.{ts,js}", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/plugin/*.{ts,js}", app.path.cwd, app.path.root)),
].map((x) => "file://" + x),
)
+1
View File
@@ -4,6 +4,7 @@ export namespace Flag {
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
+19 -7
View File
@@ -6,6 +6,7 @@ import { Log } from "../util/log"
import { createOpencodeClient } from "@opencode-ai/sdk"
import { Server } from "../server/server"
import { BunProc } from "../bun"
import { Flag } from "../flag/flag"
export namespace Plugin {
const log = Log.create({ service: "plugin" })
@@ -17,7 +18,17 @@ export namespace Plugin {
})
const config = await Config.get()
const hooks = []
for (let plugin of config.plugin ?? []) {
const input = {
client,
app,
$: Bun.$,
}
const plugins = [...(config.plugin ?? [])]
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
plugins.push("opencode-copilot-auth")
plugins.push("opencode-anthropic-auth")
}
for (let plugin of plugins) {
log.info("loading plugin", { path: plugin })
if (!plugin.startsWith("file://")) {
const [pkg, version] = plugin.split("@")
@@ -25,22 +36,19 @@ export namespace Plugin {
}
const mod = await import(plugin)
for (const [_name, fn] of Object.entries<PluginInstance>(mod)) {
const init = await fn({
client,
app,
$: Bun.$,
})
const init = await fn(input)
hooks.push(init)
}
}
return {
hooks,
input,
}
})
export async function trigger<
Name extends keyof Required<Hooks>,
Name extends Exclude<keyof Required<Hooks>, "auth" | "event">,
Input = Parameters<Required<Hooks>[Name]>[0],
Output = Parameters<Required<Hooks>[Name]>[1],
>(name: Name, input: Input, output: Output): Promise<Output> {
@@ -56,6 +64,10 @@ export namespace Plugin {
return output
}
export async function list() {
return state().then((x) => x.hooks)
}
export function init() {
Bus.subscribeAll(async (input) => {
const hooks = await state().then((x) => x.hooks)
+17 -97
View File
@@ -5,8 +5,7 @@ import { mergeDeep, sortBy } from "remeda"
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
import { Log } from "../util/log"
import { BunProc } from "../bun"
import { AuthAnthropic } from "../auth/anthropic"
import { AuthCopilot } from "../auth/copilot"
import { Plugin } from "../plugin"
import { ModelsDev } from "./models"
import { NamedError } from "../util/error"
import { Auth } from "../auth"
@@ -26,103 +25,13 @@ export namespace Provider {
type Source = "env" | "config" | "custom" | "api"
const CUSTOM_LOADERS: Record<string, CustomLoader> = {
async anthropic(provider) {
const access = await AuthAnthropic.access()
if (!access)
return {
autoload: false,
options: {
headers: {
"anthropic-beta":
"claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
},
}
for (const model of Object.values(provider.models)) {
model.cost = {
input: 0,
output: 0,
}
}
async anthropic() {
return {
autoload: true,
autoload: false,
options: {
apiKey: "",
async fetch(input: any, init: any) {
const access = await AuthAnthropic.access()
const headers = {
...init.headers,
authorization: `Bearer ${access}`,
"anthropic-beta":
"oauth-2025-04-20,claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
}
delete headers["x-api-key"]
return fetch(input, {
...init,
headers,
})
},
},
}
},
"github-copilot": async (provider) => {
const copilot = await AuthCopilot()
if (!copilot) return { autoload: false }
let info = await Auth.get("github-copilot")
if (!info || info.type !== "oauth") return { autoload: false }
if (provider && provider.models) {
for (const model of Object.values(provider.models)) {
model.cost = {
input: 0,
output: 0,
}
}
}
return {
autoload: true,
options: {
apiKey: "",
async fetch(input: any, init: any) {
const info = await Auth.get("github-copilot")
if (!info || info.type !== "oauth") return
if (!info.access || info.expires < Date.now()) {
const tokens = await copilot.access(info.refresh)
if (!tokens) throw new Error("GitHub Copilot authentication expired")
await Auth.set("github-copilot", {
type: "oauth",
...tokens,
})
info.access = tokens.access
}
let isAgentCall = false
let isVisionRequest = false
try {
const body = typeof init.body === "string" ? JSON.parse(init.body) : init.body
if (body?.messages) {
isAgentCall = body.messages.some((msg: any) => msg.role && ["tool", "assistant"].includes(msg.role))
isVisionRequest = body.messages.some(
(msg: any) =>
Array.isArray(msg.content) && msg.content.some((part: any) => part.type === "image_url"),
)
}
} catch {}
const headers: Record<string, string> = {
...init.headers,
...copilot.HEADERS,
Authorization: `Bearer ${info.access}`,
"Openai-Intent": "conversation-edits",
"X-Initiator": isAgentCall ? "agent" : "user",
}
if (isVisionRequest) {
headers["Copilot-Vision-Request"] = "true"
}
delete headers["x-api-key"]
return fetch(input, {
...init,
headers,
})
headers: {
"anthropic-beta":
"claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
},
},
}
@@ -350,6 +259,17 @@ export namespace Provider {
}
}
for (const plugin of await Plugin.list()) {
if (!plugin.auth) continue
const providerID = plugin.auth.provider
if (disabled.has(providerID)) continue
const auth = await Auth.get(providerID)
if (!auth) continue
if (!plugin.auth.loader) continue
const options = await plugin.auth.loader(() => Auth.get(providerID) as any, database[plugin.auth.provider])
mergeProvider(plugin.auth.provider, options ?? {}, "custom")
}
// load config
for (const [providerID, provider] of configProviders) {
mergeProvider(providerID, provider.options ?? {}, "config")
+64 -2
View File
@@ -20,6 +20,7 @@ import { callTui, TuiRoute } from "./tui"
import { Permission } from "../permission"
import { lazy } from "../util/lazy"
import { Agent } from "../agent/agent"
import { Auth } from "../auth"
const ERRORS = {
400: {
@@ -88,7 +89,7 @@ export namespace Server {
version: "0.0.3",
description: "opencode api",
},
openapi: "3.0.0",
openapi: "3.1.1",
},
}),
)
@@ -592,6 +593,36 @@ export namespace Server {
return c.json(msg)
},
)
.post(
"/session/:id/shell",
describeRoute({
description: "Run a shell command",
operationId: "session.shell",
responses: {
200: {
description: "Created message",
content: {
"application/json": {
schema: resolver(MessageV2.Assistant),
},
},
},
},
}),
zValidator(
"param",
z.object({
id: z.string().openapi({ description: "Session ID" }),
}),
),
zValidator("json", Session.CommandInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
const msg = await Session.shell({ ...body, sessionID })
return c.json(msg)
},
)
.post(
"/session/:id/revert",
describeRoute({
@@ -1090,6 +1121,37 @@ export namespace Server {
async (c) => c.json(await callTui(c)),
)
.route("/tui/control", TuiRoute)
.put(
"/auth/:id",
describeRoute({
description: "Set authentication credentials",
operationId: "auth.set",
responses: {
200: {
description: "Successfully set authentication credentials",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...ERRORS,
},
}),
zValidator(
"param",
z.object({
id: z.string(),
}),
),
zValidator("json", Auth.Info),
async (c) => {
const id = c.req.valid("param").id
const info = c.req.valid("json")
await Auth.set(id, info)
return c.json(true)
},
)
return result
})
@@ -1103,7 +1165,7 @@ export namespace Server {
version: "1.0.0",
description: "opencode api",
},
openapi: "3.0.0",
openapi: "3.1.1",
},
})
return result
+132 -2
View File
@@ -1,4 +1,5 @@
import path from "path"
import { spawn } from "child_process"
import { Decimal } from "decimal.js"
import { z, ZodSchema } from "zod"
import {
@@ -43,6 +44,8 @@ import { Plugin } from "../plugin"
import { Agent } from "../agent/agent"
import { Permission } from "../permission"
import { Wildcard } from "../util/wildcard"
import { ulid } from "ulid"
import { defer } from "../util/defer"
export namespace Session {
const log = Log.create({ service: "session" })
@@ -668,7 +671,7 @@ export namespace Session {
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
if (msgs.length === 1 && !session.parentID && isDefaultTitle(session.title)) {
if (msgs.filter((m) => m.info.role === "user").length === 1 && !session.parentID && isDefaultTitle(session.title)) {
const small = (await Provider.getSmallModel(input.providerID)) ?? model
generateText({
maxOutputTokens: small.info.reasoning ? 1024 : 20,
@@ -761,6 +764,11 @@ export namespace Session {
sessionID: input.sessionID,
}
await updateMessage(assistantMsg)
await using _ = defer(async () => {
if (assistantMsg.time.completed) return
await Storage.remove(`session/message/${input.sessionID}/${assistantMsg.id}`)
await Bus.publish(MessageV2.Event.Removed, { sessionID: input.sessionID, messageID: assistantMsg.id })
})
const tools: Record<string, AITool> = {}
const processor = createProcessor(assistantMsg, model.info)
@@ -965,7 +973,7 @@ export namespace Session {
content: x,
}),
),
...MessageV2.toModelMessage(msgs),
...MessageV2.toModelMessage(msgs.filter((m) => !(m.info.role === "assistant" && m.info.error))),
],
tools: model.info.tool_call === false ? undefined : tools,
model: wrapLanguageModel({
@@ -997,6 +1005,128 @@ export namespace Session {
return result
}
export const CommandInput = z.object({
sessionID: Identifier.schema("session"),
agent: z.string(),
command: z.string(),
})
export type CommandInput = z.infer<typeof CommandInput>
export async function shell(input: CommandInput) {
using abort = lock(input.sessionID)
const msg: MessageV2.Assistant = {
id: Identifier.ascending("message"),
sessionID: input.sessionID,
system: [],
mode: input.agent,
cost: 0,
path: {
cwd: App.info().path.cwd,
root: App.info().path.root,
},
time: {
created: Date.now(),
},
role: "assistant",
tokens: {
input: 0,
output: 0,
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: "",
providerID: "",
}
await updateMessage(msg)
const part: MessageV2.Part = {
type: "tool",
id: Identifier.ascending("part"),
messageID: msg.id,
sessionID: input.sessionID,
tool: "bash",
callID: ulid(),
state: {
status: "running",
time: {
start: Date.now(),
},
input: {
command: input.command,
},
},
}
await updatePart(part)
const app = App.info()
const script = `
[[ -f ~/.zshrc ]] && source ~/.zshrc >/dev/null 2>&1 || true
[[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
eval "${input.command}"
`
const shell = process.env["SHELL"] ?? "bash"
const isFish = shell.includes("fish")
const args = isFish
? ["-c", script] // fish with just -c
: ["-c", "-l", script]
const proc = spawn(shell, args, {
cwd: app.path.cwd,
signal: abort.signal,
env: {
...process.env,
TERM: "dumb",
},
})
let output = ""
proc.stdout?.on("data", (chunk) => {
output += chunk.toString()
if (part.state.status === "running") {
part.state.metadata = {
output: output,
description: "",
}
updatePart(part)
}
})
proc.stderr?.on("data", (chunk) => {
output += chunk.toString()
if (part.state.status === "running") {
part.state.metadata = {
output: output,
description: "",
}
updatePart(part)
}
})
await new Promise<void>((resolve) => {
proc.on("close", () => {
resolve()
})
})
msg.time.completed = Date.now()
await updateMessage(msg)
if (part.state.status === "running") {
part.state = {
status: "completed",
time: {
...part.state.time,
end: Date.now(),
},
input: part.state.input,
title: "",
metadata: {
output,
description: "",
},
output,
}
await updatePart(part)
}
return { info: msg, parts: [part] }
}
function createProcessor(assistantMsg: MessageV2.Assistant, model: ModelsDev.Model) {
const toolcalls: Record<string, MessageV2.ToolPart> = {}
let snapshot: string | undefined
+22 -5
View File
@@ -19,11 +19,28 @@ const MAX_TIMEOUT = 10 * 60 * 1000
const log = Log.create({ service: "bash-tool" })
const parser = lazy(async () => {
const { default: Parser } = await import("tree-sitter")
const Bash = await import("tree-sitter-bash")
const p = new Parser()
p.setLanguage(Bash.language as any)
return p
try {
const { default: Parser } = await import("tree-sitter")
const Bash = await import("tree-sitter-bash")
const p = new Parser()
p.setLanguage(Bash.language as any)
return p
} catch (e) {
const { default: Parser } = await import("web-tree-sitter")
const { default: treeWasm } = await import("web-tree-sitter/tree-sitter.wasm" as string, { with: { type: "wasm" } })
await Parser.init({
locateFile() {
return treeWasm
},
})
const { default: bashWasm } = await import("tree-sitter-bash/tree-sitter-bash.wasm" as string, {
with: { type: "wasm" },
})
const bashLanguage = await Parser.Language.load(bashWasm)
const p = new Parser()
p.setLanguage(bashLanguage)
return p
}
})
export const BashTool = Tool.define("bash", {
+1 -1
View File
@@ -48,7 +48,7 @@ function isPrime(n) {
Since a signficant piece of code was written and the task was completed, now use the code-reviewer agent to review the code
</commentary>
assistant: Now let me use the code-reviewer agent to review the code
assistant: Uses the Task tool to launch the with the code-reviewer agent
assistant: Uses the Task tool to launch the code-reviewer agent
</example>
<example>
+43 -26
View File
@@ -1,53 +1,70 @@
import Parser from "tree-sitter";
import Bash from "tree-sitter-bash";
const parser = async () => {
try {
const { default: Parser } = await import("tree-sitter")
const Bash = await import("tree-sitter-bash")
const p = new Parser()
p.setLanguage(Bash.language as any)
return p
} catch (e) {
const { default: Parser } = await import("web-tree-sitter")
const { default: treeWasm } = await import("web-tree-sitter/tree-sitter.wasm" as string, { with: { type: "wasm" } })
await Parser.init({
locateFile() {
return treeWasm
},
})
const { default: bashWasm } = await import("tree-sitter-bash/tree-sitter-bash.wasm" as string, {
with: { type: "wasm" },
})
const bashLanguage = await Parser.Language.load(bashWasm)
const p = new Parser()
p.setLanguage(bashLanguage)
return p
}
}
const parser = new Parser();
parser.setLanguage(Bash.language as any);
const sourceCode = `cd --foo foo/bar && echo "hello" && cd ../baz`
const sourceCode = `cd --foo foo/bar && echo "hello" && cd ../baz`;
const tree = parser.parse(sourceCode);
const tree = await parser().then((p) => p.parse(sourceCode))
// Function to extract commands and arguments
function extractCommands(
node: any,
): Array<{ command: string; args: string[] }> {
const commands: Array<{ command: string; args: string[] }> = [];
function extractCommands(node: any): Array<{ command: string; args: string[] }> {
const commands: Array<{ command: string; args: string[] }> = []
function traverse(node: any) {
if (node.type === "command") {
const commandNode = node.child(0);
const commandNode = node.child(0)
if (commandNode) {
const command = commandNode.text;
const args: string[] = [];
const command = commandNode.text
const args: string[] = []
// Extract arguments
for (let i = 1; i < node.childCount; i++) {
const child = node.child(i);
const child = node.child(i)
if (child && child.type === "word") {
args.push(child.text);
args.push(child.text)
}
}
commands.push({ command, args });
commands.push({ command, args })
}
}
// Traverse children
for (let i = 0; i < node.childCount; i++) {
traverse(node.child(i));
traverse(node.child(i))
}
}
traverse(node);
return commands;
traverse(node)
return commands
}
// Extract and display commands
console.log("Source code: " + sourceCode);
const commands = extractCommands(tree.rootNode);
console.log("Extracted commands:");
console.log("Source code: " + sourceCode)
const commands = extractCommands(tree.rootNode)
console.log("Extracted commands:")
commands.forEach((cmd, index) => {
console.log(`${index + 1}. Command: ${cmd.command}`);
console.log(` Args: [${cmd.args.join(", ")}]`);
});
console.log(`${index + 1}. Command: ${cmd.command}`)
console.log(` Args: [${cmd.args.join(", ")}]`)
})
+12
View File
@@ -0,0 +1,12 @@
export function defer<T extends () => void | Promise<void>>(
fn: T,
): T extends () => Promise<void> ? { [Symbol.asyncDispose]: () => Promise<void> } : { [Symbol.dispose]: () => void } {
return {
[Symbol.dispose]() {
fn()
},
[Symbol.asyncDispose]() {
return Promise.resolve(fn())
},
} as any
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "0.4.42",
"version": "0.5.1",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
+54 -1
View File
@@ -1,4 +1,14 @@
import type { Event, createOpencodeClient, App, Model, Provider, Permission, UserMessage, Part } from "@opencode-ai/sdk"
import type {
Event,
createOpencodeClient,
App,
Model,
Provider,
Permission,
UserMessage,
Part,
Auth,
} from "@opencode-ai/sdk"
import type { BunShell } from "./shell"
export type PluginInput = {
@@ -10,6 +20,49 @@ export type Plugin = (input: PluginInput) => Promise<Hooks>
export interface Hooks {
event?: (input: { event: Event }) => Promise<void>
auth?: {
provider: string
loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
methods: (
| {
type: "oauth"
label: string
authorize(): Promise<
{ url: string; instructions: string } & (
| {
method: "auto"
callback(): Promise<
| {
type: "success"
refresh: string
access: string
expires: number
}
| {
type: "failed"
}
>
}
| {
method: "code"
callback(code: string): Promise<
| {
type: "success"
refresh: string
access: string
expires: number
}
| {
type: "failed"
}
>
}
)
>
}
| { type: "api"; label: string }
)[]
}
/**
* Called when a new message is received
*/
+4 -4
View File
@@ -1,4 +1,4 @@
configured_endpoints: 34
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-b86cf7bb8df4f60ebe8b8f51e281c8076cfdccc8554178c1b78beca4b025f0ff.yml
openapi_spec_hash: 47633b7481d91708643ea7b43fffffe6
config_hash: bd7f6435ed0c0005f373b5526c07a055
configured_endpoints: 36
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-a881262c7de4ab59bdfbfc6e30a23c47dee465d7270ffb867b760b0103aff8ed.yml
openapi_spec_hash: 7dbb6f96f5c26a25c849e50298f58586
config_hash: 8d85a768523cff92b85ef06c443d49fa
+2
View File
@@ -111,6 +111,7 @@ Response Types:
Methods:
- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="patch /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Update">Update</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionUpdateParams">SessionUpdateParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
@@ -120,6 +121,7 @@ Methods:
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/shell">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Shell">Shell</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionShellParams">SessionShellParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+85 -9
View File
@@ -72,21 +72,25 @@ func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption
}
type Agent struct {
Mode AgentMode `json:"mode,required"`
Name string `json:"name,required"`
Tools map[string]bool `json:"tools,required"`
Description string `json:"description"`
Model AgentModel `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
TopP float64 `json:"topP"`
JSON agentJSON `json:"-"`
Mode AgentMode `json:"mode,required"`
Name string `json:"name,required"`
Options map[string]interface{} `json:"options,required"`
Permission AgentPermission `json:"permission,required"`
Tools map[string]bool `json:"tools,required"`
Description string `json:"description"`
Model AgentModel `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
TopP float64 `json:"topP"`
JSON agentJSON `json:"-"`
}
// agentJSON contains the JSON metadata for the struct [Agent]
type agentJSON struct {
Mode apijson.Field
Name apijson.Field
Options apijson.Field
Permission apijson.Field
Tools apijson.Field
Description apijson.Field
Model apijson.Field
@@ -121,6 +125,78 @@ func (r AgentMode) IsKnown() bool {
return false
}
type AgentPermission struct {
Bash map[string]AgentPermissionBash `json:"bash,required"`
Edit AgentPermissionEdit `json:"edit,required"`
Webfetch AgentPermissionWebfetch `json:"webfetch"`
JSON agentPermissionJSON `json:"-"`
}
// agentPermissionJSON contains the JSON metadata for the struct [AgentPermission]
type agentPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *AgentPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r agentPermissionJSON) RawJSON() string {
return r.raw
}
type AgentPermissionBash string
const (
AgentPermissionBashAsk AgentPermissionBash = "ask"
AgentPermissionBashAllow AgentPermissionBash = "allow"
AgentPermissionBashDeny AgentPermissionBash = "deny"
)
func (r AgentPermissionBash) IsKnown() bool {
switch r {
case AgentPermissionBashAsk, AgentPermissionBashAllow, AgentPermissionBashDeny:
return true
}
return false
}
type AgentPermissionEdit string
const (
AgentPermissionEditAsk AgentPermissionEdit = "ask"
AgentPermissionEditAllow AgentPermissionEdit = "allow"
AgentPermissionEditDeny AgentPermissionEdit = "deny"
)
func (r AgentPermissionEdit) IsKnown() bool {
switch r {
case AgentPermissionEditAsk, AgentPermissionEditAllow, AgentPermissionEditDeny:
return true
}
return false
}
type AgentPermissionWebfetch string
const (
AgentPermissionWebfetchAsk AgentPermissionWebfetch = "ask"
AgentPermissionWebfetchAllow AgentPermissionWebfetch = "allow"
AgentPermissionWebfetchDeny AgentPermissionWebfetch = "deny"
)
func (r AgentPermissionWebfetch) IsKnown() bool {
switch r {
case AgentPermissionWebfetchAsk, AgentPermissionWebfetchAllow, AgentPermissionWebfetchDeny:
return true
}
return false
}
type AgentModel struct {
ModelID string `json:"modelID,required"`
ProviderID string `json:"providerID,required"`
+645 -47
View File
@@ -149,15 +149,17 @@ func (r configAgentJSON) RawJSON() string {
type ConfigAgentBuild struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentBuildMode `json:"mode"`
Model string `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
JSON configAgentBuildJSON `json:"-"`
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentBuildMode `json:"mode"`
Model string `json:"model"`
Permission ConfigAgentBuildPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configAgentBuildJSON `json:"-"`
}
// configAgentBuildJSON contains the JSON metadata for the struct
@@ -167,6 +169,7 @@ type configAgentBuildJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -199,17 +202,135 @@ func (r ConfigAgentBuildMode) IsKnown() bool {
return false
}
type ConfigAgentBuildPermission struct {
Bash ConfigAgentBuildPermissionBashUnion `json:"bash"`
Edit ConfigAgentBuildPermissionEdit `json:"edit"`
Webfetch ConfigAgentBuildPermissionWebfetch `json:"webfetch"`
JSON configAgentBuildPermissionJSON `json:"-"`
}
// configAgentBuildPermissionJSON contains the JSON metadata for the struct
// [ConfigAgentBuildPermission]
type configAgentBuildPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigAgentBuildPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configAgentBuildPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigAgentBuildPermissionBashString] or
// [ConfigAgentBuildPermissionBashMap].
type ConfigAgentBuildPermissionBashUnion interface {
implementsConfigAgentBuildPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigAgentBuildPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigAgentBuildPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigAgentBuildPermissionBashMap{}),
},
)
}
type ConfigAgentBuildPermissionBashString string
const (
ConfigAgentBuildPermissionBashStringAsk ConfigAgentBuildPermissionBashString = "ask"
ConfigAgentBuildPermissionBashStringAllow ConfigAgentBuildPermissionBashString = "allow"
ConfigAgentBuildPermissionBashStringDeny ConfigAgentBuildPermissionBashString = "deny"
)
func (r ConfigAgentBuildPermissionBashString) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionBashStringAsk, ConfigAgentBuildPermissionBashStringAllow, ConfigAgentBuildPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigAgentBuildPermissionBashString) implementsConfigAgentBuildPermissionBashUnion() {}
type ConfigAgentBuildPermissionBashMap map[string]ConfigAgentBuildPermissionBashMapItem
func (r ConfigAgentBuildPermissionBashMap) implementsConfigAgentBuildPermissionBashUnion() {}
type ConfigAgentBuildPermissionBashMapItem string
const (
ConfigAgentBuildPermissionBashMapAsk ConfigAgentBuildPermissionBashMapItem = "ask"
ConfigAgentBuildPermissionBashMapAllow ConfigAgentBuildPermissionBashMapItem = "allow"
ConfigAgentBuildPermissionBashMapDeny ConfigAgentBuildPermissionBashMapItem = "deny"
)
func (r ConfigAgentBuildPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionBashMapAsk, ConfigAgentBuildPermissionBashMapAllow, ConfigAgentBuildPermissionBashMapDeny:
return true
}
return false
}
type ConfigAgentBuildPermissionEdit string
const (
ConfigAgentBuildPermissionEditAsk ConfigAgentBuildPermissionEdit = "ask"
ConfigAgentBuildPermissionEditAllow ConfigAgentBuildPermissionEdit = "allow"
ConfigAgentBuildPermissionEditDeny ConfigAgentBuildPermissionEdit = "deny"
)
func (r ConfigAgentBuildPermissionEdit) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionEditAsk, ConfigAgentBuildPermissionEditAllow, ConfigAgentBuildPermissionEditDeny:
return true
}
return false
}
type ConfigAgentBuildPermissionWebfetch string
const (
ConfigAgentBuildPermissionWebfetchAsk ConfigAgentBuildPermissionWebfetch = "ask"
ConfigAgentBuildPermissionWebfetchAllow ConfigAgentBuildPermissionWebfetch = "allow"
ConfigAgentBuildPermissionWebfetchDeny ConfigAgentBuildPermissionWebfetch = "deny"
)
func (r ConfigAgentBuildPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionWebfetchAsk, ConfigAgentBuildPermissionWebfetchAllow, ConfigAgentBuildPermissionWebfetchDeny:
return true
}
return false
}
type ConfigAgentGeneral struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentGeneralMode `json:"mode"`
Model string `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
JSON configAgentGeneralJSON `json:"-"`
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentGeneralMode `json:"mode"`
Model string `json:"model"`
Permission ConfigAgentGeneralPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configAgentGeneralJSON `json:"-"`
}
// configAgentGeneralJSON contains the JSON metadata for the struct
@@ -219,6 +340,7 @@ type configAgentGeneralJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -251,17 +373,135 @@ func (r ConfigAgentGeneralMode) IsKnown() bool {
return false
}
type ConfigAgentGeneralPermission struct {
Bash ConfigAgentGeneralPermissionBashUnion `json:"bash"`
Edit ConfigAgentGeneralPermissionEdit `json:"edit"`
Webfetch ConfigAgentGeneralPermissionWebfetch `json:"webfetch"`
JSON configAgentGeneralPermissionJSON `json:"-"`
}
// configAgentGeneralPermissionJSON contains the JSON metadata for the struct
// [ConfigAgentGeneralPermission]
type configAgentGeneralPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigAgentGeneralPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configAgentGeneralPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigAgentGeneralPermissionBashString] or
// [ConfigAgentGeneralPermissionBashMap].
type ConfigAgentGeneralPermissionBashUnion interface {
implementsConfigAgentGeneralPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigAgentGeneralPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigAgentGeneralPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigAgentGeneralPermissionBashMap{}),
},
)
}
type ConfigAgentGeneralPermissionBashString string
const (
ConfigAgentGeneralPermissionBashStringAsk ConfigAgentGeneralPermissionBashString = "ask"
ConfigAgentGeneralPermissionBashStringAllow ConfigAgentGeneralPermissionBashString = "allow"
ConfigAgentGeneralPermissionBashStringDeny ConfigAgentGeneralPermissionBashString = "deny"
)
func (r ConfigAgentGeneralPermissionBashString) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionBashStringAsk, ConfigAgentGeneralPermissionBashStringAllow, ConfigAgentGeneralPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigAgentGeneralPermissionBashString) implementsConfigAgentGeneralPermissionBashUnion() {}
type ConfigAgentGeneralPermissionBashMap map[string]ConfigAgentGeneralPermissionBashMapItem
func (r ConfigAgentGeneralPermissionBashMap) implementsConfigAgentGeneralPermissionBashUnion() {}
type ConfigAgentGeneralPermissionBashMapItem string
const (
ConfigAgentGeneralPermissionBashMapAsk ConfigAgentGeneralPermissionBashMapItem = "ask"
ConfigAgentGeneralPermissionBashMapAllow ConfigAgentGeneralPermissionBashMapItem = "allow"
ConfigAgentGeneralPermissionBashMapDeny ConfigAgentGeneralPermissionBashMapItem = "deny"
)
func (r ConfigAgentGeneralPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionBashMapAsk, ConfigAgentGeneralPermissionBashMapAllow, ConfigAgentGeneralPermissionBashMapDeny:
return true
}
return false
}
type ConfigAgentGeneralPermissionEdit string
const (
ConfigAgentGeneralPermissionEditAsk ConfigAgentGeneralPermissionEdit = "ask"
ConfigAgentGeneralPermissionEditAllow ConfigAgentGeneralPermissionEdit = "allow"
ConfigAgentGeneralPermissionEditDeny ConfigAgentGeneralPermissionEdit = "deny"
)
func (r ConfigAgentGeneralPermissionEdit) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionEditAsk, ConfigAgentGeneralPermissionEditAllow, ConfigAgentGeneralPermissionEditDeny:
return true
}
return false
}
type ConfigAgentGeneralPermissionWebfetch string
const (
ConfigAgentGeneralPermissionWebfetchAsk ConfigAgentGeneralPermissionWebfetch = "ask"
ConfigAgentGeneralPermissionWebfetchAllow ConfigAgentGeneralPermissionWebfetch = "allow"
ConfigAgentGeneralPermissionWebfetchDeny ConfigAgentGeneralPermissionWebfetch = "deny"
)
func (r ConfigAgentGeneralPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionWebfetchAsk, ConfigAgentGeneralPermissionWebfetchAllow, ConfigAgentGeneralPermissionWebfetchDeny:
return true
}
return false
}
type ConfigAgentPlan struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentPlanMode `json:"mode"`
Model string `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
JSON configAgentPlanJSON `json:"-"`
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentPlanMode `json:"mode"`
Model string `json:"model"`
Permission ConfigAgentPlanPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configAgentPlanJSON `json:"-"`
}
// configAgentPlanJSON contains the JSON metadata for the struct [ConfigAgentPlan]
@@ -270,6 +510,7 @@ type configAgentPlanJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -302,6 +543,122 @@ func (r ConfigAgentPlanMode) IsKnown() bool {
return false
}
type ConfigAgentPlanPermission struct {
Bash ConfigAgentPlanPermissionBashUnion `json:"bash"`
Edit ConfigAgentPlanPermissionEdit `json:"edit"`
Webfetch ConfigAgentPlanPermissionWebfetch `json:"webfetch"`
JSON configAgentPlanPermissionJSON `json:"-"`
}
// configAgentPlanPermissionJSON contains the JSON metadata for the struct
// [ConfigAgentPlanPermission]
type configAgentPlanPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigAgentPlanPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configAgentPlanPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigAgentPlanPermissionBashString] or
// [ConfigAgentPlanPermissionBashMap].
type ConfigAgentPlanPermissionBashUnion interface {
implementsConfigAgentPlanPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigAgentPlanPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigAgentPlanPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigAgentPlanPermissionBashMap{}),
},
)
}
type ConfigAgentPlanPermissionBashString string
const (
ConfigAgentPlanPermissionBashStringAsk ConfigAgentPlanPermissionBashString = "ask"
ConfigAgentPlanPermissionBashStringAllow ConfigAgentPlanPermissionBashString = "allow"
ConfigAgentPlanPermissionBashStringDeny ConfigAgentPlanPermissionBashString = "deny"
)
func (r ConfigAgentPlanPermissionBashString) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionBashStringAsk, ConfigAgentPlanPermissionBashStringAllow, ConfigAgentPlanPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigAgentPlanPermissionBashString) implementsConfigAgentPlanPermissionBashUnion() {}
type ConfigAgentPlanPermissionBashMap map[string]ConfigAgentPlanPermissionBashMapItem
func (r ConfigAgentPlanPermissionBashMap) implementsConfigAgentPlanPermissionBashUnion() {}
type ConfigAgentPlanPermissionBashMapItem string
const (
ConfigAgentPlanPermissionBashMapAsk ConfigAgentPlanPermissionBashMapItem = "ask"
ConfigAgentPlanPermissionBashMapAllow ConfigAgentPlanPermissionBashMapItem = "allow"
ConfigAgentPlanPermissionBashMapDeny ConfigAgentPlanPermissionBashMapItem = "deny"
)
func (r ConfigAgentPlanPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionBashMapAsk, ConfigAgentPlanPermissionBashMapAllow, ConfigAgentPlanPermissionBashMapDeny:
return true
}
return false
}
type ConfigAgentPlanPermissionEdit string
const (
ConfigAgentPlanPermissionEditAsk ConfigAgentPlanPermissionEdit = "ask"
ConfigAgentPlanPermissionEditAllow ConfigAgentPlanPermissionEdit = "allow"
ConfigAgentPlanPermissionEditDeny ConfigAgentPlanPermissionEdit = "deny"
)
func (r ConfigAgentPlanPermissionEdit) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionEditAsk, ConfigAgentPlanPermissionEditAllow, ConfigAgentPlanPermissionEditDeny:
return true
}
return false
}
type ConfigAgentPlanPermissionWebfetch string
const (
ConfigAgentPlanPermissionWebfetchAsk ConfigAgentPlanPermissionWebfetch = "ask"
ConfigAgentPlanPermissionWebfetchAllow ConfigAgentPlanPermissionWebfetch = "allow"
ConfigAgentPlanPermissionWebfetchDeny ConfigAgentPlanPermissionWebfetch = "deny"
)
func (r ConfigAgentPlanPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionWebfetchAsk, ConfigAgentPlanPermissionWebfetchAllow, ConfigAgentPlanPermissionWebfetchDeny:
return true
}
return false
}
type ConfigExperimental struct {
Hook ConfigExperimentalHook `json:"hook"`
JSON configExperimentalJSON `json:"-"`
@@ -681,15 +1038,17 @@ func (r configModeJSON) RawJSON() string {
type ConfigModeBuild struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigModeBuildMode `json:"mode"`
Model string `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
JSON configModeBuildJSON `json:"-"`
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigModeBuildMode `json:"mode"`
Model string `json:"model"`
Permission ConfigModeBuildPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configModeBuildJSON `json:"-"`
}
// configModeBuildJSON contains the JSON metadata for the struct [ConfigModeBuild]
@@ -698,6 +1057,7 @@ type configModeBuildJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -730,17 +1090,135 @@ func (r ConfigModeBuildMode) IsKnown() bool {
return false
}
type ConfigModeBuildPermission struct {
Bash ConfigModeBuildPermissionBashUnion `json:"bash"`
Edit ConfigModeBuildPermissionEdit `json:"edit"`
Webfetch ConfigModeBuildPermissionWebfetch `json:"webfetch"`
JSON configModeBuildPermissionJSON `json:"-"`
}
// configModeBuildPermissionJSON contains the JSON metadata for the struct
// [ConfigModeBuildPermission]
type configModeBuildPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigModeBuildPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configModeBuildPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigModeBuildPermissionBashString] or
// [ConfigModeBuildPermissionBashMap].
type ConfigModeBuildPermissionBashUnion interface {
implementsConfigModeBuildPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigModeBuildPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigModeBuildPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigModeBuildPermissionBashMap{}),
},
)
}
type ConfigModeBuildPermissionBashString string
const (
ConfigModeBuildPermissionBashStringAsk ConfigModeBuildPermissionBashString = "ask"
ConfigModeBuildPermissionBashStringAllow ConfigModeBuildPermissionBashString = "allow"
ConfigModeBuildPermissionBashStringDeny ConfigModeBuildPermissionBashString = "deny"
)
func (r ConfigModeBuildPermissionBashString) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionBashStringAsk, ConfigModeBuildPermissionBashStringAllow, ConfigModeBuildPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigModeBuildPermissionBashString) implementsConfigModeBuildPermissionBashUnion() {}
type ConfigModeBuildPermissionBashMap map[string]ConfigModeBuildPermissionBashMapItem
func (r ConfigModeBuildPermissionBashMap) implementsConfigModeBuildPermissionBashUnion() {}
type ConfigModeBuildPermissionBashMapItem string
const (
ConfigModeBuildPermissionBashMapAsk ConfigModeBuildPermissionBashMapItem = "ask"
ConfigModeBuildPermissionBashMapAllow ConfigModeBuildPermissionBashMapItem = "allow"
ConfigModeBuildPermissionBashMapDeny ConfigModeBuildPermissionBashMapItem = "deny"
)
func (r ConfigModeBuildPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionBashMapAsk, ConfigModeBuildPermissionBashMapAllow, ConfigModeBuildPermissionBashMapDeny:
return true
}
return false
}
type ConfigModeBuildPermissionEdit string
const (
ConfigModeBuildPermissionEditAsk ConfigModeBuildPermissionEdit = "ask"
ConfigModeBuildPermissionEditAllow ConfigModeBuildPermissionEdit = "allow"
ConfigModeBuildPermissionEditDeny ConfigModeBuildPermissionEdit = "deny"
)
func (r ConfigModeBuildPermissionEdit) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionEditAsk, ConfigModeBuildPermissionEditAllow, ConfigModeBuildPermissionEditDeny:
return true
}
return false
}
type ConfigModeBuildPermissionWebfetch string
const (
ConfigModeBuildPermissionWebfetchAsk ConfigModeBuildPermissionWebfetch = "ask"
ConfigModeBuildPermissionWebfetchAllow ConfigModeBuildPermissionWebfetch = "allow"
ConfigModeBuildPermissionWebfetchDeny ConfigModeBuildPermissionWebfetch = "deny"
)
func (r ConfigModeBuildPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionWebfetchAsk, ConfigModeBuildPermissionWebfetchAllow, ConfigModeBuildPermissionWebfetchDeny:
return true
}
return false
}
type ConfigModePlan struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigModePlanMode `json:"mode"`
Model string `json:"model"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
JSON configModePlanJSON `json:"-"`
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigModePlanMode `json:"mode"`
Model string `json:"model"`
Permission ConfigModePlanPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configModePlanJSON `json:"-"`
}
// configModePlanJSON contains the JSON metadata for the struct [ConfigModePlan]
@@ -749,6 +1227,7 @@ type configModePlanJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -781,6 +1260,122 @@ func (r ConfigModePlanMode) IsKnown() bool {
return false
}
type ConfigModePlanPermission struct {
Bash ConfigModePlanPermissionBashUnion `json:"bash"`
Edit ConfigModePlanPermissionEdit `json:"edit"`
Webfetch ConfigModePlanPermissionWebfetch `json:"webfetch"`
JSON configModePlanPermissionJSON `json:"-"`
}
// configModePlanPermissionJSON contains the JSON metadata for the struct
// [ConfigModePlanPermission]
type configModePlanPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigModePlanPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configModePlanPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigModePlanPermissionBashString] or
// [ConfigModePlanPermissionBashMap].
type ConfigModePlanPermissionBashUnion interface {
implementsConfigModePlanPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigModePlanPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigModePlanPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigModePlanPermissionBashMap{}),
},
)
}
type ConfigModePlanPermissionBashString string
const (
ConfigModePlanPermissionBashStringAsk ConfigModePlanPermissionBashString = "ask"
ConfigModePlanPermissionBashStringAllow ConfigModePlanPermissionBashString = "allow"
ConfigModePlanPermissionBashStringDeny ConfigModePlanPermissionBashString = "deny"
)
func (r ConfigModePlanPermissionBashString) IsKnown() bool {
switch r {
case ConfigModePlanPermissionBashStringAsk, ConfigModePlanPermissionBashStringAllow, ConfigModePlanPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigModePlanPermissionBashString) implementsConfigModePlanPermissionBashUnion() {}
type ConfigModePlanPermissionBashMap map[string]ConfigModePlanPermissionBashMapItem
func (r ConfigModePlanPermissionBashMap) implementsConfigModePlanPermissionBashUnion() {}
type ConfigModePlanPermissionBashMapItem string
const (
ConfigModePlanPermissionBashMapAsk ConfigModePlanPermissionBashMapItem = "ask"
ConfigModePlanPermissionBashMapAllow ConfigModePlanPermissionBashMapItem = "allow"
ConfigModePlanPermissionBashMapDeny ConfigModePlanPermissionBashMapItem = "deny"
)
func (r ConfigModePlanPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigModePlanPermissionBashMapAsk, ConfigModePlanPermissionBashMapAllow, ConfigModePlanPermissionBashMapDeny:
return true
}
return false
}
type ConfigModePlanPermissionEdit string
const (
ConfigModePlanPermissionEditAsk ConfigModePlanPermissionEdit = "ask"
ConfigModePlanPermissionEditAllow ConfigModePlanPermissionEdit = "allow"
ConfigModePlanPermissionEditDeny ConfigModePlanPermissionEdit = "deny"
)
func (r ConfigModePlanPermissionEdit) IsKnown() bool {
switch r {
case ConfigModePlanPermissionEditAsk, ConfigModePlanPermissionEditAllow, ConfigModePlanPermissionEditDeny:
return true
}
return false
}
type ConfigModePlanPermissionWebfetch string
const (
ConfigModePlanPermissionWebfetchAsk ConfigModePlanPermissionWebfetch = "ask"
ConfigModePlanPermissionWebfetchAllow ConfigModePlanPermissionWebfetch = "allow"
ConfigModePlanPermissionWebfetchDeny ConfigModePlanPermissionWebfetch = "deny"
)
func (r ConfigModePlanPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigModePlanPermissionWebfetchAsk, ConfigModePlanPermissionWebfetchAllow, ConfigModePlanPermissionWebfetchDeny:
return true
}
return false
}
type ConfigPermission struct {
Bash ConfigPermissionBashUnion `json:"bash"`
Edit ConfigPermissionEdit `json:"edit"`
@@ -897,10 +1492,10 @@ func (r ConfigPermissionWebfetch) IsKnown() bool {
}
type ConfigProvider struct {
Models map[string]ConfigProviderModel `json:"models,required"`
ID string `json:"id"`
API string `json:"api"`
Env []string `json:"env"`
Models map[string]ConfigProviderModel `json:"models"`
Name string `json:"name"`
Npm string `json:"npm"`
Options ConfigProviderOptions `json:"options"`
@@ -909,10 +1504,10 @@ type ConfigProvider struct {
// configProviderJSON contains the JSON metadata for the struct [ConfigProvider]
type configProviderJSON struct {
Models apijson.Field
ID apijson.Field
API apijson.Field
Env apijson.Field
Models apijson.Field
Name apijson.Field
Npm apijson.Field
Options apijson.Field
@@ -1138,6 +1733,8 @@ type KeybindsConfig struct {
SwitchModeReverse string `json:"switch_mode_reverse,required"`
// List available themes
ThemeList string `json:"theme_list,required"`
// Toggle thinking blocks
ThinkingBlocks string `json:"thinking_blocks,required"`
// Toggle tool details
ToolDetails string `json:"tool_details,required"`
JSON keybindsConfigJSON `json:"-"`
@@ -1184,6 +1781,7 @@ type keybindsConfigJSON struct {
SwitchMode apijson.Field
SwitchModeReverse apijson.Field
ThemeList apijson.Field
ThinkingBlocks apijson.Field
ToolDetails apijson.Field
raw string
ExtraFields map[string]apijson.Field
+99 -78
View File
@@ -46,6 +46,18 @@ func (r *SessionService) New(ctx context.Context, opts ...option.RequestOption)
return
}
// Update session properties
func (r *SessionService) Update(ctx context.Context, id string, body SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
return
}
// List all sessions
func (r *SessionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Session, err error) {
opts = append(r.Options[:], opts...)
@@ -66,18 +78,6 @@ func (r *SessionService) Delete(ctx context.Context, id string, opts ...option.R
return
}
// Update session properties
func (r *SessionService) Update(ctx context.Context, id string, body SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
return
}
// Abort a session
func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
@@ -166,6 +166,18 @@ func (r *SessionService) Share(ctx context.Context, id string, opts ...option.Re
return
}
// Run a shell command
func (r *SessionService) Shell(ctx context.Context, id string, body SessionShellParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/shell", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Summarize the session
func (r *SessionService) Summarize(ctx context.Context, id string, body SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
@@ -976,11 +988,11 @@ type Part struct {
// This field can have the runtime type of [[]string].
Files interface{} `json:"files"`
Hash string `json:"hash"`
Mime string `json:"mime"`
Name string `json:"name"`
// This field can have the runtime type of [map[string]interface{}].
ProviderMetadata interface{} `json:"providerMetadata"`
Snapshot string `json:"snapshot"`
Metadata interface{} `json:"metadata"`
Mime string `json:"mime"`
Name string `json:"name"`
Snapshot string `json:"snapshot"`
// This field can have the runtime type of [FilePartSource], [AgentPartSource].
Source interface{} `json:"source"`
// This field can have the runtime type of [ToolPartState].
@@ -999,29 +1011,29 @@ type Part struct {
// partJSON contains the JSON metadata for the struct [Part]
type partJSON struct {
ID apijson.Field
MessageID apijson.Field
SessionID apijson.Field
Type apijson.Field
CallID apijson.Field
Cost apijson.Field
Filename apijson.Field
Files apijson.Field
Hash apijson.Field
Mime apijson.Field
Name apijson.Field
ProviderMetadata apijson.Field
Snapshot apijson.Field
Source apijson.Field
State apijson.Field
Synthetic apijson.Field
Text apijson.Field
Time apijson.Field
Tokens apijson.Field
Tool apijson.Field
URL apijson.Field
raw string
ExtraFields map[string]apijson.Field
ID apijson.Field
MessageID apijson.Field
SessionID apijson.Field
Type apijson.Field
CallID apijson.Field
Cost apijson.Field
Filename apijson.Field
Files apijson.Field
Hash apijson.Field
Metadata apijson.Field
Mime apijson.Field
Name apijson.Field
Snapshot apijson.Field
Source apijson.Field
State apijson.Field
Synthetic apijson.Field
Text apijson.Field
Time apijson.Field
Tokens apijson.Field
Tool apijson.Field
URL apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r partJSON) RawJSON() string {
@@ -1175,27 +1187,27 @@ func (r PartType) IsKnown() bool {
}
type ReasoningPart struct {
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Type ReasoningPartType `json:"type,required"`
ProviderMetadata map[string]interface{} `json:"providerMetadata"`
Time ReasoningPartTime `json:"time"`
JSON reasoningPartJSON `json:"-"`
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Time ReasoningPartTime `json:"time,required"`
Type ReasoningPartType `json:"type,required"`
Metadata map[string]interface{} `json:"metadata"`
JSON reasoningPartJSON `json:"-"`
}
// reasoningPartJSON contains the JSON metadata for the struct [ReasoningPart]
type reasoningPartJSON struct {
ID apijson.Field
MessageID apijson.Field
SessionID apijson.Field
Text apijson.Field
Type apijson.Field
ProviderMetadata apijson.Field
Time apijson.Field
raw string
ExtraFields map[string]apijson.Field
ID apijson.Field
MessageID apijson.Field
SessionID apijson.Field
Text apijson.Field
Time apijson.Field
Type apijson.Field
Metadata apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ReasoningPart) UnmarshalJSON(data []byte) (err error) {
@@ -1208,20 +1220,6 @@ func (r reasoningPartJSON) RawJSON() string {
func (r ReasoningPart) implementsPart() {}
type ReasoningPartType string
const (
ReasoningPartTypeReasoning ReasoningPartType = "reasoning"
)
func (r ReasoningPartType) IsKnown() bool {
switch r {
case ReasoningPartTypeReasoning:
return true
}
return false
}
type ReasoningPartTime struct {
Start float64 `json:"start,required"`
End float64 `json:"end"`
@@ -1245,6 +1243,20 @@ func (r reasoningPartTimeJSON) RawJSON() string {
return r.raw
}
type ReasoningPartType string
const (
ReasoningPartTypeReasoning ReasoningPartType = "reasoning"
)
func (r ReasoningPartType) IsKnown() bool {
switch r {
case ReasoningPartTypeReasoning:
return true
}
return false
}
type Session struct {
ID string `json:"id,required"`
Time SessionTime `json:"time,required"`
@@ -2286,6 +2298,14 @@ func (r sessionMessagesResponseJSON) RawJSON() string {
return r.raw
}
type SessionUpdateParams struct {
Title param.Field[string] `json:"title"`
}
func (r SessionUpdateParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionChatParams struct {
ModelID param.Field[string] `json:"modelID,required"`
Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
@@ -2360,6 +2380,15 @@ func (r SessionRevertParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionShellParams struct {
Agent param.Field[string] `json:"agent,required"`
Command param.Field[string] `json:"command,required"`
}
func (r SessionShellParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionSummarizeParams struct {
ModelID param.Field[string] `json:"modelID,required"`
ProviderID param.Field[string] `json:"providerID,required"`
@@ -2368,11 +2397,3 @@ type SessionSummarizeParams struct {
func (r SessionSummarizeParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionUpdateParams struct {
Title param.Field[string] `json:"title"`
}
func (r SessionUpdateParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
+57
View File
@@ -35,6 +35,34 @@ func TestSessionNew(t *testing.T) {
}
}
func TestSessionUpdateWithOptionalParams(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
}
if !testutil.CheckTestServer(t, baseURL) {
return
}
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
_, err := client.Session.Update(
context.TODO(),
"id",
opencode.SessionUpdateParams{
Title: opencode.F("title"),
},
)
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
t.Log(string(apierr.DumpRequest(true)))
}
t.Fatalf("err should be nil: %s", err.Error())
}
}
func TestSessionList(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
@@ -275,6 +303,35 @@ func TestSessionShare(t *testing.T) {
}
}
func TestSessionShell(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
}
if !testutil.CheckTestServer(t, baseURL) {
return
}
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
_, err := client.Session.Shell(
context.TODO(),
"id",
opencode.SessionShellParams{
Agent: opencode.F("agent"),
Command: opencode.F("command"),
},
)
if err != nil {
var apierr *opencode.Error
if errors.As(err, &apierr) {
t.Log(string(apierr.DumpRequest(true)))
}
t.Fatalf("err should be nil: %s", err.Error())
}
}
func TestSessionSummarize(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "0.4.42",
"version": "0.5.1",
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
+36
View File
@@ -37,6 +37,8 @@ import type {
SessionChatResponses,
SessionMessageData,
SessionMessageResponses,
SessionShellData,
SessionShellResponses,
SessionRevertData,
SessionRevertResponses,
SessionUnrevertData,
@@ -75,6 +77,9 @@ import type {
TuiClearPromptResponses,
TuiExecuteCommandData,
TuiExecuteCommandResponses,
AuthSetData,
AuthSetResponses,
AuthSetErrors,
} from "./types.gen.js"
import { client as _heyApiClient } from "./client.gen.js"
@@ -332,6 +337,20 @@ class Session extends _HeyApiClient {
})
}
/**
* Run a shell command
*/
public shell<ThrowOnError extends boolean = false>(options: Options<SessionShellData, ThrowOnError>) {
return (options.client ?? this._client).post<SessionShellResponses, unknown, ThrowOnError>({
url: "/session/{id}/shell",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
})
}
/**
* Revert a message
*/
@@ -501,6 +520,22 @@ class Tui extends _HeyApiClient {
}
}
class Auth extends _HeyApiClient {
/**
* Set authentication credentials
*/
public set<ThrowOnError extends boolean = false>(options: Options<AuthSetData, ThrowOnError>) {
return (options.client ?? this._client).put<AuthSetResponses, AuthSetErrors, ThrowOnError>({
url: "/auth/{id}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
})
}
}
export class OpencodeClient extends _HeyApiClient {
/**
* Respond to a permission request
@@ -528,4 +563,5 @@ export class OpencodeClient extends _HeyApiClient {
find = new Find({ client: this._client })
file = new File({ client: this._client })
tui = new Tui({ client: this._client })
auth = new Auth({ client: this._client })
}
+167 -81
View File
@@ -22,18 +22,15 @@ export type Event =
| ({
type: "storage.write"
} & EventStorageWrite)
| ({
type: "file.edited"
} & EventFileEdited)
| ({
type: "server.connected"
} & EventServerConnected)
| ({
type: "permission.updated"
} & EventPermissionUpdated)
| ({
type: "permission.replied"
} & EventPermissionReplied)
| ({
type: "file.edited"
} & EventFileEdited)
| ({
type: "session.updated"
} & EventSessionUpdated)
@@ -46,6 +43,9 @@ export type Event =
| ({
type: "session.error"
} & EventSessionError)
| ({
type: "server.connected"
} & EventServerConnected)
| ({
type: "file.watcher.updated"
} & EventFileWatcherUpdated)
@@ -54,14 +54,14 @@ export type Event =
} & EventIdeInstalled)
export type EventInstallationUpdated = {
type: string
type: "installation.updated"
properties: {
version: string
}
}
export type EventLspClientDiagnostics = {
type: string
type: "lsp.client.diagnostics"
properties: {
serverID: string
path: string
@@ -69,7 +69,7 @@ export type EventLspClientDiagnostics = {
}
export type EventMessageUpdated = {
type: string
type: "message.updated"
properties: {
info: Message
}
@@ -86,7 +86,7 @@ export type Message =
export type UserMessage = {
id: string
sessionID: string
role: string
role: "user"
time: {
created: number
}
@@ -95,7 +95,7 @@ export type UserMessage = {
export type AssistantMessage = {
id: string
sessionID: string
role: string
role: "assistant"
time: {
created: number
completed?: number
@@ -135,7 +135,7 @@ export type AssistantMessage = {
}
export type ProviderAuthError = {
name: string
name: "ProviderAuthError"
data: {
providerID: string
message: string
@@ -143,28 +143,28 @@ export type ProviderAuthError = {
}
export type UnknownError = {
name: string
name: "UnknownError"
data: {
message: string
}
}
export type MessageOutputLengthError = {
name: string
name: "MessageOutputLengthError"
data: {
[key: string]: unknown
}
}
export type MessageAbortedError = {
name: string
name: "MessageAbortedError"
data: {
[key: string]: unknown
}
}
export type EventMessageRemoved = {
type: string
type: "message.removed"
properties: {
sessionID: string
messageID: string
@@ -172,7 +172,7 @@ export type EventMessageRemoved = {
}
export type EventMessagePartUpdated = {
type: string
type: "message.part.updated"
properties: {
part: Part
}
@@ -211,7 +211,7 @@ export type TextPart = {
id: string
sessionID: string
messageID: string
type: string
type: "text"
text: string
synthetic?: boolean
time?: {
@@ -224,7 +224,7 @@ export type ReasoningPart = {
id: string
sessionID: string
messageID: string
type: string
type: "reasoning"
text: string
metadata?: {
[key: string]: unknown
@@ -239,7 +239,7 @@ export type FilePart = {
id: string
sessionID: string
messageID: string
type: string
type: "file"
mime: string
filename?: string
url: string
@@ -256,7 +256,7 @@ export type FilePartSource =
export type FileSource = {
text: FilePartSourceText
type: string
type: "file"
path: string
}
@@ -268,7 +268,7 @@ export type FilePartSourceText = {
export type SymbolSource = {
text: FilePartSourceText
type: string
type: "symbol"
path: string
range: Range
name: string
@@ -290,7 +290,7 @@ export type ToolPart = {
id: string
sessionID: string
messageID: string
type: string
type: "tool"
callID: string
tool: string
state: ToolState
@@ -311,11 +311,11 @@ export type ToolState =
} & ToolStateError)
export type ToolStatePending = {
status: string
status: "pending"
}
export type ToolStateRunning = {
status: string
status: "running"
input?: unknown
title?: string
metadata?: {
@@ -327,7 +327,7 @@ export type ToolStateRunning = {
}
export type ToolStateCompleted = {
status: string
status: "completed"
input: {
[key: string]: unknown
}
@@ -343,7 +343,7 @@ export type ToolStateCompleted = {
}
export type ToolStateError = {
status: string
status: "error"
input: {
[key: string]: unknown
}
@@ -358,14 +358,14 @@ export type StepStartPart = {
id: string
sessionID: string
messageID: string
type: string
type: "step-start"
}
export type StepFinishPart = {
id: string
sessionID: string
messageID: string
type: string
type: "step-finish"
cost: number
tokens: {
input: number
@@ -382,7 +382,7 @@ export type SnapshotPart = {
id: string
sessionID: string
messageID: string
type: string
type: "snapshot"
snapshot: string
}
@@ -390,7 +390,7 @@ export type PatchPart = {
id: string
sessionID: string
messageID: string
type: string
type: "patch"
hash: string
files: Array<string>
}
@@ -399,7 +399,7 @@ export type AgentPart = {
id: string
sessionID: string
messageID: string
type: string
type: "agent"
name: string
source?: {
value: string
@@ -409,7 +409,7 @@ export type AgentPart = {
}
export type EventMessagePartRemoved = {
type: string
type: "message.part.removed"
properties: {
sessionID: string
messageID: string
@@ -418,29 +418,15 @@ export type EventMessagePartRemoved = {
}
export type EventStorageWrite = {
type: string
type: "storage.write"
properties: {
key: string
content?: unknown
}
}
export type EventFileEdited = {
type: string
properties: {
file: string
}
}
export type EventServerConnected = {
type: string
properties: {
[key: string]: unknown
}
}
export type EventPermissionUpdated = {
type: string
type: "permission.updated"
properties: Permission
}
@@ -461,7 +447,7 @@ export type Permission = {
}
export type EventPermissionReplied = {
type: string
type: "permission.replied"
properties: {
sessionID: string
permissionID: string
@@ -469,8 +455,15 @@ export type EventPermissionReplied = {
}
}
export type EventFileEdited = {
type: "file.edited"
properties: {
file: string
}
}
export type EventSessionUpdated = {
type: string
type: "session.updated"
properties: {
info: Session
}
@@ -497,21 +490,21 @@ export type Session = {
}
export type EventSessionDeleted = {
type: string
type: "session.deleted"
properties: {
info: Session
}
}
export type EventSessionIdle = {
type: string
type: "session.idle"
properties: {
sessionID: string
}
}
export type EventSessionError = {
type: string
type: "session.error"
properties: {
sessionID?: string
error?:
@@ -530,16 +523,23 @@ export type EventSessionError = {
}
}
export type EventServerConnected = {
type: "server.connected"
properties: {
[key: string]: unknown
}
}
export type EventFileWatcherUpdated = {
type: string
type: "file.watcher.updated"
properties: {
file: string
event: string
event: "rename" | "change"
}
}
export type EventIdeInstalled = {
type: string
type: "ide.installed"
properties: {
ide: string
}
@@ -569,6 +569,9 @@ export type Config = {
* Theme name to use for the interface
*/
theme?: string
/**
* Custom keybind configurations
*/
keybinds?: KeybindsConfig
plugin?: Array<string>
snapshot?: boolean
@@ -683,7 +686,7 @@ export type Config = {
lsp?: {
[key: string]:
| {
disabled: boolean
disabled: true
}
| {
command: Array<string>
@@ -701,15 +704,18 @@ export type Config = {
* Additional instruction files or patterns to include
*/
instructions?: Array<string>
/**
* @deprecated Always uses stretch layout.
*/
layout?: LayoutConfig
permission?: {
edit?: string
edit?: "ask" | "allow" | "deny"
bash?:
| string
| ("ask" | "allow" | "deny")
| {
[key: string]: string
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: string
webfetch?: "ask" | "allow" | "deny"
}
experimental?: {
hook?: {
@@ -911,15 +917,15 @@ export type AgentConfig = {
* Description of when to use the agent
*/
description?: string
mode?: string
mode?: "subagent" | "primary" | "all"
permission?: {
edit?: string
edit?: "ask" | "allow" | "deny"
bash?:
| string
| ("ask" | "allow" | "deny")
| {
[key: string]: string
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: string
webfetch?: "ask" | "allow" | "deny"
}
[key: string]:
| unknown
@@ -929,15 +935,15 @@ export type AgentConfig = {
[key: string]: boolean
}
| boolean
| string
| ("subagent" | "primary" | "all")
| {
edit?: string
edit?: "ask" | "allow" | "deny"
bash?:
| string
| ("ask" | "allow" | "deny")
| {
[key: string]: string
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: string
webfetch?: "ask" | "allow" | "deny"
}
| undefined
}
@@ -980,7 +986,7 @@ export type McpLocalConfig = {
/**
* Type of MCP server connection
*/
type: string
type: "local"
/**
* Command and arguments to run the MCP server
*/
@@ -1001,7 +1007,7 @@ export type McpRemoteConfig = {
/**
* Type of MCP server connection
*/
type: string
type: "remote"
/**
* URL of the remote MCP server
*/
@@ -1028,7 +1034,7 @@ export type _Error = {
export type TextPartInput = {
id?: string
type: string
type: "text"
text: string
synthetic?: boolean
time?: {
@@ -1039,7 +1045,7 @@ export type TextPartInput = {
export type FilePartInput = {
id?: string
type: string
type: "file"
mime: string
filename?: string
url: string
@@ -1048,7 +1054,7 @@ export type FilePartInput = {
export type AgentPartInput = {
id?: string
type: string
type: "agent"
name: string
source?: {
value: string
@@ -1076,15 +1082,15 @@ export type File = {
export type Agent = {
name: string
description?: string
mode: string
mode: "subagent" | "primary" | "all"
topP?: number
temperature?: number
permission: {
edit: string
edit: "ask" | "allow" | "deny"
bash: {
[key: string]: string
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: string
webfetch?: "ask" | "allow" | "deny"
}
model?: {
modelID: string
@@ -1099,6 +1105,35 @@ export type Agent = {
}
}
export type Auth =
| ({
type: "oauth"
} & OAuth)
| ({
type: "api"
} & ApiAuth)
| ({
type: "wellknown"
} & WellKnownAuth)
export type OAuth = {
type: "oauth"
refresh: string
access: string
expires: number
}
export type ApiAuth = {
type: "api"
key: string
}
export type WellKnownAuth = {
type: "wellknown"
key: string
token: string
}
export type EventSubscribeData = {
body?: never
path?: never
@@ -1456,6 +1491,30 @@ export type SessionMessageResponses = {
export type SessionMessageResponse = SessionMessageResponses[keyof SessionMessageResponses]
export type SessionShellData = {
body?: {
agent: string
command: string
}
path: {
/**
* Session ID
*/
id: string
}
query?: never
url: "/session/{id}/shell"
}
export type SessionShellResponses = {
/**
* Created message
*/
200: AssistantMessage
}
export type SessionShellResponse = SessionShellResponses[keyof SessionShellResponses]
export type SessionRevertData = {
body?: {
messageID: string
@@ -1828,6 +1887,33 @@ export type TuiExecuteCommandResponses = {
export type TuiExecuteCommandResponse = TuiExecuteCommandResponses[keyof TuiExecuteCommandResponses]
export type AuthSetData = {
body?: Auth
path: {
id: string
}
query?: never
url: "/auth/{id}"
}
export type AuthSetErrors = {
/**
* Bad request
*/
400: _Error
}
export type AuthSetError = AuthSetErrors[keyof AuthSetErrors]
export type AuthSetResponses = {
/**
* Successfully set authentication credentials
*/
200: boolean
}
export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]
export type ClientOptions = {
baseUrl: `${string}://${string}` | (string & {})
}
+26
View File
@@ -2,8 +2,34 @@ import { createClient } from "./gen/client/client.js"
import { type Config } from "./gen/client/types.js"
import { OpencodeClient } from "./gen/sdk.gen.js"
export * from "./gen/types.gen.js"
import { spawn } from "child_process"
export function createOpencodeClient(config?: Config) {
const client = createClient(config)
return new OpencodeClient({ client })
}
export type ServerConfig = {
host?: string
port?: number
}
export async function createOpencodeServer(config?: ServerConfig) {
config = Object.assign(
{
host: "127.0.0.1",
port: 4096,
},
config ?? {},
)
const proc = spawn(`opencode`, [`serve`, `--host=${config.host}`, `--port=${config.port}`])
const url = `http://${config.host}:${config.port}`
return {
url,
close() {
proc.kill()
},
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ console.log("=== Generating Stainless SDK ===")
console.log(process.cwd())
await $`rm -rf go`
await $`bun run ../../opencode/src/index.ts generate > openapi.json`
await $`bun run --conditions=development ../../opencode/src/index.ts generate > openapi.json`
await $`stl builds create --branch dev --pull --allow-empty --+target go`
await $`rm -rf ../go`
+2
View File
@@ -124,6 +124,8 @@ resources:
message: get /session/{id}/message/{messageID}
messages: get /session/{id}/message
chat: post /session/{id}/message
shell: post /session/{id}/shell
update: patch /session/{id}
revert: post /session/{id}/revert
unrevert: post /session/{id}/unrevert
+72 -9
View File
@@ -49,6 +49,7 @@ type App struct {
InitialSession *string
compactCancel context.CancelFunc
IsLeaderSequence bool
IsBashMode bool
}
func (a *App) Agent() *opencode.Agent {
@@ -79,6 +80,9 @@ type AgentSelectedMsg struct {
type SessionClearedMsg struct{}
type CompactSessionMsg struct{}
type SendPrompt = Prompt
type SendShell = struct {
Command string
}
type SetEditorContentMsg struct {
Text string
}
@@ -296,23 +300,41 @@ func (a *App) CycleRecentModel() (*App, tea.Cmd) {
}
nextIndex := 0
for i, recentModel := range recentModels {
if a.Provider != nil && a.Model != nil && recentModel.ProviderID == a.Provider.ID && recentModel.ModelID == a.Model.ID {
if a.Provider != nil && a.Model != nil && recentModel.ProviderID == a.Provider.ID &&
recentModel.ModelID == a.Model.ID {
nextIndex = (i + 1) % len(recentModels)
break
}
}
for range recentModels {
currentRecentModel := recentModels[nextIndex%len(recentModels)]
provider, model := findModelByProviderAndModelID(a.Providers, currentRecentModel.ProviderID, currentRecentModel.ModelID)
provider, model := findModelByProviderAndModelID(
a.Providers,
currentRecentModel.ProviderID,
currentRecentModel.ModelID,
)
if provider != nil && model != nil {
a.Provider, a.Model = provider, model
a.State.AgentModel[a.Agent().Name] = AgentModel{ProviderID: provider.ID, ModelID: model.ID}
return a, tea.Sequence(a.SaveState(), toast.NewSuccessToast(fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name)))
a.State.AgentModel[a.Agent().Name] = AgentModel{
ProviderID: provider.ID,
ModelID: model.ID,
}
return a, tea.Sequence(
a.SaveState(),
toast.NewSuccessToast(
fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name),
),
)
}
recentModels = append(recentModels[:nextIndex%len(recentModels)], recentModels[nextIndex%len(recentModels)+1:]...)
recentModels = append(
recentModels[:nextIndex%len(recentModels)],
recentModels[nextIndex%len(recentModels)+1:]...)
if len(recentModels) < 2 {
a.State.RecentlyUsedModels = recentModels
return a, tea.Sequence(a.SaveState(), toast.NewInfoToast("Not enough valid recent models to cycle"))
return a, tea.Sequence(
a.SaveState(),
toast.NewInfoToast("Not enough valid recent models to cycle"),
)
}
}
a.State.RecentlyUsedModels = recentModels
@@ -464,10 +486,19 @@ func (a *App) InitializeProvider() tea.Cmd {
// Priority 3: Current agent's preferred model
if selectedProvider == nil && a.Agent().Model.ModelID != "" {
if provider, model := findModelByProviderAndModelID(providers, a.Agent().Model.ProviderID, a.Agent().Model.ModelID); provider != nil && model != nil {
if provider, model := findModelByProviderAndModelID(providers, a.Agent().Model.ProviderID, a.Agent().Model.ModelID); provider != nil &&
model != nil {
selectedProvider = provider
selectedModel = model
slog.Debug("Selected model from current agent", "provider", provider.ID, "model", model.ID, "agent", a.Agent().Name)
slog.Debug(
"Selected model from current agent",
"provider",
provider.ID,
"model",
model.ID,
"agent",
a.Agent().Name,
)
} else {
slog.Debug("Agent model not found", "provider", a.Agent().Model.ProviderID, "model", a.Agent().Model.ModelID, "agent", a.Agent().Name)
}
@@ -600,7 +631,7 @@ func (a *App) IsBusy() bool {
if casted, ok := lastMessage.Info.(opencode.AssistantMessage); ok {
return casted.Time.Completed == 0
}
return true
return false
}
func (a *App) SaveState() tea.Cmd {
@@ -724,6 +755,38 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) {
return a, tea.Batch(cmds...)
}
func (a *App) SendShell(ctx context.Context, command string) (*App, tea.Cmd) {
var cmds []tea.Cmd
if a.Session.ID == "" {
session, err := a.CreateSession(ctx)
if err != nil {
return a, toast.NewErrorToast(err.Error())
}
a.Session = session
cmds = append(cmds, util.CmdHandler(SessionCreatedMsg{Session: session}))
}
cmds = append(cmds, func() tea.Msg {
_, err := a.Client.Session.Shell(
context.Background(),
a.Session.ID,
opencode.SessionShellParams{
Agent: opencode.F(a.Agent().Name),
Command: opencode.F(command),
},
)
if err != nil {
slog.Error("Failed to submit shell command", "error", err)
return toast.NewErrorToast("Failed to submit shell command")()
}
return nil
})
// The actual response will come through SSE
// For now, just return success
return a, tea.Batch(cmds...)
}
func (a *App) Cancel(ctx context.Context, sessionID string) error {
// Cancel any running compact operation
if a.compactCancel != nil {
@@ -39,6 +39,7 @@ type EditorComponent interface {
Focus() (tea.Model, tea.Cmd)
Blur()
Submit() (tea.Model, tea.Cmd)
SubmitBash() (tea.Model, tea.Cmd)
Clear() (tea.Model, tea.Cmd)
Paste() (tea.Model, tea.Cmd)
Newline() (tea.Model, tea.Cmd)
@@ -342,6 +343,14 @@ func (m *editorComponent) Content() string {
Padding(0, 0, 0, 1).
Bold(true)
prompt := promptStyle.Render(">")
borderForeground := t.Border()
if m.app.IsLeaderSequence {
borderForeground = t.Accent()
}
if m.app.IsBashMode {
borderForeground = t.Secondary()
prompt = promptStyle.Render("!")
}
m.textarea.SetWidth(width - 6)
textarea := lipgloss.JoinHorizontal(
@@ -349,10 +358,6 @@ func (m *editorComponent) Content() string {
prompt,
m.textarea.View(),
)
borderForeground := t.Border()
if m.app.IsLeaderSequence {
borderForeground = t.Accent()
}
textarea = styles.NewStyle().
Background(t.BackgroundElement()).
Width(width).
@@ -489,6 +494,16 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...)
}
func (m *editorComponent) SubmitBash() (tea.Model, tea.Cmd) {
command := m.textarea.Value()
var cmds []tea.Cmd
updated, cmd := m.Clear()
m = updated.(*editorComponent)
cmds = append(cmds, cmd)
cmds = append(cmds, util.CmdHandler(app.SendShell{Command: command}))
return m, tea.Batch(cmds...)
}
func (m *editorComponent) Clear() (tea.Model, tea.Cmd) {
m.textarea.Reset()
m.historyIndex = -1
@@ -687,6 +687,8 @@ func renderToolDetails(
func renderToolName(name string) string {
switch name {
case "bash":
return "Shell"
case "webfetch":
return "Fetch"
case "invalid":
@@ -203,6 +203,11 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.Properties.Part.SessionID == m.app.Session.ID {
cmds = append(cmds, m.renderView())
}
case opencode.EventListResponseEventMessageRemoved:
if msg.Properties.SessionID == m.app.Session.ID {
m.cache.Clear()
cmds = append(cmds, m.renderView())
}
case opencode.EventListResponseEventMessagePartRemoved:
if msg.Properties.SessionID == m.app.Session.ID {
// Clear the cache when a part is removed to ensure proper re-rendering
@@ -1153,7 +1158,7 @@ func NewMessagesComponent(app *app.App) MessagesComponent {
if app.State.ScrollSpeed != nil && *app.State.ScrollSpeed > 0 {
vp.MouseWheelDelta = *app.State.ScrollSpeed
} else {
vp.MouseWheelDelta = 4
vp.MouseWheelDelta = 2
}
// Default to showing tool details, hidden thinking blocks
@@ -670,6 +670,28 @@ func (m *Model) InsertAttachment(att *attachment.Attachment) {
m.SetCursorColumn(m.col)
}
// removeAttachmentAtCursor replaces the attachment at or immediately before the
// cursor with its textual display and positions the cursor at the end of the
// inserted text. Returns true if an attachment was removed.
func (m *Model) removeAttachmentAtCursor() bool {
att, startIdx, _ := m.isAttachmentAtCursor()
if att == nil {
return false
}
// Replace the attachment element with the display runes
before := m.value[m.row][:startIdx]
after := m.value[m.row][startIdx+1:]
replacement := runesToInterfaces([]rune(att.Display))
newRow := make([]any, 0, len(before)+len(replacement)+len(after))
newRow = append(newRow, before...)
newRow = append(newRow, replacement...)
newRow = append(newRow, after...)
m.value[m.row] = newRow
m.col = startIdx + len(replacement)
m.SetCursorColumn(m.col)
return true
}
// ReplaceRange replaces text from startCol to endCol on the current row with the given string.
// This preserves attachments outside the replaced range.
func (m *Model) ReplaceRange(startCol, endCol int, replacement string) {
@@ -1577,6 +1599,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
m.deleteBeforeCursor()
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
// If the cursor is at or just after an attachment, convert it to text instead of deleting
if att, _, _ := m.isAttachmentAtCursor(); att != nil {
if m.removeAttachmentAtCursor() {
break
}
}
m.col = clamp(m.col, 0, len(m.value[m.row]))
if m.col <= 0 {
m.mergeLineAbove(m.row)
@@ -1587,6 +1615,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.SetCursorColumn(m.col - 1)
}
case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
// If the cursor is on an attachment, convert it to text instead of deleting
if att, _, _ := m.isAttachmentAtCursor(); att != nil {
if m.removeAttachmentAtCursor() {
break
}
}
if len(m.value[m.row]) > 0 && m.col < len(m.value[m.row]) {
m.value[m.row] = slices.Delete(m.value[m.row], m.col, m.col+1)
}
@@ -0,0 +1,75 @@
package textarea
import (
"testing"
"github.com/sst/opencode/internal/attachment"
)
func TestRemoveAttachmentAtCursor_ConvertsToText_WhenCursorAfterAttachment(t *testing.T) {
m := New()
m.InsertString("a ")
att := &attachment.Attachment{ID: "1", Display: "@file.txt"}
m.InsertAttachment(att)
m.InsertString(" b")
// Position cursor immediately after the attachment (index 3: 'a',' ',att,' ', 'b')
m.SetCursorColumn(3)
if ok := m.removeAttachmentAtCursor(); !ok {
t.Fatalf("expected removal to occur")
}
got := m.Value()
want := "a @file.txt b"
if got != want {
t.Fatalf("expected %q, got %q", want, got)
}
}
func TestRemoveAttachmentAtCursor_ConvertsToText_WhenCursorOnAttachment(t *testing.T) {
m := New()
m.InsertString("x ")
att := &attachment.Attachment{ID: "2", Display: "@img.png"}
m.InsertAttachment(att)
m.InsertString(" y")
// Position cursor on the attachment token (index 2: 'x',' ',att,' ', 'y')
m.SetCursorColumn(2)
if ok := m.removeAttachmentAtCursor(); !ok {
t.Fatalf("expected removal to occur")
}
got := m.Value()
want := "x @img.png y"
if got != want {
t.Fatalf("expected %q, got %q", want, got)
}
}
func TestRemoveAttachmentAtCursor_StartOfLine(t *testing.T) {
m := New()
att := &attachment.Attachment{ID: "3", Display: "@a.txt"}
m.InsertAttachment(att)
m.InsertString(" tail")
// Position cursor immediately after the attachment at start of line (index 1)
m.SetCursorColumn(1)
if ok := m.removeAttachmentAtCursor(); !ok {
t.Fatalf("expected removal to occur at start of line")
}
if got := m.Value(); got != "@a.txt tail" {
t.Fatalf("unexpected value: %q", got)
}
}
func TestRemoveAttachmentAtCursor_NoAttachment_NoChange(t *testing.T) {
m := New()
m.InsertString("hello world")
col := m.CursorColumn()
if ok := m.removeAttachmentAtCursor(); ok {
t.Fatalf("did not expect removal to occur")
}
if m.Value() != "hello world" || m.CursorColumn() != col {
t.Fatalf("value or cursor unexpectedly changed")
}
}
+29 -2
View File
@@ -151,6 +151,23 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
if a.app.IsBashMode {
if keyString == "backspace" && a.editor.Length() == 0 {
a.app.IsBashMode = false
return a, nil
}
if keyString == "enter" || keyString == "esc" || keyString == "ctrl+c" {
a.app.IsBashMode = false
if keyString == "enter" {
updated, cmd := a.editor.SubmitBash()
a.editor = updated.(chat.EditorComponent)
cmds = append(cmds, cmd)
}
return a, tea.Batch(cmds...)
}
}
// 1. Handle active modal
if a.modal != nil {
switch keyString {
@@ -189,7 +206,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// 3. Handle completions trigger
if keyString == "/" &&
!a.showCompletionDialog &&
a.editor.Value() == "" {
a.editor.Value() == "" &&
!a.app.IsBashMode {
a.showCompletionDialog = true
updated, cmd := a.editor.Update(msg)
@@ -207,7 +225,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Handle file completions trigger
if keyString == "@" &&
!a.showCompletionDialog {
!a.showCompletionDialog &&
!a.app.IsBashMode {
a.showCompletionDialog = true
updated, cmd := a.editor.Update(msg)
@@ -223,6 +242,11 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, tea.Sequence(cmds...)
}
if keyString == "!" && a.editor.Value() == "" {
a.app.IsBashMode = true
return a, nil
}
if a.showCompletionDialog {
switch keyString {
case "tab", "enter", "esc", "ctrl+c", "up", "down", "ctrl+p", "ctrl+n":
@@ -378,6 +402,9 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.showCompletionDialog = false
a.app, cmd = a.app.SendPrompt(context.Background(), msg)
cmds = append(cmds, cmd)
case app.SendShell:
a.app, cmd = a.app.SendShell(context.Background(), msg.Command)
cmds = append(cmds, cmd)
case app.SetEditorContentMsg:
// Set the editor content without sending
a.editor.SetValueWithAttachments(msg.Text)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode/web",
"type": "module",
"version": "0.4.42",
"version": "0.5.1",
"scripts": {
"dev": "astro dev",
"dev:remote": "sst shell --stage=dev --target=Web astro dev",
Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

+378 -59
View File
@@ -5,7 +5,9 @@ import type { Props } from '@astrojs/starlight/props';
import CopyIcon from "../assets/lander/copy.svg";
import CheckIcon from "../assets/lander/check.svg";
import Screenshot from "../assets/lander/screenshot-splash.png";
import TuiScreenshot from "../assets/lander/screenshot-splash.png";
import VscodeScreenshot from "../assets/lander/screenshot-vscode.png";
import GithubScreenshot from "../assets/lander/screenshot-github.png";
const { data } = Astro.locals.starlightRoute.entry;
const { title = data.title, tagline, image, actions = [] } = data.hero || {};
@@ -18,6 +20,7 @@ const imageAttrs = {
};
const github = config.social.filter(s => s.icon === 'github')[0];
const discord = config.social.filter(s => s.icon === 'discord')[0];
const command = "curl -fsSL"
const protocol = "https://"
@@ -35,7 +38,7 @@ if (image) {
lightImage = image.light;
} else {
rawHtml = image.html;
}
}
}
---
<div class="hero">
@@ -53,7 +56,7 @@ if (image) {
<section class="cta">
<div class="col1">
<a href="/docs">Docs</a>
<a href="/docs">Get Started</a>
</div>
<div class="col2">
<button class="command" data-command={`${command} ${protocol}${url} ${bash}`}>
@@ -66,9 +69,6 @@ if (image) {
</span>
</button>
</div>
<div class="col3">
<a href={github.href}>GitHub</a>
</div>
</section>
<section class="content">
@@ -82,19 +82,95 @@ if (image) {
</ul>
</section>
<section class="alternatives">
<div class="col1">
<h3>npm</h3>
<button class="command" data-command="npm install -g opencode-ai">
<code>
<span>npm install -g</span> <span class="highlight">opencode-ai</span>
</code>
<span class="copy">
<CopyIcon />
<CheckIcon />
</span>
</button>
</div>
<div class="col2">
<h3>Bun</h3>
<button class="command" data-command="bun install -g opencode-ai">
<code>
<span>bun install -g</span> <span class="highlight">opencode-ai</span>
</code>
<span class="copy">
<CopyIcon />
<CheckIcon />
</span>
</button>
</div>
<div class="col3">
<h3>Homebrew</h3>
<button class="command" data-command="brew install sst/tap/opencode">
<code>
<span>brew install</span> <span class="highlight">sst/tap/opencode</span>
</code>
<span class="copy">
<CopyIcon />
<CheckIcon />
</span>
</button>
</div>
<div class="col4">
<h3>Paru</h3>
<button class="command" data-command="paru -S opencode-bin">
<code>
<span>paru -S</span> <span class="highlight">opencode-bin</span>
</code>
<span class="copy">
<CopyIcon />
<CheckIcon />
</span>
</button>
</div>
</section>
<section class="images">
<div>
<p>opencode TUI with the tokyonight theme</p>
<Image width={600} src={Screenshot} alt="opencode TUI with the tokyonight theme" />
<div class="left">
<figure>
<figcaption>opencode TUI with the tokyonight theme</figcaption>
<a href="/docs/cli">
<Image src={TuiScreenshot} alt="opencode TUI with the tokyonight theme" />
</a>
</figure>
</div>
<div class="right">
<div class="row1">
<figure>
<figcaption>opencode in VS Code</figcaption>
<a href="/docs/ide">
<Image src={VscodeScreenshot} alt="opencode in VS Code" />
</a>
</figure>
</div>
<div class="row2">
<figure>
<figcaption>opencode in GitHub</figcaption>
<a href="/docs/github">
<Image src={GithubScreenshot} alt="opencode in GitHub" />
</a>
</figure>
</div>
</div>
</section>
<section class="footer">
<div class="col1">
<span>Version: Beta</span>
<a href={github.href} target="_blank" rel="noopener noreferrer">GitHub</a>
</div>
<div class="col2">
<span>Author: <a href="https://sst.dev">SST</a></span>
<a href={discord.href} target="_blank" rel="noopener noreferrer">Discord</a>
</div>
<div class="col3">
<span>&copy;2025 <a href="https://anoma.ly" target="_blank" rel="noopener noreferrer">Anomaly Innovations</a></span>
</div>
</section>
</div>
@@ -102,7 +178,7 @@ if (image) {
<style>
.hero {
--padding: 3rem;
--vertical-padding: 2rem;
--vertical-padding: 1.5rem;
--heading-font-size: var(--sl-text-3xl);
margin: 1rem;
@@ -111,7 +187,7 @@ if (image) {
@media (max-width: 30rem) {
.hero {
--padding: 1rem;
--vertical-padding: 1rem;
--vertical-padding: 0.75rem;
--heading-font-size: var(--sl-text-2xl);
margin: 0.5rem;
@@ -143,31 +219,29 @@ section.cta {
@media (max-width: 50rem) {
flex-direction: column;
& > div.col1 { order: 1; }
& > div.col3 { order: 2; }
& > div.col2 { order: 3; }
}
& > div {
line-height: 1.4;
padding: calc(var(--padding) / 2) 1rem;
padding: var(--vertical-padding) var(--padding);
a {
font-size: 1rem;
}
}
& > div.col1, & > div.col3 {
flex: 1 1 auto;
& > div.col1 {
flex: 0 0 auto;
text-align: center;
text-transform: uppercase;
@media (max-width: 50rem) {
padding-bottom: calc(var(--padding) / 2 + 4px);
padding-bottom: calc(var(--vertical-padding) + 4px);
}
}
& > div.col2 {
flex: 0 0 auto;
flex: 1;
padding-left: 1rem;
padding-right: 1rem;
}
& > div + div {
@@ -261,13 +335,40 @@ section.content {
}
section.images {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: space-between;
--images-height: 600px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: var(--images-height);
border-top: 2px solid var(--sl-color-border);
& > div {
& > div.left {
display: flex;
grid-row: 1;
grid-column: 1;
}
& > div.right {
display: grid;
grid-template-rows: 1fr 1fr;
grid-row: 1;
grid-column: 2;
border-left: 2px solid var(--sl-color-border);
& > div.row1 {
display: flex;
grid-row: 1;
border-bottom: 2px solid var(--sl-color-border);
height: calc(var(--images-height) / 2);
}
& > div.row2 {
display: flex;
grid-row: 2;
height: calc(var(--images-height) / 2);
}
}
figure {
flex: 1;
display: flex;
flex-direction: column;
@@ -276,51 +377,237 @@ section.images {
border-width: 0;
border-style: solid;
border-color: var(--sl-color-border);
min-height: 0;
overflow: hidden;
& > div, p {
flex: 1;
& > div, figcaption {
display: flex;
align-items: center;
}
}
p {
letter-spacing: -0.03125rem;
text-transform: uppercase;
color: var(--sl-color-text-dimmed);
& > div {
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
}
@media (max-width: 30rem) {
font-size: 0.75rem;
a {
display: flex;
flex: 1;
min-height: 0;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
figcaption {
letter-spacing: -0.03125rem;
text-transform: uppercase;
color: var(--sl-color-text-dimmed);
flex-shrink: 0;
@media (max-width: 30rem) {
font-size: 0.75rem;
}
}
}
img {
align-self: center;
width: 100%;
max-width: 600px;
height: auto;
& > div.left figure {
height: var(--images-height);
box-sizing: border-box;
}
& > div + div {
border-width: 0 0 0 2px;
& > div.right figure {
height: calc(var(--images-height) / 2);
box-sizing: border-box;
}
& > div.left img {
width: 100%;
height: 100%;
min-width: 0;
object-fit: contain;
}
& > div.right img {
width: 100%;
height: calc(100% - 2rem);
object-fit: contain;
display: block;
}
@media (max-width: 30rem) {
& {
flex-direction: column;
--images-height: auto;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
}
& > div + div {
border-width: 2px 0 0 0;
& > div.left {
grid-row: 1;
grid-column: 1;
}
& > div.right {
grid-row: 2;
grid-column: 1;
border-left: none;
border-top: 2px solid var(--sl-color-border);
& > div.row1,
& > div.row2 {
height: auto;
}
}
& > div.left figure,
& > div.right figure {
height: auto;
}
& > div.left img,
& > div.right img {
width: 100%;
height: auto;
max-height: none;
}
}
}
section.approach {
section.alternatives {
border-top: 2px solid var(--sl-color-border);
padding: var(--padding);
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
p + p {
margin-top: var(--vertical-padding);
@media (max-width: 40rem) {
grid-template-columns: 1fr;
grid-template-rows: auto;
}
& > div {
display: flex;
flex-direction: column;
padding: calc(var(--vertical-padding) / 2) calc(var(--padding) / 2) calc(var(--vertical-padding) / 2 + 0.125rem);
text-align: left;
gap: 0.5rem;
@media (max-width: 40rem) {
text-align: left;
}
}
& > div.col1 {
border-bottom: 2px solid var(--sl-color-border);
@media (max-width: 40rem) {
border-bottom: none;
}
}
& > div.col2 {
border-left: 2px solid var(--sl-color-border);
border-bottom: 2px solid var(--sl-color-border);
@media (max-width: 40rem) {
border-left: none;
border-bottom: none;
border-top: 2px solid var(--sl-color-border);
}
}
& > div.col3 {
@media (max-width: 40rem) {
border-top: 2px solid var(--sl-color-border);
}
}
& > div.col4 {
border-left: 2px solid var(--sl-color-border);
@media (max-width: 40rem) {
border-left: none;
border-top: 2px solid var(--sl-color-border);
}
}
h3 {
letter-spacing: -0.03125rem;
text-transform: uppercase;
color: var(--sl-color-text-dimmed);
font-weight: normal;
font-size: 1rem;
flex-shrink: 0;
}
.command {
all: unset;
display: flex;
align-items: center;
gap: 0.625rem;
justify-content: flex-start;
cursor: pointer;
width: 100%;
@media (max-width: 40rem) {
justify-content: flex-start;
}
@media (max-width: 30rem) {
justify-content: center;
}
code {
color: var(--sl-color-text-secondary);
font-size: 1rem;
@media (max-width: 24rem) {
font-size: 0.875rem;
}
}
code .highlight {
color: var(--sl-color-text);
font-weight: 500;
}
.copy {
line-height: 1;
padding: 0;
@media (max-width: 40rem) {
display: none;
}
}
.copy svg {
width: 1rem;
height: 1rem;
vertical-align: middle;
}
.copy svg:first-child {
color: var(--sl-color-text-dimmed);
}
.copy svg:last-child {
color: var(--sl-color-text);
display: none;
}
&.success .copy {
pointer-events: none;
}
&.success .copy svg:first-child {
display: none;
}
&.success .copy svg:last-child {
display: inline;
}
}
}
@@ -333,12 +620,42 @@ section.footer {
flex: 1;
text-align: center;
text-transform: uppercase;
padding: calc(var(--padding) / 2) 0.5rem;
padding: var(--vertical-padding) 0.5rem;
}
& > div + div {
border-left: 2px solid var(--sl-color-border);
}
/* Below 800px: first two columns shrink to content, third expands */
@media (max-width: 50rem) {
& > div.col1,
& > div.col2 {
flex: 0 0 auto;
padding-left: calc(var(--padding) / 2);
padding-right: calc(var(--padding) / 2);
}
& > div.col3 {
flex: 1;
}
}
/* Mobile: third column on its own row */
@media (max-width: 30rem) {
flex-wrap: wrap;
& > div.col1,
& > div.col2 {
flex: 1;
}
& > div.col3 {
flex: 1 0 100%;
border-left: none;
border-top: 2px solid var(--sl-color-border);
}
}
}
</style>
@@ -361,13 +678,15 @@ section.footer {
</style>
<script>
const button = document.querySelector("button.command") as HTMLButtonElement;
const buttons = document.querySelectorAll("button.command") as NodeListOf<HTMLButtonElement>
button?.addEventListener("click", () => {
navigator.clipboard.writeText(button.dataset.command!);
button.classList.toggle("success");
setTimeout(() => {
button.classList.toggle("success");
}, 1500);
});
buttons.forEach(button => {
button.addEventListener("click", () => {
navigator.clipboard.writeText(button.dataset.command!)
button.classList.toggle("success")
setTimeout(() => {
button.classList.toggle("success");
}, 1500)
})
})
</script>
+21 -11
View File
@@ -95,6 +95,16 @@ Here are all available slash commands:
---
### Bash commands
Start a line with `!` to run a shell command. The output is added to the conversation as a tool result.
```bash frame="none"
!ls -la
```
---
### Editor setup
Both the `/editor` and `/export` commands use the editor specified in your `EDITOR` environment variable.
@@ -232,10 +242,10 @@ opencode github run
##### Flags
| Flag | Description |
| --------- | ------------------------------------------------ |
| `--event` | GitHub mock event to run the agent for |
| `--token` | GitHub personal access token |
| Flag | Description |
| --------- | -------------------------------------- |
| `--event` | GitHub mock event to run the agent for |
| `--token` | GitHub personal access token |
---
@@ -289,9 +299,9 @@ This starts an HTTP server that provides API access to opencode functionality wi
#### Flags
| Flag | Short | Description |
| ------------ | ----- | ------------------------------------------ |
| `--port` | `-p` | Port to listen on |
| Flag | Short | Description |
| ------------ | ----- | --------------------- |
| `--port` | `-p` | Port to listen on |
| `--hostname` | `-h` | Hostname to listen on |
---
@@ -318,8 +328,8 @@ opencode upgrade v0.1.48
#### Flags
| Flag | Short | Description |
| ---------- | ----- | --------------------------------------------------------- |
| Flag | Short | Description |
| ---------- | ----- | ----------------------------------------------------------------- |
| `--method` | `-m` | The installation method that was used; curl, npm, pnpm, bun, brew |
---
@@ -337,5 +347,5 @@ The opencode CLI takes the following global flags.
| `--prompt` | `-p` | Prompt to use |
| `--model` | `-m` | Model to use in the form of provider/model |
| `--mode` | | Mode to use |
| `--port` | | Port to listen on |
| `--hostname` | | Hostname to listen on |
| `--port` | | Port to listen on |
| `--hostname` | | Hostname to listen on |
@@ -64,6 +64,36 @@ If you've configured a [custom provider](/docs/providers#custom), the `provider_
---
## Configure models
You can globally configure a model's options through the config.
```jsonc title="opencode.jsonc" {7-11}
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"openai": {
"models": {
"gpt-5": {
"options": {
"reasoningEffort": "high",
"textVerbosity": "low",
"reasoningSummary": "auto",
"include": ["reasoning.encrypted_content"]
}
}
}
}
}
}
```
Here we are setting global options for the `gpt-5` model when used through the `openai` provider.
You can also configure these options for any agents that you are using. The agent config overrides any global options here. [Learn more](/docs/agents/#additional).
---
## Loading models
When opencode starts up, it checks for models in the following priority order:
@@ -93,13 +93,9 @@ Prevent opencode from reading `.env` files:
```javascript title=".opencode/plugin/env-protection.js"
export const EnvProtection = async ({ client, $ }) => {
return {
tool: {
execute: {
before: async (input, output) => {
if (input.tool === "read" && output.args.filePath.includes(".env")) {
throw new Error("Do not read .env files")
}
}
"tool.execute.before": async (input, output) => {
if (input.tool === "read" && output.args.filePath.includes(".env")) {
throw new Error("Do not read .env files")
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "0.4.42",
"version": "0.5.1",
"publisher": "sst-dev",
"repository": {
"type": "git",