Merge pull request #1702 from vamsi-ship-it/fix/anthropic-compatible-custom-provider-api-key
fix(engine): map anthropic-compatible custom providers to anthropic-messages api
This commit is contained in:
7
.changeset/fix-anthropic-compatible-custom-provider.md
Normal file
7
.changeset/fix-anthropic-compatible-custom-provider.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Fix anthropic-compatible custom providers failing with "No API provider registered for api: anthropic".
|
||||
|
||||
`resolveCustomProviderApiType` mapped the `anthropic-compatible` provider type to the api key `"anthropic"`, but pi-ai registers the Anthropic Messages API under `"anthropic-messages"`. Any custom provider configured as `anthropic-compatible` (self-hosted Claude proxy, gateway, etc.) therefore selected a model whose `api` did not match a registered provider and threw at stream time. Mapped it to `"anthropic-messages"` and added a regression assertion alongside the existing openai-compatible / openai-responses coverage.
|
||||
@@ -1363,6 +1363,14 @@ describe("createFnAgent", () => {
|
||||
apiKey: "RESPONSES_API_KEY",
|
||||
models: [{ id: "responses-model", name: "Responses Model" }],
|
||||
},
|
||||
{
|
||||
id: "770e8400-e29b-41d4-a716-446655440002",
|
||||
name: "Custom Anthropic",
|
||||
apiType: "anthropic-compatible",
|
||||
baseUrl: "https://anthropic.example",
|
||||
apiKey: "ANTHROPIC_API_KEY",
|
||||
models: [{ id: "anthropic-model", name: "Anthropic Model" }],
|
||||
},
|
||||
] as any);
|
||||
|
||||
const { createFnAgent } = await import("../pi.js");
|
||||
@@ -1387,6 +1395,40 @@ describe("createFnAgent", () => {
|
||||
apiKey: "RESPONSES_API_KEY",
|
||||
models: [expect.objectContaining({ id: "responses-model", name: "Responses Model" })],
|
||||
}));
|
||||
/*
|
||||
FNXC:CustomProviders 2026-06-21-13:45:
|
||||
Invariant (FN-5893 surface = providers/execution paths): every custom-provider apiType must map to an api key pi-ai's registry actually registers. anthropic-compatible maps to "anthropic-messages", NOT bare "anthropic" — the latter registered fine but threw "No API provider registered for api: anthropic" the moment a task streamed. Assert the corrected value AND that the broken bare key is never used, so a future regression to "anthropic" fails here.
|
||||
*/
|
||||
expect(registerProviderMock).toHaveBeenCalledWith("custom-anthropic", expect.objectContaining({
|
||||
baseUrl: "https://anthropic.example",
|
||||
api: "anthropic-messages",
|
||||
apiKey: "ANTHROPIC_API_KEY",
|
||||
models: [expect.objectContaining({ id: "anthropic-model", name: "Anthropic Model" })],
|
||||
}));
|
||||
// Negative guard: the unregistered bare "anthropic" api key must never be emitted for any provider.
|
||||
expect(registerProviderMock).not.toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ api: "anthropic" }),
|
||||
);
|
||||
// Invariant: every api key handed to registerProvider must be one pi-ai registers
|
||||
// (mirrors @earendil-works/pi-ai register-builtins). Catches a typo in any arm.
|
||||
const PI_AI_REGISTERED_APIS = new Set([
|
||||
"anthropic-messages",
|
||||
"openai-completions",
|
||||
"openai-responses",
|
||||
"azure-openai-responses",
|
||||
"openai-codex-responses",
|
||||
"google-generative-ai",
|
||||
"google-vertex",
|
||||
"mistral-conversations",
|
||||
"bedrock-converse-stream",
|
||||
]);
|
||||
for (const [, config] of registerProviderMock.mock.calls) {
|
||||
const api = (config as { api?: string } | undefined)?.api;
|
||||
if (typeof api === "string") {
|
||||
expect(PI_AI_REGISTERED_APIS.has(api)).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("avoids lock-based SettingsManager.create when loading extension providers", async () => {
|
||||
|
||||
@@ -1006,9 +1006,23 @@ export interface AgentOptions {
|
||||
permanentAgentGating?: PermanentAgentGatingContext;
|
||||
}
|
||||
|
||||
function resolveCustomProviderApiType(apiType: string): "anthropic" | "openai-responses" | "openai-completions" {
|
||||
/**
|
||||
* Map a user-facing custom-provider `apiType` to the pi-ai api-registry key.
|
||||
*
|
||||
* FNXC:CustomProviders 2026-06-21-13:45:
|
||||
* Every arm must return a key that pi-ai's api-registry actually registers
|
||||
* (see @earendil-works/pi-ai register-builtins). `anthropic-compatible` resolves
|
||||
* to "anthropic-messages" — the key the Anthropic Messages API is registered
|
||||
* under. The bare "anthropic" key is never registered, so returning it let a
|
||||
* provider register but threw "No API provider registered for api: anthropic"
|
||||
* the moment a task tried to stream.
|
||||
*
|
||||
* @param apiType - the custom provider's declared compatibility type.
|
||||
* @returns the registered pi-ai api key to stream against.
|
||||
*/
|
||||
function resolveCustomProviderApiType(apiType: string): "anthropic-messages" | "openai-responses" | "openai-completions" {
|
||||
if (apiType === "anthropic-compatible") {
|
||||
return "anthropic";
|
||||
return "anthropic-messages";
|
||||
}
|
||||
if (apiType === "openai-responses") {
|
||||
return "openai-responses";
|
||||
|
||||
Reference in New Issue
Block a user