feat(coding-agent): replace model registry with model runtime
Move provider auth and OAuth flows onto pi-ai Models, compose models.json and extension overlays through ModelRuntime, and retain ModelRegistry as an extension compatibility facade.
This commit is contained in:
@@ -260,12 +260,12 @@ describe("resolveModelScopeWithDiagnostics", () => {
|
||||
describe("resolveCliModel", () => {
|
||||
test("resolves --model provider/id without --provider", () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "openai/gpt-4o",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -275,13 +275,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("resolves fuzzy patterns within an explicit provider", () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliProvider: "openai",
|
||||
cliModel: "4o",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -291,12 +291,12 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("supports --model <pattern>:<thinking> (without explicit --thinking)", () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "sonnet:high",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -306,12 +306,12 @@ 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"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "openai/gpt-4o:extended",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -321,13 +321,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("does not strip invalid :suffix as thinking level in --model (treat as raw id)", () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliProvider: "openai",
|
||||
cliModel: "gpt-4o:extended",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -337,13 +337,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("allows custom model ids for explicit providers without double prefixing", () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliProvider: "openrouter",
|
||||
cliModel: "openrouter/openai/ghost-model",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -353,13 +353,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("returns a clear error when there are no models", () => {
|
||||
const registry = {
|
||||
getAll: () => [],
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => [],
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliProvider: "openai",
|
||||
cliModel: "gpt-4o",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.model).toBeUndefined();
|
||||
@@ -394,13 +394,13 @@ describe("resolveCliModel", () => {
|
||||
maxTokens: 8192,
|
||||
};
|
||||
const registry = {
|
||||
getAll: () => [...allModels, zaiModel, gatewayModel],
|
||||
getModels: () => [...allModels, zaiModel, gatewayModel],
|
||||
hasConfiguredAuth: () => true,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "zai/glm-5",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -434,13 +434,13 @@ describe("resolveCliModel", () => {
|
||||
maxTokens: 8192,
|
||||
};
|
||||
const registry = {
|
||||
getAll: () => [...allModels, commandcodeModel, xiaomiModel],
|
||||
hasConfiguredAuth: (model: Model<"anthropic-messages">) => model.provider === "commandcode",
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => [...allModels, commandcodeModel, xiaomiModel],
|
||||
hasConfiguredAuth: (provider: string) => provider === "commandcode",
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "xiaomi/mimo-v2.5-pro",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -450,12 +450,12 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("resolves provider-prefixed fuzzy patterns (openrouter/qwen -> openrouter model)", () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "openrouter/qwen",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -483,12 +483,12 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("strips :thinking suffix from custom model id in fallback path", () => {
|
||||
const registry = {
|
||||
getAll: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8:high",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -501,12 +501,12 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("custom model without thinking suffix works normally in fallback path", () => {
|
||||
const registry = {
|
||||
getAll: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -517,13 +517,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("all valid thinking levels work in fallback path", () => {
|
||||
const registry = {
|
||||
getAll: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
for (const level of ["off", "minimal", "low", "medium", "high", "xhigh", "max"]) {
|
||||
const result = resolveCliModel({
|
||||
cliModel: `neuralwatt/zai-org/GLM-5.1-FP8:${level}`,
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -534,12 +534,12 @@ describe("resolveCliModel", () => {
|
||||
|
||||
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"];
|
||||
getModels: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8:banana",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -551,13 +551,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("explicit --provider with custom model:thinking strips suffix correctly", () => {
|
||||
const registry = {
|
||||
getAll: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliProvider: "neuralwatt",
|
||||
cliModel: "zai-org/GLM-5.1-FP8:high",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -568,13 +568,13 @@ describe("resolveCliModel", () => {
|
||||
|
||||
test("with explicit --thinking, :suffix is kept as part of model id", () => {
|
||||
const registry = {
|
||||
getAll: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRegistry"];
|
||||
getModels: () => modelsWithNeuralwatt,
|
||||
} as unknown as Parameters<typeof resolveCliModel>[0]["modelRuntime"];
|
||||
|
||||
const result = resolveCliModel({
|
||||
cliModel: "neuralwatt/zai-org/GLM-5.1-FP8:high",
|
||||
cliThinking: "medium",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.error).toBeUndefined();
|
||||
@@ -606,15 +606,15 @@ describe("default model selection", () => {
|
||||
|
||||
test("findInitialModel accepts explicit provider custom model ids", async () => {
|
||||
const registry = {
|
||||
getAll: () => allModels,
|
||||
} as unknown as Parameters<typeof findInitialModel>[0]["modelRegistry"];
|
||||
getModels: () => allModels,
|
||||
} as unknown as Parameters<typeof findInitialModel>[0]["modelRuntime"];
|
||||
|
||||
const result = await findInitialModel({
|
||||
cliProvider: "openrouter",
|
||||
cliModel: "openrouter/openai/ghost-model",
|
||||
scopedModels: [],
|
||||
isContinuing: false,
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.model?.provider).toBe("openrouter");
|
||||
@@ -637,12 +637,12 @@ describe("default model selection", () => {
|
||||
|
||||
const registry = {
|
||||
getAvailable: async () => [aiGatewayModel],
|
||||
} as unknown as Parameters<typeof findInitialModel>[0]["modelRegistry"];
|
||||
} as unknown as Parameters<typeof findInitialModel>[0]["modelRuntime"];
|
||||
|
||||
const result = await findInitialModel({
|
||||
scopedModels: [],
|
||||
isContinuing: false,
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.model?.provider).toBe("vercel-ai-gateway");
|
||||
@@ -668,20 +668,20 @@ describe("default model selection", () => {
|
||||
baseUrl: "http://spark-two:8000/v1",
|
||||
};
|
||||
const registry = {
|
||||
find: (provider: string, modelId: string) =>
|
||||
getModel: (provider: string, modelId: string) =>
|
||||
provider === savedDeepSeekModel.provider && modelId === savedDeepSeekModel.id
|
||||
? savedDeepSeekModel
|
||||
: undefined,
|
||||
hasConfiguredAuth: (model: Model<"anthropic-messages">) => model.provider === "spark-two",
|
||||
hasConfiguredAuth: (provider: string) => provider === "spark-two",
|
||||
getAvailable: async () => [localDeepSeekModel],
|
||||
} as unknown as Parameters<typeof findInitialModel>[0]["modelRegistry"];
|
||||
} as unknown as Parameters<typeof findInitialModel>[0]["modelRuntime"];
|
||||
|
||||
const result = await findInitialModel({
|
||||
scopedModels: [],
|
||||
isContinuing: false,
|
||||
defaultProvider: "deepseek",
|
||||
defaultModelId: "deepseek-v4-flash",
|
||||
modelRegistry: registry,
|
||||
modelRuntime: registry,
|
||||
});
|
||||
|
||||
expect(result.model?.provider).toBe("spark-two");
|
||||
|
||||
Reference in New Issue
Block a user