// ---- Platform & Model Types ---- // Active platforms — must match server/src/providers/index.ts and // server/src/routes/keys.ts PLATFORMS allowlist. // Moonshot and MiniMax direct integrations were dropped in migrateModelsV4 // (see server/src/db/index.ts). HuggingFace was dropped in V4 and re-added // in V13 via the router.huggingface.co Inference Providers meta-router. // SambaNova was dropped in V23 (free tier permanently retired — 402 // "payment method required" once the one-time $5 trial credit lapses). export type Platform = | 'google' | 'groq' | 'cerebras' | 'nvidia' | 'mistral' | 'sambanova' | 'openrouter' | 'github' | 'cohere' | 'cloudflare' | 'zhipu' | 'ollama' | 'kilo' | 'pollinations' | 'llm7' | 'huggingface' // OpenCode Zen — OpenAI-compatible gateway. Free promotional models require a // free (no-card) account key from opencode.ai/auth; see migrateModelsV18. | 'opencode' // OVHcloud AI Endpoints — OpenAI-compatible, keyless anonymous tier // (2 req/min per IP per model); see migrateModelsV26. | 'ovh' // Agnes AI (Sapiens AI) — OpenAI-compatible (LiteLLM + vLLM backend). Serves // its own proprietary Agnes models; the free key comes from // platform.agnes-ai.com (no card). | 'agnes' // Reka — OpenAI-compatible. Native multimodal models (reka-edge takes // image/video); free via a recurring monthly credit grant, key from // platform.reka.ai (no card). | 'reka' // SiliconFlow — OpenAI-compatible. Registered for its FREE generative-media // models (FLUX.1-schnell image, CosyVoice2 TTS) routed via services/media.ts; // chat is supported too. Key from siliconflow.com (no card). | 'siliconflow' // User-configured OpenAI-compatible endpoint (llama.cpp, LM Studio, vLLM, // Ollama, any base_url). The endpoint URL lives on the api_keys row; see #117. | 'custom'; export interface Model { id: number; platform: Platform; modelId: string; displayName: string; intelligenceRank: number; speedRank: number; sizeLabel: string; rpmLimit: number | null; rpdLimit: number | null; tpmLimit: number | null; tpdLimit: number | null; monthlyTokenBudget: string; contextWindow: number | null; enabled: boolean; supportsVision: boolean; supportsTools: boolean; } // ---- Quirks ---- // Structured, reusable notes about catalog models. One quirk is applied to many // models via selector parameters (see quirk_targets / services/quirks.ts). export type QuirkSeverity = 'info' | 'warning' | 'blocker'; export interface Quirk { slug: string; title: string; body: string; severity: QuirkSeverity; } export interface QuirkTarget { platform: Platform | null; modelGlob: string | null; } export interface ModelListRow { platform: string; model_id: string; display_name: string; context_window: number | null; // 1 when the catalog row is enabled. 1 when an enabled key can serve it // (enabled AND a matching enabled api_key exists). SQLite returns 0/1. enabled: number; available: number; } export type KeyStatus = 'healthy' | 'rate_limited' | 'invalid' | 'error' | 'unknown'; export interface ApiKey { id: number; platform: Platform; label: string; maskedKey: string; status: KeyStatus; enabled: boolean; createdAt: string; lastCheckedAt: string | null; } export interface ApiKeyCreate { platform: Platform; key: string; label?: string; } // ---- Fallback Config ---- export interface FallbackEntry { modelId: number; platform: Platform; displayName: string; intelligenceRank: number; speedRank: number; priority: number; enabled: boolean; // Present when model unification is enabled — identifies the logical model // this provider row belongs to so the dashboard can render grouped rows. groupKey?: string; canonicalId?: string; groupLabel?: string; } // ---- Model Grouping (unify the same model across providers) ---- // One logical model can be served by several providers (rows in the `models` // table). When unification is enabled, those rows collapse into a single group // keyed by a normalized display name; see server/src/services/model-groups.ts. export interface ModelGroupInfo { groupKey: string; // normalized display name — the grouping identity canonicalId: string; // stable slug advertised on /v1/models groupLabel: string; // human label (suffix-stripped display name) } export interface UnifyOverrides { // Coalesce several normalized display-names (or exact "platform:model_id" // members) into one group keyed by `into`. merges: { into: string; keys: string[] }[]; // Force a specific "platform:model_id" row out of its computed group. splits: { member: string; groupKey?: string }[]; } export interface UnifySettings { enabled: boolean; overrides: UnifyOverrides; } // ---- OpenAI-Compatible Types ---- export interface ChatToolCallFunction { name: string; arguments: string; } export interface ChatToolCall { id: string; type: 'function'; function: ChatToolCallFunction; thought_signature?: string; } export interface ChatToolFunctionDefinition { name: string; description?: string; parameters?: Record; strict?: boolean; } export interface ChatToolDefinition { type: 'function'; function: ChatToolFunctionDefinition; } export type ChatToolChoice = | 'none' | 'auto' | 'required' | { type: 'function'; function: { name: string; }; }; // OpenAI's multimodal envelope: clients like opencode / continue.dev send // content as an array of typed blocks even for text-only messages, and // Gemini-lineage agents (Qwen Code, AionUI) send part-style `{ text }` blocks // with no `type` — plus bare strings inside arrays. We accept all of it on // the wire and flatten to string for providers that don't support arrays // (Cohere, Cloudflare). See server/src/lib/content.ts. (#200) export type ChatContentBlock = string | { type?: string; text?: string; [key: string]: unknown }; export type ChatContent = string | null | ChatContentBlock[]; export interface ChatMessage { role: 'system' | 'user' | 'assistant' | 'tool'; content: ChatContent; name?: string; tool_call_id?: string; tool_calls?: ChatToolCall[]; // The model's thinking trace on an assistant turn. Some thinking models // (DeepSeek on OpenCode Zen) require it to be replayed verbatim on the next // turn or they 400; the proxy preserves and forwards it. See issue #255. reasoning_content?: string; } export interface ChatCompletionRequest { model?: string; messages: ChatMessage[]; temperature?: number; max_tokens?: number; stream?: boolean; top_p?: number; tools?: ChatToolDefinition[]; tool_choice?: ChatToolChoice; parallel_tool_calls?: boolean; } export interface ChatCompletionChoice { index: number; message: ChatMessage; finish_reason: string | null; } export interface TokenUsage { prompt_tokens: number; completion_tokens: number; total_tokens: number; } export interface ChatCompletionResponse { id: string; object: 'chat.completion'; created: number; model: string; choices: ChatCompletionChoice[]; usage: TokenUsage; _routed_via?: { platform: Platform; model: string; }; } export interface ChatCompletionChunk { id: string; object: 'chat.completion.chunk'; created: number; model: string; choices: { index: number; delta: { role?: 'assistant'; content?: string; tool_calls?: ChatToolCall[]; }; finish_reason: string | null; }[]; } // ---- Analytics Types ---- export interface AnalyticsSummary { totalRequests: number; successRate: number; totalInputTokens: number; totalOutputTokens: number; avgLatencyMs: number; estimatedCostSavings: number; } export interface PlatformStats { platform: Platform; requests: number; successRate: number; avgLatencyMs: number; totalInputTokens: number; totalOutputTokens: number; } export interface TimelinePoint { timestamp: string; requests: number; successCount: number; failureCount: number; } export interface RequestLog { id: number; platform: Platform; modelId: string; status: 'success' | 'error'; inputTokens: number; outputTokens: number; latencyMs: number; error: string | null; createdAt: string; } // ---- Rate Limit Types ---- export interface RateLimitStatus { platform: Platform; modelId: string; rpm: { used: number; limit: number | null }; rpd: { used: number; limit: number | null }; tpm: { used: number; limit: number | null }; available: boolean; nextResetAt: string | null; } // ---- Provider Quota Observability ---- export type QuotaMetric = 'requests' | 'tokens' | 'credits' | 'neurons'; export type QuotaResetStrategy = 'fixed_calendar' | 'rolling_window' | 'token_bucket' | 'provider_reported' | 'unknown'; export type QuotaObservationSource = 'header' | 'quota_api' | 'error_body' | 'local_usage' | 'documentation' | 'probe'; export interface ProviderQuotaState { platform: Platform; keyId: number; quotaPoolKey: string; metric: QuotaMetric; limit: number | null; remaining: number | null; resetAt: string | null; resetStrategy: QuotaResetStrategy; source: QuotaObservationSource; confidence: number; notes: string | null; observedAt: string; updatedAt: string; } export interface ProviderQuotaObservation extends ProviderQuotaState { id: string; statusCode: number | null; retryAfterMs: number | null; providerAccountId: string | null; modelId: string | null; endpoint: string | null; rawJson: string | null; createdAt: string; }