| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- /**
- * Shared model resolution utilities for API routes.
- *
- * Extracts the repeated parseModelString → resolveApiKey → resolveBaseUrl →
- * resolveProxy → getModel boilerplate into a single call.
- */
- import type { NextRequest } from 'next/server';
- import { getModel, parseModelString, type ModelWithInfo } from '@/lib/ai/providers';
- import { resolveApiKey, resolveBaseUrl, resolveProxy } from '@/lib/server/provider-config';
- import { validateUrlForSSRF } from '@/lib/server/ssrf-guard';
- export interface ResolvedModel extends ModelWithInfo {
- /** Original model string (e.g. "openai/gpt-4o-mini") */
- modelString: string;
- /** Resolved provider ID (e.g. "openai", "ollama") */
- providerId: string;
- /** Effective API key after server-side fallback resolution */
- apiKey: string;
- }
- /**
- * Resolve a language model from explicit parameters.
- *
- * Use this when model config comes from the request body.
- */
- export async function resolveModel(params: {
- modelString?: string;
- apiKey?: string;
- baseUrl?: string;
- providerType?: string;
- }): Promise<ResolvedModel> {
- const modelString = params.modelString || process.env.DEFAULT_MODEL || 'gpt-4o-mini';
- const { providerId, modelId } = parseModelString(modelString);
- // SSRF validation applies only to client-supplied base URLs.
- // Server-configured URLs (e.g. OLLAMA_BASE_URL from env/YAML) flow through
- // resolveBaseUrl() and bypass this check — they're trusted by the operator.
- const clientBaseUrl = params.baseUrl || undefined;
- if (clientBaseUrl && process.env.NODE_ENV === 'production') {
- const ssrfError = await validateUrlForSSRF(clientBaseUrl);
- if (ssrfError) {
- throw new Error(ssrfError);
- }
- }
- const apiKey = clientBaseUrl
- ? params.apiKey || ''
- : resolveApiKey(providerId, params.apiKey || '');
- const baseUrl = clientBaseUrl ? clientBaseUrl : resolveBaseUrl(providerId, params.baseUrl);
- const proxy = resolveProxy(providerId);
- const { model, modelInfo } = getModel({
- providerId,
- modelId,
- apiKey,
- baseUrl,
- proxy,
- providerType: params.providerType as 'openai' | 'anthropic' | 'google' | undefined,
- });
- return { model, modelInfo, modelString, providerId, apiKey };
- }
- /**
- * Resolve a language model from standard request headers.
- *
- * Reads: x-model, x-api-key, x-base-url, x-provider-type
- * Note: requiresApiKey is derived server-side from the provider registry,
- * never from client headers, to prevent auth bypass.
- */
- export async function resolveModelFromHeaders(req: NextRequest): Promise<ResolvedModel> {
- return resolveModel({
- modelString: req.headers.get('x-model') || undefined,
- apiKey: req.headers.get('x-api-key') || undefined,
- baseUrl: req.headers.get('x-base-url') || undefined,
- providerType: req.headers.get('x-provider-type') || undefined,
- });
- }
|