fix(core): fix OpenRouter model resolution and add fallback for custom models

Registry model IDs used hyphens (claude-sonnet-4-5) but pi-ai expects
dots (claude-sonnet-4.5). Also adds a fallback model config for OpenRouter
models not in pi-ai's registry, since OpenRouter supports thousands of models.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jiayuan Zhang 2026-02-10 23:40:31 +08:00
parent 5c5bf46a95
commit c24ac8a098
2 changed files with 36 additions and 4 deletions

View file

@ -114,8 +114,8 @@ const PROVIDER_REGISTRY: Record<string, ProviderMeta> = {
id: "openrouter",
name: "OpenRouter",
authMethod: "api-key",
defaultModel: "anthropic/claude-sonnet-4-5",
models: ["anthropic/claude-sonnet-4-5", "anthropic/claude-opus-4-5", "openai/gpt-4o"],
defaultModel: "anthropic/claude-sonnet-4.5",
models: ["anthropic/claude-sonnet-4.5", "anthropic/claude-opus-4.5", "openai/gpt-4o"],
loginUrl: "https://openrouter.ai/keys",
},
};

View file

@ -203,6 +203,25 @@ export function resolveApiKeyForProvider(
// Model Resolution
// ============================================================
/**
* Create a fallback OpenRouter model config for models not in pi-ai's registry.
* OpenRouter supports thousands of models; we can't have all of them pre-registered.
*/
function createOpenRouterFallbackModel(modelId: string) {
return {
id: modelId,
name: modelId,
api: "openai-completions" as const,
provider: "openrouter" as const,
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false,
input: ["text" as const],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 16384,
};
}
/**
* Resolve model for pi-ai based on provider and options.
*/
@ -212,10 +231,17 @@ export function resolveModel(options: AgentOptions) {
const actualProvider = PROVIDER_ALIAS[options.provider] ?? options.provider;
// Type assertion needed because provider/model come from dynamic user config
return (getModel as (p: string, m: string) => ReturnType<typeof getModel>)(
const model = (getModel as (p: string, m: string) => ReturnType<typeof getModel> | undefined)(
actualProvider,
options.model,
);
// Fallback for OpenRouter: allow any model ID even if not in pi-ai registry
if (!model && actualProvider === "openrouter") {
return createOpenRouterFallbackModel(options.model);
}
return model;
}
// If only provider specified, use default model for that provider
@ -223,10 +249,16 @@ export function resolveModel(options: AgentOptions) {
const actualProvider = PROVIDER_ALIAS[options.provider] ?? options.provider;
const defaultModel = getDefaultModel(options.provider) ?? getDefaultModel(actualProvider);
if (defaultModel) {
return (getModel as (p: string, m: string) => ReturnType<typeof getModel>)(
const model = (getModel as (p: string, m: string) => ReturnType<typeof getModel> | undefined)(
actualProvider,
defaultModel,
);
if (!model && actualProvider === "openrouter") {
return createOpenRouterFallbackModel(defaultModel);
}
return model;
}
}