diff --git a/.github/workflows/update-nix-hashes.yml b/.github/workflows/update-nix-hashes.yml index 311874d82..2f0aeac20 100644 --- a/.github/workflows/update-nix-hashes.yml +++ b/.github/workflows/update-nix-hashes.yml @@ -37,6 +37,11 @@ jobs: git config --global user.email "action@github.com" git config --global user.name "Github Action" + - name: Update flake.lock + run: | + set -euo pipefail + nix flake update + - name: Update node_modules hash run: | set -euo pipefail @@ -62,7 +67,7 @@ jobs: echo "" >> "$GITHUB_STEP_SUMMARY" } - FILES=(flake.nix nix/node-modules.nix nix/hashes.json) + FILES=(flake.lock flake.nix nix/node-modules.nix nix/hashes.json) STATUS="$(git status --short -- "${FILES[@]}" || true)" if [ -z "$STATUS" ]; then summarize "no changes" @@ -71,7 +76,7 @@ jobs: fi git add "${FILES[@]}" - git commit -m "Update Nix hashes" + git commit -m "Update Nix flake.lock and hashes" BRANCH="${TARGET_BRANCH:-${GITHUB_REF_NAME}}" git push origin HEAD:"$BRANCH" diff --git a/.gitignore b/.gitignore index 91263f8c6..62cb12717 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ dist refs Session.vim opencode.json +a.out diff --git a/STATS.md b/STATS.md index 9aec383d2..8e34cb4cf 100644 --- a/STATS.md +++ b/STATS.md @@ -145,3 +145,4 @@ | 2025-11-17 | 780,161 (+9,092) | 723,339 (+6,743) | 1,503,500 (+15,835) | | 2025-11-18 | 791,563 (+11,402) | 732,544 (+9,205) | 1,524,107 (+20,607) | | 2025-11-19 | 804,409 (+12,846) | 747,624 (+15,080) | 1,552,033 (+27,926) | +| 2025-11-20 | 814,620 (+10,211) | 757,907 (+10,283) | 1,572,527 (+20,494) | diff --git a/a.out b/a.out deleted file mode 100644 index e69de29bb..000000000 diff --git a/bun.lock b/bun.lock index 7d6907a72..f8ca5f00d 100644 --- a/bun.lock +++ b/bun.lock @@ -41,7 +41,7 @@ }, "packages/console/core": { "name": "@opencode-ai/console-core", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "@jsx-email/render": "1.1.1", @@ -68,7 +68,7 @@ }, "packages/console/function": { "name": "@opencode-ai/console-function", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -92,7 +92,7 @@ }, "packages/console/mail": { "name": "@opencode-ai/console-mail", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", @@ -116,7 +116,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -156,7 +156,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -172,7 +172,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.80", + "version": "1.0.81", "bin": { "opencode": "./bin/opencode", }, @@ -251,7 +251,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -271,7 +271,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.80", + "version": "1.0.81", "devDependencies": { "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", @@ -282,7 +282,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -295,7 +295,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -325,7 +325,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "zod": "catalog:", }, @@ -335,7 +335,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/flake.lock b/flake.lock index c9a945db5..cdab71ce7 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1762156382, - "narHash": "sha256-Yg7Ag7ov5+36jEFC1DaZh/12SEXo6OO3/8rqADRxiqs=", + "lastModified": 1763464769, + "narHash": "sha256-AJHrsT7VoeQzErpBRlLJM1SODcaayp0joAoEA35yiwM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7241bcbb4f099a66aafca120d37c65e8dda32717", + "rev": "6f374686605df381de8541c072038472a5ea2e2d", "type": "github" }, "original": { diff --git a/nix/hashes.json b/nix/hashes.json index 1712bcc58..1a68f55ae 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-xqiDrKpODha+cfU6UpXLEUcApZ1xEkjRpqzFVJmq1uA=" + "nodeModules": "sha256-bPiUpHGtgwVxHQHXBprpc6fFeJqW6/x7dwtQZBq29oU=" } diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 2f38bc55a..cb25d839a 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -7,7 +7,7 @@ "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev", "build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json", "start": "vinxi start", - "version": "1.0.80" + "version": "1.0.81" }, "dependencies": { "@ibm/plex": "6.4.1", diff --git a/packages/console/app/src/component/icon.tsx b/packages/console/app/src/component/icon.tsx index 7bf203638..55f0940a0 100644 --- a/packages/console/app/src/component/icon.tsx +++ b/packages/console/app/src/component/icon.tsx @@ -236,3 +236,14 @@ export function IconChevronRight(props: JSX.SvgSVGAttributes) { ) } + +export function IconBreakdown(props: JSX.SvgSVGAttributes) { + return ( + + + + + + + ) +} diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.module.css b/packages/console/app/src/routes/workspace/[id]/usage-section.module.css index f11e00b21..83c783a2f 100644 --- a/packages/console/app/src/routes/workspace/[id]/usage-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/usage-section.module.css @@ -56,6 +56,53 @@ color: var(--color-text); font-weight: 500; } + + [data-slot="tokens-with-breakdown"] { + position: relative; + display: flex; + align-items: center; + gap: var(--space-2); + } + + [data-slot="breakdown-button"] { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + background: transparent; + border: none; + color: var(--color-text-muted); + cursor: pointer; + transition: color 0.15s ease; + + &:hover { + color: var(--color-text); + } + + svg { + width: 16px; + height: 16px; + } + } + + [data-slot="breakdown-popup"] { + position: absolute; + left: 0; + top: 100%; + margin-top: var(--space-2); + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + padding: var(--space-2); + z-index: 10; + min-width: 180px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + font-size: var(--font-size-xs); + + @media (prefers-color-scheme: dark) { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + } + } } tbody tr:last-child td { @@ -116,4 +163,24 @@ } } } + + /* Breakdown popup content */ + [data-slot="breakdown-row"] { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--space-4); + padding: var(--space-1) 0; + } + + [data-slot="breakdown-label"] { + color: var(--color-text-muted); + font-size: var(--font-size-xs); + } + + [data-slot="breakdown-value"] { + color: var(--color-text); + font-weight: 500; + font-size: var(--font-size-xs); + } } diff --git a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx b/packages/console/app/src/routes/workspace/[id]/usage-section.tsx index d9aa7251b..212904f3f 100644 --- a/packages/console/app/src/routes/workspace/[id]/usage-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/usage-section.tsx @@ -1,9 +1,9 @@ import { Billing } from "@opencode-ai/console-core/billing.js" import { createAsync, query, useParams } from "@solidjs/router" -import { createMemo, For, Show, createEffect } from "solid-js" +import { createMemo, For, Show, createEffect, createSignal } from "solid-js" import { formatDateUTC, formatDateForTable } from "../common" import { withActor } from "~/context/auth.withActor" -import { IconChevronLeft, IconChevronRight } from "~/component/icon" +import { IconChevronLeft, IconChevronRight, IconBreakdown } from "~/component/icon" import styles from "./usage-section.module.css" import { createStore } from "solid-js/store" @@ -22,15 +22,34 @@ export function UsageSection() { const params = useParams() const usage = createAsync(() => queryUsageInfo(params.id!, 0)) const [store, setStore] = createStore({ page: 0, usage: [] as Awaited> }) + const [openBreakdownId, setOpenBreakdownId] = createSignal(null) createEffect(() => { setStore({ usage: usage() }) }, [usage]) + createEffect(() => { + if (!openBreakdownId()) return + + const handleClickOutside = (e: MouseEvent) => { + const target = e.target as HTMLElement + if (!target.closest('[data-slot="tokens-with-breakdown"]')) { + setOpenBreakdownId(null) + } + } + + document.addEventListener("click", handleClickOutside) + return () => document.removeEventListener("click", handleClickOutside) + }) + const hasResults = createMemo(() => store.usage && store.usage.length > 0) const canGoPrev = createMemo(() => store.page > 0) const canGoNext = createMemo(() => store.usage && store.usage.length === PAGE_SIZE) + const calculateTotalInputTokens = (u: Awaited>[0]) => { + return u.inputTokens + (u.cacheReadTokens ?? 0) + (u.cacheWrite5mTokens ?? 0) + (u.cacheWrite1hTokens ?? 0) + } + const goPrev = async () => { const usage = await getUsageInfo(params.id!, store.page - 1) setStore({ @@ -73,15 +92,50 @@ export function UsageSection() { - {(usage) => { + {(usage, index) => { const date = createMemo(() => new Date(usage.timeCreated)) + const totalInputTokens = createMemo(() => calculateTotalInputTokens(usage)) + const breakdownId = `breakdown-${index()}` + const isOpen = createMemo(() => openBreakdownId() === breakdownId) + const isClaude = usage.model.toLowerCase().includes("claude") return ( {formatDateForTable(date())} {usage.model} - {usage.inputTokens} + +
e.stopPropagation()}> + + setOpenBreakdownId(null)}>{totalInputTokens()} + +
e.stopPropagation()}> +
+ Input + {usage.inputTokens} +
+
+ Cache Read + {usage.cacheReadTokens ?? 0} +
+ +
+ Cache Write + {usage.cacheWrite5mTokens ?? 0} +
+
+
+
+
+ {usage.outputTokens} ${((usage.cost ?? 0) / 100000000).toFixed(4)} diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 73739f2af..110f24f34 100644 --- a/packages/console/core/package.json +++ b/packages/console/core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/console-core", - "version": "1.0.80", + "version": "1.0.81", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 8c75791a2..b0f721053 100644 --- a/packages/console/function/package.json +++ b/packages/console/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-function", - "version": "1.0.80", + "version": "1.0.81", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/console/mail/package.json b/packages/console/mail/package.json index da6bc9995..32df87e0a 100644 --- a/packages/console/mail/package.json +++ b/packages/console/mail/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-mail", - "version": "1.0.80", + "version": "1.0.81", "dependencies": { "@jsx-email/all": "2.2.3", "@jsx-email/cli": "1.4.3", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 5f8f14583..253245277 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/desktop", - "version": "1.0.80", + "version": "1.0.81", "description": "", "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 358982385..503b99519 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The AI coding agent built for the terminal" -version = "1.0.80" +version = "1.0.81" 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.80/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.81/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.80/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.81/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.80/opencode-linux-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.81/opencode-linux-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.80/opencode-linux-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.81/opencode-linux-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.80/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.81/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index 5976e6767..d58ea138d 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.80", + "version": "1.0.81", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 10678ee79..583182db9 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "1.0.80", + "version": "1.0.81", "name": "opencode", "type": "module", "private": true, diff --git a/packages/opencode/script/publish.ts b/packages/opencode/script/publish.ts index 5277744ad..1cc07d389 100755 --- a/packages/opencode/script/publish.ts +++ b/packages/opencode/script/publish.ts @@ -132,10 +132,10 @@ if (!Script.preview) { "package() {", ` cd "opencode-\${pkgver}/packages/opencode"`, ' mkdir -p "${pkgdir}/usr/bin"', - ' arch="x64"', + ' target_arch="x64"', ' case "$CARCH" in', - ' x86_64) arch="x64" ;;', - ' aarch64) arch="arm64" ;;', + ' x86_64) target_arch="x64" ;;', + ' aarch64) target_arch="arm64" ;;', ' *) printf "unsupported architecture: %s\\n" "$CARCH" >&2 ; return 1 ;;', " esac", ' libc=""', @@ -148,14 +148,14 @@ if (!Script.preview) { ' libc="-musl"', " fi", ' base=""', - ' if [ "$arch" = "x64" ]; then', + ' if [ "$target_arch" = "x64" ]; then', " if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then", ' base="-baseline"', " fi", " fi", - ' bin="dist/opencode-linux-${arch}${base}${libc}/bin/opencode"', + ' bin="dist/opencode-linux-${target_arch}${base}${libc}/bin/opencode"', ' if [ ! -f "$bin" ]; then', - ' printf "unable to find binary for %s%s%s\\n" "$arch" "$base" "$libc" >&2', + ' printf "unable to find binary for %s%s%s\\n" "$target_arch" "$base" "$libc" >&2', " return 1", " fi", ' install -Dm755 "$bin" "${pkgdir}/usr/bin/opencode"', diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index b9b27bb1e..8cf81f1d5 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -53,15 +53,7 @@ export function Autocomplete(props: { // Track props.value to make memo reactive to text changes props.value // <- there surely is a better way to do this, like making .input() reactive - const val = props.input().getTextRange(store.index + 1, props.input().cursorOffset + 1) - - // If the filter contains a space, hide the autocomplete - if (val.includes(" ")) { - hide() - return undefined - } - - return val + return props.input().getTextRange(store.index + 1, props.input().cursorOffset) }) function insertPart(text: string, part: PromptInfo["parts"][number]) { @@ -132,9 +124,10 @@ export function Autocomplete(props: { (item): AutocompleteOption => ({ display: Locale.truncateMiddle(item, width), onSelect: () => { + const mime = Bun.file(item).type || "text/plain" insertPart(item, { type: "file", - mime: "text/plain", + mime, filename: item, url: `file://${process.cwd()}/${item}`, source: { @@ -387,17 +380,19 @@ export function Autocomplete(props: { get visible() { return store.visible }, - onInput() { + onInput(value) { if (store.visible) { - if (props.input().cursorOffset <= store.index) { + if ( + // Typed text before the trigger + props.input().cursorOffset <= store.index || + // There is a space between the trigger and the cursor + props.input().getTextRange(store.index, props.input().cursorOffset).match(/\s/) || + // "/" is not the sole content + (store.visible === "/" && value.match(/^\S+\s+\S+\s*$/)) + ) { hide() return } - // Check if a space was typed after the trigger character - const currentText = props.input().getTextRange(store.index + 1, props.input().cursorOffset + 1) - if (currentText.includes(" ")) { - hide() - } } }, onKeyDown(e: KeyEvent) { diff --git a/packages/opencode/src/cli/cmd/tui/util/editor.ts b/packages/opencode/src/cli/cmd/tui/util/editor.ts index 0aa69dcd8..f98e24b06 100644 --- a/packages/opencode/src/cli/cmd/tui/util/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/util/editor.ts @@ -6,7 +6,7 @@ import { CliRenderer } from "@opentui/core" export namespace Editor { export async function open(opts: { value: string; renderer: CliRenderer }): Promise { - const editor = process.env["EDITOR"] + const editor = process.env["VISUAL"] || process.env["EDITOR"] if (!editor) return const filepath = join(tmpdir(), `${Date.now()}.md`) diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index c40a910e0..f5b402e36 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -428,8 +428,8 @@ export namespace Config { input_newline: z.string().optional().default("shift+return,ctrl+j").describe("Insert newline in input"), history_previous: z.string().optional().default("up").describe("Previous history item"), history_next: z.string().optional().default("down").describe("Next history item"), - session_child_cycle: z.string().optional().default("ctrl+right").describe("Next child session"), - session_child_cycle_reverse: z.string().optional().default("ctrl+left").describe("Previous child session"), + session_child_cycle: z.string().optional().default("right").describe("Next child session"), + session_child_cycle_reverse: z.string().optional().default("left").describe("Previous child session"), }) .strict() .meta({ diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index b2b9be0ec..cdebad4bd 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -11,6 +11,8 @@ export namespace Flag { export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS") export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT") export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"] + export const OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH = + process.env["OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH"] // Experimental export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL") diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index efecd0879..79a2a408b 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -250,12 +250,12 @@ export namespace LSPServer { }, } - export const RubyLsp: Info = { + export const Rubocop: Info = { id: "ruby-lsp", root: NearestRoot(["Gemfile"]), extensions: [".rb", ".rake", ".gemspec", ".ru"], async spawn(root) { - let bin = Bun.which("ruby-lsp", { + let bin = Bun.which("rubocop", { PATH: process.env["PATH"] + ":" + Global.Path.bin, }) if (!bin) { @@ -266,25 +266,25 @@ export namespace LSPServer { return } if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - log.info("installing ruby-lsp") + log.info("installing rubocop") const proc = Bun.spawn({ - cmd: ["gem", "install", "ruby-lsp", "--bindir", Global.Path.bin], + cmd: ["gem", "install", "rubocop", "--bindir", Global.Path.bin], stdout: "pipe", stderr: "pipe", stdin: "pipe", }) const exit = await proc.exited if (exit !== 0) { - log.error("Failed to install ruby-lsp") + log.error("Failed to install rubocop") return } - bin = path.join(Global.Path.bin, "ruby-lsp" + (process.platform === "win32" ? ".exe" : "")) - log.info(`installed ruby-lsp`, { + bin = path.join(Global.Path.bin, "rubocop" + (process.platform === "win32" ? ".exe" : "")) + log.info(`installed rubocop`, { bin, }) } return { - process: spawn(bin!, ["--stdio"], { + process: spawn(bin!, ["--lsp"], { cwd: root, }), } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 787b08c07..ecdffa0df 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -42,6 +42,9 @@ import { SessionSummary } from "@/session/summary" import { GlobalBus } from "@/bus/global" import { SessionStatus } from "@/session/status" +// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 +globalThis.AI_SDK_LOG_WARNINGS = false + const ERRORS = { 400: { description: "Bad request", diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 690873567..ebe731abb 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -638,6 +638,7 @@ export namespace MessageV2 { state: "output-available", toolCallId: part.callID, input: part.state.input, + // TODO: prolly need something better here when dealing with synthetic user messages + attachments output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output, callProviderMetadata: part.metadata, }) diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 55a612981..c56688181 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -12,9 +12,14 @@ import { Filesystem } from "@/util/filesystem" import { Wildcard } from "@/util/wildcard" import { Permission } from "@/permission" import { fileURLToPath } from "url" +import { Flag } from "@/flag/flag.ts" import path from "path" -const MAX_OUTPUT_LENGTH = 30_000 +const DEFAULT_MAX_OUTPUT_LENGTH = 30_000 +const MAX_OUTPUT_LENGTH = (() => { + const parsed = Number(Flag.OPENCODE_EXPERIMENTAL_BASH_MAX_OUTPUT_LENGTH) + return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_OUTPUT_LENGTH +})() const DEFAULT_TIMEOUT = 1 * 60 * 1000 const MAX_TIMEOUT = 10 * 60 * 1000 const SIGKILL_TIMEOUT_MS = 200 diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 54118de1c..6d833f9b0 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/plugin", - "version": "1.0.80", + "version": "1.0.81", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 075201c93..b024c45dc 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@opencode-ai/sdk", - "version": "1.0.80", + "version": "1.0.81", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/sdk/python/src/opencode_ai/models/keybinds_config.py b/packages/sdk/python/src/opencode_ai/models/keybinds_config.py index 05053206d..f98b3b78e 100644 --- a/packages/sdk/python/src/opencode_ai/models/keybinds_config.py +++ b/packages/sdk/python/src/opencode_ai/models/keybinds_config.py @@ -81,8 +81,8 @@ class KeybindsConfig: session_unshare: Union[Unset, str] = "none" session_interrupt: Union[Unset, str] = "esc" session_compact: Union[Unset, str] = "c" - session_child_cycle: Union[Unset, str] = "ctrl+right" - session_child_cycle_reverse: Union[Unset, str] = "ctrl+left" + session_child_cycle: Union[Unset, str] = "right" + session_child_cycle_reverse: Union[Unset, str] = "left" messages_page_up: Union[Unset, str] = "pgup" messages_page_down: Union[Unset, str] = "pgdown" messages_half_page_up: Union[Unset, str] = "ctrl+alt+u" diff --git a/packages/slack/package.json b/packages/slack/package.json index e70b59162..506b403e3 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.80", + "version": "1.0.81", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index c86862f5d..7824b07d0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.80", + "version": "1.0.81", "type": "module", "exports": { ".": "./src/components/index.ts", diff --git a/packages/util/package.json b/packages/util/package.json index 3fabe7b22..74e979751 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.80", + "version": "1.0.81", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index a22466a80..4913163d9 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.80", + "version": "1.0.81", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/packages/web/src/content/docs/agents.mdx b/packages/web/src/content/docs/agents.mdx index c99988ad8..f63457cc0 100644 --- a/packages/web/src/content/docs/agents.mdx +++ b/packages/web/src/content/docs/agents.mdx @@ -89,8 +89,8 @@ A general-purpose agent for researching complex questions, searching for code, a ``` 3. **Navigation between sessions**: When subagents create their own child sessions, you can navigate between the parent session and all child sessions using: - - **Ctrl+Right** (or your configured `session_child_cycle` keybind) to cycle forward through parent → child1 → child2 → ... → parent - - **Ctrl+Left** (or your configured `session_child_cycle_reverse` keybind) to cycle backward through parent ← child1 ← child2 ← ... ← parent + - **\+Right** (or your configured `session_child_cycle` keybind) to cycle forward through parent → child1 → child2 → ... → parent + - **\+Left** (or your configured `session_child_cycle_reverse` keybind) to cycle backward through parent ← child1 ← child2 ← ... ← parent This allows you to seamlessly switch between the main conversation and specialized subagent work. diff --git a/packages/web/src/content/docs/keybinds.mdx b/packages/web/src/content/docs/keybinds.mdx index ecac695b3..afcff3a0e 100644 --- a/packages/web/src/content/docs/keybinds.mdx +++ b/packages/web/src/content/docs/keybinds.mdx @@ -23,8 +23,8 @@ OpenCode has a list of keybinds that you can customize through the OpenCode conf "session_unshare": "none", "session_interrupt": "escape", "session_compact": "c", - "session_child_cycle": "ctrl+right", - "session_child_cycle_reverse": "ctrl+left", + "session_child_cycle": "+right", + "session_child_cycle_reverse": "+left", "messages_page_up": "pageup", "messages_page_down": "pagedown", "messages_half_page_up": "ctrl+alt+u", diff --git a/packages/web/src/content/docs/lsp.mdx b/packages/web/src/content/docs/lsp.mdx index bfcdb515c..5c12f03f6 100644 --- a/packages/web/src/content/docs/lsp.mdx +++ b/packages/web/src/content/docs/lsp.mdx @@ -11,27 +11,27 @@ OpenCode integrates with your Language Server Protocol (LSP) to help the LLM int OpenCode comes with several built-in LSP servers for popular languages: -| LSP Server | Extensions | Requirements | -| ---------------- | ---------------------------------------------------- | ------------------------------------------------------------ | -| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependency in project | -| deno | .ts, .tsx, .js, .jsx, .mjs | `deno` command available (auto-detects deno.json/deno.jsonc) | -| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` dependency in project | -| gopls | .go | `go` command available | -| ruby-lsp | .rb, .rake, .gemspec, .ru | `ruby` and `gem` commands available | -| pyright | .py, .pyi | `pyright` dependency installed | -| elixir-ls | .ex, .exs | `elixir` command available | -| zls | .zig, .zon | `zig` command available | -| csharp | .cs | `.NET SDK` installed | -| vue | .vue | Auto-installs for Vue projects | -| rust | .rs | `rust-analyzer` command available | -| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects | -| svelte | .svelte | Auto-installs for Svelte projects | -| astro | .astro | Auto-installs for Astro projects | -| yaml-ls | .yaml, .yml | Auto-installs Red Hat yaml-language-server | -| jdtls | .java | `Java SDK (version 21+)` installed | -| lua-ls | .lua | Auto-installs for Lua projects | -| sourcekit-lsp | .swift, .objc, .objcpp | `swift` installed (`xcode` on macOS) | -| php intelephense | .php | Auto-installs for PHP projects | +| LSP Server | Extensions | Requirements | +| ------------------ | ---------------------------------------------------- | ------------------------------------------------------------ | +| typescript | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts | `typescript` dependency in project | +| deno | .ts, .tsx, .js, .jsx, .mjs | `deno` command available (auto-detects deno.json/deno.jsonc) | +| eslint | .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts, .vue | `eslint` dependency in project | +| gopls | .go | `go` command available | +| ruby-lsp (rubocop) | .rb, .rake, .gemspec, .ru | `ruby` and `gem` commands available | +| pyright | .py, .pyi | `pyright` dependency installed | +| elixir-ls | .ex, .exs | `elixir` command available | +| zls | .zig, .zon | `zig` command available | +| csharp | .cs | `.NET SDK` installed | +| vue | .vue | Auto-installs for Vue projects | +| rust | .rs | `rust-analyzer` command available | +| clangd | .c, .cpp, .cc, .cxx, .c++, .h, .hpp, .hh, .hxx, .h++ | Auto-installs for C/C++ projects | +| svelte | .svelte | Auto-installs for Svelte projects | +| astro | .astro | Auto-installs for Astro projects | +| yaml-ls | .yaml, .yml | Auto-installs Red Hat yaml-language-server | +| jdtls | .java | `Java SDK (version 21+)` installed | +| lua-ls | .lua | Auto-installs for Lua projects | +| sourcekit-lsp | .swift, .objc, .objcpp | `swift` installed (`xcode` on macOS) | +| php intelephense | .php | Auto-installs for PHP projects | LSP servers are automatically enabled when one of the above file extensions are detected and the requirements are met. @@ -114,7 +114,7 @@ You can add custom LSP servers by specifying the command and file extensions: ### PHP Intelephense -PHP Intelephense offers premium features through a license key. Uou can provide a license key by placing (only) the key in a text file at: +PHP Intelephense offers premium features through a license key. You can provide a license key by placing (only) the key in a text file at: - On macOS/Linux: `$HOME/intelephense/licence.txt` - On Windows: `%USERPROFILE%/intelephense/licence.txt` diff --git a/packages/web/src/content/docs/models.mdx b/packages/web/src/content/docs/models.mdx index add923609..38fa2cc4d 100644 --- a/packages/web/src/content/docs/models.mdx +++ b/packages/web/src/content/docs/models.mdx @@ -35,17 +35,16 @@ Consider using one of the models we recommend. However, there are only a few of them that are good at both generating code and tool calling. -Here are several models, in no particular order, that work well with OpenCode (to name a few): +Here are several models that work well with OpenCode, in no particular order. (This is not an exhaustive list): -- GPT 5 -- GPT 5 Codex +- GPT 5.1 +- GPT 5.1 Codex - Claude Sonnet 4.5 -- Claude Sonnet 4 -- Claude Opus 4.1 +- Claude Haiku 4.5 - Kimi K2 +- GLM 4.6 - Qwen3 Coder -- GPT 4.1 -- Gemini 2.5 Pro +- Gemini 3 Pro --- diff --git a/packages/web/src/content/docs/providers.mdx b/packages/web/src/content/docs/providers.mdx index 84a815490..ec468205a 100644 --- a/packages/web/src/content/docs/providers.mdx +++ b/packages/web/src/content/docs/providers.mdx @@ -357,6 +357,42 @@ Or if you already have an API key, you can select **Manually enter API Key** and --- +### Cortecs + +1. Head over to the [Cortecs console](https://cortecs.ai/), create an account, and generate an API key. + +2. Run `opencode auth login` and select **Cortecs**. + + ```bash + $ opencode auth login + + ┌ Add credential + │ + ◆ Select provider + │ ● Cortecs + │ ... + └ + ``` + +3. Enter your Cortecs API key. + + ```bash + $ opencode auth login + + ┌ Add credential + │ + ◇ Select provider + │ Cortecs + │ + ◇ Enter your API key + │ _ + └ + ``` + +4. Run the `/models` command to select a model like _Kimi K2 Instruct_. + +--- + ### DeepSeek 1. Head over to the [DeepSeek console](https://platform.deepseek.com/), create an account, and click **Create new API key**. diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 0c39f55b9..cf9807292 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "1.0.80", + "version": "1.0.81", "publisher": "sst-dev", "repository": { "type": "git",