feat(ai,coding-agent): add faux provider and ModelRegistry factories
This commit is contained in:
@@ -94,7 +94,7 @@ describe("ModelRegistry", () => {
|
||||
anthropic: overrideConfig("https://my-proxy.example.com/v1"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const anthropicModels = getModelsForProvider(registry, "anthropic");
|
||||
|
||||
// Should have multiple built-in models, not just one
|
||||
@@ -107,7 +107,7 @@ describe("ModelRegistry", () => {
|
||||
anthropic: overrideConfig("https://my-proxy.example.com/v1"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const anthropicModels = getModelsForProvider(registry, "anthropic");
|
||||
|
||||
// All models should have the new baseUrl
|
||||
@@ -123,7 +123,7 @@ describe("ModelRegistry", () => {
|
||||
}),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const anthropicModels = getModelsForProvider(registry, "anthropic");
|
||||
|
||||
for (const model of anthropicModels) {
|
||||
@@ -140,7 +140,7 @@ describe("ModelRegistry", () => {
|
||||
anthropic: overrideConfig("https://my-proxy.example.com/v1"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const googleModels = getModelsForProvider(registry, "google");
|
||||
|
||||
// Google models should still have their original baseUrl
|
||||
@@ -160,7 +160,7 @@ describe("ModelRegistry", () => {
|
||||
),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
// Anthropic: multiple built-in models with new baseUrl
|
||||
const anthropicModels = getModelsForProvider(registry, "anthropic");
|
||||
@@ -177,7 +177,7 @@ describe("ModelRegistry", () => {
|
||||
writeRawModelsJson({
|
||||
anthropic: overrideConfig("https://first-proxy.example.com/v1"),
|
||||
});
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
expect(getModelsForProvider(registry, "anthropic")[0].baseUrl).toBe("https://first-proxy.example.com/v1");
|
||||
|
||||
@@ -197,7 +197,7 @@ describe("ModelRegistry", () => {
|
||||
anthropic: providerConfig("https://my-proxy.example.com/v1", [{ id: "claude-custom" }]),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const anthropicModels = getModelsForProvider(registry, "anthropic");
|
||||
|
||||
expect(anthropicModels.length).toBeGreaterThan(1);
|
||||
@@ -214,7 +214,7 @@ describe("ModelRegistry", () => {
|
||||
),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
const sonnetModels = models.filter((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
|
||||
@@ -227,7 +227,7 @@ describe("ModelRegistry", () => {
|
||||
anthropic: providerConfig("https://my-proxy.example.com/v1", [{ id: "claude-custom" }]),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
expect(getModelsForProvider(registry, "google").length).toBeGreaterThan(0);
|
||||
expect(getModelsForProvider(registry, "openai").length).toBeGreaterThan(0);
|
||||
@@ -238,7 +238,7 @@ describe("ModelRegistry", () => {
|
||||
anthropic: providerConfig("https://merged-proxy.example.com/v1", [{ id: "claude-custom" }]),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const anthropicModels = getModelsForProvider(registry, "anthropic");
|
||||
|
||||
for (const model of anthropicModels) {
|
||||
@@ -269,7 +269,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const compat = registry.find("demo", "demo-model")?.compat as OpenAICompletionsCompat | undefined;
|
||||
|
||||
expect(compat?.supportsUsageInStreaming).toBe(false);
|
||||
@@ -303,7 +303,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const compat = registry.find("demo", "demo-model")?.compat as OpenAICompletionsCompat | undefined;
|
||||
|
||||
expect(compat?.supportsUsageInStreaming).toBe(true);
|
||||
@@ -320,7 +320,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
@@ -357,7 +357,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const compat = registry.find("demo", "demo-model")?.compat as OpenAICompletionsCompat | undefined;
|
||||
|
||||
expect(registry.getError()).toBeUndefined();
|
||||
@@ -394,7 +394,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const m25 = registry.find("opencode-go", "minimax-m2.5");
|
||||
const glm5 = registry.find("opencode-go", "glm-5");
|
||||
|
||||
@@ -427,7 +427,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
|
||||
expect(models.some((m) => m.id === "custom/openrouter-model")).toBe(true);
|
||||
@@ -440,7 +440,7 @@ describe("ModelRegistry", () => {
|
||||
writeModelsJson({
|
||||
anthropic: providerConfig("https://first-proxy.example.com/v1", [{ id: "claude-custom" }]),
|
||||
});
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
expect(getModelsForProvider(registry, "anthropic").some((m) => m.id === "claude-custom")).toBe(true);
|
||||
|
||||
// Update and refresh
|
||||
@@ -459,7 +459,7 @@ describe("ModelRegistry", () => {
|
||||
writeModelsJson({
|
||||
anthropic: providerConfig("https://proxy.example.com/v1", [{ id: "claude-custom" }]),
|
||||
});
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
expect(getModelsForProvider(registry, "anthropic").some((m) => m.id === "claude-custom")).toBe(true);
|
||||
|
||||
// Remove custom models and refresh
|
||||
@@ -484,7 +484,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
@@ -508,7 +508,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
@@ -529,7 +529,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
|
||||
@@ -552,7 +552,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
@@ -576,7 +576,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
|
||||
@@ -601,7 +601,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
|
||||
// Should not create a new model
|
||||
@@ -621,7 +621,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
|
||||
@@ -642,7 +642,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const models = getModelsForProvider(registry, "openrouter");
|
||||
const sonnet = models.find((m) => m.id === "anthropic/claude-sonnet-4");
|
||||
expect(sonnet).toBeDefined();
|
||||
@@ -665,7 +665,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
expect(
|
||||
getModelsForProvider(registry, "openrouter").find((m) => m.id === "anthropic/claude-sonnet-4")?.name,
|
||||
).toBe("First Name");
|
||||
@@ -698,7 +698,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const customName = getModelsForProvider(registry, "openrouter").find(
|
||||
(m) => m.id === "anthropic/claude-sonnet-4",
|
||||
)?.name;
|
||||
@@ -717,7 +717,7 @@ describe("ModelRegistry", () => {
|
||||
|
||||
describe("dynamic provider lifecycle", () => {
|
||||
test("failed registerProvider does not persist invalid streamSimple config", () => {
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
expect(() =>
|
||||
registry.registerProvider("broken-provider", {
|
||||
@@ -731,7 +731,7 @@ describe("ModelRegistry", () => {
|
||||
});
|
||||
|
||||
test("failed registerProvider does not remove existing provider models", () => {
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
registry.registerProvider("demo-provider", {
|
||||
baseUrl: "https://provider.test/v1",
|
||||
@@ -776,7 +776,7 @@ describe("ModelRegistry", () => {
|
||||
});
|
||||
|
||||
test("unregisterProvider removes custom OAuth provider and restores built-in OAuth provider", () => {
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
registry.registerProvider("anthropic", {
|
||||
oauth: {
|
||||
@@ -799,7 +799,7 @@ describe("ModelRegistry", () => {
|
||||
});
|
||||
|
||||
test("unregisterProvider removes custom streamSimple override and restores built-in API stream handler", () => {
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
registry.registerProvider("stream-override-provider", {
|
||||
api: "openai-completions",
|
||||
@@ -855,7 +855,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!echo test-api-key-from-command"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBe("test-api-key-from-command");
|
||||
@@ -866,7 +866,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!echo ' spaced-key '"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBe("spaced-key");
|
||||
@@ -877,7 +877,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!printf 'line1\\nline2'"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBe("line1\nline2");
|
||||
@@ -888,7 +888,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!exit 1"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBeUndefined();
|
||||
@@ -899,7 +899,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!nonexistent-command-12345"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBeUndefined();
|
||||
@@ -910,7 +910,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!printf ''"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBeUndefined();
|
||||
@@ -925,7 +925,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("TEST_API_KEY_12345"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBe("env-api-key-value");
|
||||
@@ -946,7 +946,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("literal_api_key_value"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBe("literal_api_key_value");
|
||||
@@ -957,7 +957,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey("!echo 'hello world' | tr ' ' '-'"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const apiKey = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
expect(apiKey).toBe("hello-world");
|
||||
@@ -974,7 +974,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey(command),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
await registry.getApiKeyForProvider("custom-provider");
|
||||
await registry.getApiKeyForProvider("custom-provider");
|
||||
await registry.getApiKeyForProvider("custom-provider");
|
||||
@@ -993,10 +993,10 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey(command),
|
||||
});
|
||||
|
||||
const registry1 = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry1 = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
await registry1.getApiKeyForProvider("custom-provider");
|
||||
|
||||
const registry2 = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry2 = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
await registry2.getApiKeyForProvider("custom-provider");
|
||||
|
||||
const count = parseInt(readFileSync(counterFile, "utf-8").trim(), 10);
|
||||
@@ -1009,7 +1009,7 @@ describe("ModelRegistry", () => {
|
||||
"provider-b": providerWithApiKey("!echo key-b"),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
const keyA = await registry.getApiKeyForProvider("provider-a");
|
||||
const keyB = await registry.getApiKeyForProvider("provider-b");
|
||||
@@ -1028,7 +1028,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey(command),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const key1 = await registry.getApiKeyForProvider("custom-provider");
|
||||
const key2 = await registry.getApiKeyForProvider("custom-provider");
|
||||
|
||||
@@ -1050,7 +1050,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey(envVarName),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
|
||||
const key1 = await registry.getApiKeyForProvider("custom-provider");
|
||||
expect(key1).toBe("first-value");
|
||||
@@ -1078,7 +1078,7 @@ describe("ModelRegistry", () => {
|
||||
"custom-provider": providerWithApiKey(command),
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const available = registry.getAvailable();
|
||||
|
||||
expect(available.some((m) => m.provider === "custom-provider")).toBe(true);
|
||||
@@ -1098,7 +1098,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const model = registry.find("custom-provider", "test-model");
|
||||
expect(model).toBeDefined();
|
||||
|
||||
@@ -1127,7 +1127,7 @@ describe("ModelRegistry", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const registry = new ModelRegistry(authStorage, modelsJsonPath);
|
||||
const registry = ModelRegistry.create(authStorage, modelsJsonPath);
|
||||
const model = registry.find("custom-provider", "test-model");
|
||||
expect(model).toBeDefined();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user