Rename to v1_models and use with openai as well

This commit is contained in:
Owen
2026-08-20 16:01:07 -04:00
parent 365a905e69
commit e65a79cc48
12 changed files with 24 additions and 24 deletions
+6 -6
View File
@@ -7,7 +7,7 @@ inference resource has more than one AI provider.
- Route → capability binding: `server/routers/aiGateway/createAiGatewayRouter.ts` - Route → capability binding: `server/routers/aiGateway/createAiGatewayRouter.ts`
- Request pipeline: `server/routers/aiGateway/pipeline.ts` (`selectProvider`) - 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` `server/lib/aiModelDiscovery.ts`
- Tie-break scoring: `server/lib/aiProviderSelection.ts` - Tie-break scoring: `server/lib/aiProviderSelection.ts`
- Allow/block matching: `server/lib/aiModelKeyMatch.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/chat/completions` | `openai_chat` |
| `POST /v1/responses` | `openai_responses` | | `POST /v1/responses` | `openai_responses` |
| `POST /v1/messages` | `anthropic_messages` | | `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 | | Gemini / Vertex / Bedrock routes | their respective capability ids |
Only attached providers that advertise that capability stay in the candidate 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 | | Provider type | Default capabilities |
|---------------|----------------------| |---------------|----------------------|
| `openai` | `openai_chat`, `openai_responses` | | `openai` | `openai_chat`, `openai_responses` |
| `anthropic` | `anthropic_messages`, `anthropic_models` | | `anthropic` | `anthropic_messages`, `v1_models` |
| `openRouter` | `openai_chat` | | `openRouter` | `openai_chat` |
| `vercelAiGateway` | `openai_chat`, `openai_responses` | | `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 | | `custom` | whatever was configured |
### 2. Allow / Block Lists ### 2. Allow / Block Lists
@@ -133,10 +133,10 @@ customs advertising the same capability for an unknown model.
## Model Discovery Is Not Selection ## 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 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 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). (lowest `providerId` wins a collision).
Discovery is answered from the gateway's own view of the allow/block lists, Discovery is answered from the gateway's own view of the allow/block lists,
+2 -2
View File
@@ -1923,8 +1923,8 @@
"aiCapabilityOpenaiResponsesDescription": "Supports /v1/responses", "aiCapabilityOpenaiResponsesDescription": "Supports /v1/responses",
"aiCapabilityAnthropicMessages": "Anthropic Messages", "aiCapabilityAnthropicMessages": "Anthropic Messages",
"aiCapabilityAnthropicMessagesDescription": "Supports /v1/messages", "aiCapabilityAnthropicMessagesDescription": "Supports /v1/messages",
"aiCapabilityAnthropicModels": "Anthropic Models", "aiCapabilityV1Models": "Models List",
"aiCapabilityAnthropicModelsDescription": "Supports /v1/models model discovery", "aiCapabilityV1ModelsDescription": "Supports /v1/models model discovery",
"aiCapabilityGeminiGenerateContent": "Gemini Generate Content", "aiCapabilityGeminiGenerateContent": "Gemini Generate Content",
"aiCapabilityGeminiGenerateContentDescription": "Supports the direct Gemini API", "aiCapabilityGeminiGenerateContentDescription": "Supports the direct Gemini API",
"aiCapabilityBedrockModelInvoke": "Bedrock Model Invoke", "aiCapabilityBedrockModelInvoke": "Bedrock Model Invoke",
+2 -2
View File
@@ -135,8 +135,8 @@ export const AI_CAPABILITY_DEFS: Record<AiCapability, AiCapabilityDefinition> =
joinUpstreamUrl(base, pathFromRequest(req)), joinUpstreamUrl(base, pathFromRequest(req)),
isStreaming: isBodyOrSseStreaming isStreaming: isBodyOrSseStreaming
}, },
anthropic_models: { v1_models: {
id: "anthropic_models", id: "v1_models",
protocolFamily: "anthropic", protocolFamily: "anthropic",
routes: [ routes: [
{ method: "GET", path: "/v1/models" }, { method: "GET", path: "/v1/models" },
+2 -2
View File
@@ -472,7 +472,7 @@ const REQUEST_NORMALIZERS: Record<
openai_responses: normalizeOpenAiResponsesRequest, openai_responses: normalizeOpenAiResponsesRequest,
anthropic_messages: normalizeAnthropicRequest, anthropic_messages: normalizeAnthropicRequest,
// Model discovery carries no transcript to normalize. // Model discovery carries no transcript to normalize.
anthropic_models: () => null, v1_models: () => null,
gemini_generate_content: normalizeGeminiRequest, gemini_generate_content: normalizeGeminiRequest,
google_generate_content: normalizeGeminiRequest, google_generate_content: normalizeGeminiRequest,
google_raw_predict: normalizeBestEffortRequest, google_raw_predict: normalizeBestEffortRequest,
@@ -487,7 +487,7 @@ const RESPONSE_NORMALIZERS: Record<
openai_chat: normalizeOpenAiChatResponse, openai_chat: normalizeOpenAiChatResponse,
openai_responses: normalizeOpenAiResponsesResponse, openai_responses: normalizeOpenAiResponsesResponse,
anthropic_messages: normalizeAnthropicResponse, anthropic_messages: normalizeAnthropicResponse,
anthropic_models: () => null, v1_models: () => null,
gemini_generate_content: normalizeGeminiResponse, gemini_generate_content: normalizeGeminiResponse,
google_generate_content: normalizeGeminiResponse, google_generate_content: normalizeGeminiResponse,
google_raw_predict: normalizeGoogleRawPredictResponse, google_raw_predict: normalizeGoogleRawPredictResponse,
+1 -1
View File
@@ -336,7 +336,7 @@ const EXTRACTORS: Record<
openai_responses: extractOpenAiResponses, openai_responses: extractOpenAiResponses,
anthropic_messages: extractAnthropicMessages, anthropic_messages: extractAnthropicMessages,
// Model discovery never runs a model, so there are no tokens to bill. // Model discovery never runs a model, so there are no tokens to bill.
anthropic_models: () => null, v1_models: () => null,
gemini_generate_content: extractGoogleGenerateContent, gemini_generate_content: extractGoogleGenerateContent,
google_generate_content: extractGoogleGenerateContent, google_generate_content: extractGoogleGenerateContent,
// rawPredict is a passthrough to whatever the underlying publisher // rawPredict is a passthrough to whatever the underlying publisher
@@ -4,7 +4,7 @@ import {
type AiCapability type AiCapability
} from "@server/lib/aiCapabilities"; } from "@server/lib/aiCapabilities";
import { handleAiGatewayProxy } from "@server/routers/aiGateway/pipeline"; import { handleAiGatewayProxy } from "@server/routers/aiGateway/pipeline";
import { handleAnthropicModels } from "@server/routers/aiGateway/anthropicModels"; import { handleV1Models } from "@server/routers/aiGateway";
type CapabilityHandler = ( type CapabilityHandler = (
req: Request, req: Request,
@@ -15,7 +15,7 @@ type CapabilityHandler = (
// Capabilities the gateway answers itself instead of proxying upstream. // Capabilities the gateway answers itself instead of proxying upstream.
// Everything else goes through the inference pipeline. // Everything else goes through the inference pipeline.
const LOCAL_HANDLERS: Partial<Record<AiCapability, CapabilityHandler>> = { const LOCAL_HANDLERS: Partial<Record<AiCapability, CapabilityHandler>> = {
anthropic_models: handleAnthropicModels v1_models: handleV1Models
}; };
export function createAiGatewayRouter() { export function createAiGatewayRouter() {
+1 -1
View File
@@ -1,3 +1,3 @@
export { handleAiGatewayProxy } from "./pipeline"; export { handleAiGatewayProxy } from "./pipeline";
export { handleAnthropicModels } from "./anthropicModels"; export { handleV1Models } from "./v1Models";
export { createAiGatewayRouter } from "./createAiGatewayRouter"; export { createAiGatewayRouter } from "./createAiGatewayRouter";
@@ -35,7 +35,7 @@ import {
import logger from "@server/logger"; import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
const CAPABILITY: AiCapability = "anthropic_models"; const CAPABILITY: AiCapability = "v1_models";
const querySchema = z.object({ const querySchema = z.object({
limit: z.coerce.number().int().min(1).max(MODEL_PAGE_MAX_LIMIT).optional(), 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, * 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. * so the response is built from the same effective lists that gate inference.
*/ */
export async function handleAnthropicModels( export async function handleV1Models(
req: Request, req: Request,
res: Response res: Response
): Promise<any> { ): Promise<any> {
+1 -1
View File
@@ -33,7 +33,7 @@ const capabilityLabels: Record<string, string> = {
openai_chat: "OpenAI Chat Completions", openai_chat: "OpenAI Chat Completions",
openai_responses: "OpenAI Responses", openai_responses: "OpenAI Responses",
anthropic_messages: "Anthropic Messages", anthropic_messages: "Anthropic Messages",
anthropic_models: "Anthropic Models", v1_models: "Models List",
gemini_generate_content: "Gemini", gemini_generate_content: "Gemini",
google_generate_content: "Vertex AI (Generate Content)", google_generate_content: "Vertex AI (Generate Content)",
google_raw_predict: "Vertex AI (Raw Predict)", google_raw_predict: "Vertex AI (Raw Predict)",
@@ -20,7 +20,7 @@ const CAPABILITY_LABEL_KEYS: Record<AiCapability, string> = {
openai_chat: "aiCapabilityOpenaiChat", openai_chat: "aiCapabilityOpenaiChat",
openai_responses: "aiCapabilityOpenaiResponses", openai_responses: "aiCapabilityOpenaiResponses",
anthropic_messages: "aiCapabilityAnthropicMessages", anthropic_messages: "aiCapabilityAnthropicMessages",
anthropic_models: "aiCapabilityAnthropicModels", v1_models: "aiCapabilityV1Models",
gemini_generate_content: "aiCapabilityGeminiGenerateContent", gemini_generate_content: "aiCapabilityGeminiGenerateContent",
bedrock_model_invoke: "aiCapabilityBedrockModelInvoke", bedrock_model_invoke: "aiCapabilityBedrockModelInvoke",
google_generate_content: "aiCapabilityGoogleGenerateContent", google_generate_content: "aiCapabilityGoogleGenerateContent",
+1 -1
View File
@@ -2,7 +2,7 @@ export const AI_CAPABILITIES = [
"openai_chat", "openai_chat",
"openai_responses", "openai_responses",
"anthropic_messages", "anthropic_messages",
"anthropic_models", "v1_models",
"gemini_generate_content", "gemini_generate_content",
"bedrock_model_invoke", "bedrock_model_invoke",
"google_generate_content", "google_generate_content",
+3 -3
View File
@@ -38,12 +38,12 @@ export const AI_PROVIDER_DEFAULTS: Record<
openai: { openai: {
upstreamUrl: "https://api.openai.com/v1", upstreamUrl: "https://api.openai.com/v1",
authType: "bearer", authType: "bearer",
capabilities: ["openai_chat", "openai_responses"] capabilities: ["openai_chat", "openai_responses", "v1_models"]
}, },
anthropic: { anthropic: {
upstreamUrl: "https://api.anthropic.com", upstreamUrl: "https://api.anthropic.com",
authType: "x-api-key", authType: "x-api-key",
capabilities: ["anthropic_messages", "anthropic_models"] capabilities: ["anthropic_messages", "v1_models"]
}, },
googleGemini: { googleGemini: {
upstreamUrl: "https://generativelanguage.googleapis.com", upstreamUrl: "https://generativelanguage.googleapis.com",
@@ -67,7 +67,7 @@ export const AI_PROVIDER_DEFAULTS: Record<
"openai_chat", "openai_chat",
"openai_responses", "openai_responses",
"anthropic_messages", "anthropic_messages",
"anthropic_models" "v1_models"
] ]
}, },
openRouter: { openRouter: {