Compare commits

..

9 Commits

Author SHA1 Message Date
OpeOginni 3a90639cb5 fix(ui): correct OC-2 weak icon color (#41504) 2026-08-10 17:08:45 -05:00
Matthias Reso b9f3b382fc fix(session): route all Muse family models to the Meta system prompt (#41581) 2026-08-10 13:28:04 -05:00
opencode-agent[bot] 550d1ffd24 fix(i18n): use widely recognized developer terminology (#41532)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
2026-08-10 22:00:00 +10:00
Brendan Allan 941e71dbbb fix(app): use current default model (#38603) 2026-08-10 15:14:17 +08:00
opencode d90532a595 sync release versions for v1.18.16 2026-08-10 06:07:05 +00:00
opencode-agent[bot] 0bff28de09 fix(stats): fall back after full sync failure (#41411) 2026-08-09 10:58:58 -05:00
opencode-agent[bot] 38e10eb140 fix(opencode): ignore unknown config fields (#41312) 2026-08-08 14:53:43 -04:00
opencode-agent[bot] fe82a1b6ca chore: generate 2026-08-08 03:39:08 +00:00
Brendan Allan 2ea728f731 fix(app): populate project picker from home (#41158) 2026-08-08 11:37:30 +08:00
50 changed files with 226 additions and 174 deletions
+28 -28
View File
@@ -29,7 +29,7 @@
}, },
"packages/app": { "packages/app": {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@corvu/drawer": "catalog:", "@corvu/drawer": "catalog:",
"@dnd-kit/abstract": "0.5.0", "@dnd-kit/abstract": "0.5.0",
@@ -96,7 +96,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@opencode-ai/cli", "name": "@opencode-ai/cli",
"version": "1.18.15", "version": "1.18.16",
"bin": { "bin": {
"lildax": "./bin/lildax.cjs", "lildax": "./bin/lildax.cjs",
}, },
@@ -144,7 +144,7 @@
}, },
"packages/codemode": { "packages/codemode": {
"name": "@opencode-ai/codemode", "name": "@opencode-ai/codemode",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"acorn": "8.15.0", "acorn": "8.15.0",
"effect": "catalog:", "effect": "catalog:",
@@ -158,7 +158,7 @@
}, },
"packages/console/app": { "packages/console/app": {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
@@ -194,7 +194,7 @@
}, },
"packages/console/core": { "packages/console/core": {
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@aws-sdk/client-sts": "3.782.0", "@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1", "@jsx-email/render": "1.1.1",
@@ -221,7 +221,7 @@
}, },
"packages/console/function": { "packages/console/function": {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "3.0.82", "@ai-sdk/anthropic": "3.0.82",
"@ai-sdk/openai": "3.0.48", "@ai-sdk/openai": "3.0.48",
@@ -243,7 +243,7 @@
}, },
"packages/console/mail": { "packages/console/mail": {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
@@ -267,7 +267,7 @@
}, },
"packages/console/support": { "packages/console/support": {
"name": "@opencode-ai/console-support", "name": "@opencode-ai/console-support",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@cloudflare/vite-plugin": "1.15.2", "@cloudflare/vite-plugin": "1.15.2",
"@opencode-ai/console-core": "workspace:*", "@opencode-ai/console-core": "workspace:*",
@@ -287,7 +287,7 @@
}, },
"packages/core": { "packages/core": {
"name": "@opencode-ai/core", "name": "@opencode-ai/core",
"version": "1.18.15", "version": "1.18.16",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -381,7 +381,7 @@
}, },
"packages/desktop": { "packages/desktop": {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@zip.js/zip.js": "2.7.62", "@zip.js/zip.js": "2.7.62",
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
@@ -435,7 +435,7 @@
}, },
"packages/effect-drizzle-sqlite": { "packages/effect-drizzle-sqlite": {
"name": "@opencode-ai/effect-drizzle-sqlite", "name": "@opencode-ai/effect-drizzle-sqlite",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"drizzle-orm": "catalog:", "drizzle-orm": "catalog:",
"effect": "catalog:", "effect": "catalog:",
@@ -449,7 +449,7 @@
}, },
"packages/effect-sqlite-node": { "packages/effect-sqlite-node": {
"name": "@opencode-ai/effect-sqlite-node", "name": "@opencode-ai/effect-sqlite-node",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"effect": "catalog:", "effect": "catalog:",
}, },
@@ -461,7 +461,7 @@
}, },
"packages/enterprise": { "packages/enterprise": {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@hono/standard-validator": "catalog:", "@hono/standard-validator": "catalog:",
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
@@ -493,7 +493,7 @@
}, },
"packages/function": { "packages/function": {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@octokit/auth-app": "8.0.1", "@octokit/auth-app": "8.0.1",
"@octokit/rest": "catalog:", "@octokit/rest": "catalog:",
@@ -509,7 +509,7 @@
}, },
"packages/http-recorder": { "packages/http-recorder": {
"name": "@opencode-ai/http-recorder", "name": "@opencode-ai/http-recorder",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@effect/platform-node": "4.0.0-beta.83", "@effect/platform-node": "4.0.0-beta.83",
"@effect/platform-node-shared": "4.0.0-beta.83", "@effect/platform-node-shared": "4.0.0-beta.83",
@@ -540,7 +540,7 @@
}, },
"packages/llm": { "packages/llm": {
"name": "@opencode-ai/llm", "name": "@opencode-ai/llm",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@opencode-ai/schema": "workspace:*", "@opencode-ai/schema": "workspace:*",
"@smithy/eventstream-codec": "4.2.14", "@smithy/eventstream-codec": "4.2.14",
@@ -559,7 +559,7 @@
}, },
"packages/opencode": { "packages/opencode": {
"name": "opencode", "name": "opencode",
"version": "1.18.15", "version": "1.18.16",
"bin": { "bin": {
"opencode": "./bin/opencode", "opencode": "./bin/opencode",
}, },
@@ -690,7 +690,7 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@ai-sdk/provider": "3.0.8", "@ai-sdk/provider": "3.0.8",
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
@@ -766,7 +766,7 @@
}, },
"packages/sdk/js": { "packages/sdk/js": {
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"cross-spawn": "catalog:", "cross-spawn": "catalog:",
}, },
@@ -781,7 +781,7 @@
}, },
"packages/server": { "packages/server": {
"name": "@opencode-ai/server", "name": "@opencode-ai/server",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/protocol": "workspace:*", "@opencode-ai/protocol": "workspace:*",
@@ -796,7 +796,7 @@
}, },
"packages/session-ui": { "packages/session-ui": {
"name": "@opencode-ai/session-ui", "name": "@opencode-ai/session-ui",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz", "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz",
@@ -836,7 +836,7 @@
}, },
"packages/slack": { "packages/slack": {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@opencode-ai/sdk": "workspace:*", "@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1", "@slack/bolt": "^3.17.1",
@@ -849,7 +849,7 @@
}, },
"packages/stats/app": { "packages/stats/app": {
"name": "@opencode-ai/stats-app", "name": "@opencode-ai/stats-app",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@ibm/plex": "6.4.1", "@ibm/plex": "6.4.1",
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
@@ -883,7 +883,7 @@
}, },
"packages/stats/core": { "packages/stats/core": {
"name": "@opencode-ai/stats-core", "name": "@opencode-ai/stats-core",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@aws-sdk/client-athena": "3.933.0", "@aws-sdk/client-athena": "3.933.0",
"@planetscale/database": "1.19.0", "@planetscale/database": "1.19.0",
@@ -902,7 +902,7 @@
}, },
"packages/stats/server": { "packages/stats/server": {
"name": "@opencode-ai/stats-server", "name": "@opencode-ai/stats-server",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@aws-sdk/client-firehose": "3.933.0", "@aws-sdk/client-firehose": "3.933.0",
"@effect/platform-node": "catalog:", "@effect/platform-node": "catalog:",
@@ -944,7 +944,7 @@
}, },
"packages/tui": { "packages/tui": {
"name": "@opencode-ai/tui", "name": "@opencode-ai/tui",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@opencode-ai/core": "workspace:*", "@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*", "@opencode-ai/plugin": "workspace:*",
@@ -971,7 +971,7 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@kobalte/core": "catalog:", "@kobalte/core": "catalog:",
"@pierre/diffs": "catalog:", "@pierre/diffs": "catalog:",
@@ -1022,7 +1022,7 @@
}, },
"packages/web": { "packages/web": {
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@astrojs/cloudflare": "12.6.3", "@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1", "@astrojs/markdown-remark": "6.3.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/app", "name": "@opencode-ai/app",
"version": "1.18.15", "version": "1.18.16",
"description": "", "description": "",
"type": "module", "type": "module",
"exports": { "exports": {
@@ -139,6 +139,7 @@ test("resolves directory autocomplete from the current browser root", async () =
directories.push(input.location?.directory ?? "") directories.push(input.location?.directory ?? "")
return Promise.resolve({ data: [] }) return Promise.resolve({ data: [] })
}, },
list: () => Promise.resolve({ data: [] }),
}, },
}, },
} as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"] } as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"]
@@ -152,6 +153,67 @@ test("resolves directory autocomplete from the current browser root", async () =
expect(directories).toEqual(["/repo", "/repo/src"]) expect(directories).toEqual(["/repo", "/repo/src"])
}) })
test("keeps indexed directory results for servers that support empty search", async () => {
const sdk = {
api: {
file: {
find: () => Promise.resolve({ data: [{ path: "projects/", type: "directory" }] }),
list: () => Promise.reject(new Error("listing should not run when search returns results")),
},
},
} as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"]
const search = createDirectorySearch({ sdk, home: () => "/home/luke", base: () => "/home/luke" })
expect(await search("")).toEqual(["/home/luke/projects"])
})
test("lists the default directory when empty search is unsupported", async () => {
const calls: string[] = []
const directories = Array.from({ length: 60 }, (_, index) => ({
path: `project-${index}/`,
type: "directory" as const,
}))
const sdk = {
api: {
file: {
find: () => Promise.resolve({ data: [] }),
list: (input: { location?: { directory?: string } }) => {
calls.push(input.location?.directory ?? "")
return Promise.resolve({
data: [...directories, { path: "README.md", type: "file" }],
})
},
},
},
} as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"]
const search = createDirectorySearch({ sdk, home: () => "/home/luke", base: () => "/home/luke" })
const results = await search("")
expect(results).toHaveLength(60)
expect(results.at(-1)).toBe("/home/luke/project-59")
expect(calls).toEqual(["/home/luke"])
})
test("matches the default directory listing when typed search is unsupported", async () => {
const sdk = {
api: {
file: {
find: () => Promise.resolve({ data: [] }),
list: () =>
Promise.resolve({
data: [
{ path: "Documents/", type: "directory" },
{ path: "Downloads/", type: "directory" },
],
}),
},
},
} as unknown as Parameters<typeof createDirectorySearch>[0]["sdk"]
const search = createDirectorySearch({ sdk, home: () => "/home/luke", base: () => "/home/luke" })
expect(await search("documents")).toEqual(["/home/luke/Documents"])
})
test("searches from an absolute root without a default base", async () => { test("searches from an absolute root without a default base", async () => {
const directories: string[] = [] const directories: string[] = []
const sdk = { const sdk = {
@@ -379,7 +379,14 @@ export function createDirectorySearch(args: { sdk: ServerSDK; base: () => string
.then((result) => result.data.map((entry) => entry.path)) .then((result) => result.data.map((entry) => entry.path))
.catch(() => []) .catch(() => [])
if (!active()) return [] if (!active()) return []
return results.map((path) => joinPickerPath(input.directory, path)).slice(0, 50) if (results.length) {
return results.map((path) => joinPickerPath(input.directory, path)).slice(0, 50)
}
const fallback = query
? await match(input.directory, query, 50)
: (await directories(input.directory)).map((item) => item.absolute)
if (!active()) return []
return fallback
} }
const segments = query.replace(/^\/+/, "").split("/") const segments = query.replace(/^\/+/, "").split("/")
const head = segments.slice(0, -1).filter((part) => part && part !== ".") const head = segments.slice(0, -1).filter((part) => part && part !== ".")
@@ -103,6 +103,7 @@ describe("normalizeProviderList", () => {
) )
expect(result.connected).toEqual(["openai"]) expect(result.connected).toEqual(["openai"])
expect(result.defaultModel).toEqual({ providerID: "openai", modelID: "gpt-5" })
expect(result.default).toEqual({ openai: "gpt-5" }) expect(result.default).toEqual({ openai: "gpt-5" })
expect(result.all.get("openai")?.models["gpt-old"]).toBeUndefined() expect(result.all.get("openai")?.models["gpt-old"]).toBeUndefined()
expect(result.all.get("openai")?.models["gpt-5"]).toMatchObject({ expect(result.all.get("openai")?.models["gpt-5"]).toMatchObject({
@@ -113,6 +114,10 @@ describe("normalizeProviderList", () => {
variants: { high: {} }, variants: { high: {} },
}) })
}) })
test("preserves an empty current default", () => {
expect(normalizeProviderList([] as ProviderListOutput["data"], [], null).defaultModel).toBeNull()
})
}) })
describe("directoryKey", () => { describe("directoryKey", () => {
@@ -139,6 +139,7 @@ export function normalizeProviderList(
return { return {
all, all,
connected: providers.map((provider) => provider.id), connected: providers.map((provider) => provider.id),
defaultModel: defaultModel ? { providerID: defaultModel.providerID, modelID: defaultModel.id } : null,
default: Object.fromEntries( default: Object.fromEntries(
providers.flatMap((provider) => { providers.flatMap((provider) => {
const model = const model =
+3 -4
View File
@@ -6,6 +6,7 @@ import { createStore } from "solid-js/store"
import { useModels } from "@/context/models" import { useModels } from "@/context/models"
import { useSettings } from "@/context/settings" import { useSettings } from "@/context/settings"
import { useProviders } from "@/hooks/use-providers" import { useProviders } from "@/hooks/use-providers"
import { resolveDefaultModel } from "@/hooks/provider-catalog"
import { Persist, persisted } from "@/utils/persist" import { Persist, persisted } from "@/utils/persist"
import { hasCustomAgent, resolveAgent } from "./local-agent" import { hasCustomAgent, resolveAgent } from "./local-agent"
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant" import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
@@ -149,10 +150,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}) })
const configuredModel = () => { const configuredModel = () => {
const configured = sync().data.config.model const model = resolveDefaultModel(providers.defaultModel(), sync().data.config.model)
if (!configured) return if (!model) return
const [providerID, modelID] = configured.split("/")
const model = { providerID, modelID }
if (validModel(model)) return model if (validModel(model)) return model
} }
@@ -1,6 +1,6 @@
import { expect, test } from "bun:test" import { expect, test } from "bun:test"
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
import { selectProviderCatalog } from "./provider-catalog" import { resolveDefaultModel, selectProviderCatalog } from "./provider-catalog"
const catalog = (id: string): NormalizedProviderListResponse => ({ const catalog = (id: string): NormalizedProviderListResponse => ({
all: new Map([[id, { id, name: id, source: "api", env: [], options: {}, models: {} }]]), all: new Map([[id, { id, name: id, source: "api", env: [], options: {}, models: {} }]]),
@@ -57,3 +57,21 @@ test("falls back to the global catalog for route consumers", () => {
}), }),
).toBe(global) ).toBe(global)
}) })
test("uses the current server default model", () => {
expect(resolveDefaultModel({ providerID: "openai", modelID: "gpt-5" }, "anthropic/claude")).toEqual({
providerID: "openai",
modelID: "gpt-5",
})
})
test("does not use legacy config when the current server has no default", () => {
expect(resolveDefaultModel(null, "anthropic/claude")).toBeUndefined()
})
test("uses config for legacy servers", () => {
expect(resolveDefaultModel(undefined, "anthropic/claude")).toEqual({
providerID: "anthropic",
modelID: "claude",
})
})
@@ -25,3 +25,13 @@ export function selectProviderCatalog(input: ProviderCatalogInput) {
if (input.explicit) return emptyProviderCatalog if (input.explicit) return emptyProviderCatalog
return input.global return input.global
} }
export function resolveDefaultModel(
current: NormalizedProviderListResponse["defaultModel"],
legacy: string | undefined,
) {
if (current !== undefined) return current ?? undefined
if (!legacy) return undefined
const [providerID, modelID] = legacy.split("/")
return { providerID, modelID }
}
+1
View File
@@ -41,6 +41,7 @@ export function useProviders(directory: Accessor<string | undefined>) {
return { return {
all: () => providers().all, all: () => providers().all,
default: () => providers().default, default: () => providers().default,
defaultModel: () => providers().defaultModel,
popular: () => popular: () =>
pipe( pipe(
providers().all, providers().all,
+7 -7
View File
@@ -583,7 +583,7 @@ export const dict = {
"dialog.usageExceeded.dontShowAgain": "不再显示", "dialog.usageExceeded.dontShowAgain": "不再显示",
"context.breakdown.title": "上下文细分", "context.breakdown.title": "上下文细分",
"context.breakdown.note": "输入词元的大致细分。“其他”包含工具定义和开销。", "context.breakdown.note": "输入 token 的大致细分。“其他”包含工具定义和开销。",
"context.breakdown.system": "系统", "context.breakdown.system": "系统",
"context.breakdown.user": "用户", "context.breakdown.user": "用户",
"context.breakdown.assistant": "助手", "context.breakdown.assistant": "助手",
@@ -598,18 +598,18 @@ export const dict = {
"context.stats.provider": "提供商", "context.stats.provider": "提供商",
"context.stats.model": "模型", "context.stats.model": "模型",
"context.stats.limit": "上下文限制", "context.stats.limit": "上下文限制",
"context.stats.totalTokens": "词元总数", "context.stats.totalTokens": "总 token",
"context.stats.usage": "使用率", "context.stats.usage": "使用率",
"context.stats.inputTokens": "输入词元", "context.stats.inputTokens": "输入 token",
"context.stats.outputTokens": "输出词元", "context.stats.outputTokens": "输出 token",
"context.stats.reasoningTokens": "推理词元", "context.stats.reasoningTokens": "推理 token",
"context.stats.cacheTokens": "缓存词元(读/写)", "context.stats.cacheTokens": "缓存 token(读/写)",
"context.stats.userMessages": "用户消息", "context.stats.userMessages": "用户消息",
"context.stats.assistantMessages": "助手消息", "context.stats.assistantMessages": "助手消息",
"context.stats.totalCost": "总成本", "context.stats.totalCost": "总成本",
"context.stats.sessionCreated": "创建时间", "context.stats.sessionCreated": "创建时间",
"context.stats.lastActivity": "最后活动", "context.stats.lastActivity": "最后活动",
"context.usage.tokens": "词元数", "context.usage.tokens": "Token",
"context.usage.usage": "使用率", "context.usage.usage": "使用率",
"context.usage.cost": "成本", "context.usage.cost": "成本",
"context.usage.clickToView": "点击查看上下文", "context.usage.clickToView": "点击查看上下文",
@@ -6,6 +6,7 @@ import { usePrompt } from "@/context/prompt"
import { useSDK } from "@/context/sdk" import { useSDK } from "@/context/sdk"
import { useSync } from "@/context/sync" import { useSync } from "@/context/sync"
import { useProviders } from "@/hooks/use-providers" import { useProviders } from "@/hooks/use-providers"
import { resolveDefaultModel } from "@/hooks/provider-catalog"
export function createPromptModelSelection(input: { agent: () => { model?: ModelKey; variant?: string } | undefined }) { export function createPromptModelSelection(input: { agent: () => { model?: ModelKey; variant?: string } | undefined }) {
const sdk = useSDK() const sdk = useSDK()
@@ -21,10 +22,8 @@ export function createPromptModelSelection(input: { agent: () => { model?: Model
} }
const configured = () => { const configured = () => {
const value = sync().data.config.model const model = resolveDefaultModel(providers.defaultModel(), sync().data.config.model)
if (!value) return if (!model) return
const [providerID, modelID] = value.split("/")
const model = { providerID, modelID }
if (valid(model)) return model if (valid(model)) return model
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/cli", "name": "@opencode-ai/cli",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/codemode", "name": "@opencode-ai/codemode",
"version": "1.18.15", "version": "1.18.16",
"description": "Effect-native confined code execution over schema-described tools", "description": "Effect-native confined code execution over schema-described tools",
"private": true, "private": true,
"type": "module", "type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-app", "name": "@opencode-ai/console-app",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core", "name": "@opencode-ai/console-core",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-function", "name": "@opencode-ai/console-function",
"version": "1.18.15", "version": "1.18.16",
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"private": true, "private": true,
"type": "module", "type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-mail", "name": "@opencode-ai/console-mail",
"version": "1.18.15", "version": "1.18.16",
"dependencies": { "dependencies": {
"@jsx-email/all": "2.2.3", "@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3", "@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/console-support", "name": "@opencode-ai/console-support",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.18.15", "version": "1.18.16",
"name": "@opencode-ai/core", "name": "@opencode-ai/core",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
@@ -498,60 +498,4 @@ Recent work
}, },
]) ])
}) })
test("does not lower duplicate tool call IDs from interrupted history", () => {
const messages = toLLMMessages(
[
SessionMessage.Assistant.make({
id: id("duplicate-tool-call"),
type: "assistant",
agent: "build",
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
content: [
SessionMessage.AssistantTool.make({
type: "tool",
id: "call_1",
name: "read",
state: SessionMessage.ToolStateCompleted.make({
status: "completed",
input: { path: "README.md" },
content: [{ type: "text", text: "done" }],
structured: {},
}),
time: { created, completed: created },
}),
SessionMessage.AssistantTool.make({
type: "tool",
id: "call_1",
name: "unknown",
state: SessionMessage.ToolStateError.make({
status: "error",
input: {},
content: [],
structured: {},
error: { type: "unknown", message: "Tool execution interrupted" },
}),
time: { created, completed: created },
}),
],
time: { created, completed: created },
}),
],
model,
)
const calls = messages.flatMap((message) =>
message.content.filter((part) => part.type === "tool-call" && part.id === "call_1"),
)
expect(calls).toEqual([
{
type: "tool-call",
id: "call_1",
name: "read",
input: { path: "README.md" },
providerExecuted: undefined,
providerMetadata: undefined,
},
])
})
}) })
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@opencode-ai/desktop", "name": "@opencode-ai/desktop",
"private": true, "private": true,
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"homepage": "https://opencode.ai", "homepage": "https://opencode.ai",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.18.15", "version": "1.18.16",
"name": "@opencode-ai/effect-drizzle-sqlite", "name": "@opencode-ai/effect-drizzle-sqlite",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.18.15", "version": "1.18.16",
"name": "@opencode-ai/effect-sqlite-node", "name": "@opencode-ai/effect-sqlite-node",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/enterprise", "name": "@opencode-ai/enterprise",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/function", "name": "@opencode-ai/function",
"version": "1.18.15", "version": "1.18.16",
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"private": true, "private": true,
"type": "module", "type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.18.15", "version": "1.18.16",
"name": "@opencode-ai/http-recorder", "name": "@opencode-ai/http-recorder",
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
"type": "module", "type": "module",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.18.15", "version": "1.18.16",
"name": "@opencode-ai/llm", "name": "@opencode-ai/llm",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"version": "1.18.15", "version": "1.18.16",
"name": "opencode", "name": "opencode",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+5 -23
View File
@@ -37,22 +37,11 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
data: unknown, data: unknown,
source: string, source: string,
): DeepMutable<S["Type"]> { ): DeepMutable<S["Type"]> {
const extra = topLevelExtraKeys(schema, data) const decoded = EffectSchema.decodeUnknownExit(schema)(data, {
if (extra.length) { errors: "all",
throw new InvalidError({ onExcessProperty: "ignore",
path: source, propertyOrder: "original",
issues: [ })
{
code: "unrecognized_keys",
keys: extra,
path: [],
message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`,
},
],
})
}
const decoded = EffectSchema.decodeUnknownExit(schema)(data, { errors: "all", propertyOrder: "original" })
if (Exit.isSuccess(decoded)) return decoded.value as DeepMutable<S["Type"]> if (Exit.isSuccess(decoded)) return decoded.value as DeepMutable<S["Type"]>
const error = Cause.squash(decoded.cause) const error = Cause.squash(decoded.cause)
@@ -70,10 +59,3 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
{ cause: error }, { cause: error },
) )
} }
function topLevelExtraKeys(schema: EffectSchema.Top, data: unknown) {
if (typeof data !== "object" || data === null || Array.isArray(data)) return []
if (schema.ast._tag !== "Objects" || schema.ast.indexSignatures.length > 0) return []
const known = new Set(schema.ast.propertySignatures.map((item) => String(item.name)))
return Object.keys(data).filter((key) => !known.has(key))
}
@@ -1,4 +1,4 @@
You are OpenCode, a coding agent that helps users with software engineering tasks. You are powered by Muse Spark, a large language model trained by Meta MSL. You are OpenCode, a coding agent that helps users with software engineering tasks. You are powered by {{MODEL_NAME}}, a large language model trained by Meta MSL.
Use the instructions below and the tools available to assist the user. Use the instructions below and the tools available to assist the user.
@@ -61,5 +61,5 @@ Use the instructions below and the tools available to assist the user.
- NEVER use comments as a place for long-winded chain-of-thought. Long thinking texts must be generated as private reasoning. Comments in code must be appropriately concise. - NEVER use comments as a place for long-winded chain-of-thought. Long thinking texts must be generated as private reasoning. Comments in code must be appropriately concise.
# User Help & Feedback # User Help & Feedback
- Users can give feedback or report issues at https://github.com/anomalyco/opencode and mention that they are using Meta Muse Spark. - Users can give feedback or report issues at https://github.com/anomalyco/opencode and mention that they are using Meta {{MODEL_NAME}}.
- When users ask directly about OpenCode (eg. "can OpenCode do...", "are you able to do...") or its features (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from the OpenCode docs at https://opencode.ai/docs. - When users ask directly about OpenCode (eg. "can OpenCode do...", "are you able to do...") or its features (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from the OpenCode docs at https://opencode.ai/docs.
+4 -1
View File
@@ -25,7 +25,10 @@ import { MCP } from "@/mcp"
import { PermissionV1 } from "@opencode-ai/core/v1/permission" import { PermissionV1 } from "@opencode-ai/core/v1/permission"
export function provider(model: Provider.Model) { export function provider(model: Provider.Model) {
if (model.api.id.includes("muse-spark")) return [PROMPT_META] if (model.api.id.includes("muse")) {
const name = model.api.id.includes("muse-glimmer") ? "Muse Glimmer" : "Muse Spark"
return [PROMPT_META.replaceAll("{{MODEL_NAME}}", name)]
}
if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3")) if (model.api.id.includes("gpt-4") || model.api.id.includes("o1") || model.api.id.includes("o3"))
return [PROMPT_BEAST] return [PROMPT_BEAST]
if (model.api.id.includes("gpt")) { if (model.api.id.includes("gpt")) {
+5 -10
View File
@@ -597,12 +597,12 @@ accountTokenIt.instance("resolves env templates in account config with account t
}), }),
) )
it.instance("validates config schema and throws on invalid fields", () => it.instance("validates config schema and throws on invalid values", () =>
Effect.gen(function* () { Effect.gen(function* () {
const test = yield* TestInstance const test = yield* TestInstance
yield* writeConfigEffect(test.directory, { yield* writeConfigEffect(test.directory, {
$schema: "https://opencode.ai/config.json", $schema: "https://opencode.ai/config.json",
invalid_field: "should cause error", model: 42,
}) })
const exit = yield* Config.use.get().pipe(Effect.exit) const exit = yield* Config.use.get().pipe(Effect.exit)
expect(Exit.isFailure(exit)).toBe(true) expect(Exit.isFailure(exit)).toBe(true)
@@ -1331,7 +1331,7 @@ it.instance("permission config preserves user key order", () =>
}), }),
) )
test("config parser preserves permission order while rejecting unknown top-level keys", () => { test("config parser preserves permission order while ignoring unknown top-level keys", () => {
const config = ConfigParse.schema( const config = ConfigParse.schema(
ConfigV1.Info, ConfigV1.Info,
{ {
@@ -1340,18 +1340,13 @@ test("config parser preserves permission order while rejecting unknown top-level
"*": "deny", "*": "deny",
edit: "ask", edit: "ask",
}, },
plugins: ["example"],
}, },
"test", "test",
) )
expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"]) expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"])
try { expect(config).not.toHaveProperty("plugins")
ConfigParse.schema(ConfigV1.Info, { invalid_field: true }, "test")
throw new Error("expected config parse to fail")
} catch (err) {
const error = err as { data?: { issues?: Array<{ code?: string; keys?: string[]; path?: string[] }> } }
expect(error.data?.issues?.[0]).toMatchObject({ code: "unrecognized_keys", keys: ["invalid_field"], path: [] })
}
}) })
// MCP config merging tests // MCP config merging tests
+15 -3
View File
@@ -85,9 +85,21 @@ const it = testEffect(
describe("session.system", () => { describe("session.system", () => {
test("selects the Meta prompt for Muse Spark model IDs", () => { test("selects the Meta prompt for Muse Spark model IDs", () => {
expect(SystemPrompt.provider({ api: { id: "meta/muse-spark-preview" } } as Provider.Model)[0]).toContain( for (const id of ["meta/muse-spark-preview", "muse-spark-1.1", "muse-spark-1.2"]) {
"Meta Muse Spark", const prompt = SystemPrompt.provider({ api: { id } } as Provider.Model)[0]
) expect(prompt).toContain("powered by Muse Spark,")
expect(prompt).toContain("using Meta Muse Spark.")
expect(prompt).not.toContain("{{MODEL_NAME}}")
}
})
test("selects the Meta prompt for Muse Glimmer model IDs", () => {
for (const id of ["meta/muse-glimmer", "meta/muse-glimmer-30b", "muse-glimmer-30b"]) {
const prompt = SystemPrompt.provider({ api: { id } } as Provider.Model)[0]
expect(prompt).toContain("powered by Muse Glimmer,")
expect(prompt).toContain("using Meta Muse Glimmer.")
expect(prompt).not.toContain("{{MODEL_NAME}}")
}
}) })
it.effect("skills output is sorted by name and stable across calls", () => it.effect("skills output is sorted by name and stable across calls", () =>
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/plugin", "name": "@opencode-ai/plugin",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/sdk", "name": "@opencode-ai/sdk",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/server", "name": "@opencode-ai/server",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/session-ui", "name": "@opencode-ai/session-ui",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+4
View File
@@ -5,6 +5,10 @@ import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
export type NormalizedProviderListResponse = { export type NormalizedProviderListResponse = {
all: Map<string, Provider> all: Map<string, Provider>
defaultModel?: {
providerID: string
modelID: string
} | null
default: { default: {
[key: string]: string [key: string]: string
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/slack", "name": "@opencode-ai/slack",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-app", "name": "@opencode-ai/stats-app",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-core", "name": "@opencode-ai/stats-core",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -9,7 +9,7 @@ import { Effect, Layer } from "effect"
import * as Context from "effect/Context" import * as Context from "effect/Context"
import { Resource } from "sst/resource" import { Resource } from "sst/resource"
const ATHENA_MAX_POLL_ATTEMPTS = 300 const ATHENA_MAX_POLL_ATTEMPTS = 900
const ATHENA_PAGE_SIZE = 1000 const ATHENA_PAGE_SIZE = 1000
export type AthenaData = Record<string, string> export type AthenaData = Record<string, string>
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/stats-server", "name": "@opencode-ai/stats-server",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+13 -3
View File
@@ -19,9 +19,19 @@ const daemon = Effect.gen(function* () {
let lastFullDay = "" let lastFullDay = ""
const pass = Effect.gen(function* () { const pass = Effect.gen(function* () {
const today = new Date().toISOString().slice(0, 10) const today = new Date().toISOString().slice(0, 10)
const full = lastFullDay !== today if (lastFullDay !== today) {
yield* syncStats({ full }) const completed = yield* syncStats({ full: true }).pipe(
if (full) lastFullDay = today Effect.as(true),
Effect.catchCause((cause) =>
Effect.logWarning(`full stats sync failed; falling back to incremental sync ${Cause.pretty(cause)}`).pipe(
Effect.as(false),
),
),
)
lastFullDay = today
if (completed) return
}
yield* syncStats({ full: false })
}).pipe( }).pipe(
Effect.catchCause((cause) => Effect.catchCause((cause) =>
Effect.logWarning(`stats sync failed ${JSON.stringify({ cause: Cause.pretty(cause) })}`), Effect.logWarning(`stats sync failed ${JSON.stringify({ cause: Cause.pretty(cause) })}`),
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package.json", "$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/tui", "name": "@opencode-ai/tui",
"version": "1.18.15", "version": "1.18.16",
"private": true, "private": true,
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@opencode-ai/ui", "name": "@opencode-ai/ui",
"version": "1.18.15", "version": "1.18.16",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
+1 -1
View File
@@ -25,7 +25,7 @@
"border-weak-base": "#DBDBDB", "border-weak-base": "#DBDBDB",
"border-weaker-base": "#E8E8E8", "border-weaker-base": "#E8E8E8",
"icon-base": "#8F8F8F", "icon-base": "#8F8F8F",
"icon-weak-base": "C7C7C7", "icon-weak-base": "#C7C7C7",
"surface-raised-base": "#F3F3F3", "surface-raised-base": "#F3F3F3",
"surface-raised-base-hover": "#EDEDED", "surface-raised-base-hover": "#EDEDED",
"surface-base": "#F8F8F8", "surface-base": "#F8F8F8",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@opencode-ai/web", "name": "@opencode-ai/web",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"version": "1.18.15", "version": "1.18.16",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev", "dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "opencode", "name": "opencode",
"displayName": "opencode", "displayName": "opencode",
"description": "opencode for VS Code", "description": "opencode for VS Code",
"version": "1.18.15", "version": "1.18.16",
"publisher": "sst-dev", "publisher": "sst-dev",
"repository": { "repository": {
"type": "git", "type": "git",