From d59357c89b242c58594f15b569b91855b8244f87 Mon Sep 17 00:00:00 2001 From: Github Action Date: Thu, 1 Jan 2026 01:19:17 +0000 Subject: [PATCH 01/12] Update Nix flake.lock and hashes --- flake.lock | 6 +++--- nix/hashes.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index 5dae5a88cfa..2a06923c2de 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1767026758, - "narHash": "sha256-7fsac/f7nh/VaKJ/qm3I338+wAJa/3J57cOGpXi0Sbg=", + "lastModified": 1767151656, + "narHash": "sha256-ujL2AoYBnJBN262HD95yer7QYUmYp5kFZGYbyCCKxq8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "346dd96ad74dc4457a9db9de4f4f57dab2e5731d", + "rev": "f665af0cdb70ed27e1bd8f9fdfecaf451260fc55", "type": "github" }, "original": { diff --git a/nix/hashes.json b/nix/hashes.json index 29e7e527240..d578992a984 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,3 +1,3 @@ { - "nodeModules": "sha256-7zMUWgMCnoe2As8WdEKazkKiGEcUIk5rP4zFvX9USgA=" + "nodeModules": "sha256-uJDhOieOdMQLORyuOWtgtjLoMnNEQPrDcyij9TX0aTw=" } From 4039670a24d873a27d9bbe15864e360ff0e1cb16 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Wed, 31 Dec 2025 22:58:06 -0600 Subject: [PATCH 02/12] Reapply "fix(tui): don't show 'Agent not found' toast for subagents (#6528)" This reverts commit 97a0fd1d54414e5563d0b47a02666d1d044c2cac. --- .../opencode/src/cli/cmd/tui/component/prompt/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 2a0ac846165..ab9487e1dd4 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -202,7 +202,11 @@ export function Prompt(props: PromptProps) { syncedSessionID = sessionID - if (msg.agent) local.agent.set(msg.agent) + // Only set agent if it's a primary agent (not a subagent) + const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent) + if (msg.agent && isPrimaryAgent) { + local.agent.set(msg.agent) + } if (msg.model) local.model.set(msg.model) if (msg.variant) local.model.variant.set(msg.variant) } From 80db00841972be3aa83d5fa6f0e69a3d43c89b54 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 1 Jan 2026 04:59:38 +0000 Subject: [PATCH 03/12] chore: generate --- packages/plugin/package.json | 2 +- packages/sdk/js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 73e83fe0b2f..4ed3e3f71f4 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} \ No newline at end of file +} diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 5d9c660cebf..e1a3ad73f9d 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -29,4 +29,4 @@ "publishConfig": { "directory": "dist" } -} \ No newline at end of file +} From ed745df3757d0e7a88dbe5fbda9f720ed0f6a195 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 1 Jan 2026 03:41:16 -0600 Subject: [PATCH 04/12] fix(app): update primitive colors --- packages/ui/src/styles/colors.css | 52 +++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/styles/colors.css b/packages/ui/src/styles/colors.css index d64fa925ba1..4e78f81da21 100644 --- a/packages/ui/src/styles/colors.css +++ b/packages/ui/src/styles/colors.css @@ -113,8 +113,8 @@ --cobalt-light-4: #daeaff; --cobalt-light-5: #c8e0ff; --cobalt-light-6: #b4d2ff; - --cobalt-dark-alpha-1: #0011f211; --cobalt-light-7: #98bfff; + --cobalt-dark-alpha-1: #0011f211; --cobalt-dark-alpha-2: #0048fe1c; --cobalt-dark-alpha-3: #004dff49; --cobalt-dark-alpha-4: #064dfd6b; @@ -125,8 +125,8 @@ --cobalt-dark-alpha-9: #034cff; --cobalt-dark-alpha-10: #003bffed; --cobalt-dark-alpha-11: #89b5ff; - --cobalt-dark-alpha-12: #cde2ff; --cobalt-light-8: #73a4ff; + --cobalt-dark-alpha-12: #cde2ff; --cobalt-light-9: #034cff; --cobalt-light-10: #0443de; --cobalt-light-11: #1251ec; @@ -541,4 +541,52 @@ --ink-light-alpha-10: #0004049c; --ink-light-alpha-11: #0007077e; --ink-light-alpha-12: #000202df; + --amber-light-1: #fefdfb; + --amber-light-2: #fff9ed; + --amber-light-3: #fff4d5; + --amber-light-4: #ffecbc; + --amber-light-5: #ffe3a2; + --amber-light-6: #ffd386; + --amber-light-7: #f3ba63; + --amber-light-8: #ee9d2b; + --amber-light-9: #ffb224; + --amber-light-10: #ffa01c; + --amber-light-11: #ad5700; + --amber-light-12: #4e2009; + --amber-dark-1: #1f1300; + --amber-dark-2: #271700; + --amber-dark-3: #341c00; + --amber-dark-4: #3f2200; + --amber-dark-5: #4a2900; + --amber-dark-6: #573300; + --amber-dark-7: #693f05; + --amber-dark-8: #824e00; + --amber-dark-9: #ffb224; + --amber-dark-10: #ffcb47; + --amber-dark-11: #f1a10d; + --amber-dark-12: #fef3dd; + --amber-lightalpha-1: #c0820505; + --amber-lightalpha-2: #ffab0211; + --amber-lightalpha-3: #ffbb012b; + --amber-lightalpha-4: #ffb70042; + --amber-lightalpha-5: #ffb3005e; + --amber-lightalpha-6: #ffa20177; + --amber-lightalpha-7: #ec8d009b; + --amber-lightalpha-8: #ea8900d3; + --amber-lightalpha-9: #ffa600db; + --amber-lightalpha-10: #ff9500e2; + --amber-lightalpha-11: #ab5300f9; + --amber-lightalpha-12: #481800f4; + --amber-darkalpha-1: #00000000; + --amber-darkalpha-2: #fd83000a; + --amber-darkalpha-3: #fe730016; + --amber-darkalpha-4: #ff7b0023; + --amber-darkalpha-5: #ff840030; + --amber-darkalpha-6: #ff95003f; + --amber-darkalpha-7: #ff970f54; + --amber-darkalpha-8: #ff990070; + --amber-darkalpha-9: #ffb625f9; + --amber-darkalpha-10: #ffce48f9; + --amber-darkalpha-11: #ffab0eef; + --amber-darkalpha-12: #fff8e1f9; } From 6341ed506cb64e347dc5ada355df5c485d6ed490 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 1 Jan 2026 04:48:19 -0600 Subject: [PATCH 05/12] fix(app): update primitive colors --- packages/ui/src/styles/colors.css | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/ui/src/styles/colors.css b/packages/ui/src/styles/colors.css index 4e78f81da21..61e3ac950ad 100644 --- a/packages/ui/src/styles/colors.css +++ b/packages/ui/src/styles/colors.css @@ -565,28 +565,28 @@ --amber-dark-10: #ffcb47; --amber-dark-11: #f1a10d; --amber-dark-12: #fef3dd; - --amber-lightalpha-1: #c0820505; - --amber-lightalpha-2: #ffab0211; - --amber-lightalpha-3: #ffbb012b; - --amber-lightalpha-4: #ffb70042; - --amber-lightalpha-5: #ffb3005e; - --amber-lightalpha-6: #ffa20177; - --amber-lightalpha-7: #ec8d009b; - --amber-lightalpha-8: #ea8900d3; - --amber-lightalpha-9: #ffa600db; - --amber-lightalpha-10: #ff9500e2; - --amber-lightalpha-11: #ab5300f9; - --amber-lightalpha-12: #481800f4; - --amber-darkalpha-1: #00000000; - --amber-darkalpha-2: #fd83000a; - --amber-darkalpha-3: #fe730016; - --amber-darkalpha-4: #ff7b0023; - --amber-darkalpha-5: #ff840030; - --amber-darkalpha-6: #ff95003f; - --amber-darkalpha-7: #ff970f54; - --amber-darkalpha-8: #ff990070; - --amber-darkalpha-9: #ffb625f9; - --amber-darkalpha-10: #ffce48f9; - --amber-darkalpha-11: #ffab0eef; - --amber-darkalpha-12: #fff8e1f9; + --amber-light-alpha-1: #c0820505; + --amber-light-alpha-2: #ffab0211; + --amber-light-alpha-3: #ffbb012b; + --amber-light-alpha-4: #ffb70042; + --amber-light-alpha-5: #ffb3005e; + --amber-light-alpha-6: #ffa20177; + --amber-light-alpha-7: #ec8d009b; + --amber-light-alpha-8: #ea8900d3; + --amber-light-alpha-9: #ffa600db; + --amber-light-alpha-10: #ff9500e2; + --amber-light-alpha-11: #ab5300f9; + --amber-light-alpha-12: #481800f4; + --amber-dark-alpha-1: #00000000; + --amber-dark-alpha-2: #fd83000a; + --amber-dark-alpha-3: #fe730016; + --amber-dark-alpha-4: #ff7b0023; + --amber-dark-alpha-5: #ff840030; + --amber-dark-alpha-6: #ff95003f; + --amber-dark-alpha-7: #ff970f54; + --amber-dark-alpha-8: #ff990070; + --amber-dark-alpha-9: #ffb625f9; + --amber-dark-alpha-10: #ffce48f9; + --amber-dark-alpha-11: #ffab0eef; + --amber-dark-alpha-12: #fff8e1f9; } From d1a4295a3284e13ca715359c06e1245d926313d3 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 1 Jan 2026 05:02:28 -0600 Subject: [PATCH 06/12] fix(util): checksum defensiveness --- packages/util/src/encode.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/util/src/encode.ts b/packages/util/src/encode.ts index fc1f783bf27..bf6fa75dc28 100644 --- a/packages/util/src/encode.ts +++ b/packages/util/src/encode.ts @@ -20,6 +20,7 @@ export async function hash(content: string, algorithm = "SHA-256"): Promise Date: Thu, 1 Jan 2026 05:04:26 -0600 Subject: [PATCH 07/12] fix(util): checksum defensiveness --- packages/util/src/encode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/util/src/encode.ts b/packages/util/src/encode.ts index bf6fa75dc28..138cf16086d 100644 --- a/packages/util/src/encode.ts +++ b/packages/util/src/encode.ts @@ -19,8 +19,8 @@ export async function hash(content: string, algorithm = "SHA-256"): Promise Date: Thu, 1 Jan 2026 05:23:00 -0600 Subject: [PATCH 08/12] feat(app): context window window --- .../src/components/session-context-usage.tsx | 100 +++-- packages/app/src/context/layout.tsx | 62 ++- packages/app/src/pages/session.tsx | 397 +++++++++++++++++- 3 files changed, 502 insertions(+), 57 deletions(-) diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index ece1f869573..53e578214b9 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -1,13 +1,25 @@ -import { createMemo, Show } from "solid-js" +import { Match, Show, Switch, createMemo } from "solid-js" import { Tooltip } from "@opencode-ai/ui/tooltip" import { ProgressCircle } from "@opencode-ai/ui/progress-circle" -import { useSync } from "@/context/sync" +import { Button } from "@opencode-ai/ui/button" import { useParams } from "@solidjs/router" import { AssistantMessage } from "@opencode-ai/sdk/v2/client" -export function SessionContextUsage() { +import { useLayout } from "@/context/layout" +import { useSync } from "@/context/sync" + +interface SessionContextUsageProps { + variant?: "button" | "indicator" +} + +export function SessionContextUsage(props: SessionContextUsageProps) { const sync = useSync() const params = useParams() + const layout = useLayout() + + const variant = createMemo(() => props.variant ?? "button") + const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) + const tabs = createMemo(() => layout.tabs(sessionKey())) const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) const cost = createMemo(() => { @@ -19,7 +31,11 @@ export function SessionContextUsage() { }) const context = createMemo(() => { - const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage + const last = messages().findLast((x) => { + if (x.role !== "assistant") return false + const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write + return total > 0 + }) as AssistantMessage if (!last) return const total = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write @@ -30,33 +46,57 @@ export function SessionContextUsage() { } }) - return ( - - {(ctx) => ( - -
- {ctx().tokens} - Tokens -
-
- {ctx().percentage ?? 0}% - Usage -
-
- {cost()} - Cost -
+ const openContext = () => { + if (!params.id) return + layout.review.open() + tabs().open("context") + tabs().setActive("context") + } + + const circle = () => ( +
+ +
+ ) + + const tooltipValue = () => ( +
+ + {(ctx) => ( + <> +
+ {ctx().tokens} + Tokens +
+
+ {ctx().percentage ?? 0}% + Usage
- } - placement="top" - > -
- -
- - )} + + )} +
+
+ {cost()} + Cost +
+ +
Click to view context
+
+
+ ) + + return ( + + + + {circle()} + + + + + ) } diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 156adc4ffd2..613a0e0c172 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -209,38 +209,58 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, async open(tab: string) { const current = store.sessionTabs[sessionKey] ?? { all: [] } - if (tab !== "review") { - if (!current.all.includes(tab)) { - if (!store.sessionTabs[sessionKey]) { - setStore("sessionTabs", sessionKey, { all: [tab], active: tab }) - } else { - setStore("sessionTabs", sessionKey, "all", [...current.all, tab]) - setStore("sessionTabs", sessionKey, "active", tab) - } + + if (tab === "review") { + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all: [], active: tab }) return } + setStore("sessionTabs", sessionKey, "active", tab) + return } - if (!store.sessionTabs[sessionKey]) { - setStore("sessionTabs", sessionKey, { all: [], active: tab }) - } else { + + if (tab === "context") { + const all = [tab, ...current.all.filter((x) => x !== tab)] + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all, active: tab }) + return + } + setStore("sessionTabs", sessionKey, "all", all) setStore("sessionTabs", sessionKey, "active", tab) + return } + + if (!current.all.includes(tab)) { + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all: [tab], active: tab }) + return + } + setStore("sessionTabs", sessionKey, "all", [...current.all, tab]) + setStore("sessionTabs", sessionKey, "active", tab) + return + } + + if (!store.sessionTabs[sessionKey]) { + setStore("sessionTabs", sessionKey, { all: current.all, active: tab }) + return + } + setStore("sessionTabs", sessionKey, "active", tab) }, close(tab: string) { const current = store.sessionTabs[sessionKey] if (!current) return + + const all = current.all.filter((x) => x !== tab) batch(() => { - setStore( - "sessionTabs", - sessionKey, - "all", - current.all.filter((x) => x !== tab), - ) - if (current.active === tab) { - const index = current.all.findIndex((f) => f === tab) - const previous = current.all[Math.max(0, index - 1)] - setStore("sessionTabs", sessionKey, "active", previous) + setStore("sessionTabs", sessionKey, "all", all) + if (current.active !== tab) return + + const index = current.all.findIndex((f) => f === tab) + if (index <= 0) { + setStore("sessionTabs", sessionKey, "active", undefined) + return } + setStore("sessionTabs", sessionKey, "active", current.all[index - 1]) }) }, move(tab: string, to: number) { diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 24d7bb94f50..f738fec33e2 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -17,6 +17,7 @@ import { Dynamic } from "solid-js/web" import { useLocal, type LocalFile } from "@/context/local" import { createStore } from "solid-js/store" import { PromptInput } from "@/components/prompt-input" +import { SessionContextUsage } from "@/components/session-context-usage" import { DateTime } from "luxon" import { FileIcon } from "@opencode-ai/ui/file-icon" import { IconButton } from "@opencode-ai/ui/icon-button" @@ -30,6 +31,10 @@ import { SessionTurn } from "@opencode-ai/ui/session-turn" import { createAutoScroll } from "@opencode-ai/ui/hooks" import { SessionMessageRail } from "@opencode-ai/ui/session-message-rail" import { SessionReview } from "@opencode-ai/ui/session-review" +import { Markdown } from "@opencode-ai/ui/markdown" +import { Accordion } from "@opencode-ai/ui/accordion" +import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header" +import { Code } from "@opencode-ai/ui/code" import { DragDropProvider, DragDropSensors, @@ -70,7 +75,7 @@ import { Select } from "@opencode-ai/ui/select" import { TextField } from "@opencode-ai/ui/text-field" import { base64Encode } from "@opencode-ai/util/encode" import { iife } from "@opencode-ai/util/iife" -import { Session } from "@opencode-ai/sdk/v2/client" +import { AssistantMessage, Session, type Message, type Part } from "@opencode-ai/sdk/v2/client" function same(a: readonly T[], b: readonly T[]) { if (a === b) return true @@ -817,7 +822,23 @@ export default function Page() { ) } - const showTabs = createMemo(() => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0)) + const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context")) + const openedTabs = createMemo(() => + tabs() + .all() + .filter((tab) => tab !== "context"), + ) + + const showTabs = createMemo( + () => layout.review.opened() && (diffs().length > 0 || tabs().all().length > 0 || contextOpen()), + ) + + const activeTab = createMemo(() => { + const active = tabs().active() + if (active) return active + if (diffs().length > 0) return "review" + return tabs().all()[0] ?? "review" + }) const mobileWorking = createMemo(() => status().type !== "idle") const mobileAutoScroll = createAutoScroll({ @@ -916,6 +937,347 @@ export default function Page() { ) + const ContextTab = () => { + const ctx = createMemo(() => { + const last = messages().findLast((x) => { + if (x.role !== "assistant") return false + const total = x.tokens.input + x.tokens.output + x.tokens.reasoning + x.tokens.cache.read + x.tokens.cache.write + return total > 0 + }) as AssistantMessage + if (!last) return + + const provider = sync.data.provider.all.find((x) => x.id === last.providerID) + const model = provider?.models[last.modelID] + const limit = model?.limit.context + + const input = last.tokens.input + const output = last.tokens.output + const reasoning = last.tokens.reasoning + const cacheRead = last.tokens.cache.read + const cacheWrite = last.tokens.cache.write + const total = input + output + reasoning + cacheRead + cacheWrite + const usage = limit ? Math.round((total / limit) * 100) : null + + return { + message: last, + provider, + model, + limit, + input, + output, + reasoning, + cacheRead, + cacheWrite, + total, + usage, + } + }) + + const cost = createMemo(() => { + const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0) + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(total) + }) + + const counts = createMemo(() => { + const all = messages() + const user = all.reduce((count, x) => count + (x.role === "user" ? 1 : 0), 0) + const assistant = all.reduce((count, x) => count + (x.role === "assistant" ? 1 : 0), 0) + return { + all: all.length, + user, + assistant, + } + }) + + const systemPrompt = createMemo(() => { + const msg = visibleUserMessages().findLast((m) => !!m.system) + const system = msg?.system + if (!system) return + const trimmed = system.trim() + if (!trimmed) return + return trimmed + }) + + const number = (value: number | null | undefined) => { + if (value === undefined) return "—" + if (value === null) return "—" + return value.toLocaleString() + } + + const percent = (value: number | null | undefined) => { + if (value === undefined) return "—" + if (value === null) return "—" + return value.toString() + "%" + } + + const time = (value: number | undefined) => { + if (!value) return "—" + return DateTime.fromMillis(value).toLocaleString(DateTime.DATETIME_MED) + } + + const providerLabel = createMemo(() => { + const c = ctx() + if (!c) return "—" + return c.provider?.name ?? c.message.providerID + }) + + const modelLabel = createMemo(() => { + const c = ctx() + if (!c) return "—" + if (c.model?.name) return c.model.name + return c.message.modelID + }) + + const breakdown = createMemo( + on( + () => [ctx()?.message.id, ctx()?.input, messages().length, systemPrompt()], + () => { + const c = ctx() + if (!c) return [] + const input = c.input + if (!input) return [] + + const out = { + system: systemPrompt()?.length ?? 0, + user: 0, + assistant: 0, + tool: 0, + } + + for (const msg of messages()) { + const parts = (sync.data.part[msg.id] ?? []) as Part[] + + if (msg.role === "user") { + for (const part of parts) { + if (part.type === "text") out.user += part.text.length + if (part.type === "file") out.user += part.source?.text.value.length ?? 0 + if (part.type === "agent") out.user += part.source?.value.length ?? 0 + } + continue + } + + if (msg.role === "assistant") { + for (const part of parts) { + if (part.type === "text") out.assistant += part.text.length + if (part.type === "reasoning") out.assistant += part.text.length + if (part.type === "tool") { + out.tool += Object.keys(part.state.input).length * 16 + if (part.state.status === "pending") out.tool += part.state.raw.length + if (part.state.status === "completed") out.tool += part.state.output.length + if (part.state.status === "error") out.tool += part.state.error.length + } + } + } + } + + const estimateTokens = (chars: number) => Math.ceil(chars / 4) + const system = estimateTokens(out.system) + const user = estimateTokens(out.user) + const assistant = estimateTokens(out.assistant) + const tool = estimateTokens(out.tool) + const estimated = system + user + assistant + tool + + const pct = (tokens: number) => (tokens / input) * 100 + const pctLabel = (tokens: number) => (Math.round(pct(tokens) * 10) / 10).toString() + "%" + + const build = (tokens: { system: number; user: number; assistant: number; tool: number; other: number }) => { + return [ + { + key: "system", + label: "System", + tokens: tokens.system, + width: pct(tokens.system), + percent: pctLabel(tokens.system), + color: "var(--syntax-info)", + }, + { + key: "user", + label: "User", + tokens: tokens.user, + width: pct(tokens.user), + percent: pctLabel(tokens.user), + color: "var(--syntax-success)", + }, + { + key: "assistant", + label: "Assistant", + tokens: tokens.assistant, + width: pct(tokens.assistant), + percent: pctLabel(tokens.assistant), + color: "var(--syntax-property)", + }, + { + key: "tool", + label: "Tool Calls", + tokens: tokens.tool, + width: pct(tokens.tool), + percent: pctLabel(tokens.tool), + color: "var(--syntax-warning)", + }, + { + key: "other", + label: "Other", + tokens: tokens.other, + width: pct(tokens.other), + percent: pctLabel(tokens.other), + color: "var(--syntax-comment)", + }, + ].filter((x) => x.tokens > 0) + } + + if (estimated <= input) { + return build({ system, user, assistant, tool, other: input - estimated }) + } + + const scale = input / estimated + const scaled = { + system: Math.floor(system * scale), + user: Math.floor(user * scale), + assistant: Math.floor(assistant * scale), + tool: Math.floor(tool * scale), + } + const scaledTotal = scaled.system + scaled.user + scaled.assistant + scaled.tool + return build({ ...scaled, other: Math.max(0, input - scaledTotal) }) + }, + ), + ) + + function Stat(props: { label: string; value: JSX.Element }) { + return ( +
+
{props.label}
+
{props.value}
+
+ ) + } + + const stats = createMemo(() => { + const c = ctx() + const count = counts() + return [ + { label: "Session", value: info()?.title ?? params.id ?? "—" }, + { label: "Messages", value: count.all.toLocaleString() }, + { label: "Provider", value: providerLabel() }, + { label: "Model", value: modelLabel() }, + { label: "Context Limit", value: number(c?.limit) }, + { label: "Total Tokens", value: number(c?.total) }, + { label: "Usage", value: percent(c?.usage) }, + { label: "Input Tokens", value: number(c?.input) }, + { label: "Output Tokens", value: number(c?.output) }, + { label: "Reasoning Tokens", value: number(c?.reasoning) }, + { label: "Cache Tokens (read/write)", value: `${number(c?.cacheRead)} / ${number(c?.cacheWrite)}` }, + { label: "User Messages", value: count.user.toLocaleString() }, + { label: "Assistant Messages", value: count.assistant.toLocaleString() }, + { label: "Total Cost", value: cost() }, + { label: "Session Created", value: time(info()?.time.created) }, + { label: "Last Activity", value: time(c?.message.time.created) }, + ] satisfies { label: string; value: JSX.Element }[] + }) + + function RawMessageContent(props: { message: Message }) { + const file = createMemo(() => { + const parts = (sync.data.part[props.message.id] ?? []) as Part[] + const contents = JSON.stringify({ message: props.message, parts }, null, 2) + return { + name: `${props.message.role}-${props.message.id}.json`, + contents, + cacheKey: checksum(contents), + } + }) + + return + } + + function RawMessage(props: { message: Message }) { + return ( + + + +
+
+ {props.message.role} • {props.message.id} +
+
+
{time(props.message.time.created)}
+ +
+
+
+
+ +
+ +
+
+
+ ) + } + + return ( +
+
+
+ {(stat) => } +
+ + 0}> +
+
Context Breakdown
+
+ + {(segment) => ( +
+ )} + +
+
+ + {(segment) => ( +
+
+
{segment.label}
+
{segment.percent}
+
+ )} + +
+ +
+ + + + {(prompt) => ( +
+
System Prompt
+
+ +
+
+ )} +
+ +
+
Raw messages
+ + {(message) => } + +
+
+
+ ) + } + return (
@@ -1015,7 +1377,7 @@ export default function Page() { > - +
@@ -1035,8 +1397,24 @@ export default function Page() {
- - + + + tabs().close("context")} /> + + } + hideCloseButton + > +
+ +
Context
+
+
+
+ + {(tab) => } @@ -1072,7 +1450,14 @@ export default function Page() {
- + + +
+ +
+
+
+ {(tab) => { const [file] = createResource( () => tab, From 01237c5325f5c3eda142f459b22f902b5aa07d7c Mon Sep 17 00:00:00 2001 From: opencode Date: Thu, 1 Jan 2026 11:25:32 +0000 Subject: [PATCH 09/12] release: v1.0.223 --- bun.lock | 30 +++++++++++++------------- packages/app/package.json | 2 +- packages/console/app/package.json | 2 +- packages/console/core/package.json | 2 +- packages/console/function/package.json | 2 +- packages/console/mail/package.json | 2 +- packages/desktop/package.json | 2 +- packages/enterprise/package.json | 2 +- packages/extensions/zed/extension.toml | 12 +++++------ packages/function/package.json | 2 +- packages/opencode/package.json | 2 +- packages/plugin/package.json | 4 ++-- packages/sdk/js/package.json | 4 ++-- packages/slack/package.json | 2 +- packages/ui/package.json | 2 +- packages/util/package.json | 2 +- packages/web/package.json | 2 +- sdks/vscode/package.json | 2 +- 18 files changed, 39 insertions(+), 39 deletions(-) diff --git a/bun.lock b/bun.lock index abf241f2407..26e9cd16818 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -70,7 +70,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.222", + "version": "1.0.223", "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.222", + "version": "1.0.223", "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.222", + "version": "1.0.223", "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.222", + "version": "1.0.223", "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.222", + "version": "1.0.223", "dependencies": { "@opencode-ai/app": "workspace:*", "@solid-primitives/storage": "catalog:", @@ -201,7 +201,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -230,7 +230,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -246,7 +246,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.222", + "version": "1.0.223", "bin": { "opencode": "./bin/opencode", }, @@ -348,7 +348,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -368,7 +368,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.222", + "version": "1.0.223", "devDependencies": { "@hey-api/openapi-ts": "0.88.1", "@tsconfig/node22": "catalog:", @@ -379,7 +379,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -392,7 +392,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -430,7 +430,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "zod": "catalog:", }, @@ -441,7 +441,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.222", + "version": "1.0.223", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", diff --git a/packages/app/package.json b/packages/app/package.json index a21c9f19709..a75285db8e3 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.0.222", + "version": "1.0.223", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index e2475cb1b19..f8f79046e7c 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/console-app", - "version": "1.0.222", + "version": "1.0.223", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index 76d08f9631d..98b10997799 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.222", + "version": "1.0.223", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 7794ae718a2..d83e0860245 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.222", + "version": "1.0.223", "$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 d0e2700d849..17152678a45 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.222", + "version": "1.0.223", "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 172c555ad3c..cb91f0e22b2 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.0.222", + "version": "1.0.223", "type": "module", "scripts": { "typecheck": "tsgo -b", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index 86a3a89785d..db5f3219b7f 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.222", + "version": "1.0.223", "private": true, "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 3c6982e6ca0..d5524639ecb 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "opencode" name = "OpenCode" description = "The open source coding agent." -version = "1.0.222" +version = "1.0.223" 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.222/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.223/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.222/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.223/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.222/opencode-linux-arm64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.223/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.222/opencode-linux-x64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.223/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.222/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.223/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index b1653cd76fc..3a1a792244f 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.222", + "version": "1.0.223", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 3a09ebfcf82..fc87255db3e 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.222", + "version": "1.0.223", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 4ed3e3f71f4..998477e7382 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.222", + "version": "1.0.223", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -24,4 +24,4 @@ "typescript": "catalog:", "@typescript/native-preview": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index e1a3ad73f9d..6f7ddaccab1 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.222", + "version": "1.0.223", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", @@ -29,4 +29,4 @@ "publishConfig": { "directory": "dist" } -} +} \ No newline at end of file diff --git a/packages/slack/package.json b/packages/slack/package.json index c5ca2eb36fc..c14563cce9a 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.222", + "version": "1.0.223", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index 83ccc4edec9..8200cd2f911 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.222", + "version": "1.0.223", "type": "module", "exports": { "./*": "./src/components/*.tsx", diff --git a/packages/util/package.json b/packages/util/package.json index 7fc471f425a..7f434301a69 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.222", + "version": "1.0.223", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index 5c3bdd2453b..a856419f9b3 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.222", + "version": "1.0.223", "scripts": { "dev": "astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index c93a1edde3d..1ead3d20eda 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.222", + "version": "1.0.223", "publisher": "sst-dev", "repository": { "type": "git", From 94872da334f882c5c6715e251cee7898994b0483 Mon Sep 17 00:00:00 2001 From: shuv Date: Thu, 1 Jan 2026 10:44:30 -0800 Subject: [PATCH 10/12] fix(mobile): hide status bar on mobile to prevent iPhone dynamic island safe area issues --- packages/app/src/pages/session.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 90eb310f908..aa49b454da2 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1346,10 +1346,13 @@ permission.toggleAutoAccept(params.id, sdk.directory)
- - - - + {/* Hide status bar on mobile to prevent safe area issues with iPhone dynamic island */} +
) } From e67716a9b70e65f8b691412095cbc4d655f1ebb3 Mon Sep 17 00:00:00 2001 From: shuv Date: Thu, 1 Jan 2026 10:48:23 -0800 Subject: [PATCH 11/12] sync: record last synced tag v1.0.223 --- .github/last-synced-tag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/last-synced-tag b/.github/last-synced-tag index 6a7d6124186..0ea75634958 100644 --- a/.github/last-synced-tag +++ b/.github/last-synced-tag @@ -1 +1 @@ -v1.0.222 +v1.0.223 From 486205f4a79219c2a268cfb6ad3f0d7d539c2451 Mon Sep 17 00:00:00 2001 From: shuv Date: Thu, 1 Jan 2026 10:50:02 -0800 Subject: [PATCH 12/12] fix(tui): fix Editor.open Result type handling and add onSearchToggle prop Upstream v1.0.223 changed Editor.open to return Result type but didn't update all call sites. Also adds missing onSearchToggle prop to PromptProps. --- .../opencode/src/cli/cmd/tui/component/prompt/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index ab9487e1dd4..e932903b697 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -35,6 +35,7 @@ export type PromptProps = { sessionID?: string disabled?: boolean onSubmit?: () => void + onSearchToggle?: () => void ref?: (ref: PromptRef) => void hint?: JSX.Element showPlaceholder?: boolean @@ -304,9 +305,10 @@ export function Prompt(props: PromptProps) { const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text") const value = trigger === "prompt" ? "" : text - const content = await Editor.open({ value, renderer }) - if (!content) return + const result = await Editor.open({ value, renderer }) + if (!result.ok) return + const content = result.content input.setText(content) // Update positions for nonTextParts based on their location in new content