/* * registry.ts * Zyquo Cloud Web * * Author: Simon-Pierre Boucher * Mail: contact@spboucher.ai * * Provider metadata + client resolution, ported from ProviderID.swift and * ProviderRegistry.swift. The only place that knows which wire format each * provider speaks and where its API lives. */ import type { AIModel, Provider, WireFormat } from '../types' export interface ProviderMeta { displayName: string wireFormat: WireFormat /** Base URL of the provider's API (chat + models live under this root). */ defaultBaseURL: string | null /** Whether the provider exposes a usable `/models` listing endpoint. */ supportsModelListing: boolean /** * Providers whose final streamed chunk carries usage only when asked via * stream_options. Mistral rejects unknown params; Qwen/DeepInfra/Perplexity * include usage automatically. */ wantsStreamOptions: boolean /** CORS verdict from docs/CORS-MATRIX.md (all 12 work; Anthropic needs a header). */ corsDirect: boolean } export const PROVIDER_META: Record = { openai: { displayName: 'OpenAI', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.openai.com/v1', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, anthropic: { displayName: 'Anthropic', wireFormat: 'anthropicMessages', defaultBaseURL: 'https://api.anthropic.com/v1', supportsModelListing: true, wantsStreamOptions: false, corsDirect: true, }, xai: { displayName: 'xAI', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.x.ai/v1', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, mistral: { displayName: 'Mistral', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.mistral.ai/v1', supportsModelListing: true, wantsStreamOptions: false, corsDirect: true, }, gemini: { displayName: 'Google Gemini', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://generativelanguage.googleapis.com/v1beta/openai', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, qwen: { displayName: 'Alibaba Qwen', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1', supportsModelListing: true, wantsStreamOptions: false, corsDirect: true, }, deepseek: { displayName: 'DeepSeek', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.deepseek.com', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, kimi: { displayName: 'Kimi', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.moonshot.ai/v1', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, perplexity: { displayName: 'Perplexity', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.perplexity.ai', supportsModelListing: false, wantsStreamOptions: false, corsDirect: true, }, together: { displayName: 'Together AI', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.together.xyz/v1', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, deepinfra: { displayName: 'DeepInfra', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.deepinfra.com/v1/openai', supportsModelListing: true, wantsStreamOptions: false, corsDirect: true, }, cerebras: { displayName: 'Cerebras', wireFormat: 'openAIChatCompletions', defaultBaseURL: 'https://api.cerebras.ai/v1', supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, custom: { displayName: 'Custom', wireFormat: 'openAIChatCompletions', defaultBaseURL: null, supportsModelListing: true, wantsStreamOptions: true, corsDirect: true, }, } import type { ProviderClient } from './types' import { OpenAICompatibleClient } from './openaiCompatible' import { AnthropicClient } from './anthropic' /** Resolves the right client for a provider or custom model. */ export function clientFor(providerOrModel: Provider | AIModel): ProviderClient { const provider = typeof providerOrModel === 'string' ? providerOrModel : providerOrModel.provider if (PROVIDER_META[provider].wireFormat === 'anthropicMessages') { return new AnthropicClient() } return new OpenAICompatibleClient(provider) }