Skip to content

Commit c80f810

Browse files
committed
test(opencode): detect Zen catalog drift
Signed-off-by: sallyom <[email protected]>
1 parent ff4b500 commit c80f810

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

extensions/opencode/opencode.live.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ import {
88
import { extractNonEmptyAssistantText, isLiveTestEnabled } from "openclaw/plugin-sdk/test-env";
99
import { Type } from "typebox";
1010
import { describe, expect, it } from "vitest";
11+
import { buildStaticOpencodeZenProviderConfig } from "./provider-catalog.js";
1112

13+
const OPENCODE_ZEN_MODELS_URL = "https://opencode.ai/zen/v1/models";
1214
const OPENCODE_API_KEY =
1315
process.env.OPENCODE_API_KEY?.trim() || process.env.OPENCODE_ZEN_API_KEY?.trim() || "";
1416
const LIVE_MODEL_ID =
1517
process.env.OPENCLAW_LIVE_OPENCODE_DEEPSEEK_MODEL?.trim() || "deepseek-v4-flash-free";
1618
const LIVE = isLiveTestEnabled(["OPENCODE_LIVE_TEST"]) && OPENCODE_API_KEY.length > 0;
1719
const describeLive = LIVE ? describe : describe.skip;
20+
const describeCatalogLive = isLiveTestEnabled(["OPENCODE_LIVE_TEST"]) ? describe : describe.skip;
21+
22+
type OpencodeModelsResponse = {
23+
data?: Array<{ id?: unknown; object?: unknown }>;
24+
};
1825

1926
function resolveOpencodeDeepSeekLiveModel(): Model<"openai-completions"> {
2027
return {
@@ -58,6 +65,47 @@ function hasReasoningContentReplay(message: AssistantMessage): boolean {
5865
);
5966
}
6067

68+
async function fetchOpencodeZenModelIds(): Promise<string[]> {
69+
const response = await fetch(OPENCODE_ZEN_MODELS_URL, {
70+
headers: { "accept-encoding": "identity" },
71+
});
72+
expect(response.ok).toBe(true);
73+
const json = (await response.json()) as OpencodeModelsResponse;
74+
return (json.data ?? [])
75+
.filter((model) => model.object === undefined || model.object === "model")
76+
.map((model) => model.id)
77+
.filter((id): id is string => typeof id === "string" && id.trim().length > 0)
78+
.map((id) => id.trim().toLowerCase())
79+
.toSorted();
80+
}
81+
82+
function listStaticOpencodeZenModelIds(): string[] {
83+
return buildStaticOpencodeZenProviderConfig()
84+
.models.map((model) => model.id)
85+
.toSorted();
86+
}
87+
88+
describeCatalogLive("opencode Zen live catalog drift", () => {
89+
it("keeps the provider-owned static seed aligned with the live model ids", async () => {
90+
const liveIds = await fetchOpencodeZenModelIds();
91+
const staticIds = listStaticOpencodeZenModelIds();
92+
93+
const staticIdSet = new Set(staticIds);
94+
const liveIdSet = new Set(liveIds);
95+
const missingStaticMetadata = liveIds.filter((id) => !staticIdSet.has(id));
96+
const staleStaticRows = staticIds.filter((id) => !liveIdSet.has(id));
97+
98+
expect(
99+
{ missingStaticMetadata, staleStaticRows },
100+
[
101+
"OpenCode Zen live catalog drifted from the provider-owned static seed.",
102+
"Add routing/baseUrl/cost/context/capability metadata for missing live ids,",
103+
"or remove stale static rows if OpenCode retired them.",
104+
].join(" "),
105+
).toEqual({ missingStaticMetadata: [], staleStaticRows: [] });
106+
}, 30_000);
107+
});
108+
61109
describeLive("opencode plugin live", () => {
62110
it("accepts DeepSeek V4 tier-suffixed thinking replay after a tool call", async () => {
63111
const model = resolveOpencodeDeepSeekLiveModel();

0 commit comments

Comments
 (0)