diff --git a/docs/ai-gateway-provider-selection.md b/docs/ai-gateway-provider-selection.md index af67fd4f6..f80bd8b4f 100644 --- a/docs/ai-gateway-provider-selection.md +++ b/docs/ai-gateway-provider-selection.md @@ -7,7 +7,7 @@ inference resource has more than one AI provider. - Route → capability binding: `server/routers/aiGateway/createAiGatewayRouter.ts` - Request pipeline: `server/routers/aiGateway/pipeline.ts` (`selectProvider`) -- Model discovery: `server/routers/aiGateway/anthropicModels.ts` and +- Model discovery: `server/routers/aiGateway/v1Models.ts` and `server/lib/aiModelDiscovery.ts` - Tie-break scoring: `server/lib/aiProviderSelection.ts` - Allow/block matching: `server/lib/aiModelKeyMatch.ts` @@ -41,7 +41,7 @@ The incoming path selects a capability before any provider logic runs. | `POST /v1/chat/completions` | `openai_chat` | | `POST /v1/responses` | `openai_responses` | | `POST /v1/messages` | `anthropic_messages` | -| `GET /v1/models`, `GET /v1/models/{id}` | `anthropic_models` | +| `GET /v1/models`, `GET /v1/models/{id}` | `v1_models` | | Gemini / Vertex / Bedrock routes | their respective capability ids | Only attached providers that advertise that capability stay in the candidate @@ -50,10 +50,10 @@ set. Default capabilities do not overlap for native OpenAI vs Anthropic: | Provider type | Default capabilities | |---------------|----------------------| | `openai` | `openai_chat`, `openai_responses` | -| `anthropic` | `anthropic_messages`, `anthropic_models` | +| `anthropic` | `anthropic_messages`, `v1_models` | | `openRouter` | `openai_chat` | | `vercelAiGateway` | `openai_chat`, `openai_responses` | -| `microsoftFoundry` | `openai_chat`, `openai_responses`, `anthropic_messages`, `anthropic_models` | +| `microsoftFoundry` | `openai_chat`, `openai_responses`, `anthropic_messages`, `v1_models` | | `custom` | whatever was configured | ### 2. Allow / Block Lists @@ -133,10 +133,10 @@ customs advertising the same capability for an unknown model. ## Model Discovery Is Not Selection -`GET /v1/models` and `GET /v1/models/{id}` (`anthropic_models`) skip steps 3-6 +`GET /v1/models` and `GET /v1/models/{id}` (`v1_models`) skip steps 3-6 entirely. There is no requested model to disambiguate on, so the gateway does not pick one provider - it returns the **union** of what every attached -provider advertising `anthropic_models` would accept, deduplicated by model id +provider advertising `v1_models` would accept, deduplicated by model id (lowest `providerId` wins a collision). Discovery is answered from the gateway's own view of the allow/block lists, diff --git a/messages/en-US.json b/messages/en-US.json index 8952bbcbc..b6e24adcc 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1923,8 +1923,8 @@ "aiCapabilityOpenaiResponsesDescription": "Supports /v1/responses", "aiCapabilityAnthropicMessages": "Anthropic Messages", "aiCapabilityAnthropicMessagesDescription": "Supports /v1/messages", - "aiCapabilityAnthropicModels": "Anthropic Models", - "aiCapabilityAnthropicModelsDescription": "Supports /v1/models model discovery", + "aiCapabilityV1Models": "Models List", + "aiCapabilityV1ModelsDescription": "Supports /v1/models model discovery", "aiCapabilityGeminiGenerateContent": "Gemini Generate Content", "aiCapabilityGeminiGenerateContentDescription": "Supports the direct Gemini API", "aiCapabilityBedrockModelInvoke": "Bedrock Model Invoke", diff --git a/server/lib/aiCapabilities.ts b/server/lib/aiCapabilities.ts index 15e2595e2..152fa684f 100644 --- a/server/lib/aiCapabilities.ts +++ b/server/lib/aiCapabilities.ts @@ -135,8 +135,8 @@ export const AI_CAPABILITY_DEFS: Record = joinUpstreamUrl(base, pathFromRequest(req)), isStreaming: isBodyOrSseStreaming }, - anthropic_models: { - id: "anthropic_models", + v1_models: { + id: "v1_models", protocolFamily: "anthropic", routes: [ { method: "GET", path: "/v1/models" }, diff --git a/server/lib/aiMessageNormalization.ts b/server/lib/aiMessageNormalization.ts index 4dd8cef8c..bc7ac3cfc 100644 --- a/server/lib/aiMessageNormalization.ts +++ b/server/lib/aiMessageNormalization.ts @@ -472,7 +472,7 @@ const REQUEST_NORMALIZERS: Record< openai_responses: normalizeOpenAiResponsesRequest, anthropic_messages: normalizeAnthropicRequest, // Model discovery carries no transcript to normalize. - anthropic_models: () => null, + v1_models: () => null, gemini_generate_content: normalizeGeminiRequest, google_generate_content: normalizeGeminiRequest, google_raw_predict: normalizeBestEffortRequest, @@ -487,7 +487,7 @@ const RESPONSE_NORMALIZERS: Record< openai_chat: normalizeOpenAiChatResponse, openai_responses: normalizeOpenAiResponsesResponse, anthropic_messages: normalizeAnthropicResponse, - anthropic_models: () => null, + v1_models: () => null, gemini_generate_content: normalizeGeminiResponse, google_generate_content: normalizeGeminiResponse, google_raw_predict: normalizeGoogleRawPredictResponse, diff --git a/server/lib/aiUsageExtraction.ts b/server/lib/aiUsageExtraction.ts index 55c562e3b..0dae1b1ab 100644 --- a/server/lib/aiUsageExtraction.ts +++ b/server/lib/aiUsageExtraction.ts @@ -336,7 +336,7 @@ const EXTRACTORS: Record< openai_responses: extractOpenAiResponses, anthropic_messages: extractAnthropicMessages, // Model discovery never runs a model, so there are no tokens to bill. - anthropic_models: () => null, + v1_models: () => null, gemini_generate_content: extractGoogleGenerateContent, google_generate_content: extractGoogleGenerateContent, // rawPredict is a passthrough to whatever the underlying publisher diff --git a/server/routers/aiGateway/createAiGatewayRouter.ts b/server/routers/aiGateway/createAiGatewayRouter.ts index 3e30dd6fb..0a697faf2 100644 --- a/server/routers/aiGateway/createAiGatewayRouter.ts +++ b/server/routers/aiGateway/createAiGatewayRouter.ts @@ -4,7 +4,7 @@ import { type AiCapability } from "@server/lib/aiCapabilities"; import { handleAiGatewayProxy } from "@server/routers/aiGateway/pipeline"; -import { handleAnthropicModels } from "@server/routers/aiGateway/anthropicModels"; +import { handleV1Models } from "@server/routers/aiGateway"; type CapabilityHandler = ( req: Request, @@ -15,7 +15,7 @@ type CapabilityHandler = ( // Capabilities the gateway answers itself instead of proxying upstream. // Everything else goes through the inference pipeline. const LOCAL_HANDLERS: Partial> = { - anthropic_models: handleAnthropicModels + v1_models: handleV1Models }; export function createAiGatewayRouter() { diff --git a/server/routers/aiGateway/index.ts b/server/routers/aiGateway/index.ts index a10a7aed4..fc219faf6 100644 --- a/server/routers/aiGateway/index.ts +++ b/server/routers/aiGateway/index.ts @@ -1,3 +1,3 @@ export { handleAiGatewayProxy } from "./pipeline"; -export { handleAnthropicModels } from "./anthropicModels"; +export { handleV1Models } from "./v1Models"; export { createAiGatewayRouter } from "./createAiGatewayRouter"; diff --git a/server/routers/aiGateway/anthropicModels.ts b/server/routers/aiGateway/v1Models.ts similarity index 99% rename from server/routers/aiGateway/anthropicModels.ts rename to server/routers/aiGateway/v1Models.ts index 9e15a78de..0d79bc3db 100644 --- a/server/routers/aiGateway/anthropicModels.ts +++ b/server/routers/aiGateway/v1Models.ts @@ -35,7 +35,7 @@ import { import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; -const CAPABILITY: AiCapability = "anthropic_models"; +const CAPABILITY: AiCapability = "v1_models"; const querySchema = z.object({ limit: z.coerce.number().int().min(1).max(MODEL_PAGE_MAX_LIMIT).optional(), @@ -163,7 +163,7 @@ function buildDiscoveryProviders( * list at all or would expose models the resource's allow/block lists forbid, * so the response is built from the same effective lists that gate inference. */ -export async function handleAnthropicModels( +export async function handleV1Models( req: Request, res: Response ): Promise { diff --git a/src/app/[orgId]/settings/logs/ai/page.tsx b/src/app/[orgId]/settings/logs/ai/page.tsx index be043dd30..2499e8a20 100644 --- a/src/app/[orgId]/settings/logs/ai/page.tsx +++ b/src/app/[orgId]/settings/logs/ai/page.tsx @@ -33,7 +33,7 @@ const capabilityLabels: Record = { openai_chat: "OpenAI Chat Completions", openai_responses: "OpenAI Responses", anthropic_messages: "Anthropic Messages", - anthropic_models: "Anthropic Models", + v1_models: "Models List", gemini_generate_content: "Gemini", google_generate_content: "Vertex AI (Generate Content)", google_raw_predict: "Vertex AI (Raw Predict)", diff --git a/src/components/AiProviderCapabilitiesSelect.tsx b/src/components/AiProviderCapabilitiesSelect.tsx index 7383998b4..43c1b5c89 100644 --- a/src/components/AiProviderCapabilitiesSelect.tsx +++ b/src/components/AiProviderCapabilitiesSelect.tsx @@ -20,7 +20,7 @@ const CAPABILITY_LABEL_KEYS: Record = { openai_chat: "aiCapabilityOpenaiChat", openai_responses: "aiCapabilityOpenaiResponses", anthropic_messages: "aiCapabilityAnthropicMessages", - anthropic_models: "aiCapabilityAnthropicModels", + v1_models: "aiCapabilityV1Models", gemini_generate_content: "aiCapabilityGeminiGenerateContent", bedrock_model_invoke: "aiCapabilityBedrockModelInvoke", google_generate_content: "aiCapabilityGoogleGenerateContent", diff --git a/src/lib/aiCapabilities.ts b/src/lib/aiCapabilities.ts index 885cf4a9f..39c9b462a 100644 --- a/src/lib/aiCapabilities.ts +++ b/src/lib/aiCapabilities.ts @@ -2,7 +2,7 @@ export const AI_CAPABILITIES = [ "openai_chat", "openai_responses", "anthropic_messages", - "anthropic_models", + "v1_models", "gemini_generate_content", "bedrock_model_invoke", "google_generate_content", diff --git a/src/lib/aiProviderDefaults.ts b/src/lib/aiProviderDefaults.ts index f30f6dbd1..fd15d1868 100644 --- a/src/lib/aiProviderDefaults.ts +++ b/src/lib/aiProviderDefaults.ts @@ -38,12 +38,12 @@ export const AI_PROVIDER_DEFAULTS: Record< openai: { upstreamUrl: "https://api.openai.com/v1", authType: "bearer", - capabilities: ["openai_chat", "openai_responses"] + capabilities: ["openai_chat", "openai_responses", "v1_models"] }, anthropic: { upstreamUrl: "https://api.anthropic.com", authType: "x-api-key", - capabilities: ["anthropic_messages", "anthropic_models"] + capabilities: ["anthropic_messages", "v1_models"] }, googleGemini: { upstreamUrl: "https://generativelanguage.googleapis.com", @@ -67,7 +67,7 @@ export const AI_PROVIDER_DEFAULTS: Record< "openai_chat", "openai_responses", "anthropic_messages", - "anthropic_models" + "v1_models" ] }, openRouter: {