Merge pull request #5560 from haoqixu/fix-5552

fix(coding-agent): parse :thinking suffix from custom model IDs in fallback path
This commit is contained in:
Mario Zechner
2026-06-10 11:39:52 +02:00
committed by GitHub
3 changed files with 144 additions and 5 deletions

View File

@@ -340,9 +340,10 @@ export interface ResolveCliModelResult {
export function resolveCliModel(options: {
cliProvider?: string;
cliModel?: string;
cliThinking?: string;
modelRegistry: ModelRegistry;
}): ResolveCliModelResult {
const { cliProvider, cliModel, modelRegistry } = options;
const { cliProvider, cliModel, cliThinking, modelRegistry } = options;
if (!cliModel) {
return { model: undefined, warning: undefined, error: undefined };
@@ -451,12 +452,28 @@ export function resolveCliModel(options: {
}
if (provider) {
const fallbackModel = buildFallbackModel(provider, pattern, availableModels);
// Parse thinking level suffix from the pattern before building the fallback model,
// but only when --thinking is not explicitly provided.
// e.g. "zai-org/GLM-5.1-FP8:high" → modelId="zai-org/GLM-5.1-FP8", fallbackThinking="high"
let fallbackPattern = pattern;
let fallbackThinking: ThinkingLevel | undefined;
if (!cliThinking) {
const lastColon = pattern.lastIndexOf(":");
if (lastColon !== -1) {
const suffix = pattern.substring(lastColon + 1);
if (isValidThinkingLevel(suffix)) {
fallbackPattern = pattern.substring(0, lastColon);
fallbackThinking = suffix;
}
}
}
const fallbackModel = buildFallbackModel(provider, fallbackPattern, availableModels);
if (fallbackModel) {
const fallbackWarning = warning
? `${warning} Model "${pattern}" not found for provider "${provider}". Using custom model id.`
: `Model "${pattern}" not found for provider "${provider}". Using custom model id.`;
return { model: fallbackModel, thinkingLevel: undefined, warning: fallbackWarning, error: undefined };
? `${warning} Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`
: `Model "${fallbackPattern}" not found for provider "${provider}". Using custom model id.`;
return { model: fallbackModel, thinkingLevel: fallbackThinking, warning: fallbackWarning, error: undefined };
}
}

View File

@@ -360,6 +360,7 @@ function buildSessionOptions(
const resolved = resolveCliModel({
cliProvider: parsed.provider,
cliModel: parsed.model,
cliThinking: parsed.thinking,
modelRegistry,
});
if (resolved.warning) {

View File

@@ -370,6 +370,127 @@ describe("resolveCliModel", () => {
expect(result.model?.provider).toBe("openrouter");
expect(result.model?.id).toBe("qwen/qwen3-coder:exacto");
});
describe("custom model fallback with :thinking suffix (#5552)", () => {
// Models for a provider that has registered models but the specific model ID
// is not in the registry (triggers buildFallbackModel path).
const neuralwattModel: Model<"anthropic-messages"> = {
id: "some-base-model",
name: "Some Base Model",
api: "anthropic-messages",
provider: "neuralwatt",
baseUrl: "https://api.neuralwatt.com",
reasoning: false,
input: ["text"],
cost: { input: 1, output: 2, cacheRead: 0.1, cacheWrite: 1 },
contextWindow: 128000,
maxTokens: 8192,
};
const modelsWithNeuralwatt = [...allModels, neuralwattModel];
test("strips :thinking suffix from custom model id in fallback path", () => {
const registry = {
getAll: () => modelsWithNeuralwatt,
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
const result = resolveCliModel({
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8:high",
modelRegistry: registry,
});
expect(result.error).toBeUndefined();
expect(result.model?.provider).toBe("neuralwatt");
// The :high suffix must NOT leak into the model id sent to the API
expect(result.model?.id).toBe("zai-org/GLM-5.1-FP8");
expect(result.thinkingLevel).toBe("high");
});
test("custom model without thinking suffix works normally in fallback path", () => {
const registry = {
getAll: () => modelsWithNeuralwatt,
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
const result = resolveCliModel({
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8",
modelRegistry: registry,
});
expect(result.error).toBeUndefined();
expect(result.model?.provider).toBe("neuralwatt");
expect(result.model?.id).toBe("zai-org/GLM-5.1-FP8");
expect(result.thinkingLevel).toBeUndefined();
});
test("all valid thinking levels work in fallback path", () => {
const registry = {
getAll: () => modelsWithNeuralwatt,
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
for (const level of ["off", "minimal", "low", "medium", "high", "xhigh"]) {
const result = resolveCliModel({
cliModel: `neuralwatt/zai-org/GLM-5.1-FP8:${level}`,
modelRegistry: registry,
});
expect(result.error).toBeUndefined();
expect(result.model?.id).toBe("zai-org/GLM-5.1-FP8");
expect(result.thinkingLevel).toBe(level);
}
});
test("invalid thinking suffix on custom model is treated as part of model id", () => {
const registry = {
getAll: () => modelsWithNeuralwatt,
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
const result = resolveCliModel({
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8:banana",
modelRegistry: registry,
});
expect(result.error).toBeUndefined();
expect(result.model?.provider).toBe("neuralwatt");
// Invalid suffix stays in the id (it's not a thinking level)
expect(result.model?.id).toBe("zai-org/GLM-5.1-FP8:banana");
expect(result.thinkingLevel).toBeUndefined();
});
test("explicit --provider with custom model:thinking strips suffix correctly", () => {
const registry = {
getAll: () => modelsWithNeuralwatt,
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
const result = resolveCliModel({
cliProvider: "neuralwatt",
cliModel: "zai-org/GLM-5.1-FP8:high",
modelRegistry: registry,
});
expect(result.error).toBeUndefined();
expect(result.model?.provider).toBe("neuralwatt");
expect(result.model?.id).toBe("zai-org/GLM-5.1-FP8");
expect(result.thinkingLevel).toBe("high");
});
test("with explicit --thinking, :suffix is kept as part of model id", () => {
const registry = {
getAll: () => modelsWithNeuralwatt,
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
const result = resolveCliModel({
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8:high",
cliThinking: "medium",
modelRegistry: registry,
});
expect(result.error).toBeUndefined();
expect(result.model?.provider).toBe("neuralwatt");
// :high is kept as part of the model id since --thinking was explicit
expect(result.model?.id).toBe("zai-org/GLM-5.1-FP8:high");
expect(result.thinkingLevel).toBeUndefined();
});
});
});
describe("default model selection", () => {