Restore model-default variant selection (#3281)

* fix(providers): restore implicit model variant selection

Provider catalogs list only explicit overrides, so treating the first override as the default made base model behavior unreachable. Represent the base choice in the catalog and translate it back to an omitted variant at the provider boundary.

* test(providers): exercise variant catalog boundary

The regression concerns the model catalog callers receive, so verify normalization through fetchCatalog instead of coupling the test to its internal builder.
This commit is contained in:
Mohamed Boudra
2026-08-12 19:54:06 +02:00
committed by GitHub
parent 8e916fd2dc
commit 635a8be437
2 changed files with 65 additions and 14 deletions
@@ -480,6 +480,7 @@ describe("OpenCodeAgentClient adapter smoke tests", () => {
models: {
"big-pickle": {
name: "Big Pickle",
variants: { max: { reasoningEffort: "max" } },
limit: {
context: 200_000,
},
@@ -529,6 +530,11 @@ describe("OpenCodeAgentClient adapter smoke tests", () => {
expect(catalog.models[0]).toMatchObject({
id: TEST_MODEL,
label: "Big Pickle",
thinkingOptions: [
{ id: "default", label: "Default", isDefault: true },
{ id: "max", label: "max" },
],
defaultThinkingOptionId: "default",
metadata: {
providerId: "opencode",
modelId: "big-pickle",
@@ -824,7 +830,40 @@ describe("OpenCodeAgentClient adapter smoke tests", () => {
}, 180_000);
});
describe("OpenCode adapter context-window normalization", () => {
describe("OpenCode adapter normalization", () => {
test("omits OpenCode's implicit default variant from new and updated sessions", async () => {
const runtime = new TestOpenCodeHarness();
const openCode = new TestOpenCodeClient();
openCode.sessionCreateResponse = { data: { id: "ses_default_variant" } };
openCode.sessionPromptAsyncEvents = [
{ type: "session.idle", properties: { sessionID: "ses_default_variant" } },
];
runtime.enqueueClient(openCode);
const client = new OpenCodeAgentClient(createTestLogger(), undefined, {
serverManager: runtime,
createClient: runtime.createClient,
});
const session = await client.createSession({
provider: "opencode",
cwd: "/workspace/repo",
model: "catalog-provider/single-variant-model",
thinkingOptionId: "default",
});
await collectTurnEvents(streamSession(session, "Use the model default"));
await session.setThinkingOption?.("max");
await collectTurnEvents(streamSession(session, "Use max"));
await session.setThinkingOption?.("default");
await collectTurnEvents(streamSession(session, "Return to the model default"));
expect(openCode.calls.sessionPromptAsync).toEqual([
expect.not.objectContaining({ variant: expect.anything() }),
expect.objectContaining({ variant: "max" }),
expect.not.objectContaining({ variant: expect.anything() }),
]);
await session.close();
});
test("builds OpenCode file parts for image prompt blocks", () => {
expect(
__openCodeInternals.buildOpenCodePromptParts([
@@ -111,6 +111,7 @@ const OPENCODE_CAPABILITIES: AgentCapabilityFlags = {
const OPENCODE_BUILD_MODE_ID = "build";
const OPENCODE_LEGACY_FULL_ACCESS_MODE_ID = "full-access";
const OPENCODE_DEFAULT_VARIANT_ID = "default";
const OPENCODE_AUTO_ACCEPT_FEATURE_ID = "auto_accept";
const OPENCODE_PERSISTED_SESSION_LIMIT = 200;
const OPENCODE_PENDING_ABORT_START_TIMEOUT_MS = 10_000;
@@ -580,6 +581,14 @@ function normalizeOpenCodeModeId(modeId: string | null | undefined): string | nu
return trimmed;
}
function normalizeOpenCodeVariantId(variantId: string | null | undefined): string | null {
const trimmed = typeof variantId === "string" ? variantId.trim() : "";
if (!trimmed || trimmed === OPENCODE_DEFAULT_VARIANT_ID) {
return null;
}
return trimmed;
}
function resolveOpenCodeRuntimeAgentId(modeId: string | null | undefined): string | undefined {
const normalizedModeId = normalizeOpenCodeModeId(modeId);
if (normalizedModeId === null) {
@@ -591,15 +600,19 @@ function resolveOpenCodeRuntimeAgentId(modeId: string | null | undefined): strin
}
function normalizeOpenCodeConfig(config: OpenCodeAgentConfig): OpenCodeAgentConfig {
if (normalizeOpenCodeModeId(config.modeId) !== OPENCODE_LEGACY_FULL_ACCESS_MODE_ID) {
return { ...config };
const normalized = {
...config,
thinkingOptionId: normalizeOpenCodeVariantId(config.thinkingOptionId) ?? undefined,
};
if (normalizeOpenCodeModeId(normalized.modeId) !== OPENCODE_LEGACY_FULL_ACCESS_MODE_ID) {
return normalized;
}
return {
...config,
...normalized,
modeId: OPENCODE_BUILD_MODE_ID,
featureValues: {
...config.featureValues,
...normalized.featureValues,
[OPENCODE_AUTO_ACCEPT_FEATURE_ID]: true,
},
};
@@ -723,11 +736,13 @@ function buildOpenCodeModelDefinition(
},
): AgentModelDefinition {
const rawVariants = model.variants ? Object.keys(model.variants) : [];
const thinkingOptions = rawVariants.map((id, index) => ({
id,
label: id,
isDefault: index === 0,
}));
// OpenCode lists only overrides; its base model behavior is selected by omitting `variant`.
const thinkingOptions = rawVariants.length
? [
{ id: OPENCODE_DEFAULT_VARIANT_ID, label: "Default", isDefault: true },
...rawVariants.map((id) => ({ id, label: id })),
]
: [];
return {
provider: "opencode",
@@ -3195,10 +3210,7 @@ class OpenCodeAgentSession implements AgentSession {
}
async setThinkingOption(thinkingOptionId: string | null): Promise<void> {
const normalizedThinkingOptionId =
typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
? thinkingOptionId
: null;
const normalizedThinkingOptionId = normalizeOpenCodeVariantId(thinkingOptionId);
this.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
}