Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/clients/config-export/zcode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ZCode config export.
import type { ExportContext, ManagedContribution } from "./contracts";
import { normalizeExportModels, inputModalitiesForClient, exportModelLabel, authoritativeContextWindow, singleFragment } from "./model-metadata";
import { sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";


Expand All @@ -15,6 +16,16 @@ export interface ZcodeModelEntry {
name?: string;
limit?: { context: number; output?: number };
modalities: { input: string[]; output: string[] };
/**
* On-disk Thought Level block. ZCode 3.x persists `variants`/`defaultVariant`
* and parses them into in-memory `levels`/`defaultLevel`. Omit the field when
* the catalog has no selectable ladder, so the picker stays hidden.
*/
reasoning?: {
enabled: boolean;
variants: string[];
defaultVariant?: string;
};
}

export interface ZcodeProviderBlock {
Expand Down Expand Up @@ -61,6 +72,19 @@ export function buildZcodeClientConfig(ctx: ExportContext): ZcodeGeneratedConfig
if (context !== undefined) {
entry.limit = { context };
}
// `none` is a Codex omit-sentinel, not a ZCode picker option. Keep catalog
// `ultra` when present: ZCode forwards the selected variant as
// `reasoning_effort`. Set `defaultVariant` only when it survives that filter.
const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
?.filter(effort => effort !== "none");
if (efforts && efforts.length > 0) {
const defaultVariant = model.defaultReasoningEffort?.trim().toLowerCase();
entry.reasoning = {
enabled: true,
variants: efforts,
...(defaultVariant && efforts.includes(defaultVariant) ? { defaultVariant } : {}),
};
}
models[model.namespaced] = entry;
}
return {
Expand Down
2 changes: 1 addition & 1 deletion tests/config/client-config-export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ describe("split config-export public facade", () => {
["omp", ["providers", "opencodex"], '{"providers":{"opencodex":{"baseUrl":"http://127.0.0.1:10100/v1","api":"openai-completions","apiKey":"opencodex-loopback","models":[{"id":"test/known","name":"known (test)","input":["text","image"],"contextWindow":8192,"maxTokens":8192,"reasoning":true,"thinking":{"mode":"effort","efforts":["high"]}}]}}}'],
["dsh", ["llm-pi-ai", "providers", "opencodex"], '{"llm-pi-ai":{"providers":{"opencodex":{"displayName":"OpenCodex","api":"openai-responses","baseURL":"http://127.0.0.1:10100/v1","headers":{"Authorization":"Bearer ocx_data_dsh"},"models":[{"id":"test/known","name":"known (test)","input":["text","image"],"contextWindow":8192,"reasoningEfforts":{"high":"high"}}]}}}}'],
["mcode", ["custom_provider", "opencodex"], '{"custom_provider":{"opencodex":{"name":"OpenCodex","kind":"custom","enabled":true,"api":"anthropic-messages","options":{"apiKey":"opencodex-loopback","baseURL":"http://127.0.0.1:10100","authMode":"api-key"},"models":{"test/known":{"limit":{"context":8192},"thinking":{"effortOptions":["high"]}}}}}}'],
["zcode", ["provider", "opencodex"], '{"provider":{"opencodex":{"name":"OpenCodex","kind":"openai-compatible","enabled":true,"source":"custom","options":{"apiKey":"opencodex-loopback","baseURL":"http://127.0.0.1:10100/v1","apiKeyRequired":true},"models":{"test/known":{"name":"known (test)","modalities":{"input":["text","image"],"output":["text"]},"limit":{"context":8192}}}}}}'],
["zcode", ["provider", "opencodex"], '{"provider":{"opencodex":{"name":"OpenCodex","kind":"openai-compatible","enabled":true,"source":"custom","options":{"apiKey":"opencodex-loopback","baseURL":"http://127.0.0.1:10100/v1","apiKeyRequired":true},"models":{"test/known":{"name":"known (test)","modalities":{"input":["text","image"],"output":["text"]},"limit":{"context":8192},"reasoning":{"enabled":true,"variants":["high"]}}}}}}'],
] as const;
for (const [id, path, expectedBytes] of cases) {
const built = buildClientConfigText(id, context);
Expand Down
59 changes: 58 additions & 1 deletion tests/providers/zcode-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,64 @@ describe("ZCode client config", () => {
});
});

describe("ZCode reasoning export", () => {
test("emits on-disk variants for thought-capable models and omits them otherwise", () => {
const document = buildClientConfig("zcode", {
...context(),
models: [
{
namespaced: "google-antigravity/gemini-3.8-flash",
provider: "google-antigravity",
id: "gemini-3.8-flash",
contextWindow: 1_048_576,
inputModalities: ["text", "image"],
reasoningEfforts: ["low", "medium", "high", "max"],
defaultReasoningEffort: "medium",
},
{
namespaced: "CommandCode/meituan-LongCat-2.0:free",
provider: "CommandCode",
id: "meituan-LongCat-2.0:free",
contextWindow: 128_000,
},
],
}) as ZcodeGeneratedConfig;
const models = document.provider[OPENCODE_PROVIDER_ID]!.models;
expect(models["google-antigravity/gemini-3.8-flash"]).toEqual({
name: "gemini-3.8-flash (google-antigravity)",
modalities: { input: ["text", "image"], output: ["text"] },
limit: { context: 1_048_576 },
reasoning: { enabled: true, variants: ["low", "medium", "high", "max"], defaultVariant: "medium" },
});
expect(models["CommandCode/meituan-LongCat-2.0:free"]).not.toHaveProperty("reasoning");
});

test("drops none, keeps ultra, and omits defaultVariant when it is not in the emitted ladder", () => {
const document = buildClientConfig("zcode", {
...context(),
models: [
{
namespaced: "opencode-go/muse-spark-1.3-contributor",
provider: "opencode-go",
id: "muse-spark-1.3-contributor",
reasoningEfforts: ["none", "high", "ultra", "turbo"],
defaultReasoningEffort: "none",
},
{ namespaced: "a/none-only", provider: "a", id: "none-only", reasoningEfforts: ["none"] },
{ namespaced: "a/empty", provider: "a", id: "empty", reasoningEfforts: [] },
],
}) as ZcodeGeneratedConfig;
const models = document.provider[OPENCODE_PROVIDER_ID]!.models;
expect(models["opencode-go/muse-spark-1.3-contributor"]).toEqual({
name: "muse-spark-1.3-contributor (opencode-go)",
modalities: { input: ["text"], output: ["text"] },
reasoning: { enabled: true, variants: ["high", "ultra"] },
});
expect(models["a/none-only"]).not.toHaveProperty("reasoning");
expect(models["a/empty"]).not.toHaveProperty("reasoning");
});
});

describe("ocx zcode CLI alias", () => {
/** Captures the client-integration requests the alias forwards. */
function fakeRuntime(): { deps: { baseUrl: string; fetchImpl: typeof fetch }; requests: Array<{ path: string; method: string; body: unknown }> } {
Expand Down Expand Up @@ -149,4 +207,3 @@ describe("ocx zcode CLI alias", () => {
expect(requests.some(r => r.path === "/api/client-integrations/restore" && r.method === "POST" && (r.body as { opId?: string }).opId === "op-1")).toBe(true);
});
});

Loading