Compare commits

..
22 Commits
Author SHA1 Message Date
opencode 0c48e6a116 release: v1.0.209
Update Nix Hashes / update (push) Has been cancelled
2025-12-29 23:37:04 +00:00
Adam ef266b2c74 fix(desktop): error page formatting 2025-12-29 17:33:59 -06:00
Aiden Cline 0a1cdc7a58 ci: use .env 2025-12-29 17:31:41 -06:00
Adam 2dec956a17 fix(desktop): better error messages 2025-12-29 17:29:56 -06:00
Aiden Cline ef8388f0ee Revert "feat: read global ~/.claude/skills"
This reverts commit a1c9a1b8c5.
2025-12-29 17:20:04 -06:00
opencode-agent[bot]opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>rekram1-nodeAiden Cline
e5c5b5e872 docs: add run from source guide (#6405)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2025-12-29 17:12:42 -06:00
Aiden Cline a1c9a1b8c5 feat: read global ~/.claude/skills 2025-12-29 17:11:28 -06:00
Aiden Cline 76b012139a fix: add timeout to filewatcher subscriptions 2025-12-29 16:16:38 -06:00
Aiden Cline 02e5a19242 tweak: adjust git watcher to ignore files other than HEAD 2025-12-29 16:16:38 -06:00
af967648cb docs: opencode notificator plugin (fixed link) (#6341)
Co-authored-by: Ivan Pantic <panta@talentkit.io>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-29 16:03:35 -06:00
Dominik Engelhardt 504a668a26 Set smallOptions for google models on openrouter (#6362) 2025-12-29 16:01:31 -06:00
ja 5efb1c7b2d feat(highlight): add nix syntax highlighting (#6386) 2025-12-29 15:53:41 -06:00
samcornorandSomair Ansar fd973d242e fix(webfetch): make format parameter optional with markdown default (#6345)
Co-authored-by: Somair Ansar <somairansar@Somairs-MacBook-Air.local>
2025-12-29 15:53:12 -06:00
ja c3d8672753 ci: use env vars for DRY workflow config (#6395) 2025-12-29 15:47:47 -06:00
Shoubhit Dash fe8ef041f6 add supermemory plugin to ecosystem (#6399) 2025-12-29 15:46:22 -06:00
Frank c841de947e zen: add gpt 5.1 codex mini 2025-12-29 16:44:11 -05:00
GitHub Action 825dfd48b1 chore: generate 2025-12-29 21:16:16 +00:00
Graham Bennett 923d114ffa Support different Nix store path prefixes (#6367) 2025-12-29 15:15:42 -06:00
Cole Leavitt b157fd10a7 fix: filter messages with only step-start parts in toModelMessage (#6383) 2025-12-29 14:58:11 -06:00
ja 67ebe68160 feat(format): add nixfmt formatter for Nix files (#6380) 2025-12-29 14:57:52 -06:00
GitHub Action 7b63c14154 chore: generate 2025-12-29 20:50:47 +00:00
Adam cdc11cde2e ignore: hide provider connect button until providers loaded 2025-12-29 14:50:06 -06:00
36 changed files with 280 additions and 125 deletions
+6 -3
View File
@@ -5,6 +5,9 @@ on:
- cron: "0 */12 * * *"
workflow_dispatch:
env:
LOOKBACK_HOURS: 4
jobs:
update-docs:
if: github.repository == 'sst/opencode'
@@ -25,9 +28,9 @@ jobs:
- name: Get recent commits
id: commits
run: |
COMMITS=$(git log --since="4 hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
COMMITS=$(git log --since="${{ env.LOOKBACK_HOURS }} hours ago" --pretty=format:"- %h %s" 2>/dev/null || echo "")
if [ -z "$COMMITS" ]; then
echo "No commits in the last 4 hours"
echo "No commits in the last ${{ env.LOOKBACK_HOURS }} hours"
echo "has_commits=false" >> $GITHUB_OUTPUT
else
echo "has_commits=true" >> $GITHUB_OUTPUT
@@ -47,7 +50,7 @@ jobs:
model: opencode/gpt-5.2
agent: docs
prompt: |
Review the following commits from the last 4 hours and identify any new features that may need documentation.
Review the following commits from the last ${{ env.LOOKBACK_HOURS }} hours and identify any new features that may need documentation.
<recent_commits>
${{ steps.commits.outputs.list }}
+9 -5
View File
@@ -5,6 +5,10 @@ on:
- cron: "30 1 * * *" # Daily at 1:30 AM
workflow_dispatch:
env:
DAYS_BEFORE_STALE: 90
DAYS_BEFORE_CLOSE: 7
jobs:
stale:
runs-on: ubuntu-latest
@@ -13,17 +17,17 @@ jobs:
steps:
- uses: actions/stale@v10
with:
days-before-stale: 90
days-before-close: 7
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
stale-issue-label: "stale"
close-issue-message: |
[automated] Closing due to 90+ days of inactivity.
[automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity.
Feel free to reopen if you still need this!
stale-issue-message: |
[automated] This issue has had no activity for 90 days.
[automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days.
It will be closed in 7 days if there's no new activity.
It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity.
remove-stale-when-updated: true
exempt-issue-labels: "pinned,security,feature-request,on-hold"
start-date: "2025-12-27"
+4
View File
@@ -20,3 +20,7 @@ opencode.json
a.out
target
.scripts
# Local dev files
opencode-dev
logs/
+30
View File
@@ -34,6 +34,36 @@ Want to take on an issue? Leave a comment and a maintainer may assign it to you
bun dev
```
### Running against a different directory
By default, `bun dev` runs OpenCode in the `packages/opencode` directory. To run it against a different directory or repository:
```bash
bun dev <directory>
```
To run OpenCode in the root of the opencode repo itself:
```bash
bun dev .
```
### Building a "localcode"
To compile a standalone executable:
```bash
./packages/opencode/script/build.ts --single
```
Then run it with:
```bash
./packages/opencode/dist/opencode-<platform>/bin/opencode
```
Replace `<platform>` with your platform (e.g., `darwin-arm64`, `linux-x64`).
- Core pieces:
- `packages/opencode`: OpenCode core business logic & server.
- `packages/opencode/src/cli/cmd/tui/`: The TUI code, written in SolidJS with [opentui](https://github.com/sst/opentui)
+15 -15
View File
@@ -22,7 +22,7 @@
},
"packages/app": {
"name": "@opencode-ai/app",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -70,7 +70,7 @@
},
"packages/console/app": {
"name": "@opencode-ai/console-app",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1",
@@ -98,7 +98,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -125,7 +125,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -149,7 +149,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -173,7 +173,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@opencode-ai/app": "workspace:*",
"@solid-primitives/storage": "catalog:",
@@ -200,7 +200,7 @@
},
"packages/enterprise": {
"name": "@opencode-ai/enterprise",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@opencode-ai/ui": "workspace:*",
"@opencode-ai/util": "workspace:*",
@@ -229,7 +229,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:",
@@ -245,7 +245,7 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.208",
"version": "1.0.209",
"bin": {
"opencode": "./bin/opencode",
},
@@ -347,7 +347,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -367,7 +367,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.208",
"version": "1.0.209",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.1",
"@tsconfig/node22": "catalog:",
@@ -378,7 +378,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -391,7 +391,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -429,7 +429,7 @@
},
"packages/util": {
"name": "@opencode-ai/util",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"zod": "catalog:",
},
@@ -440,7 +440,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
+7 -3
View File
@@ -31,9 +31,13 @@ for (const [name, wasmPath] of byName) {
next = next.replaceAll("tree-sitter.wasm", mainWasm).replaceAll("web-tree-sitter/tree-sitter.wasm", mainWasm)
// Collapse any relative prefixes before absolute store paths (e.g., "../../../..//nix/store/...")
const nixStorePrefix = process.env.NIX_STORE || "/nix/store"
next = next.replace(/(\.\/)+/g, "./")
next = next.replace(/(\.\.\/)+\/?(\/nix\/store[^"']+)/g, "/$2")
next = next.replace(/(["'])\/{2,}(\/nix\/store[^"']+)(["'])/g, "$1/$2$3")
next = next.replace(/(["'])\/\/(nix\/store[^"']+)(["'])/g, "$1/$2$3")
next = next.replace(
new RegExp(`(\\.\\.\\/)+\\/{1,2}(${nixStorePrefix.replace(/^\//, "").replace(/\//g, "\\/")}[^"']+)`, "g"),
"/$2",
)
next = next.replace(new RegExp(`(["'])\\/{2,}(\\/${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3")
next = next.replace(new RegExp(`(["'])\\/\\/(${nixStorePrefix.replace(/\//g, "\\/")}[^"']+)(["'])`, "g"), "$1$2$3")
if (next !== content) fs.writeFileSync(file, next)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/app",
"version": "1.0.208",
"version": "1.0.209",
"description": "",
"type": "module",
"exports": {
+83 -20
View File
@@ -20,11 +20,51 @@ function isInitError(error: unknown): error is InitError {
)
}
function safeJson(value: unknown): string {
const seen = new WeakSet<object>()
const json = JSON.stringify(
value,
(_key, val) => {
if (typeof val === "bigint") return val.toString()
if (typeof val === "object" && val) {
if (seen.has(val)) return "[Circular]"
seen.add(val)
}
return val
},
2,
)
return json ?? String(value)
}
function formatInitError(error: InitError): string {
const data = error.data
switch (error.name) {
case "MCPFailed":
return `MCP server "${data.name}" failed. Note, opencode does not support MCP authentication yet.`
case "ProviderAuthError": {
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
const message = typeof data.message === "string" ? data.message : safeJson(data.message)
return `Provider authentication failed (${providerID}): ${message}`
}
case "APIError": {
const message = typeof data.message === "string" ? data.message : "API error"
const lines: string[] = [message]
if (typeof data.statusCode === "number") {
lines.push(`Status: ${data.statusCode}`)
}
if (typeof data.isRetryable === "boolean") {
lines.push(`Retryable: ${data.isRetryable}`)
}
if (typeof data.responseBody === "string" && data.responseBody) {
lines.push(`Response body:\n${data.responseBody}`)
}
return lines.join("\n")
}
case "ProviderModelNotFoundError": {
const { providerID, modelID, suggestions } = data as {
providerID: string
@@ -37,10 +77,14 @@ function formatInitError(error: InitError): string {
`Check your config (opencode.json) provider/model names`,
].join("\n")
}
case "ProviderInitError":
return `Failed to initialize provider "${data.providerID}". Check credentials and configuration.`
case "ConfigJsonError":
return `Config file at ${data.path} is not valid JSON(C)` + (data.message ? `: ${data.message}` : "")
case "ProviderInitError": {
const providerID = typeof data.providerID === "string" ? data.providerID : "unknown"
return `Failed to initialize provider "${providerID}". Check credentials and configuration.`
}
case "ConfigJsonError": {
const message = typeof data.message === "string" ? data.message : ""
return `Config file at ${data.path} is not valid JSON(C)` + (message ? `: ${message}` : "")
}
case "ConfigDirectoryTypoError":
return `Directory "${data.dir}" in ${data.path} is not valid. Rename the directory to "${data.suggestion}" or remove it. This is a common typo.`
case "ConfigFrontmatterError":
@@ -51,14 +95,14 @@ function formatInitError(error: InitError): string {
(issue: { message: string; path: string[] }) => "↳ " + issue.message + " " + issue.path.join("."),
)
: []
return [`Config file at ${data.path} is invalid` + (data.message ? `: ${data.message}` : ""), ...issues].join(
"\n",
)
const message = typeof data.message === "string" ? data.message : ""
return [`Config file at ${data.path} is invalid` + (message ? `: ${message}` : ""), ...issues].join("\n")
}
case "UnknownError":
return String(data.message)
return typeof data.message === "string" ? data.message : safeJson(data)
default:
return data.message ? String(data.message) : JSON.stringify(data, null, 2)
if (typeof data.message === "string") return data.message
return safeJson(data)
}
}
@@ -69,7 +113,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
const message = formatInitError(error)
if (depth > 0 && parentMessage === message) return ""
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
return indent + message
return indent + `${error.name}\n${message}`
}
if (error instanceof Error) {
@@ -77,15 +121,34 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
const parts: string[] = []
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
if (!isDuplicate) {
// Stack already includes error name and message, so prefer it
parts.push(indent + (error.stack ?? `${error.name}: ${error.message}`))
} else if (error.stack) {
// Duplicate message - only show the stack trace lines (skip message)
const trace = error.stack.split("\n").slice(1).join("\n").trim()
if (trace) {
parts.push(trace)
const header = `${error.name}${error.message ? `: ${error.message}` : ""}`
const stack = error.stack?.trim()
if (stack) {
const startsWithHeader = stack.startsWith(header)
if (isDuplicate && startsWithHeader) {
const trace = stack.split("\n").slice(1).join("\n").trim()
if (trace) {
parts.push(indent + trace)
}
}
if (isDuplicate && !startsWithHeader) {
parts.push(indent + stack)
}
if (!isDuplicate && startsWithHeader) {
parts.push(indent + stack)
}
if (!isDuplicate && !startsWithHeader) {
parts.push(indent + `${header}\n${stack}`)
}
}
if (!stack && !isDuplicate) {
parts.push(indent + header)
}
if (error.cause) {
@@ -105,7 +168,7 @@ function formatErrorChain(error: unknown, depth = 0, parentMessage?: string): st
}
const indent = depth > 0 ? `\n${"─".repeat(40)}\nCaused by:\n` : ""
return indent + JSON.stringify(error, null, 2)
return indent + safeJson(error)
}
function formatError(error: unknown): string {
@@ -131,7 +194,7 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
readOnly
copyable
multiline
class="max-h-96 w-full font-mono text-xs no-scrollbar whitespace-pre"
class="max-h-96 w-full font-mono text-xs no-scrollbar"
label="Error Details"
hideLabel
/>
+2 -2
View File
@@ -916,7 +916,7 @@ export default function Layout(props: ParentProps) {
</div>
<div class="flex flex-col gap-1.5 self-stretch items-start shrink-0 px-2 py-3">
<Switch>
<Match when={!providers.paid().length && expanded()}>
<Match when={providers.all().length > 0 && !providers.paid().length && expanded()}>
<div class="rounded-md bg-background-stronger shadow-xs-border-base">
<div class="p-3 flex flex-col gap-2">
<div class="text-12-medium text-text-strong">Getting started</div>
@@ -935,7 +935,7 @@ export default function Layout(props: ParentProps) {
</Tooltip>
</div>
</Match>
<Match when={true}>
<Match when={providers.all().length > 0}>
<Tooltip placement="right" value="Connect provider" inactive={expanded()}>
<Button
class="flex w-full text-left justify-start text-text-base stroke-[1.5px] rounded-lg px-2"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-app",
"version": "1.0.208",
"version": "1.0.209",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.208",
"version": "1.0.209",
"private": true,
"type": "module",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.208",
"version": "1.0.209",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.208",
"version": "1.0.209",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/desktop",
"private": true,
"version": "1.0.208",
"version": "1.0.209",
"type": "module",
"scripts": {
"typecheck": "tsgo -b",
+4
View File
@@ -10,6 +10,10 @@ export default defineConfig({
//
// 1. prevent Vite from obscuring rust errors
clearScreen: false,
esbuild: {
// Improves production stack traces (less "kQ@..." noise)
keepNames: true,
},
build: {
sourcemap: true,
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/enterprise",
"version": "1.0.208",
"version": "1.0.209",
"private": true,
"type": "module",
"scripts": {
+6 -6
View File
@@ -1,7 +1,7 @@
id = "opencode"
name = "OpenCode"
description = "The open source coding agent."
version = "1.0.208"
version = "1.0.209"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/sst/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-darwin-arm64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.209/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-darwin-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.209/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-linux-arm64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.209/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-linux-x64.tar.gz"
archive = "https://github.com/sst/opencode/releases/download/v1.0.209/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/sst/opencode/releases/download/v1.0.208/opencode-windows-x64.zip"
archive = "https://github.com/sst/opencode/releases/download/v1.0.209/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/function",
"version": "1.0.208",
"version": "1.0.209",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.0.208",
"version": "1.0.209",
"name": "opencode",
"type": "module",
"private": true,
+14
View File
@@ -235,5 +235,19 @@ export default {
],
},
},
{
filetype: "nix",
// TODO: Replace with official tree-sitter-nix WASM when published
// See: https://github.com/nix-community/tree-sitter-nix/issues/66
wasm: "https://github.com/ast-grep/ast-grep.github.io/raw/40b84530640aa83a0d34a20a2b0623d7b8e5ea97/website/public/parsers/tree-sitter-nix.wasm",
queries: {
highlights: [
"https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/nix/highlights.scm",
],
locals: [
"https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/nix/locals.scm",
],
},
},
],
}
+26 -12
View File
@@ -9,9 +9,13 @@ import path from "path"
// @ts-ignore
import { createWrapper } from "@parcel/watcher/wrapper"
import { lazy } from "@/util/lazy"
import { withTimeout } from "@/util/timeout"
import type ParcelWatcher from "@parcel/watcher"
import { $ } from "bun"
import { Flag } from "@/flag/flag"
import { readdir } from "fs/promises"
const SUBSCRIBE_TIMEOUT_MS = 10_000
declare const OPENCODE_LIBC: string | undefined
@@ -63,12 +67,16 @@ export namespace FileWatcher {
const cfgIgnores = cfg.watcher?.ignore ?? []
if (Flag.OPENCODE_EXPERIMENTAL_FILEWATCHER) {
subs.push(
await watcher().subscribe(Instance.directory, subscribe, {
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
backend,
}),
)
const pending = watcher().subscribe(Instance.directory, subscribe, {
ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
backend,
})
const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => {
log.error("failed to subscribe to Instance.directory", { error: err })
pending.then((s) => s.unsubscribe()).catch(() => {})
return undefined
})
if (sub) subs.push(sub)
}
const vcsDir = await $`git rev-parse --git-dir`
@@ -78,12 +86,18 @@ export namespace FileWatcher {
.text()
.then((x) => path.resolve(Instance.worktree, x.trim()))
if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
subs.push(
await watcher().subscribe(vcsDir, subscribe, {
ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"],
backend,
}),
)
const gitDirContents = await readdir(vcsDir).catch(() => [])
const ignoreList = gitDirContents.filter((entry) => entry !== "HEAD")
const pending = watcher().subscribe(vcsDir, subscribe, {
ignore: ignoreList,
backend,
})
const sub = await withTimeout(pending, SUBSCRIBE_TIMEOUT_MS).catch((err) => {
log.error("failed to subscribe to vcsDir", { error: err })
pending.then((s) => s.unsubscribe()).catch(() => {})
return undefined
})
if (sub) subs.push(sub)
}
return { subs }
@@ -322,3 +322,12 @@ export const shfmt: Info = {
return Bun.which("shfmt") !== null
},
}
export const nixfmt: Info = {
name: "nixfmt",
command: ["nixfmt", "$FILE"],
extensions: [".nix"],
async enabled() {
return Bun.which("nixfmt") !== null
},
}
+10 -10
View File
@@ -302,22 +302,22 @@ export namespace ProviderTransform {
}
export function smallOptions(model: Provider.Model) {
const options: Record<string, any> = {}
if (model.providerID === "openai" || model.api.id.includes("gpt-5")) {
if (model.api.id.includes("5.")) {
options["reasoningEffort"] = "low"
} else {
options["reasoningEffort"] = "minimal"
return { reasoningEffort: "low" }
}
return { reasoningEffort: "minimal" }
}
if (model.providerID === "google") {
options["thinkingConfig"] = {
thinkingBudget: 0,
}
return { thinkingConfig: { thinkingBudget: 0 } }
}
return options
if (model.providerID === "openrouter") {
if (model.api.id.includes("google")) {
return { reasoning: { enabled: false } }
}
return { reasoningEffort: "minimal" }
}
return {}
}
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
+1 -1
View File
@@ -539,7 +539,7 @@ export namespace MessageV2 {
}
}
return convertToModelMessages(result.filter((msg) => msg.parts.length > 0))
return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")))
}
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
+2 -1
View File
@@ -15,7 +15,8 @@ export const WebFetchTool = Tool.define("webfetch", {
url: z.string().describe("The URL to fetch content from"),
format: z
.enum(["text", "markdown", "html"])
.describe("The format to return the content in (text, markdown, or html)"),
.default("markdown")
.describe("The format to return the content in (text, markdown, or html). Defaults to markdown."),
timeout: z.number().describe("Optional timeout in seconds (max 120)").optional(),
}),
async execute(params, ctx) {
+4 -4
View File
@@ -1,13 +1,13 @@
- Fetches content from a specified URL
- Takes a URL and a prompt as input
- Fetches the URL content, converts HTML to markdown
- Returns the model's response about the content
- Takes a URL and optional format as input
- Fetches the URL content, converts to requested format (markdown by default)
- Returns the content in the specified format
- Use this tool when you need to retrieve and analyze web content
Usage notes:
- IMPORTANT: if another tool is present that offers better web fetching capabilities, is more targeted to the task, or has fewer restrictions, prefer using that tool instead of this one.
- The URL must be a fully-formed valid URL
- HTTP URLs will be automatically upgraded to HTTPS
- The prompt should describe what information you want to extract from the page
- Format options: "markdown" (default), "text", or "html"
- This tool is read-only and does not modify any files
- Results may be summarized if the content is very large
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin",
"version": "1.0.208",
"version": "1.0.209",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk",
"version": "1.0.208",
"version": "1.0.209",
"type": "module",
"scripts": {
"typecheck": "tsgo --noEmit",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/slack",
"version": "1.0.208",
"version": "1.0.209",
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/ui",
"version": "1.0.208",
"version": "1.0.209",
"type": "module",
"exports": {
"./*": "./src/components/*.tsx",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/util",
"version": "1.0.208",
"version": "1.0.209",
"private": true,
"type": "module",
"exports": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@opencode-ai/web",
"type": "module",
"version": "1.0.208",
"version": "1.0.209",
"scripts": {
"dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+2 -1
View File
@@ -32,10 +32,11 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw
| [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs |
| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers |
| [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible |
| [opencode-notificator](https://github.com/panta/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions |
| [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions |
| [opencode-notifier](https://github.com/mohak34/opencode-notifier) | Desktop notifications and sound alerts for permission, completion, and error events |
| [opencode-zellij-namer](https://github.com/24601/opencode-zellij-namer) | AI-powered automatic Zellij session naming based on OpenCode context |
| [opencode-skillful](https://github.com/zenobi-us/opencode-skillful) | Allow OpenCode agents to lazy load prompts on demand with skill discovery and injection |
| [opencode-supermemory](https://github.com/supermemoryai/opencode-supermemory) | Persistent memory across sessions using Supermemory |
---
+27 -25
View File
@@ -62,31 +62,32 @@ You are charged per request and you can add credits to your account.
You can also access our models through the following API endpoints.
| Model | Model ID | Endpoint | AI SDK Package |
| ----------------- | ----------------- | -------------------------------------------------- | --------------------------- |
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| MiniMax M2.1 | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.7 | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Grok Code Fast 1 | grok-code | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Model | Model ID | Endpoint | AI SDK Package |
| ------------------ | ------------------ | -------------------------------------------------- | --------------------------- |
| GPT 5.2 | gpt-5.2 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 | gpt-5.1 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 Codex | gpt-5.1-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 Codex Max | gpt-5.1-codex-max | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5.1 Codex Mini | gpt-5.1-codex-mini | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 | gpt-5 | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 Codex | gpt-5-codex | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| GPT 5 Nano | gpt-5-nano | `https://opencode.ai/zen/v1/responses` | `@ai-sdk/openai` |
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Sonnet 4 | claude-sonnet-4 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Haiku 3.5 | claude-3-5-haiku | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Opus 4.5 | claude-opus-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Claude Opus 4.1 | claude-opus-4-1 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| MiniMax M2.1 | minimax-m2.1-free | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
| Gemini 3 Pro | gemini-3-pro | `https://opencode.ai/zen/v1/models/gemini-3-pro` | `@ai-sdk/google` |
| Gemini 3 Flash | gemini-3-flash | `https://opencode.ai/zen/v1/models/gemini-3-flash` | `@ai-sdk/google` |
| GLM 4.6 | glm-4.6 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| GLM 4.7 | glm-4.7-free | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2 | kimi-k2 | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2 Thinking | kimi-k2-thinking | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Qwen3 Coder 480B | qwen3-coder | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Grok Code Fast 1 | grok-code | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Big Pickle | big-pickle | `https://opencode.ai/zen/v1/chat/completions` | `@ai-sdk/openai-compatible` |
The [model id](/docs/config/#models) in your OpenCode config
uses the format `opencode/<model-id>`. For example, for GPT 5.1 Codex, you would
@@ -133,6 +134,7 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
| GPT 5.1 | $1.07 | $8.50 | $0.107 | - |
| GPT 5.1 Codex | $1.07 | $8.50 | $0.107 | - |
| GPT 5.1 Codex Max | $1.25 | $10.00 | $0.125 | - |
| GPT 5.1 Codex Mini | $0.25 | $2.00 | $0.025 | - |
| GPT 5 | $1.07 | $8.50 | $0.107 | - |
| GPT 5 Codex | $1.07 | $8.50 | $0.107 | - |
| GPT 5 Nano | Free | Free | Free | - |
+3 -1
View File
@@ -107,7 +107,9 @@ async function main() {
console.log(`📬 Creating pull request...`)
const prUrl =
await $`GH_TOKEN=${token} gh pr create --repo ${UPSTREAM_REPO} --base main --head ${FORK_REPO.split("/")[0]}:${branchName} --title "Update ${EXTENSION_NAME} to v${cleanVersion}" --body "Updating OpenCode extension to v${cleanVersion}"`.text()
await $`gh pr create --repo ${UPSTREAM_REPO} --base main --head ${FORK_REPO.split("/")[0]}:${branchName} --title "Update ${EXTENSION_NAME} to v${cleanVersion}" --body "Updating OpenCode extension to v${cleanVersion}"`
.env({ GH_TOKEN: token })
.text()
console.log(`✅ Pull request created: ${prUrl}`)
console.log(`🎉 Done!`)
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "1.0.208",
"version": "1.0.209",
"publisher": "sst-dev",
"repository": {
"type": "git",