From e0bb96a9f9edf925c445607bc3e19742ba14af8c Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 28 Dec 2025 20:00:47 -0500 Subject: [PATCH 01/11] wip: bench --- .../console/app/src/routes/bench/[id].tsx | 365 ++++++++++++++++++ .../console/app/src/routes/bench/index.tsx | 209 +--------- 2 files changed, 383 insertions(+), 191 deletions(-) create mode 100644 packages/console/app/src/routes/bench/[id].tsx diff --git a/packages/console/app/src/routes/bench/[id].tsx b/packages/console/app/src/routes/bench/[id].tsx new file mode 100644 index 0000000000..4586eef9bf --- /dev/null +++ b/packages/console/app/src/routes/bench/[id].tsx @@ -0,0 +1,365 @@ +import { Title } from "@solidjs/meta" +import { createAsync, query, useParams } from "@solidjs/router" +import { createSignal, For, Show } from "solid-js" +import { Database, desc, eq } from "@opencode-ai/console-core/drizzle/index.js" +import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js" + +interface TaskSource { + repo: string + from: string + to: string +} + +interface Judge { + score: number + rationale: string + judge: string +} + +interface ScoreDetail { + criterion: string + weight: number + average: number + variance?: number + judges?: Judge[] +} + +interface RunUsage { + input: number + output: number + cost: number +} + +interface Run { + task: string + model: string + agent: string + score: { + final: number + base: number + penalty: number + } + scoreDetails: ScoreDetail[] + usage?: RunUsage + duration?: number +} + +interface Prompt { + commit: string + prompt: string +} + +interface AverageUsage { + input: number + output: number + cost: number +} + +interface Task { + averageScore: number + averageDuration?: number + averageUsage?: AverageUsage + model?: string + agent?: string + summary?: string + runs?: Run[] + task: { + id: string + source: TaskSource + prompts?: Prompt[] + } +} + +interface BenchmarkResult { + averageScore: number + tasks: Task[] +} + +async function getTaskDetail(benchmarkId: string, taskId: string) { + "use server" + const rows = await Database.use((tx) => + tx.select().from(BenchmarkTable).where(eq(BenchmarkTable.id, benchmarkId)).limit(1), + ) + if (!rows[0]) return null + const parsed = JSON.parse(rows[0].result) as BenchmarkResult + const task = parsed.tasks.find((t) => t.task.id === taskId) + return task ?? null +} + +const queryTaskDetail = query(getTaskDetail, "benchmark.task.detail") + +function formatDuration(ms: number): string { + const seconds = Math.floor(ms / 1000) + const minutes = Math.floor(seconds / 60) + const remainingSeconds = seconds % 60 + if (minutes > 0) { + return `${minutes}m ${remainingSeconds}s` + } + return `${remainingSeconds}s` +} + +export default function BenchDetail() { + const params = useParams() + const [benchmarkId, taskId] = (params.id ?? "").split(":") + const task = createAsync(() => queryTaskDetail(benchmarkId, taskId)) + + return ( +
+ Benchmark - {taskId} +
+ Task not found

}> +
+
+ Agent: + {task()?.agent ?? "N/A"} +
+
+ Model: + {task()?.model ?? "N/A"} +
+
+ Task: + {task()!.task.id} +
+
+ +
+ + + +
+ + 0}> +
+ Prompt: + + {(p) => ( +
+
Commit: {p.commit.slice(0, 7)}
+

{p.prompt}

+
+ )} +
+
+
+ +
+ +
+
+ Average Duration: + {task()?.averageDuration ? formatDuration(task()!.averageDuration!) : "N/A"} +
+
+ Average Score: + {task()?.averageScore?.toFixed(3) ?? "N/A"} +
+
+ Average Cost: + {task()?.averageUsage?.cost ? `$${task()!.averageUsage!.cost.toFixed(4)}` : "N/A"} +
+
+ + +
+ Summary: +

{task()!.summary}

+
+
+ + 0}> +
+ Runs: + + + + + + + + + {(detail) => ( + + )} + + + + + + {(run, index) => ( + + + + + + + {(detail) => ( + + )} + + + )} + + +
Run + Score (Base - Penalty) + CostDuration + {detail.criterion} ({detail.weight}) +
{index() + 1} + {run.score.final.toFixed(3)} ({run.score.base.toFixed(3)} - {run.score.penalty.toFixed(3)}) + + {run.usage?.cost ? `$${run.usage.cost.toFixed(4)}` : "N/A"} + + {run.duration ? formatDuration(run.duration) : "N/A"} + + + {(judge) => ( + + {judge.score === 1 ? "✓" : judge.score === 0 ? "✗" : judge.score} + + )} + +
+ + {(run, index) => ( +
+

Run {index() + 1}

+
+ Score: + {run.score.final.toFixed(3)} (Base: {run.score.base.toFixed(3)} - Penalty:{" "} + {run.score.penalty.toFixed(3)}) +
+ + {(detail) => ( +
+
+ {detail.criterion} (weight: {detail.weight}){" "} + + {(judge) => ( + + {judge.score === 1 ? "✓" : judge.score === 0 ? "✗" : judge.score} + + )} + +
+ 0}> + + {(judge) => { + const [expanded, setExpanded] = createSignal(false) + return ( +
+
setExpanded(!expanded())} + > + {expanded() ? "▼" : "▶"} + + {judge.score === 1 ? "✓" : judge.score === 0 ? "✗" : judge.score} + {" "} + {judge.judge} +
+ +

+ {judge.rationale} +

+
+
+ ) + }} +
+
+
+ )} +
+
+ )} +
+
+
+ + {(() => { + const [jsonExpanded, setJsonExpanded] = createSignal(false) + return ( +
+ + +
{JSON.stringify(task(), null, 2)}
+
+
+ ) + })()} +
+
+
+ ) +} diff --git a/packages/console/app/src/routes/bench/index.tsx b/packages/console/app/src/routes/bench/index.tsx index 6339c80179..9b8d0b8f24 100644 --- a/packages/console/app/src/routes/bench/index.tsx +++ b/packages/console/app/src/routes/bench/index.tsx @@ -1,52 +1,12 @@ import { Title } from "@solidjs/meta" -import { createAsync, query } from "@solidjs/router" -import { createMemo, createSignal, For, Show } from "solid-js" +import { A, createAsync, query } from "@solidjs/router" +import { createMemo, For, Show } from "solid-js" import { Database, desc } from "@opencode-ai/console-core/drizzle/index.js" import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js" -interface TaskSource { - repo: string - from: string - to: string -} - -interface ScoreDetail { - criterion: string - weight: number - average: number -} - -interface Run { - task: string - model: string - agent: string - score: { - final: number - base: number - penalty: number - } - scoreDetails: ScoreDetail[] -} - -interface Prompt { - commit: string - prompt: string -} - -interface Task { - averageScore: number - summary?: string - runs?: Run[] - task: { - id: string - source: TaskSource - prompts?: Prompt[] - } -} - interface BenchmarkResult { averageScore: number - tasks: Task[] + tasks: { averageScore: number; task: { id: string } }[] } async function getBenchmarks() { @@ -57,17 +17,15 @@ async function getBenchmarks() { return rows.map((row) => { const parsed = JSON.parse(row.result) as BenchmarkResult const taskScores: Record = {} - const taskData: Record = {} for (const t of parsed.tasks) { taskScores[t.task.id] = t.averageScore - taskData[t.task.id] = t } return { + id: row.id, agent: row.agent, model: row.model, averageScore: parsed.averageScore, taskScores, - taskData, } }) } @@ -76,7 +34,6 @@ const queryBenchmarks = query(getBenchmarks, "benchmarks.list") export default function Bench() { const benchmarks = createAsync(() => queryBenchmarks()) - const [modalTask, setModalTask] = createSignal(null) const taskIds = createMemo(() => { const ids = new Set() @@ -89,34 +46,32 @@ export default function Bench() { }) return ( -
+
Benchmark - +

Benchmarks

+
- - - - {(id) => } + + + + {(id) => } {(row) => ( - - - + + + {(id) => ( - )} @@ -126,134 +81,6 @@ export default function Bench() {
AgentModelFinal Score{id}AgentModelScore{id}
{row.agent}{row.model}{row.averageScore.toFixed(3)}{row.agent}{row.model}{row.averageScore.toFixed(3)} - - setModalTask(row.taskData[id])} - > + + + {row.taskScores[id]?.toFixed(3)} - +
- - -
setModalTask(null)} - > -
e.stopPropagation()} - > - - 0}> -
- Prompt: - - {(p) => ( -
-
Commit: {p.commit.slice(0, 7)}
-

{p.prompt}

-
- )} -
-
-
- 0}> -
- Runs: - - - - - - - - - {(detail) => ( - - )} - - - - - - {(run, index) => ( - - - - - - - {(detail) => ( - - )} - - - )} - - -
RunFinalBasePenalty - {detail.criterion} ({detail.weight}) -
{index() + 1}{run.score.final.toFixed(3)}{run.score.base.toFixed(3)} - {run.score.penalty.toFixed(3)} - - {detail.average.toFixed(3)} -
-
-
- -
- Summary: -

{modalTask()!.summary}

-
-
-
{JSON.stringify(modalTask(), null, 2)}
-
-
-
) } From 0156f03e0ef4e256236256aac05f4fc10577280f Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:26:46 -0600 Subject: [PATCH 02/11] chore: cleanup theme stuff --- packages/app/src/context/command.tsx | 18 +- packages/app/src/pages/layout.tsx | 16 +- packages/ui/src/theme/context.tsx | 297 +++++++++++---------------- 3 files changed, 134 insertions(+), 197 deletions(-) diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx index 626bfdbe4e..efd83bec86 100644 --- a/packages/app/src/context/command.tsx +++ b/packages/app/src/context/command.tsx @@ -3,7 +3,6 @@ import { createSimpleContext } from "@opencode-ai/ui/context" import { useDialog } from "@opencode-ai/ui/context/dialog" import { Dialog } from "@opencode-ai/ui/dialog" import { List } from "@opencode-ai/ui/list" -import { useTheme } from "@opencode-ai/ui/theme" const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) @@ -27,6 +26,7 @@ export interface CommandOption { suggested?: boolean disabled?: boolean onSelect?: (source?: "palette" | "keybind" | "slash") => void + onHighlight?: () => (() => void) | void } export function parseKeybind(config: string): Keybind[] { @@ -116,24 +116,18 @@ export function formatKeybind(config: string): string { function DialogCommand(props: { options: CommandOption[] }) { const dialog = useDialog() - const theme = useTheme() + let cleanup: (() => void) | void let committed = false const handleMove = (option: CommandOption | undefined) => { - if (!option) return - if (option.id.startsWith("theme.set.")) { - const id = option.id.replace("theme.set.", "") - theme.previewTheme(id) - } else if (option.id.startsWith("theme.scheme.") && !option.id.includes("cycle")) { - const scheme = option.id.replace("theme.scheme.", "") as "light" | "dark" | "system" - theme.previewColorScheme(scheme) - } + cleanup?.() + cleanup = option?.onHighlight?.() } const handleSelect = (option: CommandOption | undefined) => { if (option) { - theme.commitPreview() committed = true + cleanup = undefined dialog.close() option.onSelect?.("palette") } @@ -141,7 +135,7 @@ function DialogCommand(props: { options: CommandOption[] }) { onCleanup(() => { if (!committed) { - theme.cancelPreview() + cleanup?.() } }) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 08b3401871..2bc0c31314 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -49,7 +49,7 @@ import { Header } from "@/components/header" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" import { DialogSelectProvider } from "@/components/dialog-select-provider" -import { useCommand } from "@/context/command" +import { useCommand, type CommandOption } from "@/context/command" import { ConstrainDragXAxis } from "@/utils/solid-dnd" export default function Layout(props: ParentProps) { @@ -323,7 +323,7 @@ export default function Layout(props: ParentProps) { } command.register(() => { - const commands = [ + const commands: CommandOption[] = [ { id: "sidebar.toggle", title: "Toggle sidebar", @@ -387,7 +387,11 @@ export default function Layout(props: ParentProps) { id: `theme.set.${id}`, title: `Use theme: ${definition.name ?? id}`, category: "Theme", - onSelect: () => theme.setTheme(id), + onSelect: () => theme.commitPreview(), + onHighlight: () => { + theme.previewTheme(id) + return () => theme.cancelPreview() + }, }) } @@ -404,7 +408,11 @@ export default function Layout(props: ParentProps) { id: `theme.scheme.${scheme}`, title: `Use color scheme: ${colorSchemeLabel[scheme]}`, category: "Theme", - onSelect: () => theme.setColorScheme(scheme), + onSelect: () => theme.commitPreview(), + onHighlight: () => { + theme.previewColorScheme(scheme) + return () => theme.cancelPreview() + }, }) } diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx index 210773f0c8..c1c1637d67 100644 --- a/packages/ui/src/theme/context.tsx +++ b/packages/ui/src/theme/context.tsx @@ -1,52 +1,24 @@ -import { - createContext, - useContext, - createSignal, - onMount, - onCleanup, - createEffect, - type JSX, - type Accessor, -} from "solid-js" +import { onMount, onCleanup, createEffect } from "solid-js" +import { createStore } from "solid-js/store" import type { DesktopTheme } from "./types" import { resolveThemeVariant, themeToCss } from "./resolve" import { DEFAULT_THEMES } from "./default-themes" +import { createSimpleContext } from "../context/helper" export type ColorScheme = "light" | "dark" | "system" -interface ThemeContextValue { - themeId: Accessor - colorScheme: Accessor - mode: Accessor<"light" | "dark"> - themes: Accessor> - setTheme: (id: string) => void - setColorScheme: (scheme: ColorScheme) => void - registerTheme: (theme: DesktopTheme) => void - previewTheme: (id: string) => void - previewColorScheme: (scheme: ColorScheme) => void - commitPreview: () => void - cancelPreview: () => void -} - -const ThemeContext = createContext() - const STORAGE_KEYS = { THEME_ID: "opencode-theme-id", COLOR_SCHEME: "opencode-color-scheme", - THEME_CSS_PREFIX: "opencode-theme-css", + THEME_CSS_LIGHT: "opencode-theme-css-light", + THEME_CSS_DARK: "opencode-theme-css-dark", } as const -function getThemeCacheKey(themeId: string, mode: "light" | "dark"): string { - return `${STORAGE_KEYS.THEME_CSS_PREFIX}-${themeId}-${mode}` -} - const THEME_STYLE_ID = "oc-theme" function ensureThemeStyleElement(): HTMLStyleElement { const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null - if (existing) { - return existing - } + if (existing) return existing const element = document.createElement("style") element.id = THEME_STYLE_ID document.head.appendChild(element) @@ -57,16 +29,15 @@ function getSystemMode(): "light" | "dark" { return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" } -function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark"): void { +function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark") { const isDark = mode === "dark" const variant = isDark ? theme.dark : theme.light const tokens = resolveThemeVariant(variant, isDark) const css = themeToCss(tokens) if (themeId !== "oc-1") { - const cacheKey = getThemeCacheKey(themeId, mode) try { - localStorage.setItem(cacheKey, css) + localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css) } catch {} } @@ -76,170 +47,134 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da ${css} }` - const preloadStyle = document.getElementById("oc-theme-preload") - if (preloadStyle) { - preloadStyle.remove() - } - - const themeStyleElement = ensureThemeStyleElement() - themeStyleElement.textContent = fullCss - + document.getElementById("oc-theme-preload")?.remove() + ensureThemeStyleElement().textContent = fullCss document.documentElement.dataset.theme = themeId document.documentElement.dataset.colorScheme = mode } -function cacheThemeVariants(theme: DesktopTheme, themeId: string): void { +function cacheThemeVariants(theme: DesktopTheme, themeId: string) { if (themeId === "oc-1") return - for (const mode of ["light", "dark"] as const) { const isDark = mode === "dark" const variant = isDark ? theme.dark : theme.light const tokens = resolveThemeVariant(variant, isDark) const css = themeToCss(tokens) - const cacheKey = getThemeCacheKey(themeId, mode) try { - localStorage.setItem(cacheKey, css) + localStorage.setItem(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css) } catch {} } } -export function ThemeProvider(props: { children: JSX.Element; defaultTheme?: string }) { - const [themes, setThemes] = createSignal>(DEFAULT_THEMES) - const [themeId, setThemeIdSignal] = createSignal(props.defaultTheme ?? "oc-1") - const [colorScheme, setColorSchemeSignal] = createSignal("system") - const [mode, setMode] = createSignal<"light" | "dark">(getSystemMode()) - const [previewThemeId, setPreviewThemeId] = createSignal(null) - const [previewScheme, setPreviewScheme] = createSignal(null) +export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({ + name: "Theme", + init: (props: { defaultTheme?: string }) => { + const [store, setStore] = createStore({ + themes: DEFAULT_THEMES as Record, + themeId: props.defaultTheme ?? "oc-1", + colorScheme: "system" as ColorScheme, + mode: getSystemMode(), + previewThemeId: null as string | null, + previewScheme: null as ColorScheme | null, + }) - onMount(() => { - const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") - const handler = () => { - if (colorScheme() === "system") { - setMode(getSystemMode()) + onMount(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + const handler = () => { + if (store.colorScheme === "system") { + setStore("mode", getSystemMode()) + } } - } - mediaQuery.addEventListener("change", handler) - onCleanup(() => mediaQuery.removeEventListener("change", handler)) + mediaQuery.addEventListener("change", handler) + onCleanup(() => mediaQuery.removeEventListener("change", handler)) - const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID) - const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null - if (savedTheme && themes()[savedTheme]) { - setThemeIdSignal(savedTheme) - } - if (savedScheme) { - setColorSchemeSignal(savedScheme) - if (savedScheme !== "system") { - setMode(savedScheme) + const savedTheme = localStorage.getItem(STORAGE_KEYS.THEME_ID) + const savedScheme = localStorage.getItem(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null + if (savedTheme && store.themes[savedTheme]) { + setStore("themeId", savedTheme) } + if (savedScheme) { + setStore("colorScheme", savedScheme) + if (savedScheme !== "system") { + setStore("mode", savedScheme) + } + } + const currentTheme = store.themes[store.themeId] + if (currentTheme) { + cacheThemeVariants(currentTheme, store.themeId) + } + }) + + createEffect(() => { + const theme = store.themes[store.themeId] + if (theme) { + applyThemeCss(theme, store.themeId, store.mode) + } + }) + + const setTheme = (id: string) => { + const theme = store.themes[id] + if (!theme) { + console.warn(`Theme "${id}" not found`) + return + } + setStore("themeId", id) + localStorage.setItem(STORAGE_KEYS.THEME_ID, id) + cacheThemeVariants(theme, id) } - const currentTheme = themes()[themeId()] - if (currentTheme) { - cacheThemeVariants(currentTheme, themeId()) + + const setColorScheme = (scheme: ColorScheme) => { + setStore("colorScheme", scheme) + localStorage.setItem(STORAGE_KEYS.COLOR_SCHEME, scheme) + setStore("mode", scheme === "system" ? getSystemMode() : scheme) } - }) - createEffect(() => { - const id = themeId() - const m = mode() - const theme = themes()[id] - if (theme) { - applyThemeCss(theme, id, m) + return { + themeId: () => store.themeId, + colorScheme: () => store.colorScheme, + mode: () => store.mode, + themes: () => store.themes, + setTheme, + setColorScheme, + registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme), + previewTheme: (id: string) => { + const theme = store.themes[id] + if (!theme) return + setStore("previewThemeId", id) + const previewMode = store.previewScheme + ? store.previewScheme === "system" + ? getSystemMode() + : store.previewScheme + : store.mode + applyThemeCss(theme, id, previewMode) + }, + previewColorScheme: (scheme: ColorScheme) => { + setStore("previewScheme", scheme) + const previewMode = scheme === "system" ? getSystemMode() : scheme + const id = store.previewThemeId ?? store.themeId + const theme = store.themes[id] + if (theme) { + applyThemeCss(theme, id, previewMode) + } + }, + commitPreview: () => { + if (store.previewThemeId) { + setTheme(store.previewThemeId) + } + if (store.previewScheme) { + setColorScheme(store.previewScheme) + } + setStore("previewThemeId", null) + setStore("previewScheme", null) + }, + cancelPreview: () => { + setStore("previewThemeId", null) + setStore("previewScheme", null) + const theme = store.themes[store.themeId] + if (theme) { + applyThemeCss(theme, store.themeId, store.mode) + } + }, } - }) - - const setTheme = (id: string) => { - const theme = themes()[id] - if (!theme) { - console.warn(`Theme "${id}" not found`) - return - } - setThemeIdSignal(id) - localStorage.setItem(STORAGE_KEYS.THEME_ID, id) - cacheThemeVariants(theme, id) - } - - const setColorSchemePref = (scheme: ColorScheme) => { - setColorSchemeSignal(scheme) - localStorage.setItem(STORAGE_KEYS.COLOR_SCHEME, scheme) - if (scheme === "system") { - setMode(getSystemMode()) - } else { - setMode(scheme) - } - } - - const registerTheme = (theme: DesktopTheme) => { - setThemes((prev) => ({ - ...prev, - [theme.id]: theme, - })) - } - - const previewTheme = (id: string) => { - const theme = themes()[id] - if (!theme) return - setPreviewThemeId(id) - const previewMode = previewScheme() ? (previewScheme() === "system" ? getSystemMode() : previewScheme()!) : mode() - applyThemeCss(theme, id, previewMode as "light" | "dark") - } - - const previewColorScheme = (scheme: ColorScheme) => { - setPreviewScheme(scheme) - const previewMode = scheme === "system" ? getSystemMode() : scheme - const id = previewThemeId() ?? themeId() - const theme = themes()[id] - if (theme) { - applyThemeCss(theme, id, previewMode) - } - } - - const commitPreview = () => { - const id = previewThemeId() - const scheme = previewScheme() - if (id) { - setTheme(id) - } - if (scheme) { - setColorSchemePref(scheme) - } - setPreviewThemeId(null) - setPreviewScheme(null) - } - - const cancelPreview = () => { - setPreviewThemeId(null) - setPreviewScheme(null) - const theme = themes()[themeId()] - if (theme) { - applyThemeCss(theme, themeId(), mode()) - } - } - - return ( - - {props.children} - - ) -} - -export function useTheme(): ThemeContextValue { - const ctx = useContext(ThemeContext) - if (!ctx) { - throw new Error("useTheme must be used within a ThemeProvider") - } - return ctx -} + }, +}) From a71c9e3f2e5093fa3658c8e75e09dafcefa43c70 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:49:39 -0600 Subject: [PATCH 03/11] fix(desktop): edit diffs --- packages/ui/src/components/message-part.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 0a1518b796..2d39207ec9 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -807,19 +807,19 @@ ToolRegistry.register({ } > - +
From aafffb5b4b651b890cbfc7dcb96ba3463c8d8fa8 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Sun, 28 Dec 2025 19:54:22 -0600 Subject: [PATCH 04/11] chore: cleanup --- packages/ui/src/components/message-part.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 92323876ca..cf784fd52e 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -390,12 +390,12 @@ conic-gradient( from var(--border-angle), transparent 0deg, - transparent 270deg, + transparent 0deg, var(--border-warning-strong, var(--border-warning-selected)) 300deg, var(--border-warning-base) 360deg ) border-box; - animation: chase-border 1.5s linear infinite; + animation: chase-border 2.5s linear infinite; pointer-events: none; z-index: -1; } From 76880dce0d67998c03f9dbb162f4f91f1a762a47 Mon Sep 17 00:00:00 2001 From: opencode Date: Mon, 29 Dec 2025 01:57:53 +0000 Subject: [PATCH 05/11] release: v1.0.207 --- 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 fa5d84e4ea..b8e6b86e9f 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -70,7 +70,7 @@ }, "packages/console/app": { "name": "@opencode-ai/console-app", - "version": "1.0.206", + "version": "1.0.207", "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.206", + "version": "1.0.207", "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.206", + "version": "1.0.207", "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.206", + "version": "1.0.207", "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.206", + "version": "1.0.207", "dependencies": { "@opencode-ai/app": "workspace:*", "@solid-primitives/storage": "catalog:", @@ -200,7 +200,7 @@ }, "packages/enterprise": { "name": "@opencode-ai/enterprise", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "@opencode-ai/ui": "workspace:*", "@opencode-ai/util": "workspace:*", @@ -229,7 +229,7 @@ }, "packages/function": { "name": "@opencode-ai/function", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "catalog:", @@ -245,7 +245,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "1.0.206", + "version": "1.0.207", "bin": { "opencode": "./bin/opencode", }, @@ -347,7 +347,7 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "@opencode-ai/sdk": "workspace:*", "zod": "catalog:", @@ -367,7 +367,7 @@ }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "1.0.206", + "version": "1.0.207", "devDependencies": { "@hey-api/openapi-ts": "0.88.1", "@tsconfig/node22": "catalog:", @@ -378,7 +378,7 @@ }, "packages/slack": { "name": "@opencode-ai/slack", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "@opencode-ai/sdk": "workspace:*", "@slack/bolt": "^3.17.1", @@ -391,7 +391,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/sdk": "workspace:*", @@ -426,7 +426,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "1.0.206", + "version": "1.0.207", "dependencies": { "zod": "catalog:", }, @@ -437,7 +437,7 @@ }, "packages/web": { "name": "@opencode-ai/web", - "version": "1.0.206", + "version": "1.0.207", "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 84e70e63aa..01721f6e49 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.0.206", + "version": "1.0.207", "description": "", "type": "module", "exports": { diff --git a/packages/console/app/package.json b/packages/console/app/package.json index 50d1edc6cd..9e88c92e82 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.206", + "version": "1.0.207", "type": "module", "scripts": { "typecheck": "tsgo --noEmit", diff --git a/packages/console/core/package.json b/packages/console/core/package.json index b57f1b1009..1f205090fe 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.206", + "version": "1.0.207", "private": true, "type": "module", "dependencies": { diff --git a/packages/console/function/package.json b/packages/console/function/package.json index 319cf487e1..e769f1a0e9 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.206", + "version": "1.0.207", "$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 f1550f782d..74cf1440e2 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.206", + "version": "1.0.207", "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 3e0fc7410d..dc619cf198 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "1.0.206", + "version": "1.0.207", "type": "module", "scripts": { "typecheck": "tsgo -b", diff --git a/packages/enterprise/package.json b/packages/enterprise/package.json index c4cd2d6491..8c8336e386 100644 --- a/packages/enterprise/package.json +++ b/packages/enterprise/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/enterprise", - "version": "1.0.206", + "version": "1.0.207", "private": true, "type": "module", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index a9b30fa5b1..70da5b4bd8 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.206" +version = "1.0.207" 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.206/opencode-darwin-arm64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.207/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.206/opencode-darwin-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.207/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/sst/opencode/releases/download/v1.0.206/opencode-linux-arm64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.207/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.206/opencode-linux-x64.tar.gz" +archive = "https://github.com/sst/opencode/releases/download/v1.0.207/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.206/opencode-windows-x64.zip" +archive = "https://github.com/sst/opencode/releases/download/v1.0.207/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/function/package.json b/packages/function/package.json index c5d63e60a3..1fa670dea6 100644 --- a/packages/function/package.json +++ b/packages/function/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/function", - "version": "1.0.206", + "version": "1.0.207", "$schema": "https://json.schemastore.org/package.json", "private": true, "type": "module", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 68dfb3c0a1..d6ab63172e 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.206", + "version": "1.0.207", "name": "opencode", "type": "module", "private": true, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d1da551f18..cee002366a 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.206", + "version": "1.0.207", "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 a1d9071fdb..6982f316be 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.206", + "version": "1.0.207", "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 c8bebcf8bf..c564858257 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/slack", - "version": "1.0.206", + "version": "1.0.207", "type": "module", "scripts": { "dev": "bun run src/index.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index 2dd9b7817d..3e8422bcd4 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "1.0.206", + "version": "1.0.207", "type": "module", "exports": { "./*": "./src/components/*.tsx", diff --git a/packages/util/package.json b/packages/util/package.json index 13883dbc6a..a91b5e288a 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "1.0.206", + "version": "1.0.207", "private": true, "type": "module", "exports": { diff --git a/packages/web/package.json b/packages/web/package.json index ff4d6ac72c..bd287b7fe6 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/web", "type": "module", - "version": "1.0.206", + "version": "1.0.207", "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 0060a2ee21..13eaad66aa 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.206", + "version": "1.0.207", "publisher": "sst-dev", "repository": { "type": "git", From ae67f43ff0f241dfb6a48ad949b8d6a56d62a675 Mon Sep 17 00:00:00 2001 From: Connor Adams Date: Mon, 29 Dec 2025 04:39:10 +0000 Subject: [PATCH 06/11] feat: add support for `.claude/skills` directory (#6252) --- packages/opencode/src/skill/skill.ts | 63 +++++++++++++--------- packages/opencode/test/skill/skill.test.ts | 52 +++++++++--------- 2 files changed, 65 insertions(+), 50 deletions(-) diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 41df88f8b6..16fa1d08f6 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -32,44 +32,59 @@ export namespace Skill { }), ) - const SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md") + const OPENCODE_SKILL_GLOB = new Bun.Glob("skill/**/SKILL.md") + const CLAUDE_SKILL_GLOB = new Bun.Glob(".claude/skills/**/SKILL.md") export const state = Instance.state(async () => { const directories = await Config.directories() const skills: Record = {} + const addSkill = async (match: string) => { + const md = await ConfigMarkdown.parse(match) + if (!md) { + return + } + + const parsed = Info.pick({ name: true, description: true }).safeParse(md.data) + if (!parsed.success) return + + // Warn on duplicate skill names + if (skills[parsed.data.name]) { + log.warn("duplicate skill name", { + name: parsed.data.name, + existing: skills[parsed.data.name].location, + duplicate: match, + }) + } + + skills[parsed.data.name] = { + name: parsed.data.name, + description: parsed.data.description, + location: match, + } + } + for (const dir of directories) { - for await (const match of SKILL_GLOB.scan({ + for await (const match of OPENCODE_SKILL_GLOB.scan({ cwd: dir, absolute: true, onlyFiles: true, followSymlinks: true, })) { - const md = await ConfigMarkdown.parse(match) - if (!md) { - continue - } - - const parsed = Info.pick({ name: true, description: true }).safeParse(md.data) - if (!parsed.success) continue - - // Warn on duplicate skill names - if (skills[parsed.data.name]) { - log.warn("duplicate skill name", { - name: parsed.data.name, - existing: skills[parsed.data.name].location, - duplicate: match, - }) - } - - skills[parsed.data.name] = { - name: parsed.data.name, - description: parsed.data.description, - location: match, - } + await addSkill(match) } } + for await (const match of CLAUDE_SKILL_GLOB.scan({ + cwd: Instance.worktree, + absolute: true, + onlyFiles: true, + followSymlinks: true, + dot: true, + })) { + await addSkill(match) + } + return skills }) diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 4a1d75f9fc..1da8105bd8 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -101,31 +101,31 @@ test("returns empty array when no skills exist", async () => { }) }) -// test("discovers skills from .claude/skills/ directory", async () => { -// await using tmp = await tmpdir({ -// git: true, -// init: async (dir) => { -// const skillDir = path.join(dir, ".claude", "skills", "claude-skill") -// await Bun.write( -// path.join(skillDir, "SKILL.md"), -// `--- -// name: claude-skill -// description: A skill in the .claude/skills directory. -// --- +test("discovers skills from .claude/skills/ directory", async () => { + await using tmp = await tmpdir({ + git: true, + init: async (dir) => { + const skillDir = path.join(dir, ".claude", "skills", "claude-skill") + await Bun.write( + path.join(skillDir, "SKILL.md"), + `--- +name: claude-skill +description: A skill in the .claude/skills directory. +--- -// # Claude Skill -// `, -// ) -// }, -// }) +# Claude Skill +`, + ) + }, + }) -// await Instance.provide({ -// directory: tmp.path, -// fn: async () => { -// const skills = await Skill.all() -// expect(skills.length).toBe(1) -// expect(skills[0].name).toBe("claude-skill") -// expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md") -// }, -// }) -// }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const skills = await Skill.all() + expect(skills.length).toBe(1) + expect(skills[0].name).toBe("claude-skill") + expect(skills[0].location).toContain(".claude/skills/claude-skill/SKILL.md") + }, + }) +}) From c6221fc8b3cc3d63db0270e8f207bd3a33d39034 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 29 Dec 2025 04:39:43 +0000 Subject: [PATCH 07/11] 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 cee002366a..50115e769d 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 6982f316be..62187f754c 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 893888536a75a8e84831c1fa6d426377e4e2d326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Morpain?= Date: Mon, 29 Dec 2025 05:44:15 +0100 Subject: [PATCH 08/11] fix(bedrock): support region and bearer token configuration (#6332) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- packages/opencode/src/provider/provider.ts | 33 ++- .../test/provider/amazon-bedrock.test.ts | 236 ++++++++++++++++++ 2 files changed, 260 insertions(+), 9 deletions(-) create mode 100644 packages/opencode/test/provider/amazon-bedrock.test.ts diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 0fdf26392f..0f00c3a303 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -165,29 +165,44 @@ export namespace Provider { } }, "amazon-bedrock": async () => { - const [awsProfile, awsAccessKeyId, awsBearerToken, awsRegion] = await Promise.all([ - Env.get("AWS_PROFILE"), - Env.get("AWS_ACCESS_KEY_ID"), - Env.get("AWS_BEARER_TOKEN_BEDROCK"), - Env.get("AWS_REGION"), - ]) + const auth = await Auth.get("amazon-bedrock") + const awsProfile = Env.get("AWS_PROFILE") + const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID") + const awsRegion = Env.get("AWS_REGION") + + const awsBearerToken = iife(() => { + const envToken = Env.get("AWS_BEARER_TOKEN_BEDROCK") + if (envToken) return envToken + if (auth?.type === "api") { + Env.set("AWS_BEARER_TOKEN_BEDROCK", auth.key) + return auth.key + } + return undefined + }) + if (!awsProfile && !awsAccessKeyId && !awsBearerToken) return { autoload: false } - const region = awsRegion ?? "us-east-1" + const defaultRegion = awsRegion ?? "us-east-1" const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers")) return { autoload: true, options: { - region, + region: defaultRegion, credentialProvider: fromNodeProviderChain(), }, - async getModel(sdk: any, modelID: string, _options?: Record) { + async getModel(sdk: any, modelID: string, options?: Record) { // Skip region prefixing if model already has global prefix if (modelID.startsWith("global.")) { return sdk.languageModel(modelID) } + // Region resolution precedence (highest to lowest): + // 1. options.region from opencode.json provider config + // 2. defaultRegion from AWS_REGION environment variable + // 3. Default "us-east-1" (baked into defaultRegion) + const region = options?.region ?? defaultRegion + let regionPrefix = region.split("-")[0] switch (regionPrefix) { diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts new file mode 100644 index 0000000000..30cd2d0b64 --- /dev/null +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -0,0 +1,236 @@ +import { test, expect } from "bun:test" +import path from "path" +import { tmpdir } from "../fixture/fixture" +import { Instance } from "../../src/project/instance" +import { Provider } from "../../src/provider/provider" +import { Env } from "../../src/env" +import { Auth } from "../../src/auth" +import { Global } from "../../src/global" + +test("Bedrock: config region takes precedence over AWS_REGION env var", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "amazon-bedrock": { + options: { + region: "eu-west-1", + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("AWS_REGION", "us-east-1") + Env.set("AWS_PROFILE", "default") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["amazon-bedrock"]).toBeDefined() + // Region from config should be used (not env var) + expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1") + }, + }) +}) + +test("Bedrock: falls back to AWS_REGION env var when no config", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("AWS_REGION", "eu-west-1") + Env.set("AWS_PROFILE", "default") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["amazon-bedrock"]).toBeDefined() + expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1") + }, + }) +}) + +test("Bedrock: without explicit region config, uses AWS_REGION env or defaults", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("AWS_PROFILE", "default") + // AWS_REGION might be set in the environment, use that or default + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["amazon-bedrock"]).toBeDefined() + // Should have some region set (either from env or default) + expect(providers["amazon-bedrock"].options?.region).toBeDefined() + expect(typeof providers["amazon-bedrock"].options?.region).toBe("string") + }, + }) +}) + +test("Bedrock: uses config region in provider options", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "amazon-bedrock": { + options: { + region: "eu-north-1", + }, + }, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + Env.set("AWS_PROFILE", "default") + }, + fn: async () => { + const providers = await Provider.list() + const bedrockProvider = providers["amazon-bedrock"] + expect(bedrockProvider).toBeDefined() + expect(bedrockProvider.options?.region).toBe("eu-north-1") + }, + }) +}) + +test("Bedrock: respects config region for different instances", async () => { + // First instance with EU config + await using tmp1 = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "amazon-bedrock": { + options: { + region: "eu-west-1", + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp1.path, + init: async () => { + Env.set("AWS_PROFILE", "default") + Env.set("AWS_REGION", "us-east-1") + }, + fn: async () => { + const providers1 = await Provider.list() + expect(providers1["amazon-bedrock"].options?.region).toBe("eu-west-1") + }, + }) + + // Second instance with US config + await using tmp2 = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "amazon-bedrock": { + options: { + region: "us-west-2", + }, + }, + }, + }), + ) + }, + }) + + await Instance.provide({ + directory: tmp2.path, + init: async () => { + Env.set("AWS_PROFILE", "default") + Env.set("AWS_REGION", "eu-west-1") + }, + fn: async () => { + const providers2 = await Provider.list() + expect(providers2["amazon-bedrock"].options?.region).toBe("us-west-2") + }, + }) +}) + +test("Bedrock: loads when bearer token from auth.json is present", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + provider: { + "amazon-bedrock": { + options: { + region: "eu-west-1", + }, + }, + }, + }), + ) + }, + }) + + // Setup auth.json with bearer token for amazon-bedrock + const authPath = path.join(Global.Path.data, "auth.json") + await Bun.write( + authPath, + JSON.stringify({ + "amazon-bedrock": { + type: "api", + key: "test-bearer-token", + }, + }), + ) + + await Instance.provide({ + directory: tmp.path, + init: async () => { + // Clear env vars so only auth.json should trigger autoload + Env.set("AWS_PROFILE", "") + Env.set("AWS_ACCESS_KEY_ID", "") + Env.set("AWS_BEARER_TOKEN_BEDROCK", "") + }, + fn: async () => { + const providers = await Provider.list() + expect(providers["amazon-bedrock"]).toBeDefined() + expect(providers["amazon-bedrock"].options?.region).toBe("eu-west-1") + }, + }) +}) From 896d18ab3fd588bf835dbe14553da1c83f7b05bd Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 29 Dec 2025 04:44:48 +0000 Subject: [PATCH 09/11] chore: generate --- packages/opencode/src/provider/provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 0f00c3a303..62bc5beaa0 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -169,7 +169,7 @@ export namespace Provider { const awsProfile = Env.get("AWS_PROFILE") const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID") const awsRegion = Env.get("AWS_REGION") - + const awsBearerToken = iife(() => { const envToken = Env.get("AWS_BEARER_TOKEN_BEDROCK") if (envToken) return envToken @@ -179,7 +179,7 @@ export namespace Provider { } return undefined }) - + if (!awsProfile && !awsAccessKeyId && !awsBearerToken) return { autoload: false } const defaultRegion = awsRegion ?? "us-east-1" From 05a9e7ce7a5acefa11fa9379757d0f05a2b30b5a Mon Sep 17 00:00:00 2001 From: Alice Alexandra Moore <86723305+3mdistal@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:56:22 -0500 Subject: [PATCH 10/11] docs: clarify that MCP tools require glob patterns to disable (#6306) Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> --- packages/web/src/content/docs/mcp-servers.mdx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/web/src/content/docs/mcp-servers.mdx b/packages/web/src/content/docs/mcp-servers.mdx index 03cbbe1129..eba7dfbd0f 100644 --- a/packages/web/src/content/docs/mcp-servers.mdx +++ b/packages/web/src/content/docs/mcp-servers.mdx @@ -354,12 +354,20 @@ If you have a large number of MCP servers you may want to only enable them per a #### Glob patterns -The glob pattern uses simple regex globbing patterns. +The glob pattern uses simple regex globbing patterns: -- `*` matches zero or more of any character +- `*` matches zero or more of any character (e.g., `"my-mcp*"` matches `my-mcp_search`, `my-mcp_list`, etc.) - `?` matches exactly one character - All other characters match literally +:::note +MCP server tools are registered with server name as prefix, so to diable all tools for a server simply use: +``` +"mymcpservername_*": false +``` +::: + + --- ## Examples From 6963f96d4b492b40f5e95ad80f98c40841c32bfd Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 29 Dec 2025 04:56:54 +0000 Subject: [PATCH 11/11] chore: generate --- packages/web/src/content/docs/mcp-servers.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/web/src/content/docs/mcp-servers.mdx b/packages/web/src/content/docs/mcp-servers.mdx index eba7dfbd0f..2101129b4d 100644 --- a/packages/web/src/content/docs/mcp-servers.mdx +++ b/packages/web/src/content/docs/mcp-servers.mdx @@ -362,11 +362,12 @@ The glob pattern uses simple regex globbing patterns: :::note MCP server tools are registered with server name as prefix, so to diable all tools for a server simply use: + ``` "mymcpservername_*": false ``` -::: +::: ---