mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-11 04:18:53 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d953482ab | ||
|
|
3a90639cb5 | ||
|
|
b9f3b382fc | ||
|
|
550d1ffd24 | ||
|
|
941e71dbbb | ||
|
|
d90532a595 |
@@ -103,6 +103,7 @@ describe("normalizeProviderList", () => {
|
||||
)
|
||||
|
||||
expect(result.connected).toEqual(["openai"])
|
||||
expect(result.defaultModel).toEqual({ providerID: "openai", modelID: "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-5"]).toMatchObject({
|
||||
@@ -113,6 +114,10 @@ describe("normalizeProviderList", () => {
|
||||
variants: { high: {} },
|
||||
})
|
||||
})
|
||||
|
||||
test("preserves an empty current default", () => {
|
||||
expect(normalizeProviderList([] as ProviderListOutput["data"], [], null).defaultModel).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("directoryKey", () => {
|
||||
|
||||
@@ -139,6 +139,7 @@ export function normalizeProviderList(
|
||||
return {
|
||||
all,
|
||||
connected: providers.map((provider) => provider.id),
|
||||
defaultModel: defaultModel ? { providerID: defaultModel.providerID, modelID: defaultModel.id } : null,
|
||||
default: Object.fromEntries(
|
||||
providers.flatMap((provider) => {
|
||||
const model =
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createStore } from "solid-js/store"
|
||||
import { useModels } from "@/context/models"
|
||||
import { useSettings } from "@/context/settings"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { resolveDefaultModel } from "@/hooks/provider-catalog"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import { hasCustomAgent, resolveAgent } from "./local-agent"
|
||||
import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant"
|
||||
@@ -149,10 +150,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
})
|
||||
|
||||
const configuredModel = () => {
|
||||
const configured = sync().data.config.model
|
||||
if (!configured) return
|
||||
const [providerID, modelID] = configured.split("/")
|
||||
const model = { providerID, modelID }
|
||||
const model = resolveDefaultModel(providers.defaultModel(), sync().data.config.model)
|
||||
if (!model) return
|
||||
if (validModel(model)) return model
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from "bun:test"
|
||||
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 => ({
|
||||
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)
|
||||
})
|
||||
|
||||
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
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export function useProviders(directory: Accessor<string | undefined>) {
|
||||
return {
|
||||
all: () => providers().all,
|
||||
default: () => providers().default,
|
||||
defaultModel: () => providers().defaultModel,
|
||||
popular: () =>
|
||||
pipe(
|
||||
providers().all,
|
||||
|
||||
@@ -583,7 +583,7 @@ export const dict = {
|
||||
"dialog.usageExceeded.dontShowAgain": "不再显示",
|
||||
|
||||
"context.breakdown.title": "上下文细分",
|
||||
"context.breakdown.note": "输入词元的大致细分。“其他”包含工具定义和开销。",
|
||||
"context.breakdown.note": "输入 token 的大致细分。“其他”包含工具定义和开销。",
|
||||
"context.breakdown.system": "系统",
|
||||
"context.breakdown.user": "用户",
|
||||
"context.breakdown.assistant": "助手",
|
||||
@@ -598,18 +598,18 @@ export const dict = {
|
||||
"context.stats.provider": "提供商",
|
||||
"context.stats.model": "模型",
|
||||
"context.stats.limit": "上下文限制",
|
||||
"context.stats.totalTokens": "词元总数",
|
||||
"context.stats.totalTokens": "总 token",
|
||||
"context.stats.usage": "使用率",
|
||||
"context.stats.inputTokens": "输入词元",
|
||||
"context.stats.outputTokens": "输出词元",
|
||||
"context.stats.reasoningTokens": "推理词元",
|
||||
"context.stats.cacheTokens": "缓存词元(读/写)",
|
||||
"context.stats.inputTokens": "输入 token",
|
||||
"context.stats.outputTokens": "输出 token",
|
||||
"context.stats.reasoningTokens": "推理 token",
|
||||
"context.stats.cacheTokens": "缓存 token(读/写)",
|
||||
"context.stats.userMessages": "用户消息",
|
||||
"context.stats.assistantMessages": "助手消息",
|
||||
"context.stats.totalCost": "总成本",
|
||||
"context.stats.sessionCreated": "创建时间",
|
||||
"context.stats.lastActivity": "最后活动",
|
||||
"context.usage.tokens": "词元数",
|
||||
"context.usage.tokens": "Token",
|
||||
"context.usage.usage": "使用率",
|
||||
"context.usage.cost": "成本",
|
||||
"context.usage.clickToView": "点击查看上下文",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { usePrompt } from "@/context/prompt"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { useSync } from "@/context/sync"
|
||||
import { useProviders } from "@/hooks/use-providers"
|
||||
import { resolveDefaultModel } from "@/hooks/provider-catalog"
|
||||
|
||||
export function createPromptModelSelection(input: { agent: () => { model?: ModelKey; variant?: string } | undefined }) {
|
||||
const sdk = useSDK()
|
||||
@@ -21,10 +22,8 @@ export function createPromptModelSelection(input: { agent: () => { model?: Model
|
||||
}
|
||||
|
||||
const configured = () => {
|
||||
const value = sync().data.config.model
|
||||
if (!value) return
|
||||
const [providerID, modelID] = value.split("/")
|
||||
const model = { providerID, modelID }
|
||||
const model = resolveDefaultModel(providers.defaultModel(), sync().data.config.model)
|
||||
if (!model) return
|
||||
if (valid(model)) return model
|
||||
}
|
||||
|
||||
|
||||
@@ -551,6 +551,12 @@ export function topP(model: Provider.Model) {
|
||||
if (["minimax-m2", "kimi-k2.5", "kimi-k2p5", "kimi-k2-5"].some((s) => id.includes(s))) {
|
||||
return 0.95
|
||||
}
|
||||
if (
|
||||
["deepseek-v4-flash-0731", "deepseek-v4-flash:0731"].some((name) => id.includes(name)) ||
|
||||
(id.includes("deepseek-v4-flash") && (model.providerID === "deepseek" || model.providerID.startsWith("opencode")))
|
||||
) {
|
||||
return 0.95
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -25,7 +25,10 @@ import { MCP } from "@/mcp"
|
||||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
|
||||
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"))
|
||||
return [PROMPT_BEAST]
|
||||
if (model.api.id.includes("gpt")) {
|
||||
|
||||
@@ -3272,6 +3272,37 @@ describe("ProviderTransform sampling defaults - Gemini", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform sampling defaults - DeepSeek", () => {
|
||||
const model = (providerID: string, id: string) =>
|
||||
({
|
||||
id: `${providerID}/${id}`,
|
||||
providerID,
|
||||
api: { id },
|
||||
}) as any
|
||||
|
||||
test.each([
|
||||
["deepseek", "deepseek-v4-flash"],
|
||||
["opencode", "deepseek-v4-flash"],
|
||||
["opencode-go", "deepseek-v4-flash"],
|
||||
["openrouter", "deepseek/deepseek-v4-flash-0731"],
|
||||
["ollama-cloud", "deepseek-v4-flash:0731"],
|
||||
])("defaults top_p for %s/%s", (providerID, id) => {
|
||||
expect(ProviderTransform.temperature(model(providerID, id))).toBeUndefined()
|
||||
expect(ProviderTransform.topP(model(providerID, id))).toBe(0.95)
|
||||
expect(ProviderTransform.topK(model(providerID, id))).toBeUndefined()
|
||||
})
|
||||
|
||||
test.each([
|
||||
["openrouter", "deepseek/deepseek-v4-flash"],
|
||||
["vercel", "deepseek/deepseek-v4-flash"],
|
||||
["custom", "deepseek-ai/DeepSeek-V4-Flash"],
|
||||
])("preserves legacy defaults for %s/%s", (providerID, id) => {
|
||||
expect(ProviderTransform.temperature(model(providerID, id))).toBeUndefined()
|
||||
expect(ProviderTransform.topP(model(providerID, id))).toBeUndefined()
|
||||
expect(ProviderTransform.topK(model(providerID, id))).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("ProviderTransform.reasoningVariants", () => {
|
||||
const model = (reasoning_options: ModelsDev.Model["reasoning_options"]) => ({ reasoning_options }) as ModelsDev.Model
|
||||
const target = (npm: string, id = "test-model") =>
|
||||
|
||||
@@ -85,9 +85,21 @@ const it = testEffect(
|
||||
|
||||
describe("session.system", () => {
|
||||
test("selects the Meta prompt for Muse Spark model IDs", () => {
|
||||
expect(SystemPrompt.provider({ api: { id: "meta/muse-spark-preview" } } as Provider.Model)[0]).toContain(
|
||||
"Meta Muse Spark",
|
||||
)
|
||||
for (const id of ["meta/muse-spark-preview", "muse-spark-1.1", "muse-spark-1.2"]) {
|
||||
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", () =>
|
||||
|
||||
@@ -5,6 +5,10 @@ import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
|
||||
export type NormalizedProviderListResponse = {
|
||||
all: Map<string, Provider>
|
||||
defaultModel?: {
|
||||
providerID: string
|
||||
modelID: string
|
||||
} | null
|
||||
default: {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"border-weak-base": "#DBDBDB",
|
||||
"border-weaker-base": "#E8E8E8",
|
||||
"icon-base": "#8F8F8F",
|
||||
"icon-weak-base": "C7C7C7",
|
||||
"icon-weak-base": "#C7C7C7",
|
||||
"surface-raised-base": "#F3F3F3",
|
||||
"surface-raised-base-hover": "#EDEDED",
|
||||
"surface-base": "#F8F8F8",
|
||||
|
||||
Reference in New Issue
Block a user