Skip to content

Commit 80d8225

Browse files
cc-switch contributorverify
authored andcommitted
feat(codex): auto-populate OpenCode Go model catalog on fetch
For OpenCode Go providers, 'Fetch Models' now enriches the upstream /v1/models result into the full Codex catalog: each model gets its per-model context window, messages-only models (e.g. qwen3.7-max) are excluded, and newly added upstream models are picked up automatically with a single click. Adds src/config/opencodeGoModelMeta.ts (base-url detection, per-family context windows, messages-only set, enrichment) and wires it into the Codex form's fetch success handler. No backend change: reuses the existing fetch_models_for_config command. Other providers keep the existing fetched-models dropdown behavior.
1 parent 09d9214 commit 80d8225

2 files changed

Lines changed: 126 additions & 3 deletions

File tree

src/components/providers/forms/CodexFormFields.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import {
2525
showFetchModelsError,
2626
type FetchedModel,
2727
} from "@/lib/api/model-fetch";
28+
import {
29+
enrichOpencodeGoModels,
30+
isOpencodeGoBaseUrl,
31+
} from "@/config/opencodeGoModelMeta";
2832
import { CustomUserAgentField } from "./CustomUserAgentField";
2933
import { cn } from "@/lib/utils";
3034
import type {
@@ -245,18 +249,36 @@ export function CodexFormFields({
245249
setFetchedModels(models);
246250
if (models.length === 0) {
247251
toast.info(t("providerForm.fetchModelsEmpty"));
248-
} else {
252+
return;
253+
}
254+
// OpenCode Go: directly enrich the fetched models into the full catalog
255+
// (with per-model context windows, excluding messages-only models), so a
256+
// single "Fetch Models" click auto-syncs any newly added upstream models.
257+
if (isOpencodeGoBaseUrl(codexBaseUrl) && onCatalogModelsChange) {
258+
const enriched = enrichOpencodeGoModels(models);
259+
setCatalogRows(enriched.map((m) => createCatalogRow(m)));
249260
toast.success(
250-
t("providerForm.fetchModelsSuccess", { count: models.length }),
261+
t("providerForm.fetchModelsSuccess", { count: enriched.length }),
251262
);
263+
return;
252264
}
265+
toast.success(
266+
t("providerForm.fetchModelsSuccess", { count: models.length }),
267+
);
253268
})
254269
.catch((err) => {
255270
console.warn("[ModelFetch] Failed:", err);
256271
showFetchModelsError(err, t);
257272
})
258273
.finally(() => setIsFetchingModels(false));
259-
}, [codexBaseUrl, codexApiKey, isFullUrl, customUserAgent, t]);
274+
}, [
275+
codexBaseUrl,
276+
codexApiKey,
277+
isFullUrl,
278+
customUserAgent,
279+
onCatalogModelsChange,
280+
t,
281+
]);
260282

261283
const handleAddCatalogRow = useCallback(() => {
262284
if (!onCatalogModelsChange) return;

src/config/opencodeGoModelMeta.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* OpenCode Go 模型元数据辅助:用于在「获取模型」时把上游 /v1/models 返回的模型
3+
* 富化为带正确上下文窗口的 Codex 模型目录条目,并排除仅 /messages 通路的模型。
4+
*
5+
* 这样当 OpenCode Go 上新增模型时,用户点一次「获取模型」即可自动拉取并套用上下文窗口,
6+
* 无需手动逐个填写。
7+
*/
8+
import type { CodexCatalogModel } from "../types";
9+
10+
/** OpenCode Go 的标准 base_url。 */
11+
export const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
12+
13+
/** 判断某个 base_url 是否指向 OpenCode Go(容忍有无 /v1 与大小写)。 */
14+
export function isOpencodeGoBaseUrl(baseUrl?: string | null): boolean {
15+
return !!baseUrl && baseUrl.toLowerCase().includes("opencode.ai/zen/go");
16+
}
17+
18+
/**
19+
* 仅在 Anthropic /messages 通路可用、无法走 Codex 的 openai_chat 通路的模型。
20+
* 这些模型会从 chat 目录中排除(参见 docs:Go 的部分模型仅 /messages)。
21+
*/
22+
const MESSAGES_ONLY_MODELS = new Set<string>(["qwen3.7-max"]);
23+
24+
/**
25+
* 上下文窗口按模型 id 的有序前缀规则匹配;新加入的同系模型会自动继承同族窗口。
26+
* 数据依据 models.dev 的 opencode-go provider 与实测。
27+
*/
28+
const CONTEXT_RULES: Array<[RegExp, number]> = [
29+
[/^deepseek-v4/, 1_000_000],
30+
[/^glm-5\.2/, 1_000_000],
31+
[/^glm-5(\.1)?$/, 200_000],
32+
[/^glm-/, 200_000],
33+
[/^kimi-/, 262_144],
34+
[/^mimo-v2\.5-pro/, 1_048_576],
35+
[/^mimo-v2-pro/, 1_048_576],
36+
[/^mimo-v2\.5/, 1_000_000],
37+
[/^mimo-/, 262_144],
38+
[/^qwen3\.[67]-plus/, 1_000_000],
39+
[/^qwen3\.5-plus/, 262_144],
40+
[/^qwen/, 262_144],
41+
[/^minimax-m3/, 512_000],
42+
[/^minimax-/, 204_800],
43+
];
44+
45+
/** 未知模型的兜底上下文窗口(保守值)。 */
46+
const DEFAULT_CONTEXT_WINDOW = 200_000;
47+
48+
function contextWindowFor(id: string): number {
49+
for (const [pattern, ctx] of CONTEXT_RULES) {
50+
if (pattern.test(id)) return ctx;
51+
}
52+
return DEFAULT_CONTEXT_WINDOW;
53+
}
54+
55+
/** 已知模型的友好显示名;未知模型回退到「按 - 分词 + 首字母大写」。 */
56+
const DISPLAY_NAME_OVERRIDES: Record<string, string> = {
57+
"deepseek-v4-flash": "DeepSeek V4 Flash",
58+
"deepseek-v4-pro": "DeepSeek V4 Pro",
59+
"glm-5.2": "GLM-5.2",
60+
"glm-5.1": "GLM-5.1",
61+
"glm-5": "GLM-5",
62+
"kimi-k2.7-code": "Kimi K2.7 Code",
63+
"kimi-k2.6": "Kimi K2.6",
64+
"kimi-k2.5": "Kimi K2.5",
65+
"mimo-v2.5-pro": "MiMo V2.5 Pro",
66+
"mimo-v2.5": "MiMo V2.5",
67+
"mimo-v2-pro": "MiMo V2 Pro",
68+
"mimo-v2-omni": "MiMo V2 Omni",
69+
"qwen3.7-plus": "Qwen3.7 Plus",
70+
"qwen3.6-plus": "Qwen3.6 Plus",
71+
"qwen3.5-plus": "Qwen3.5 Plus",
72+
"minimax-m3": "MiniMax M3",
73+
"minimax-m2.7": "MiniMax M2.7",
74+
"minimax-m2.5": "MiniMax M2.5",
75+
};
76+
77+
function displayNameFor(id: string): string {
78+
const override = DISPLAY_NAME_OVERRIDES[id];
79+
if (override) return override;
80+
return id
81+
.split("-")
82+
.map((part) => (part ? part[0].toUpperCase() + part.slice(1) : part))
83+
.join(" ");
84+
}
85+
86+
/**
87+
* 把 /v1/models 返回的模型(结构上只需 `{ id }`)富化为 Codex 目录条目,
88+
* 排除仅 /messages 的模型,并按族套用上下文窗口。
89+
*/
90+
export function enrichOpencodeGoModels(
91+
models: ReadonlyArray<{ id: string }>,
92+
): CodexCatalogModel[] {
93+
return models
94+
.map((m) => m.id)
95+
.filter((id) => !!id && !MESSAGES_ONLY_MODELS.has(id))
96+
.map((id) => ({
97+
model: id,
98+
displayName: displayNameFor(id),
99+
contextWindow: contextWindowFor(id),
100+
}));
101+
}

0 commit comments

Comments
 (0)