|
|
|
@@ -992,7 +992,9 @@ describe("resolveModelOverride", () => {
|
|
|
|
|
});
|
|
|
|
|
describe("resolveCliModel", () => {
|
|
|
|
|
test("resolves --model provider/id without --provider", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "openai/gpt-4o",
|
|
|
|
@@ -1006,9 +1008,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
|
|
|
|
|
test("prefers an authenticated provider for an unqualified exact model id", () => {
|
|
|
|
|
const availableModels = openaiGpt55Models.filter(model => model.provider === "openai-codex");
|
|
|
|
|
const registry = {
|
|
|
|
|
getAll: () => openaiGpt55Models,
|
|
|
|
|
};
|
|
|
|
|
const registry = { getAll: () => openaiGpt55Models, getAvailable: () => openaiGpt55Models };
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "gpt-5.5",
|
|
|
|
@@ -1043,7 +1043,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "openai/gpt-oss-120b",
|
|
|
|
|
modelRegistry: { getAll: () => catalog },
|
|
|
|
|
modelRegistry: { getAll: () => catalog, getAvailable: () => [authenticated] },
|
|
|
|
|
availableModels: [authenticated],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@@ -1053,7 +1053,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("resolves bare configured role names from --model", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: { task: "openai/gpt-4o" },
|
|
|
|
|
});
|
|
|
|
@@ -1070,7 +1070,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("resolves bare configured role names with thinking suffixes", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: { task: "anthropic/claude-sonnet-4-5" },
|
|
|
|
|
});
|
|
|
|
@@ -1088,7 +1088,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("preserves configured role fallback selectors for deferred resolution", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: {
|
|
|
|
|
task: "openrouter/z-ai/glm-4.7@cerebras,anthropic/claude-sonnet-4-5",
|
|
|
|
@@ -1105,7 +1105,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("reports when a configured role matches after unresolved candidates", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: {
|
|
|
|
|
task: "runtime-provider/runtime-model,anthropic/claude-sonnet-4-5",
|
|
|
|
@@ -1124,7 +1124,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("does not fuzzy-match unresolved configured roles", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: { sonnet: "runtime-provider/runtime-model" },
|
|
|
|
|
});
|
|
|
|
@@ -1141,7 +1141,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("keeps unknown --model names on the not-found path", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "not-a-model",
|
|
|
|
@@ -1165,7 +1165,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
contextWindow: 128000,
|
|
|
|
|
maxTokens: 4096,
|
|
|
|
|
});
|
|
|
|
|
const registry = { getAll: () => [...allModels, exactModel] };
|
|
|
|
|
const registry = { getAll: () => [...allModels, exactModel], getAvailable: () => [...allModels, exactModel] };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: { task: "anthropic/claude-sonnet-4-5" },
|
|
|
|
|
});
|
|
|
|
@@ -1206,7 +1206,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
contextWindow: 128000,
|
|
|
|
|
maxTokens: 4096,
|
|
|
|
|
});
|
|
|
|
|
const registry = { getAll: () => [...allModels, cursorDefault] };
|
|
|
|
|
const registry = { getAll: () => [...allModels, cursorDefault], getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: { default: "openai/gpt-4o" },
|
|
|
|
|
});
|
|
|
|
@@ -1215,8 +1215,6 @@ describe("resolveCliModel", () => {
|
|
|
|
|
cliModel: "default",
|
|
|
|
|
modelRegistry: registry,
|
|
|
|
|
settings,
|
|
|
|
|
// Authenticated set omits the catalog-only Cursor model.
|
|
|
|
|
availableModels: allModels,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result.error).toBeUndefined();
|
|
|
|
@@ -1240,14 +1238,13 @@ describe("resolveCliModel", () => {
|
|
|
|
|
contextWindow: 128000,
|
|
|
|
|
maxTokens: 4096,
|
|
|
|
|
});
|
|
|
|
|
const registry = { getAll: () => [...allModels, cursorDefault] };
|
|
|
|
|
const registry = { getAll: () => [...allModels, cursorDefault], getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({ modelRoles: {} });
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "default",
|
|
|
|
|
modelRegistry: registry,
|
|
|
|
|
settings,
|
|
|
|
|
availableModels: allModels,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result.model?.provider).toBe("cursor");
|
|
|
|
@@ -1255,7 +1252,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("resolves configured custom, legacy, and default role aliases from --model", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: {
|
|
|
|
|
default: "openai/gpt-4o",
|
|
|
|
@@ -1289,7 +1286,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("splits thinking suffixes and abbreviations off the * default alias", () => {
|
|
|
|
|
const registry = { getAll: () => allModels };
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels };
|
|
|
|
|
const settings = Settings.isolated({
|
|
|
|
|
modelRoles: { default: "anthropic/claude-sonnet-4-5" },
|
|
|
|
|
});
|
|
|
|
@@ -1315,7 +1312,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("resolves fuzzy patterns within an explicit provider", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliProvider: "openai",
|
|
|
|
@@ -1329,7 +1328,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("supports --model <pattern>:<thinking> (without explicit --thinking)", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "sonnet:high",
|
|
|
|
@@ -1342,7 +1343,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("prefers exact model id match over provider inference (OpenRouter-style ids)", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "openai/gpt-4o:extended",
|
|
|
|
@@ -1355,7 +1358,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("does not strip invalid :suffix as thinking level in --model (fail fast)", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliProvider: "openai",
|
|
|
|
@@ -1368,7 +1373,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("supports provider-prefixed OpenRouter route suffixes even when the base model is cataloged without them", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "openrouter/z-ai/glm-4.7-20251222:nitro",
|
|
|
|
@@ -1381,7 +1388,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("supports explicit OpenRouter provider with route suffixes that are not in the catalog", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliProvider: "openrouter",
|
|
|
|
@@ -1401,12 +1410,12 @@ describe("resolveCliModel", () => {
|
|
|
|
|
const baseResult = resolveCliModel({
|
|
|
|
|
cliProvider: "amazon-bedrock",
|
|
|
|
|
cliModel: profileArn,
|
|
|
|
|
modelRegistry: { getAll: () => [defaultBedrockModel] },
|
|
|
|
|
modelRegistry: { getAll: () => [defaultBedrockModel], getAvailable: () => [defaultBedrockModel] },
|
|
|
|
|
});
|
|
|
|
|
const offResult = resolveCliModel({
|
|
|
|
|
cliProvider: "amazon-bedrock",
|
|
|
|
|
cliModel: `${profileArn}:off`,
|
|
|
|
|
modelRegistry: { getAll: () => [defaultBedrockModel] },
|
|
|
|
|
modelRegistry: { getAll: () => [defaultBedrockModel], getAvailable: () => [defaultBedrockModel] },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(baseResult.error).toBeUndefined();
|
|
|
|
@@ -1425,7 +1434,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("returns a clear error when there are no models", () => {
|
|
|
|
|
const registry = { getAll: () => [] } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => [], getAvailable: () => [] } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliProvider: "openai",
|
|
|
|
@@ -1438,7 +1449,9 @@ describe("resolveCliModel", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("resolves provider-prefixed fuzzy patterns (openrouter/qwen -> openrouter model)", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
|
const result = resolveCliModel({
|
|
|
|
|
cliModel: "openrouter/qwen",
|
|
|
|
@@ -1479,7 +1492,7 @@ describe("resolveCliModel", () => {
|
|
|
|
|
maxTokens: 4096,
|
|
|
|
|
}),
|
|
|
|
|
];
|
|
|
|
|
const registry = { getAll: () => ambiguousModels } as unknown as Parameters<
|
|
|
|
|
const registry = { getAll: () => ambiguousModels, getAvailable: () => ambiguousModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
|
|
|
|
@@ -1843,7 +1856,9 @@ describe("provider routing selector (@upstream)", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("resolveCliModel round-trips @upstream in the selector and carries compat", () => {
|
|
|
|
|
const registry = { getAll: () => allModels } as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
|
|
|
|
const registry = { getAll: () => allModels, getAvailable: () => allModels } as unknown as Parameters<
|
|
|
|
|
typeof resolveCliModel
|
|
|
|
|
>[0]["modelRegistry"];
|
|
|
|
|
const result = resolveCliModel({ cliModel: "openrouter/z-ai/glm-4.7@cerebras", modelRegistry: registry });
|
|
|
|
|
expect(result.model?.id).toBe("z-ai/glm-4.7");
|
|
|
|
|
expect(result.selector).toBe("openrouter/z-ai/glm-4.7@cerebras");
|
|
|
|
|