Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/asyncat-oss_asyncat-oss.json
T

1 line
192 KiB
JSON

{"repo": "asyncat-oss/asyncat-oss", "files": {"den/src/ai/controllers/ai/providerCatalog.js": {"d40ff38bb5e69e7c5e63f58cc51460dccb8ec451": {"t": 1785746671.582822, "n": 29899, "h": "d0988e3695c621bb", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-builtin';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'openai-codex',\n name: 'OpenAI Codex',\n providerType: 'cloud',\n providerId: 'openai-codex',\n baseUrl: 'https://chatgpt.com/backend-api/codex',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: false,\n managed: false,\n directCodex: true,\n setup: [\n 'Uses a local Codex/ChatGPT login against the Codex Responses backend.',\n 'Asyncat keeps its own token bundle after connection; it should not live-share refresh tokens with Codex CLI.',\n 'If you already ran codex login, Asyncat can import that token once to bootstrap its own session.'\n ],\n description: 'Use your Codex login from Asyncat. Experimental; not a normal OpenAI API key.',\n },\n {\n id: 'codex-cli',\n name: 'Codex CLI Runtime',\n providerType: 'local',\n providerId: 'codex-cli',\n baseUrl: 'runtime://codex-cli',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: true,\n managed: false,\n runtime: true,\n setup: [\n 'Uses codex exec as a subprocess and lets Codex own its auth session.',\n 'Asyncat does not read or refresh ~/.codex/auth.json.',\n 'Run codex login or open codex once before activating this provider.'\n ],\n description: 'Use your local Codex CLI login as a runtime boundary.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'vllm',\n name: 'vLLM',\n providerType: 'local',\n providerId: 'vllm',\n baseUrl: 'http://localhost:8000/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html',\n setup: [\n 'Start vLLM with `vllm serve <model>` (defaults to port 8000).',\n 'The served model name is the Hugging Face repo id you launched with; use Models to pick it from the list.',\n 'vLLM supports OpenAI tool_calls for many models — enable native tools after confirming your model handles them.'\n ],\n description: 'High-throughput local serving through the vLLM OpenAI-compatible server.',\n },\n {\n id: 'jan',\n name: 'Jan',\n providerType: 'local',\n providerId: 'jan',\n baseUrl: 'http://localhost:1337/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://jan.ai/docs/local-server',\n setup: [\n 'Enable the Local API Server in Jan (default port 1337).',\n 'Load a model in Jan, then pick it from the model list after connecting.'\n ],\n description: 'Use models served by the Jan local API server.',\n },\n {\n id: 'localai',\n name: 'LocalAI',\n providerType: 'local',\n providerId: 'localai',\n baseUrl: 'http://localhost:8080/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://localai.io/features/openai-functions/',\n setup: [\n 'Run LocalAI (default port 8080); it is a drop-in OpenAI-compatible API.',\n 'Model IDs match the model configs installed in LocalAI; pick one from the list after connecting.',\n 'LocalAI supports function calling on capable models — enable native tools after testing.'\n ],\n description: 'LocalAI drop-in OpenAI-compatible API for local models.',\n },\n {\n id: 'koboldcpp',\n name: 'KoboldCpp',\n providerType: 'local',\n providerId: 'koboldcpp',\n baseUrl: 'http://localhost:5001/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://github.com/LostRuins/koboldcpp/wiki',\n setup: [\n 'Launch KoboldCpp with a GGUF model; its OpenAI-compatible API defaults to port 5001.',\n 'The model name reflects the loaded GGUF; select it from the list after connecting.'\n ],\n description: 'Use a local KoboldCpp OpenAI-compatible server.',\n },\n {\n id: 'gpt4all',\n name: 'GPT4All',\n providerType: 'local',\n providerId: 'gpt4all',\n baseUrl: 'http://localhost:4891/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://docs.gpt4all.io/gpt4all_api_server/home.html',\n setup: [\n 'Enable the API Server in GPT4All settings (default port 4891).',\n 'Load a model in GPT4All, then pick it from the model list after connecting.'\n ],\n description: 'Use models served by the GPT4All local API server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5.2',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 400000,\n local: false,\n apiKeyEnv: 'OPENAI_API_KEY',\n docsUrl: 'https://platform.openai.com/docs',\n setup: [\n 'Create a Platform API key in the OpenAI dashboard.',\n 'ChatGPT Plus/Pro/Business subscriptions and Codex plan access do not replace an API key for this app.',\n 'Codex CLI sign-in can create API credentials for Codex CLI, but there is no general-purpose ChatGPT subscription token that Asyncat can safely use.'\n ],\n description: 'OpenAI Platform API for GPT-5.2 and other API models.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n docsUrl: 'https://docs.anthropic.com/en/api/openai-sdk',\n compatibility: 'Anthropic documents this OpenAI SDK layer as useful for testing and comparison, with native Anthropic API recommended for the full feature set. OpenAI reasoning_effort is ignored by the compatibility layer.',\n setup: [\n 'Use an Anthropic API key.',\n 'Claude model listing is not exposed through the OpenAI-compatible /models flow here, so enter the model ID manually.',\n 'Some OpenAI parameters are ignored or mapped differently by Anthropic compatibility.'\n ],\n description: 'Claude through Anthropic OpenAI SDK compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n apiKeyEnv: 'GEMINI_API_KEY',\n docsUrl: 'https://ai.google.dev/gemini-api/docs/openai',\n setup: [\n 'Create a Gemini API key in Google AI Studio.',\n 'Use the Google OpenAI-compatible endpoint ending in /v1beta/openai/.'\n ],\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'xai',\n name: 'xAI',\n providerType: 'cloud',\n providerId: 'xai',\n baseUrl: 'https://api.x.ai/v1',\n model: 'grok-4.3',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'XAI_API_KEY',\n docsUrl: 'https://docs.x.ai/docs/guides/chat-completions',\n setup: [\n 'Create an xAI API key in the xAI Console.',\n 'Long-running reasoning models may need higher client timeouts; Asyncat streams responses but provider-side limits still apply.',\n 'Grok 4.3 supports function calling, structured outputs, and configurable reasoning.'\n ],\n description: 'Grok models through xAI OpenAI-compatible Chat Completions.',\n },\n {\n id: 'opencode',\n name: 'OpenCode Zen',\n providerType: 'cloud',\n providerId: 'opencode',\n baseUrl: 'https://opencode.ai/zen/v1',\n model: 'opencode/big-pickle',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'OPENCODE_API_KEY',\n docsUrl: 'https://opencode.ai/docs/zen/',\n description: 'OpenCode Zen curated models via OpenAI-compatible API.',\n },\n {\n id: 'opencode-go',\n name: 'OpenCode Go',\n providerType: 'cloud',\n providerId: 'opencode-go',\n baseUrl: 'https://opencode.ai/zen/go/v1',\n model: 'opencode-go/kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'OPENCODE_API_KEY',\n docsUrl: 'https://opencode.ai/docs/go/',\n description: 'OpenCode Go low-cost open models via OpenAI-compatible API.',\n },\n {\n id: 'mistral',\n name: 'Mistral AI',\n providerType: 'cloud',\n providerId: 'mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-large-latest',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'MISTRAL_API_KEY',\n docsUrl: 'https://docs.mistral.ai/resources/migration-guides',\n setup: [\n 'Create a Mistral API key.',\n 'Mistral follows the Chat Completions request structure, so the OpenAI SDK works after changing base URL, key, and model name.'\n ],\n description: 'Mistral chat models through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n providerType: 'cloud',\n providerId: 'deepseek',\n baseUrl: 'https://api.deepseek.com',\n model: 'deepseek-v4-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'DEEPSEEK_API_KEY',\n docsUrl: 'https://api-docs.deepseek.com/',\n setup: [\n 'Create a DeepSeek API key.',\n 'DeepSeek uses https://api.deepseek.com as the OpenAI SDK base URL; do not append /v1.',\n 'Use deepseek-v4-flash or deepseek-v4-pro. The older deepseek-chat and deepseek-reasoner aliases are scheduled for deprecation on July 24, 2026.'\n ],\n description: 'DeepSeek models through an OpenAI-compatible API.',\n },\n {\n id: 'qwen',\n name: 'Alibaba Qwen',\n providerType: 'cloud',\n providerId: 'qwen',\n baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',\n model: 'qwen-plus',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DASHSCOPE_API_KEY',\n docsUrl: 'https://www.alibabacloud.com/help/en/model-studio/compatibility-of-openai-with-dashscope',\n setup: [\n 'Create an Alibaba Cloud Model Studio API key.',\n 'The default endpoint is the Singapore international region. Change the base URL for US, Beijing, Hong Kong, or workspace-specific regions.',\n 'Qwen supports OpenAI-compatible chat completions and tool calls; thinking controls may require provider-specific extra_body parameters.'\n ],\n description: 'Qwen models through Alibaba Cloud Model Studio OpenAI compatibility.',\n },\n {\n id: 'kimi',\n name: 'Kimi',\n providerType: 'cloud',\n providerId: 'kimi',\n baseUrl: 'https://api.moonshot.ai/v1',\n model: 'kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'MOONSHOT_API_KEY',\n docsUrl: 'https://platform.kimi.ai/docs/api/overview',\n setup: [\n 'Create a Kimi/Moonshot API key.',\n 'Kimi exposes OpenAI-compatible /v1/chat/completions and /v1/models endpoints.',\n 'Provider-specific thinking controls use Kimi extra_body fields, so leave reasoning controls on auto unless you have tested the model.'\n ],\n description: 'Kimi K-series models through Moonshot OpenAI-compatible APIs.',\n },\n {\n id: 'together',\n name: 'Together AI',\n providerType: 'cloud',\n providerId: 'together',\n baseUrl: 'https://api.together.ai/v1',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'TOGETHER_API_KEY',\n docsUrl: 'https://docs.together.ai/docs/openai-api-compatibility',\n setup: [\n 'Create a Together API key.',\n 'Choose a concrete model ID from Together before activating if the default is unavailable in your account.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'Open-source and hosted models through Together OpenAI compatibility.',\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n providerType: 'cloud',\n providerId: 'perplexity',\n baseUrl: 'https://api.perplexity.ai',\n model: 'sonar-pro',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'PERPLEXITY_API_KEY',\n docsUrl: 'https://docs.perplexity.ai/docs/sonar/openai-compatibility',\n setup: [\n 'Create a Perplexity API key.',\n 'Perplexity uses https://api.perplexity.ai as the OpenAI SDK base URL; do not append /v1.',\n 'Sonar is optimized for grounded web answers, not general agent tool calling, so native tools are off by default.'\n ],\n description: 'Perplexity Sonar API through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimax.io/platform/document',\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimaxi.com/document',\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'GROQ_API_KEY',\n docsUrl: 'https://console.groq.com/docs/overview',\n setup: [\n 'Create a GroqCloud API key.',\n 'Groq uses https://api.groq.com/openai/v1 and supports /models for available model IDs.'\n ],\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'huggingface',\n name: 'Hugging Face',\n providerType: 'cloud',\n providerId: 'huggingface',\n baseUrl: 'https://router.huggingface.co/v1',\n model: 'openai/gpt-oss-120b:fastest',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'HF_TOKEN',\n docsUrl: 'https://huggingface.co/docs/inference-providers/main/index',\n setup: [\n 'Create a Hugging Face token with Inference Providers permission.',\n 'The router endpoint is chat-completions only and can route to providers with suffixes such as :fastest or :cerebras.',\n 'Tool support is model/provider-dependent, so enable native tools only after testing the selected route.'\n ],\n description: 'Hugging Face Inference Providers through its OpenAI-compatible router.',\n },\n {\n id: 'nvidia-nim',\n name: 'NVIDIA NIM',\n providerType: 'cloud',\n providerId: 'nvidia-nim',\n baseUrl: 'https://integrate.api.nvidia.com/v1',\n model: 'openai/gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'NVIDIA_API_KEY',\n docsUrl: 'https://docs.api.nvidia.com/nim/reference/llm-apis',\n setup: [\n 'Create an NVIDIA API catalog key.',\n 'Hosted NIM uses https://integrate.api.nvidia.com/v1/chat/completions.',\n 'For self-hosted NIM, use a Custom provider pointed at your local or private /v1 endpoint.'\n ],\n description: 'Hosted NVIDIA NIM language models through OpenAI-compatible chat completions.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'OPENROUTER_API_KEY',\n docsUrl: 'https://openrouter.ai/docs/api/reference/overview',\n setup: [\n 'Create an OpenRouter API key.',\n 'openrouter/auto lets OpenRouter route automatically; pick a concrete provider/model when you need predictable behavior.',\n 'Asyncat sends OpenRouter app attribution headers for better dashboard attribution.'\n ],\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'cohere',\n name: 'Cohere',\n providerType: 'cloud',\n providerId: 'cohere',\n baseUrl: 'https://api.cohere.ai/compatibility/v1',\n model: 'command-a-03-2025',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'COHERE_API_KEY',\n docsUrl: 'https://docs.cohere.com/docs/compatibility-api',\n compatibility: 'Cohere compatibility supports many OpenAI chat parameters, but not every OpenAI field.',\n setup: [\n 'Create a Cohere API key.',\n 'Use the compatibility endpoint ending in /compatibility/v1.'\n ],\n description: 'Cohere Command models through the OpenAI SDK compatibility API.',\n },\n {\n id: 'fireworks',\n name: 'Fireworks AI',\n providerType: 'cloud',\n providerId: 'fireworks',\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n model: 'accounts/fireworks/models/llama-v3p1-8b-instruct',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'FIREWORKS_API_KEY',\n docsUrl: 'https://docs.fireworks.ai/tools-sdks/openai-compatibility',\n setup: [\n 'Create a Fireworks API key.',\n 'Fireworks model IDs often include the account path, for example accounts/fireworks/models/...',\n 'Tool support depends on the selected model, so this preset starts with native tools disabled.'\n ],\n description: 'Fireworks serverless and dedicated models through OpenAI compatibility.',\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n providerType: 'cloud',\n providerId: 'cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'CEREBRAS_API_KEY',\n docsUrl: 'https://inference-docs.cerebras.ai/resources/openai',\n setup: [\n 'Create a Cerebras API key.',\n 'Use Cerebras model IDs such as gpt-oss-120b or another model enabled for your account.'\n ],\n description: 'Fast Cerebras-hosted inference through OpenAI-compatible APIs.',\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n providerType: 'cloud',\n providerId: 'deepinfra',\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DEEPINFRA_TOKEN',\n docsUrl: 'https://docs.deepinfra.com/chat/overview',\n setup: [\n 'Create a DeepInfra API token.',\n 'Use the OpenAI-compatible base URL ending in /v1/openai.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'DeepInfra hosted open-source models through OpenAI-compatible chat.',\n },\n {\n id: 'bedrock',\n name: 'Amazon Bedrock',\n providerType: 'cloud',\n providerId: 'bedrock',\n baseUrl: '',\n model: 'openai.gpt-oss-20b-1:0',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'AWS_BEARER_TOKEN_BEDROCK',\n docsUrl: 'https://docs.aws.amazon.com/bedrock/latest/userguide/inference-chat-completions.html',\n setup: [\n 'Generate an Amazon Bedrock API key.',\n 'Set Base URL to your region endpoint plus /openai/v1, for example https://bedrock-runtime.us-west-2.amazonaws.com/openai/v1.',\n 'Bedrock region and model availability vary; choose a model ID enabled in your AWS account.'\n ],\n description: 'Amazon Bedrock models through its OpenAI Chat Completions compatibility endpoint.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n apiKeyEnv: 'AZURE_OPENAI_API_KEY',\n docsUrl: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference',\n setup: [\n 'Enter your Azure OpenAI resource endpoint, for example https://resource-name.openai.azure.com.',\n 'Set Model to the Azure deployment name, or set deployment in advanced settings.',\n 'Azure requires an api-version query parameter; Asyncat defaults to 2024-10-21.'\n ],\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n setup: [\n 'Use this for any endpoint that implements OpenAI-compatible /chat/completions.',\n 'Set the exact base URL expected by the provider and a model ID that endpoint accepts.',\n 'Enable native tools only if the endpoint supports OpenAI tool_calls.'\n ],\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n const normalized = providerId === 'mlx-local' ? MLX_PROVIDER_ID : providerId;\n return PROVIDER_CATALOG.find(p => p.id === normalized || p.providerId === normalized) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'openai-codex') return url.replace(/\\/+$/, '');\n if (providerId === 'codex-cli' || url.startsWith('runtime://')) return url;\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (providerId === 'deepseek') return url.replace(/\\/v1\\/?$/i, '').replace(/\\/+$/, '');\n if (providerId === 'perplexity') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|v\\d+\\/openai|api\\/v\\d+|v\\d+beta\\/openai|compatibility\\/v\\d+|compatible-mode\\/v\\d+|inference\\/v\\d+)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const effectiveProviderId = row.provider_id === 'mlx-local' ? MLX_PROVIDER_ID : row.provider_id;\n const preset = getProviderPreset(effectiveProviderId);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: effectiveProviderId,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || effectiveProviderId || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, effectiveProviderId),\n provider_id: effectiveProviderId || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: effectiveProviderId === LLAMA_PROVIDER_ID || effectiveProviderId === MLX_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "1bee66d775834663f7221dbf03fb91aeea741f7d": {"t": 1785766730.139728, "n": 29899, "h": "d0988e3695c621bb", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-builtin';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'openai-codex',\n name: 'OpenAI Codex',\n providerType: 'cloud',\n providerId: 'openai-codex',\n baseUrl: 'https://chatgpt.com/backend-api/codex',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: false,\n managed: false,\n directCodex: true,\n setup: [\n 'Uses a local Codex/ChatGPT login against the Codex Responses backend.',\n 'Asyncat keeps its own token bundle after connection; it should not live-share refresh tokens with Codex CLI.',\n 'If you already ran codex login, Asyncat can import that token once to bootstrap its own session.'\n ],\n description: 'Use your Codex login from Asyncat. Experimental; not a normal OpenAI API key.',\n },\n {\n id: 'codex-cli',\n name: 'Codex CLI Runtime',\n providerType: 'local',\n providerId: 'codex-cli',\n baseUrl: 'runtime://codex-cli',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: true,\n managed: false,\n runtime: true,\n setup: [\n 'Uses codex exec as a subprocess and lets Codex own its auth session.',\n 'Asyncat does not read or refresh ~/.codex/auth.json.',\n 'Run codex login or open codex once before activating this provider.'\n ],\n description: 'Use your local Codex CLI login as a runtime boundary.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'vllm',\n name: 'vLLM',\n providerType: 'local',\n providerId: 'vllm',\n baseUrl: 'http://localhost:8000/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html',\n setup: [\n 'Start vLLM with `vllm serve <model>` (defaults to port 8000).',\n 'The served model name is the Hugging Face repo id you launched with; use Models to pick it from the list.',\n 'vLLM supports OpenAI tool_calls for many models — enable native tools after confirming your model handles them.'\n ],\n description: 'High-throughput local serving through the vLLM OpenAI-compatible server.',\n },\n {\n id: 'jan',\n name: 'Jan',\n providerType: 'local',\n providerId: 'jan',\n baseUrl: 'http://localhost:1337/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://jan.ai/docs/local-server',\n setup: [\n 'Enable the Local API Server in Jan (default port 1337).',\n 'Load a model in Jan, then pick it from the model list after connecting.'\n ],\n description: 'Use models served by the Jan local API server.',\n },\n {\n id: 'localai',\n name: 'LocalAI',\n providerType: 'local',\n providerId: 'localai',\n baseUrl: 'http://localhost:8080/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://localai.io/features/openai-functions/',\n setup: [\n 'Run LocalAI (default port 8080); it is a drop-in OpenAI-compatible API.',\n 'Model IDs match the model configs installed in LocalAI; pick one from the list after connecting.',\n 'LocalAI supports function calling on capable models — enable native tools after testing.'\n ],\n description: 'LocalAI drop-in OpenAI-compatible API for local models.',\n },\n {\n id: 'koboldcpp',\n name: 'KoboldCpp',\n providerType: 'local',\n providerId: 'koboldcpp',\n baseUrl: 'http://localhost:5001/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://github.com/LostRuins/koboldcpp/wiki',\n setup: [\n 'Launch KoboldCpp with a GGUF model; its OpenAI-compatible API defaults to port 5001.',\n 'The model name reflects the loaded GGUF; select it from the list after connecting.'\n ],\n description: 'Use a local KoboldCpp OpenAI-compatible server.',\n },\n {\n id: 'gpt4all',\n name: 'GPT4All',\n providerType: 'local',\n providerId: 'gpt4all',\n baseUrl: 'http://localhost:4891/v1',\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n docsUrl: 'https://docs.gpt4all.io/gpt4all_api_server/home.html',\n setup: [\n 'Enable the API Server in GPT4All settings (default port 4891).',\n 'Load a model in GPT4All, then pick it from the model list after connecting.'\n ],\n description: 'Use models served by the GPT4All local API server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5.2',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 400000,\n local: false,\n apiKeyEnv: 'OPENAI_API_KEY',\n docsUrl: 'https://platform.openai.com/docs',\n setup: [\n 'Create a Platform API key in the OpenAI dashboard.',\n 'ChatGPT Plus/Pro/Business subscriptions and Codex plan access do not replace an API key for this app.',\n 'Codex CLI sign-in can create API credentials for Codex CLI, but there is no general-purpose ChatGPT subscription token that Asyncat can safely use.'\n ],\n description: 'OpenAI Platform API for GPT-5.2 and other API models.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n docsUrl: 'https://docs.anthropic.com/en/api/openai-sdk',\n compatibility: 'Anthropic documents this OpenAI SDK layer as useful for testing and comparison, with native Anthropic API recommended for the full feature set. OpenAI reasoning_effort is ignored by the compatibility layer.',\n setup: [\n 'Use an Anthropic API key.',\n 'Claude model listing is not exposed through the OpenAI-compatible /models flow here, so enter the model ID manually.',\n 'Some OpenAI parameters are ignored or mapped differently by Anthropic compatibility.'\n ],\n description: 'Claude through Anthropic OpenAI SDK compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n apiKeyEnv: 'GEMINI_API_KEY',\n docsUrl: 'https://ai.google.dev/gemini-api/docs/openai',\n setup: [\n 'Create a Gemini API key in Google AI Studio.',\n 'Use the Google OpenAI-compatible endpoint ending in /v1beta/openai/.'\n ],\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'xai',\n name: 'xAI',\n providerType: 'cloud',\n providerId: 'xai',\n baseUrl: 'https://api.x.ai/v1',\n model: 'grok-4.3',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'XAI_API_KEY',\n docsUrl: 'https://docs.x.ai/docs/guides/chat-completions',\n setup: [\n 'Create an xAI API key in the xAI Console.',\n 'Long-running reasoning models may need higher client timeouts; Asyncat streams responses but provider-side limits still apply.',\n 'Grok 4.3 supports function calling, structured outputs, and configurable reasoning.'\n ],\n description: 'Grok models through xAI OpenAI-compatible Chat Completions.',\n },\n {\n id: 'opencode',\n name: 'OpenCode Zen',\n providerType: 'cloud',\n providerId: 'opencode',\n baseUrl: 'https://opencode.ai/zen/v1',\n model: 'opencode/big-pickle',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'OPENCODE_API_KEY',\n docsUrl: 'https://opencode.ai/docs/zen/',\n description: 'OpenCode Zen curated models via OpenAI-compatible API.',\n },\n {\n id: 'opencode-go',\n name: 'OpenCode Go',\n providerType: 'cloud',\n providerId: 'opencode-go',\n baseUrl: 'https://opencode.ai/zen/go/v1',\n model: 'opencode-go/kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'OPENCODE_API_KEY',\n docsUrl: 'https://opencode.ai/docs/go/',\n description: 'OpenCode Go low-cost open models via OpenAI-compatible API.',\n },\n {\n id: 'mistral',\n name: 'Mistral AI',\n providerType: 'cloud',\n providerId: 'mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-large-latest',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'MISTRAL_API_KEY',\n docsUrl: 'https://docs.mistral.ai/resources/migration-guides',\n setup: [\n 'Create a Mistral API key.',\n 'Mistral follows the Chat Completions request structure, so the OpenAI SDK works after changing base URL, key, and model name.'\n ],\n description: 'Mistral chat models through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n providerType: 'cloud',\n providerId: 'deepseek',\n baseUrl: 'https://api.deepseek.com',\n model: 'deepseek-v4-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'DEEPSEEK_API_KEY',\n docsUrl: 'https://api-docs.deepseek.com/',\n setup: [\n 'Create a DeepSeek API key.',\n 'DeepSeek uses https://api.deepseek.com as the OpenAI SDK base URL; do not append /v1.',\n 'Use deepseek-v4-flash or deepseek-v4-pro. The older deepseek-chat and deepseek-reasoner aliases are scheduled for deprecation on July 24, 2026.'\n ],\n description: 'DeepSeek models through an OpenAI-compatible API.',\n },\n {\n id: 'qwen',\n name: 'Alibaba Qwen',\n providerType: 'cloud',\n providerId: 'qwen',\n baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',\n model: 'qwen-plus',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DASHSCOPE_API_KEY',\n docsUrl: 'https://www.alibabacloud.com/help/en/model-studio/compatibility-of-openai-with-dashscope',\n setup: [\n 'Create an Alibaba Cloud Model Studio API key.',\n 'The default endpoint is the Singapore international region. Change the base URL for US, Beijing, Hong Kong, or workspace-specific regions.',\n 'Qwen supports OpenAI-compatible chat completions and tool calls; thinking controls may require provider-specific extra_body parameters.'\n ],\n description: 'Qwen models through Alibaba Cloud Model Studio OpenAI compatibility.',\n },\n {\n id: 'kimi',\n name: 'Kimi',\n providerType: 'cloud',\n providerId: 'kimi',\n baseUrl: 'https://api.moonshot.ai/v1',\n model: 'kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'MOONSHOT_API_KEY',\n docsUrl: 'https://platform.kimi.ai/docs/api/overview',\n setup: [\n 'Create a Kimi/Moonshot API key.',\n 'Kimi exposes OpenAI-compatible /v1/chat/completions and /v1/models endpoints.',\n 'Provider-specific thinking controls use Kimi extra_body fields, so leave reasoning controls on auto unless you have tested the model.'\n ],\n description: 'Kimi K-series models through Moonshot OpenAI-compatible APIs.',\n },\n {\n id: 'together',\n name: 'Together AI',\n providerType: 'cloud',\n providerId: 'together',\n baseUrl: 'https://api.together.ai/v1',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'TOGETHER_API_KEY',\n docsUrl: 'https://docs.together.ai/docs/openai-api-compatibility',\n setup: [\n 'Create a Together API key.',\n 'Choose a concrete model ID from Together before activating if the default is unavailable in your account.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'Open-source and hosted models through Together OpenAI compatibility.',\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n providerType: 'cloud',\n providerId: 'perplexity',\n baseUrl: 'https://api.perplexity.ai',\n model: 'sonar-pro',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'PERPLEXITY_API_KEY',\n docsUrl: 'https://docs.perplexity.ai/docs/sonar/openai-compatibility',\n setup: [\n 'Create a Perplexity API key.',\n 'Perplexity uses https://api.perplexity.ai as the OpenAI SDK base URL; do not append /v1.',\n 'Sonar is optimized for grounded web answers, not general agent tool calling, so native tools are off by default.'\n ],\n description: 'Perplexity Sonar API through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimax.io/platform/document',\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimaxi.com/document',\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'GROQ_API_KEY',\n docsUrl: 'https://console.groq.com/docs/overview',\n setup: [\n 'Create a GroqCloud API key.',\n 'Groq uses https://api.groq.com/openai/v1 and supports /models for available model IDs.'\n ],\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'huggingface',\n name: 'Hugging Face',\n providerType: 'cloud',\n providerId: 'huggingface',\n baseUrl: 'https://router.huggingface.co/v1',\n model: 'openai/gpt-oss-120b:fastest',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'HF_TOKEN',\n docsUrl: 'https://huggingface.co/docs/inference-providers/main/index',\n setup: [\n 'Create a Hugging Face token with Inference Providers permission.',\n 'The router endpoint is chat-completions only and can route to providers with suffixes such as :fastest or :cerebras.',\n 'Tool support is model/provider-dependent, so enable native tools only after testing the selected route.'\n ],\n description: 'Hugging Face Inference Providers through its OpenAI-compatible router.',\n },\n {\n id: 'nvidia-nim',\n name: 'NVIDIA NIM',\n providerType: 'cloud',\n providerId: 'nvidia-nim',\n baseUrl: 'https://integrate.api.nvidia.com/v1',\n model: 'openai/gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'NVIDIA_API_KEY',\n docsUrl: 'https://docs.api.nvidia.com/nim/reference/llm-apis',\n setup: [\n 'Create an NVIDIA API catalog key.',\n 'Hosted NIM uses https://integrate.api.nvidia.com/v1/chat/completions.',\n 'For self-hosted NIM, use a Custom provider pointed at your local or private /v1 endpoint.'\n ],\n description: 'Hosted NVIDIA NIM language models through OpenAI-compatible chat completions.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'OPENROUTER_API_KEY',\n docsUrl: 'https://openrouter.ai/docs/api/reference/overview',\n setup: [\n 'Create an OpenRouter API key.',\n 'openrouter/auto lets OpenRouter route automatically; pick a concrete provider/model when you need predictable behavior.',\n 'Asyncat sends OpenRouter app attribution headers for better dashboard attribution.'\n ],\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'cohere',\n name: 'Cohere',\n providerType: 'cloud',\n providerId: 'cohere',\n baseUrl: 'https://api.cohere.ai/compatibility/v1',\n model: 'command-a-03-2025',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'COHERE_API_KEY',\n docsUrl: 'https://docs.cohere.com/docs/compatibility-api',\n compatibility: 'Cohere compatibility supports many OpenAI chat parameters, but not every OpenAI field.',\n setup: [\n 'Create a Cohere API key.',\n 'Use the compatibility endpoint ending in /compatibility/v1.'\n ],\n description: 'Cohere Command models through the OpenAI SDK compatibility API.',\n },\n {\n id: 'fireworks',\n name: 'Fireworks AI',\n providerType: 'cloud',\n providerId: 'fireworks',\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n model: 'accounts/fireworks/models/llama-v3p1-8b-instruct',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'FIREWORKS_API_KEY',\n docsUrl: 'https://docs.fireworks.ai/tools-sdks/openai-compatibility',\n setup: [\n 'Create a Fireworks API key.',\n 'Fireworks model IDs often include the account path, for example accounts/fireworks/models/...',\n 'Tool support depends on the selected model, so this preset starts with native tools disabled.'\n ],\n description: 'Fireworks serverless and dedicated models through OpenAI compatibility.',\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n providerType: 'cloud',\n providerId: 'cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'CEREBRAS_API_KEY',\n docsUrl: 'https://inference-docs.cerebras.ai/resources/openai',\n setup: [\n 'Create a Cerebras API key.',\n 'Use Cerebras model IDs such as gpt-oss-120b or another model enabled for your account.'\n ],\n description: 'Fast Cerebras-hosted inference through OpenAI-compatible APIs.',\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n providerType: 'cloud',\n providerId: 'deepinfra',\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DEEPINFRA_TOKEN',\n docsUrl: 'https://docs.deepinfra.com/chat/overview',\n setup: [\n 'Create a DeepInfra API token.',\n 'Use the OpenAI-compatible base URL ending in /v1/openai.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'DeepInfra hosted open-source models through OpenAI-compatible chat.',\n },\n {\n id: 'bedrock',\n name: 'Amazon Bedrock',\n providerType: 'cloud',\n providerId: 'bedrock',\n baseUrl: '',\n model: 'openai.gpt-oss-20b-1:0',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'AWS_BEARER_TOKEN_BEDROCK',\n docsUrl: 'https://docs.aws.amazon.com/bedrock/latest/userguide/inference-chat-completions.html',\n setup: [\n 'Generate an Amazon Bedrock API key.',\n 'Set Base URL to your region endpoint plus /openai/v1, for example https://bedrock-runtime.us-west-2.amazonaws.com/openai/v1.',\n 'Bedrock region and model availability vary; choose a model ID enabled in your AWS account.'\n ],\n description: 'Amazon Bedrock models through its OpenAI Chat Completions compatibility endpoint.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n apiKeyEnv: 'AZURE_OPENAI_API_KEY',\n docsUrl: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference',\n setup: [\n 'Enter your Azure OpenAI resource endpoint, for example https://resource-name.openai.azure.com.',\n 'Set Model to the Azure deployment name, or set deployment in advanced settings.',\n 'Azure requires an api-version query parameter; Asyncat defaults to 2024-10-21.'\n ],\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n setup: [\n 'Use this for any endpoint that implements OpenAI-compatible /chat/completions.',\n 'Set the exact base URL expected by the provider and a model ID that endpoint accepts.',\n 'Enable native tools only if the endpoint supports OpenAI tool_calls.'\n ],\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n const normalized = providerId === 'mlx-local' ? MLX_PROVIDER_ID : providerId;\n return PROVIDER_CATALOG.find(p => p.id === normalized || p.providerId === normalized) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'openai-codex') return url.replace(/\\/+$/, '');\n if (providerId === 'codex-cli' || url.startsWith('runtime://')) return url;\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (providerId === 'deepseek') return url.replace(/\\/v1\\/?$/i, '').replace(/\\/+$/, '');\n if (providerId === 'perplexity') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|v\\d+\\/openai|api\\/v\\d+|v\\d+beta\\/openai|compatibility\\/v\\d+|compatible-mode\\/v\\d+|inference\\/v\\d+)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const effectiveProviderId = row.provider_id === 'mlx-local' ? MLX_PROVIDER_ID : row.provider_id;\n const preset = getProviderPreset(effectiveProviderId);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: effectiveProviderId,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || effectiveProviderId || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, effectiveProviderId),\n provider_id: effectiveProviderId || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: effectiveProviderId === LLAMA_PROVIDER_ID || effectiveProviderId === MLX_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "1f4503308e7687d8f3906d520ffa9167e3c30773": {"t": 1785766732.6956608, "n": 26648, "h": "d8a5060ca8012331", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-builtin';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'openai-codex',\n name: 'OpenAI Codex',\n providerType: 'cloud',\n providerId: 'openai-codex',\n baseUrl: 'https://chatgpt.com/backend-api/codex',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: false,\n managed: false,\n directCodex: true,\n setup: [\n 'Uses a local Codex/ChatGPT login against the Codex Responses backend.',\n 'Asyncat keeps its own token bundle after connection; it should not live-share refresh tokens with Codex CLI.',\n 'If you already ran codex login, Asyncat can import that token once to bootstrap its own session.'\n ],\n description: 'Use your Codex login from Asyncat. Experimental; not a normal OpenAI API key.',\n },\n {\n id: 'codex-cli',\n name: 'Codex CLI Runtime',\n providerType: 'local',\n providerId: 'codex-cli',\n baseUrl: 'runtime://codex-cli',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: true,\n managed: false,\n runtime: true,\n setup: [\n 'Uses codex exec as a subprocess and lets Codex own its auth session.',\n 'Asyncat does not read or refresh ~/.codex/auth.json.',\n 'Run codex login or open codex once before activating this provider.'\n ],\n description: 'Use your local Codex CLI login as a runtime boundary.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5.2',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 400000,\n local: false,\n apiKeyEnv: 'OPENAI_API_KEY',\n docsUrl: 'https://platform.openai.com/docs',\n setup: [\n 'Create a Platform API key in the OpenAI dashboard.',\n 'ChatGPT Plus/Pro/Business subscriptions and Codex plan access do not replace an API key for this app.',\n 'Codex CLI sign-in can create API credentials for Codex CLI, but there is no general-purpose ChatGPT subscription token that Asyncat can safely use.'\n ],\n description: 'OpenAI Platform API for GPT-5.2 and other API models.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n docsUrl: 'https://docs.anthropic.com/en/api/openai-sdk',\n compatibility: 'Anthropic documents this OpenAI SDK layer as useful for testing and comparison, with native Anthropic API recommended for the full feature set. OpenAI reasoning_effort is ignored by the compatibility layer.',\n setup: [\n 'Use an Anthropic API key.',\n 'Claude model listing is not exposed through the OpenAI-compatible /models flow here, so enter the model ID manually.',\n 'Some OpenAI parameters are ignored or mapped differently by Anthropic compatibility.'\n ],\n description: 'Claude through Anthropic OpenAI SDK compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n apiKeyEnv: 'GEMINI_API_KEY',\n docsUrl: 'https://ai.google.dev/gemini-api/docs/openai',\n setup: [\n 'Create a Gemini API key in Google AI Studio.',\n 'Use the Google OpenAI-compatible endpoint ending in /v1beta/openai/.'\n ],\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'xai',\n name: 'xAI',\n providerType: 'cloud',\n providerId: 'xai',\n baseUrl: 'https://api.x.ai/v1',\n model: 'grok-4.3',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'XAI_API_KEY',\n docsUrl: 'https://docs.x.ai/docs/guides/chat-completions',\n setup: [\n 'Create an xAI API key in the xAI Console.',\n 'Long-running reasoning models may need higher client timeouts; Asyncat streams responses but provider-side limits still apply.',\n 'Grok 4.3 supports function calling, structured outputs, and configurable reasoning.'\n ],\n description: 'Grok models through xAI OpenAI-compatible Chat Completions.',\n },\n {\n id: 'opencode',\n name: 'OpenCode Zen',\n providerType: 'cloud',\n providerId: 'opencode',\n baseUrl: 'https://opencode.ai/zen/v1',\n model: 'opencode/big-pickle',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'OPENCODE_API_KEY',\n docsUrl: 'https://opencode.ai/docs/zen/',\n description: 'OpenCode Zen curated models via OpenAI-compatible API.',\n },\n {\n id: 'opencode-go',\n name: 'OpenCode Go',\n providerType: 'cloud',\n providerId: 'opencode-go',\n baseUrl: 'https://opencode.ai/zen/go/v1',\n model: 'opencode-go/kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'OPENCODE_API_KEY',\n docsUrl: 'https://opencode.ai/docs/go/',\n description: 'OpenCode Go low-cost open models via OpenAI-compatible API.',\n },\n {\n id: 'mistral',\n name: 'Mistral AI',\n providerType: 'cloud',\n providerId: 'mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-large-latest',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'MISTRAL_API_KEY',\n docsUrl: 'https://docs.mistral.ai/resources/migration-guides',\n setup: [\n 'Create a Mistral API key.',\n 'Mistral follows the Chat Completions request structure, so the OpenAI SDK works after changing base URL, key, and model name.'\n ],\n description: 'Mistral chat models through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n providerType: 'cloud',\n providerId: 'deepseek',\n baseUrl: 'https://api.deepseek.com',\n model: 'deepseek-v4-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'DEEPSEEK_API_KEY',\n docsUrl: 'https://api-docs.deepseek.com/',\n setup: [\n 'Create a DeepSeek API key.',\n 'DeepSeek uses https://api.deepseek.com as the OpenAI SDK base URL; do not append /v1.',\n 'Use deepseek-v4-flash or deepseek-v4-pro. The older deepseek-chat and deepseek-reasoner aliases are scheduled for deprecation on July 24, 2026.'\n ],\n description: 'DeepSeek models through an OpenAI-compatible API.',\n },\n {\n id: 'qwen',\n name: 'Alibaba Qwen',\n providerType: 'cloud',\n providerId: 'qwen',\n baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',\n model: 'qwen-plus',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DASHSCOPE_API_KEY',\n docsUrl: 'https://www.alibabacloud.com/help/en/model-studio/compatibility-of-openai-with-dashscope',\n setup: [\n 'Create an Alibaba Cloud Model Studio API key.',\n 'The default endpoint is the Singapore international region. Change the base URL for US, Beijing, Hong Kong, or workspace-specific regions.',\n 'Qwen supports OpenAI-compatible chat completions and tool calls; thinking controls may require provider-specific extra_body parameters.'\n ],\n description: 'Qwen models through Alibaba Cloud Model Studio OpenAI compatibility.',\n },\n {\n id: 'kimi',\n name: 'Kimi',\n providerType: 'cloud',\n providerId: 'kimi',\n baseUrl: 'https://api.moonshot.ai/v1',\n model: 'kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'MOONSHOT_API_KEY',\n docsUrl: 'https://platform.kimi.ai/docs/api/overview',\n setup: [\n 'Create a Kimi/Moonshot API key.',\n 'Kimi exposes OpenAI-compatible /v1/chat/completions and /v1/models endpoints.',\n 'Provider-specific thinking controls use Kimi extra_body fields, so leave reasoning controls on auto unless you have tested the model.'\n ],\n description: 'Kimi K-series models through Moonshot OpenAI-compatible APIs.',\n },\n {\n id: 'together',\n name: 'Together AI',\n providerType: 'cloud',\n providerId: 'together',\n baseUrl: 'https://api.together.ai/v1',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'TOGETHER_API_KEY',\n docsUrl: 'https://docs.together.ai/docs/openai-api-compatibility',\n setup: [\n 'Create a Together API key.',\n 'Choose a concrete model ID from Together before activating if the default is unavailable in your account.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'Open-source and hosted models through Together OpenAI compatibility.',\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n providerType: 'cloud',\n providerId: 'perplexity',\n baseUrl: 'https://api.perplexity.ai',\n model: 'sonar-pro',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'PERPLEXITY_API_KEY',\n docsUrl: 'https://docs.perplexity.ai/docs/sonar/openai-compatibility',\n setup: [\n 'Create a Perplexity API key.',\n 'Perplexity uses https://api.perplexity.ai as the OpenAI SDK base URL; do not append /v1.',\n 'Sonar is optimized for grounded web answers, not general agent tool calling, so native tools are off by default.'\n ],\n description: 'Perplexity Sonar API through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimax.io/platform/document',\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimaxi.com/document',\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'GROQ_API_KEY',\n docsUrl: 'https://console.groq.com/docs/overview',\n setup: [\n 'Create a GroqCloud API key.',\n 'Groq uses https://api.groq.com/openai/v1 and supports /models for available model IDs.'\n ],\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'huggingface',\n name: 'Hugging Face',\n providerType: 'cloud',\n providerId: 'huggingface',\n baseUrl: 'https://router.huggingface.co/v1',\n model: 'openai/gpt-oss-120b:fastest',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'HF_TOKEN',\n docsUrl: 'https://huggingface.co/docs/inference-providers/main/index',\n setup: [\n 'Create a Hugging Face token with Inference Providers permission.',\n 'The router endpoint is chat-completions only and can route to providers with suffixes such as :fastest or :cerebras.',\n 'Tool support is model/provider-dependent, so enable native tools only after testing the selected route.'\n ],\n description: 'Hugging Face Inference Providers through its OpenAI-compatible router.',\n },\n {\n id: 'nvidia-nim',\n name: 'NVIDIA NIM',\n providerType: 'cloud',\n providerId: 'nvidia-nim',\n baseUrl: 'https://integrate.api.nvidia.com/v1',\n model: 'openai/gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'NVIDIA_API_KEY',\n docsUrl: 'https://docs.api.nvidia.com/nim/reference/llm-apis',\n setup: [\n 'Create an NVIDIA API catalog key.',\n 'Hosted NIM uses https://integrate.api.nvidia.com/v1/chat/completions.',\n 'For self-hosted NIM, use a Custom provider pointed at your local or private /v1 endpoint.'\n ],\n description: 'Hosted NVIDIA NIM language models through OpenAI-compatible chat completions.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'OPENROUTER_API_KEY',\n docsUrl: 'https://openrouter.ai/docs/api/reference/overview',\n setup: [\n 'Create an OpenRouter API key.',\n 'openrouter/auto lets OpenRouter route automatically; pick a concrete provider/model when you need predictable behavior.',\n 'Asyncat sends OpenRouter app attribution headers for better dashboard attribution.'\n ],\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'cohere',\n name: 'Cohere',\n providerType: 'cloud',\n providerId: 'cohere',\n baseUrl: 'https://api.cohere.ai/compatibility/v1',\n model: 'command-a-03-2025',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'COHERE_API_KEY',\n docsUrl: 'https://docs.cohere.com/docs/compatibility-api',\n compatibility: 'Cohere compatibility supports many OpenAI chat parameters, but not every OpenAI field.',\n setup: [\n 'Create a Cohere API key.',\n 'Use the compatibility endpoint ending in /compatibility/v1.'\n ],\n description: 'Cohere Command models through the OpenAI SDK compatibility API.',\n },\n {\n id: 'fireworks',\n name: 'Fireworks AI',\n providerType: 'cloud',\n providerId: 'fireworks',\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n model: 'accounts/fireworks/models/llama-v3p1-8b-instruct',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'FIREWORKS_API_KEY',\n docsUrl: 'https://docs.fireworks.ai/tools-sdks/openai-compatibility',\n setup: [\n 'Create a Fireworks API key.',\n 'Fireworks model IDs often include the account path, for example accounts/fireworks/models/...',\n 'Tool support depends on the selected model, so this preset starts with native tools disabled.'\n ],\n description: 'Fireworks serverless and dedicated models through OpenAI compatibility.',\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n providerType: 'cloud',\n providerId: 'cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'CEREBRAS_API_KEY',\n docsUrl: 'https://inference-docs.cerebras.ai/resources/openai',\n setup: [\n 'Create a Cerebras API key.',\n 'Use Cerebras model IDs such as gpt-oss-120b or another model enabled for your account.'\n ],\n description: 'Fast Cerebras-hosted inference through OpenAI-compatible APIs.',\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n providerType: 'cloud',\n providerId: 'deepinfra',\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DEEPINFRA_TOKEN',\n docsUrl: 'https://docs.deepinfra.com/chat/overview',\n setup: [\n 'Create a DeepInfra API token.',\n 'Use the OpenAI-compatible base URL ending in /v1/openai.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'DeepInfra hosted open-source models through OpenAI-compatible chat.',\n },\n {\n id: 'bedrock',\n name: 'Amazon Bedrock',\n providerType: 'cloud',\n providerId: 'bedrock',\n baseUrl: '',\n model: 'openai.gpt-oss-20b-1:0',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'AWS_BEARER_TOKEN_BEDROCK',\n docsUrl: 'https://docs.aws.amazon.com/bedrock/latest/userguide/inference-chat-completions.html',\n setup: [\n 'Generate an Amazon Bedrock API key.',\n 'Set Base URL to your region endpoint plus /openai/v1, for example https://bedrock-runtime.us-west-2.amazonaws.com/openai/v1.',\n 'Bedrock region and model availability vary; choose a model ID enabled in your AWS account.'\n ],\n description: 'Amazon Bedrock models through its OpenAI Chat Completions compatibility endpoint.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n apiKeyEnv: 'AZURE_OPENAI_API_KEY',\n docsUrl: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference',\n setup: [\n 'Enter your Azure OpenAI resource endpoint, for example https://resource-name.openai.azure.com.',\n 'Set Model to the Azure deployment name, or set deployment in advanced settings.',\n 'Azure requires an api-version query parameter; Asyncat defaults to 2024-10-21.'\n ],\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n setup: [\n 'Use this for any endpoint that implements OpenAI-compatible /chat/completions.',\n 'Set the exact base URL expected by the provider and a model ID that endpoint accepts.',\n 'Enable native tools only if the endpoint supports OpenAI tool_calls.'\n ],\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n const normalized = providerId === 'mlx-local' ? MLX_PROVIDER_ID : providerId;\n return PROVIDER_CATALOG.find(p => p.id === normalized || p.providerId === normalized) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'openai-codex') return url.replace(/\\/+$/, '');\n if (providerId === 'codex-cli' || url.startsWith('runtime://')) return url;\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (providerId === 'deepseek') return url.replace(/\\/v1\\/?$/i, '').replace(/\\/+$/, '');\n if (providerId === 'perplexity') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|v\\d+\\/openai|api\\/v\\d+|v\\d+beta\\/openai|compatibility\\/v\\d+|compatible-mode\\/v\\d+|inference\\/v\\d+)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const effectiveProviderId = row.provider_id === 'mlx-local' ? MLX_PROVIDER_ID : row.provider_id;\n const preset = getProviderPreset(effectiveProviderId);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: effectiveProviderId,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || effectiveProviderId || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, effectiveProviderId),\n provider_id: effectiveProviderId || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: effectiveProviderId === LLAMA_PROVIDER_ID || effectiveProviderId === MLX_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "27fd588bd72087ece462e44f698ede42e4fc23e6": {"t": 1785766734.5679429, "n": 25698, "h": "4be17e57a80a34c3", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-builtin';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'openai-codex',\n name: 'OpenAI Codex',\n providerType: 'cloud',\n providerId: 'openai-codex',\n baseUrl: 'https://chatgpt.com/backend-api/codex',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: false,\n managed: false,\n directCodex: true,\n setup: [\n 'Uses a local Codex/ChatGPT login against the Codex Responses backend.',\n 'Asyncat keeps its own token bundle after connection; it should not live-share refresh tokens with Codex CLI.',\n 'If you already ran codex login, Asyncat can import that token once to bootstrap its own session.'\n ],\n description: 'Use your Codex login from Asyncat. Experimental; not a normal OpenAI API key.',\n },\n {\n id: 'codex-cli',\n name: 'Codex CLI Runtime',\n providerType: 'local',\n providerId: 'codex-cli',\n baseUrl: 'runtime://codex-cli',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: true,\n managed: false,\n runtime: true,\n setup: [\n 'Uses codex exec as a subprocess and lets Codex own its auth session.',\n 'Asyncat does not read or refresh ~/.codex/auth.json.',\n 'Run codex login or open codex once before activating this provider.'\n ],\n description: 'Use your local Codex CLI login as a runtime boundary.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5.2',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 400000,\n local: false,\n apiKeyEnv: 'OPENAI_API_KEY',\n docsUrl: 'https://platform.openai.com/docs',\n setup: [\n 'Create a Platform API key in the OpenAI dashboard.',\n 'ChatGPT Plus/Pro/Business subscriptions and Codex plan access do not replace an API key for this app.',\n 'Codex CLI sign-in can create API credentials for Codex CLI, but there is no general-purpose ChatGPT subscription token that Asyncat can safely use.'\n ],\n description: 'OpenAI Platform API for GPT-5.2 and other API models.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n docsUrl: 'https://docs.anthropic.com/en/api/openai-sdk',\n compatibility: 'Anthropic documents this OpenAI SDK layer as useful for testing and comparison, with native Anthropic API recommended for the full feature set. OpenAI reasoning_effort is ignored by the compatibility layer.',\n setup: [\n 'Use an Anthropic API key.',\n 'Claude model listing is not exposed through the OpenAI-compatible /models flow here, so enter the model ID manually.',\n 'Some OpenAI parameters are ignored or mapped differently by Anthropic compatibility.'\n ],\n description: 'Claude through Anthropic OpenAI SDK compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n apiKeyEnv: 'GEMINI_API_KEY',\n docsUrl: 'https://ai.google.dev/gemini-api/docs/openai',\n setup: [\n 'Create a Gemini API key in Google AI Studio.',\n 'Use the Google OpenAI-compatible endpoint ending in /v1beta/openai/.'\n ],\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'xai',\n name: 'xAI',\n providerType: 'cloud',\n providerId: 'xai',\n baseUrl: 'https://api.x.ai/v1',\n model: 'grok-4.3',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'XAI_API_KEY',\n docsUrl: 'https://docs.x.ai/docs/guides/chat-completions',\n setup: [\n 'Create an xAI API key in the xAI Console.',\n 'Long-running reasoning models may need higher client timeouts; Asyncat streams responses but provider-side limits still apply.',\n 'Grok 4.3 supports function calling, structured outputs, and configurable reasoning.'\n ],\n description: 'Grok models through xAI OpenAI-compatible Chat Completions.',\n },\n {\n id: 'mistral',\n name: 'Mistral AI',\n providerType: 'cloud',\n providerId: 'mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-large-latest',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'MISTRAL_API_KEY',\n docsUrl: 'https://docs.mistral.ai/resources/migration-guides',\n setup: [\n 'Create a Mistral API key.',\n 'Mistral follows the Chat Completions request structure, so the OpenAI SDK works after changing base URL, key, and model name.'\n ],\n description: 'Mistral chat models through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n providerType: 'cloud',\n providerId: 'deepseek',\n baseUrl: 'https://api.deepseek.com',\n model: 'deepseek-v4-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 1000000,\n local: false,\n apiKeyEnv: 'DEEPSEEK_API_KEY',\n docsUrl: 'https://api-docs.deepseek.com/',\n setup: [\n 'Create a DeepSeek API key.',\n 'DeepSeek uses https://api.deepseek.com as the OpenAI SDK base URL; do not append /v1.',\n 'Use deepseek-v4-flash or deepseek-v4-pro. The older deepseek-chat and deepseek-reasoner aliases are scheduled for deprecation on July 24, 2026.'\n ],\n description: 'DeepSeek models through an OpenAI-compatible API.',\n },\n {\n id: 'qwen',\n name: 'Alibaba Qwen',\n providerType: 'cloud',\n providerId: 'qwen',\n baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',\n model: 'qwen-plus',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DASHSCOPE_API_KEY',\n docsUrl: 'https://www.alibabacloud.com/help/en/model-studio/compatibility-of-openai-with-dashscope',\n setup: [\n 'Create an Alibaba Cloud Model Studio API key.',\n 'The default endpoint is the Singapore international region. Change the base URL for US, Beijing, Hong Kong, or workspace-specific regions.',\n 'Qwen supports OpenAI-compatible chat completions and tool calls; thinking controls may require provider-specific extra_body parameters.'\n ],\n description: 'Qwen models through Alibaba Cloud Model Studio OpenAI compatibility.',\n },\n {\n id: 'kimi',\n name: 'Kimi',\n providerType: 'cloud',\n providerId: 'kimi',\n baseUrl: 'https://api.moonshot.ai/v1',\n model: 'kimi-k2.6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'MOONSHOT_API_KEY',\n docsUrl: 'https://platform.kimi.ai/docs/api/overview',\n setup: [\n 'Create a Kimi/Moonshot API key.',\n 'Kimi exposes OpenAI-compatible /v1/chat/completions and /v1/models endpoints.',\n 'Provider-specific thinking controls use Kimi extra_body fields, so leave reasoning controls on auto unless you have tested the model.'\n ],\n description: 'Kimi K-series models through Moonshot OpenAI-compatible APIs.',\n },\n {\n id: 'together',\n name: 'Together AI',\n providerType: 'cloud',\n providerId: 'together',\n baseUrl: 'https://api.together.ai/v1',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'TOGETHER_API_KEY',\n docsUrl: 'https://docs.together.ai/docs/openai-api-compatibility',\n setup: [\n 'Create a Together API key.',\n 'Choose a concrete model ID from Together before activating if the default is unavailable in your account.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'Open-source and hosted models through Together OpenAI compatibility.',\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n providerType: 'cloud',\n providerId: 'perplexity',\n baseUrl: 'https://api.perplexity.ai',\n model: 'sonar-pro',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'PERPLEXITY_API_KEY',\n docsUrl: 'https://docs.perplexity.ai/docs/sonar/openai-compatibility',\n setup: [\n 'Create a Perplexity API key.',\n 'Perplexity uses https://api.perplexity.ai as the OpenAI SDK base URL; do not append /v1.',\n 'Sonar is optimized for grounded web answers, not general agent tool calling, so native tools are off by default.'\n ],\n description: 'Perplexity Sonar API through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimax.io/platform/document',\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimaxi.com/document',\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'GROQ_API_KEY',\n docsUrl: 'https://console.groq.com/docs/overview',\n setup: [\n 'Create a GroqCloud API key.',\n 'Groq uses https://api.groq.com/openai/v1 and supports /models for available model IDs.'\n ],\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'huggingface',\n name: 'Hugging Face',\n providerType: 'cloud',\n providerId: 'huggingface',\n baseUrl: 'https://router.huggingface.co/v1',\n model: 'openai/gpt-oss-120b:fastest',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'HF_TOKEN',\n docsUrl: 'https://huggingface.co/docs/inference-providers/main/index',\n setup: [\n 'Create a Hugging Face token with Inference Providers permission.',\n 'The router endpoint is chat-completions only and can route to providers with suffixes such as :fastest or :cerebras.',\n 'Tool support is model/provider-dependent, so enable native tools only after testing the selected route.'\n ],\n description: 'Hugging Face Inference Providers through its OpenAI-compatible router.',\n },\n {\n id: 'nvidia-nim',\n name: 'NVIDIA NIM',\n providerType: 'cloud',\n providerId: 'nvidia-nim',\n baseUrl: 'https://integrate.api.nvidia.com/v1',\n model: 'openai/gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'NVIDIA_API_KEY',\n docsUrl: 'https://docs.api.nvidia.com/nim/reference/llm-apis',\n setup: [\n 'Create an NVIDIA API catalog key.',\n 'Hosted NIM uses https://integrate.api.nvidia.com/v1/chat/completions.',\n 'For self-hosted NIM, use a Custom provider pointed at your local or private /v1 endpoint.'\n ],\n description: 'Hosted NVIDIA NIM language models through OpenAI-compatible chat completions.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'OPENROUTER_API_KEY',\n docsUrl: 'https://openrouter.ai/docs/api/reference/overview',\n setup: [\n 'Create an OpenRouter API key.',\n 'openrouter/auto lets OpenRouter route automatically; pick a concrete provider/model when you need predictable behavior.',\n 'Asyncat sends OpenRouter app attribution headers for better dashboard attribution.'\n ],\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'cohere',\n name: 'Cohere',\n providerType: 'cloud',\n providerId: 'cohere',\n baseUrl: 'https://api.cohere.ai/compatibility/v1',\n model: 'command-a-03-2025',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'COHERE_API_KEY',\n docsUrl: 'https://docs.cohere.com/docs/compatibility-api',\n compatibility: 'Cohere compatibility supports many OpenAI chat parameters, but not every OpenAI field.',\n setup: [\n 'Create a Cohere API key.',\n 'Use the compatibility endpoint ending in /compatibility/v1.'\n ],\n description: 'Cohere Command models through the OpenAI SDK compatibility API.',\n },\n {\n id: 'fireworks',\n name: 'Fireworks AI',\n providerType: 'cloud',\n providerId: 'fireworks',\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n model: 'accounts/fireworks/models/llama-v3p1-8b-instruct',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'FIREWORKS_API_KEY',\n docsUrl: 'https://docs.fireworks.ai/tools-sdks/openai-compatibility',\n setup: [\n 'Create a Fireworks API key.',\n 'Fireworks model IDs often include the account path, for example accounts/fireworks/models/...',\n 'Tool support depends on the selected model, so this preset starts with native tools disabled.'\n ],\n description: 'Fireworks serverless and dedicated models through OpenAI compatibility.',\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n providerType: 'cloud',\n providerId: 'cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'CEREBRAS_API_KEY',\n docsUrl: 'https://inference-docs.cerebras.ai/resources/openai',\n setup: [\n 'Create a Cerebras API key.',\n 'Use Cerebras model IDs such as gpt-oss-120b or another model enabled for your account.'\n ],\n description: 'Fast Cerebras-hosted inference through OpenAI-compatible APIs.',\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n providerType: 'cloud',\n providerId: 'deepinfra',\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DEEPINFRA_TOKEN',\n docsUrl: 'https://docs.deepinfra.com/chat/overview',\n setup: [\n 'Create a DeepInfra API token.',\n 'Use the OpenAI-compatible base URL ending in /v1/openai.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'DeepInfra hosted open-source models through OpenAI-compatible chat.',\n },\n {\n id: 'bedrock',\n name: 'Amazon Bedrock',\n providerType: 'cloud',\n providerId: 'bedrock',\n baseUrl: '',\n model: 'openai.gpt-oss-20b-1:0',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'AWS_BEARER_TOKEN_BEDROCK',\n docsUrl: 'https://docs.aws.amazon.com/bedrock/latest/userguide/inference-chat-completions.html',\n setup: [\n 'Generate an Amazon Bedrock API key.',\n 'Set Base URL to your region endpoint plus /openai/v1, for example https://bedrock-runtime.us-west-2.amazonaws.com/openai/v1.',\n 'Bedrock region and model availability vary; choose a model ID enabled in your AWS account.'\n ],\n description: 'Amazon Bedrock models through its OpenAI Chat Completions compatibility endpoint.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n apiKeyEnv: 'AZURE_OPENAI_API_KEY',\n docsUrl: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference',\n setup: [\n 'Enter your Azure OpenAI resource endpoint, for example https://resource-name.openai.azure.com.',\n 'Set Model to the Azure deployment name, or set deployment in advanced settings.',\n 'Azure requires an api-version query parameter; Asyncat defaults to 2024-10-21.'\n ],\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n setup: [\n 'Use this for any endpoint that implements OpenAI-compatible /chat/completions.',\n 'Set the exact base URL expected by the provider and a model ID that endpoint accepts.',\n 'Enable native tools only if the endpoint supports OpenAI tool_calls.'\n ],\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n const normalized = providerId === 'mlx-local' ? MLX_PROVIDER_ID : providerId;\n return PROVIDER_CATALOG.find(p => p.id === normalized || p.providerId === normalized) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'openai-codex') return url.replace(/\\/+$/, '');\n if (providerId === 'codex-cli' || url.startsWith('runtime://')) return url;\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (providerId === 'deepseek') return url.replace(/\\/v1\\/?$/i, '').replace(/\\/+$/, '');\n if (providerId === 'perplexity') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|v\\d+\\/openai|api\\/v\\d+|v\\d+beta\\/openai|compatibility\\/v\\d+|compatible-mode\\/v\\d+|inference\\/v\\d+)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const effectiveProviderId = row.provider_id === 'mlx-local' ? MLX_PROVIDER_ID : row.provider_id;\n const preset = getProviderPreset(effectiveProviderId);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: effectiveProviderId,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || effectiveProviderId || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, effectiveProviderId),\n provider_id: effectiveProviderId || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: effectiveProviderId === LLAMA_PROVIDER_ID || effectiveProviderId === MLX_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "30a3b6a526a00064a3accb00891dcae075a7bd80": {"t": 1785766736.2360327, "n": 21081, "h": "1270175ce5a41190", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-local';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'openai-codex',\n name: 'OpenAI Codex',\n providerType: 'cloud',\n providerId: 'openai-codex',\n baseUrl: 'https://chatgpt.com/backend-api/codex',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: false,\n managed: false,\n directCodex: true,\n setup: [\n 'Uses a local Codex/ChatGPT login against the Codex Responses backend.',\n 'Asyncat keeps its own token bundle after connection; it should not live-share refresh tokens with Codex CLI.',\n 'If you already ran codex login, Asyncat can import that token once to bootstrap its own session.'\n ],\n description: 'Use your Codex login from Asyncat. Experimental; not a normal OpenAI API key.',\n },\n {\n id: 'codex-cli',\n name: 'Codex CLI Runtime',\n providerType: 'local',\n providerId: 'codex-cli',\n baseUrl: 'runtime://codex-cli',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: true,\n managed: false,\n runtime: true,\n setup: [\n 'Uses codex exec as a subprocess and lets Codex own its auth session.',\n 'Asyncat does not read or refresh ~/.codex/auth.json.',\n 'Run codex login or open codex once before activating this provider.'\n ],\n description: 'Use your local Codex CLI login as a runtime boundary.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5.2',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 400000,\n local: false,\n apiKeyEnv: 'OPENAI_API_KEY',\n docsUrl: 'https://platform.openai.com/docs',\n setup: [\n 'Create a Platform API key in the OpenAI dashboard.',\n 'ChatGPT Plus/Pro/Business subscriptions and Codex plan access do not replace an API key for this app.',\n 'Codex CLI sign-in can create API credentials for Codex CLI, but there is no general-purpose ChatGPT subscription token that Asyncat can safely use.'\n ],\n description: 'OpenAI Platform API for GPT-5.2 and other API models.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n docsUrl: 'https://docs.anthropic.com/en/api/openai-sdk',\n compatibility: 'Anthropic documents this OpenAI SDK layer as useful for testing and comparison, with native Anthropic API recommended for the full feature set.',\n setup: [\n 'Use an Anthropic API key.',\n 'Claude model listing is not exposed through the OpenAI-compatible /models flow here, so enter the model ID manually.',\n 'Some OpenAI parameters are ignored or mapped differently by Anthropic compatibility.'\n ],\n description: 'Claude through Anthropic OpenAI SDK compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n apiKeyEnv: 'GEMINI_API_KEY',\n docsUrl: 'https://ai.google.dev/gemini-api/docs/openai',\n setup: [\n 'Create a Gemini API key in Google AI Studio.',\n 'Use the Google OpenAI-compatible endpoint ending in /v1beta/openai/.'\n ],\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'xai',\n name: 'xAI',\n providerType: 'cloud',\n providerId: 'xai',\n baseUrl: 'https://api.x.ai/v1',\n model: 'grok-4.20-reasoning',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'XAI_API_KEY',\n docsUrl: 'https://docs.x.ai/docs/guides/chat-completions',\n setup: [\n 'Create an xAI API key in the xAI Console.',\n 'Long-running reasoning models may need higher client timeouts; Asyncat streams responses but provider-side limits still apply.',\n 'Native tool support varies by xAI model, so this preset keeps tool calling off by default.'\n ],\n description: 'Grok models through xAI OpenAI-compatible Chat Completions.',\n },\n {\n id: 'mistral',\n name: 'Mistral AI',\n providerType: 'cloud',\n providerId: 'mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-large-latest',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'MISTRAL_API_KEY',\n docsUrl: 'https://docs.mistral.ai/resources/migration-guides',\n setup: [\n 'Create a Mistral API key.',\n 'Mistral follows the Chat Completions request structure, so the OpenAI SDK works after changing base URL, key, and model name.'\n ],\n description: 'Mistral chat models through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n providerType: 'cloud',\n providerId: 'deepseek',\n baseUrl: 'https://api.deepseek.com/v1',\n model: 'deepseek-chat',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 64000,\n local: false,\n apiKeyEnv: 'DEEPSEEK_API_KEY',\n docsUrl: 'https://api-docs.deepseek.com/',\n setup: [\n 'Create a DeepSeek API key.',\n 'DeepSeek supports OpenAI-compatible chat completions. Model aliases can change over time, so verify the selected model in DeepSeek docs before production use.'\n ],\n description: 'DeepSeek models through an OpenAI-compatible API.',\n },\n {\n id: 'together',\n name: 'Together AI',\n providerType: 'cloud',\n providerId: 'together',\n baseUrl: 'https://api.together.ai/v1',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'TOGETHER_API_KEY',\n docsUrl: 'https://docs.together.ai/docs/openai-api-compatibility',\n setup: [\n 'Create a Together API key.',\n 'Choose a concrete model ID from Together before activating if the default is unavailable in your account.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'Open-source and hosted models through Together OpenAI compatibility.',\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n providerType: 'cloud',\n providerId: 'perplexity',\n baseUrl: 'https://api.perplexity.ai',\n model: 'sonar-pro',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'PERPLEXITY_API_KEY',\n docsUrl: 'https://docs.perplexity.ai/docs/sonar/openai-compatibility',\n setup: [\n 'Create a Perplexity API key.',\n 'Perplexity uses https://api.perplexity.ai as the OpenAI SDK base URL; do not append /v1.',\n 'Sonar is optimized for grounded web answers, not general agent tool calling, so native tools are off by default.'\n ],\n description: 'Perplexity Sonar API through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimax.io/platform/document',\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimaxi.com/document',\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'GROQ_API_KEY',\n docsUrl: 'https://console.groq.com/docs/overview',\n setup: [\n 'Create a GroqCloud API key.',\n 'Groq uses https://api.groq.com/openai/v1 and supports /models for available model IDs.'\n ],\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'OPENROUTER_API_KEY',\n docsUrl: 'https://openrouter.ai/docs/api/reference/overview',\n setup: [\n 'Create an OpenRouter API key.',\n 'openrouter/auto lets OpenRouter route automatically; pick a concrete provider/model when you need predictable behavior.',\n 'Asyncat sends OpenRouter app attribution headers for better dashboard attribution.'\n ],\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'cohere',\n name: 'Cohere',\n providerType: 'cloud',\n providerId: 'cohere',\n baseUrl: 'https://api.cohere.ai/compatibility/v1',\n model: 'command-a-03-2025',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'COHERE_API_KEY',\n docsUrl: 'https://docs.cohere.com/docs/compatibility-api',\n compatibility: 'Cohere compatibility supports many OpenAI chat parameters, but not every OpenAI field.',\n setup: [\n 'Create a Cohere API key.',\n 'Use the compatibility endpoint ending in /compatibility/v1.'\n ],\n description: 'Cohere Command models through the OpenAI SDK compatibility API.',\n },\n {\n id: 'fireworks',\n name: 'Fireworks AI',\n providerType: 'cloud',\n providerId: 'fireworks',\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n model: 'accounts/fireworks/models/llama-v3p1-8b-instruct',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'FIREWORKS_API_KEY',\n docsUrl: 'https://docs.fireworks.ai/tools-sdks/openai-compatibility',\n setup: [\n 'Create a Fireworks API key.',\n 'Fireworks model IDs often include the account path, for example accounts/fireworks/models/...',\n 'Tool support depends on the selected model, so this preset starts with native tools disabled.'\n ],\n description: 'Fireworks serverless and dedicated models through OpenAI compatibility.',\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n providerType: 'cloud',\n providerId: 'cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'CEREBRAS_API_KEY',\n docsUrl: 'https://inference-docs.cerebras.ai/resources/openai',\n setup: [\n 'Create a Cerebras API key.',\n 'Use Cerebras model IDs such as gpt-oss-120b or another model enabled for your account.'\n ],\n description: 'Fast Cerebras-hosted inference through OpenAI-compatible APIs.',\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n providerType: 'cloud',\n providerId: 'deepinfra',\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DEEPINFRA_TOKEN',\n docsUrl: 'https://docs.deepinfra.com/chat/overview',\n setup: [\n 'Create a DeepInfra API token.',\n 'Use the OpenAI-compatible base URL ending in /v1/openai.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'DeepInfra hosted open-source models through OpenAI-compatible chat.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n apiKeyEnv: 'AZURE_OPENAI_API_KEY',\n docsUrl: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference',\n setup: [\n 'Enter your Azure OpenAI resource endpoint, for example https://resource-name.openai.azure.com.',\n 'Set Model to the Azure deployment name, or set deployment in advanced settings.',\n 'Azure requires an api-version query parameter; Asyncat defaults to 2024-10-21.'\n ],\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n setup: [\n 'Use this for any endpoint that implements OpenAI-compatible /chat/completions.',\n 'Set the exact base URL expected by the provider and a model ID that endpoint accepts.',\n 'Enable native tools only if the endpoint supports OpenAI tool_calls.'\n ],\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n return PROVIDER_CATALOG.find(p => p.id === providerId || p.providerId === providerId) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'openai-codex') return url.replace(/\\/+$/, '');\n if (providerId === 'codex-cli' || url.startsWith('runtime://')) return url;\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (providerId === 'perplexity') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|v\\d+\\/openai|api\\/v\\d+|v\\d+beta\\/openai|compatibility\\/v\\d+|inference\\/v\\d+)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const preset = getProviderPreset(row.provider_id);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: row.provider_id,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || row.provider_id || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, row.provider_id),\n provider_id: row.provider_id || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: row.provider_id === LLAMA_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "f5997f6053e573b218974fafbeff3ceb63fb27f1": {"t": 1785766737.6550808, "n": 21046, "h": "e6dad96eb0a05833", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-local';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'openai-codex',\n name: 'OpenAI Codex',\n providerType: 'cloud',\n providerId: 'openai-codex',\n baseUrl: 'https://chatgpt.com/backend-api/codex',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: false,\n managed: false,\n directCodex: true,\n setup: [\n 'Uses Codex/ChatGPT OAuth against the Codex Responses backend.',\n 'Asyncat keeps its own token bundle after connection; it should not live-share refresh tokens with Codex CLI.',\n 'If you already ran codex login, Asyncat can import that token once to bootstrap its own session.'\n ],\n description: 'Use Codex subscription auth directly from Asyncat.',\n },\n {\n id: 'codex-cli',\n name: 'Codex CLI Runtime',\n providerType: 'local',\n providerId: 'codex-cli',\n baseUrl: 'runtime://codex-cli',\n model: 'gpt-5.5',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 272000,\n local: true,\n managed: false,\n runtime: true,\n setup: [\n 'Uses codex exec as a subprocess and lets Codex own its auth session.',\n 'Asyncat does not read or refresh ~/.codex/auth.json.',\n 'Run codex login or open codex once before activating this provider.'\n ],\n description: 'Use your local Codex CLI login as a runtime boundary.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-5.2',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 400000,\n local: false,\n apiKeyEnv: 'OPENAI_API_KEY',\n docsUrl: 'https://platform.openai.com/docs',\n setup: [\n 'Create a Platform API key in the OpenAI dashboard.',\n 'ChatGPT Plus/Pro/Business subscriptions and Codex plan access do not replace an API key for this app.',\n 'Codex CLI sign-in can create API credentials for Codex CLI, but there is no general-purpose ChatGPT subscription token that Asyncat can safely use.'\n ],\n description: 'OpenAI Platform API for GPT-5.2 and other API models.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'ANTHROPIC_API_KEY',\n docsUrl: 'https://docs.anthropic.com/en/api/openai-sdk',\n compatibility: 'Anthropic documents this OpenAI SDK layer as useful for testing and comparison, with native Anthropic API recommended for the full feature set.',\n setup: [\n 'Use an Anthropic API key.',\n 'Claude model listing is not exposed through the OpenAI-compatible /models flow here, so enter the model ID manually.',\n 'Some OpenAI parameters are ignored or mapped differently by Anthropic compatibility.'\n ],\n description: 'Claude through Anthropic OpenAI SDK compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n apiKeyEnv: 'GEMINI_API_KEY',\n docsUrl: 'https://ai.google.dev/gemini-api/docs/openai',\n setup: [\n 'Create a Gemini API key in Google AI Studio.',\n 'Use the Google OpenAI-compatible endpoint ending in /v1beta/openai/.'\n ],\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'xai',\n name: 'xAI',\n providerType: 'cloud',\n providerId: 'xai',\n baseUrl: 'https://api.x.ai/v1',\n model: 'grok-4.20-reasoning',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'XAI_API_KEY',\n docsUrl: 'https://docs.x.ai/docs/guides/chat-completions',\n setup: [\n 'Create an xAI API key in the xAI Console.',\n 'Long-running reasoning models may need higher client timeouts; Asyncat streams responses but provider-side limits still apply.',\n 'Native tool support varies by xAI model, so this preset keeps tool calling off by default.'\n ],\n description: 'Grok models through xAI OpenAI-compatible Chat Completions.',\n },\n {\n id: 'mistral',\n name: 'Mistral AI',\n providerType: 'cloud',\n providerId: 'mistral',\n baseUrl: 'https://api.mistral.ai/v1',\n model: 'mistral-large-latest',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'MISTRAL_API_KEY',\n docsUrl: 'https://docs.mistral.ai/resources/migration-guides',\n setup: [\n 'Create a Mistral API key.',\n 'Mistral follows the Chat Completions request structure, so the OpenAI SDK works after changing base URL, key, and model name.'\n ],\n description: 'Mistral chat models through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'deepseek',\n name: 'DeepSeek',\n providerType: 'cloud',\n providerId: 'deepseek',\n baseUrl: 'https://api.deepseek.com/v1',\n model: 'deepseek-chat',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 64000,\n local: false,\n apiKeyEnv: 'DEEPSEEK_API_KEY',\n docsUrl: 'https://api-docs.deepseek.com/',\n setup: [\n 'Create a DeepSeek API key.',\n 'DeepSeek supports OpenAI-compatible chat completions. Model aliases can change over time, so verify the selected model in DeepSeek docs before production use.'\n ],\n description: 'DeepSeek models through an OpenAI-compatible API.',\n },\n {\n id: 'together',\n name: 'Together AI',\n providerType: 'cloud',\n providerId: 'together',\n baseUrl: 'https://api.together.ai/v1',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'TOGETHER_API_KEY',\n docsUrl: 'https://docs.together.ai/docs/openai-api-compatibility',\n setup: [\n 'Create a Together API key.',\n 'Choose a concrete model ID from Together before activating if the default is unavailable in your account.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'Open-source and hosted models through Together OpenAI compatibility.',\n },\n {\n id: 'perplexity',\n name: 'Perplexity',\n providerType: 'cloud',\n providerId: 'perplexity',\n baseUrl: 'https://api.perplexity.ai',\n model: 'sonar-pro',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n apiKeyEnv: 'PERPLEXITY_API_KEY',\n docsUrl: 'https://docs.perplexity.ai/docs/sonar/openai-compatibility',\n setup: [\n 'Create a Perplexity API key.',\n 'Perplexity uses https://api.perplexity.ai as the OpenAI SDK base URL; do not append /v1.',\n 'Sonar is optimized for grounded web answers, not general agent tool calling, so native tools are off by default.'\n ],\n description: 'Perplexity Sonar API through OpenAI-compatible Chat Completions.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimax.io/platform/document',\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n apiKeyEnv: 'MINIMAX_API_KEY',\n docsUrl: 'https://www.minimaxi.com/document',\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'GROQ_API_KEY',\n docsUrl: 'https://console.groq.com/docs/overview',\n setup: [\n 'Create a GroqCloud API key.',\n 'Groq uses https://api.groq.com/openai/v1 and supports /models for available model IDs.'\n ],\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n apiKeyEnv: 'OPENROUTER_API_KEY',\n docsUrl: 'https://openrouter.ai/docs/api/reference/overview',\n setup: [\n 'Create an OpenRouter API key.',\n 'openrouter/auto lets OpenRouter route automatically; pick a concrete provider/model when you need predictable behavior.',\n 'Asyncat sends OpenRouter app attribution headers for better dashboard attribution.'\n ],\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'cohere',\n name: 'Cohere',\n providerType: 'cloud',\n providerId: 'cohere',\n baseUrl: 'https://api.cohere.ai/compatibility/v1',\n model: 'command-a-03-2025',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 256000,\n local: false,\n apiKeyEnv: 'COHERE_API_KEY',\n docsUrl: 'https://docs.cohere.com/docs/compatibility-api',\n compatibility: 'Cohere compatibility supports many OpenAI chat parameters, but not every OpenAI field.',\n setup: [\n 'Create a Cohere API key.',\n 'Use the compatibility endpoint ending in /compatibility/v1.'\n ],\n description: 'Cohere Command models through the OpenAI SDK compatibility API.',\n },\n {\n id: 'fireworks',\n name: 'Fireworks AI',\n providerType: 'cloud',\n providerId: 'fireworks',\n baseUrl: 'https://api.fireworks.ai/inference/v1',\n model: 'accounts/fireworks/models/llama-v3p1-8b-instruct',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'FIREWORKS_API_KEY',\n docsUrl: 'https://docs.fireworks.ai/tools-sdks/openai-compatibility',\n setup: [\n 'Create a Fireworks API key.',\n 'Fireworks model IDs often include the account path, for example accounts/fireworks/models/...',\n 'Tool support depends on the selected model, so this preset starts with native tools disabled.'\n ],\n description: 'Fireworks serverless and dedicated models through OpenAI compatibility.',\n },\n {\n id: 'cerebras',\n name: 'Cerebras',\n providerType: 'cloud',\n providerId: 'cerebras',\n baseUrl: 'https://api.cerebras.ai/v1',\n model: 'gpt-oss-120b',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'CEREBRAS_API_KEY',\n docsUrl: 'https://inference-docs.cerebras.ai/resources/openai',\n setup: [\n 'Create a Cerebras API key.',\n 'Use Cerebras model IDs such as gpt-oss-120b or another model enabled for your account.'\n ],\n description: 'Fast Cerebras-hosted inference through OpenAI-compatible APIs.',\n },\n {\n id: 'deepinfra',\n name: 'DeepInfra',\n providerType: 'cloud',\n providerId: 'deepinfra',\n baseUrl: 'https://api.deepinfra.com/v1/openai',\n model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n requiresApiKey: true,\n supportsTools: false,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n apiKeyEnv: 'DEEPINFRA_TOKEN',\n docsUrl: 'https://docs.deepinfra.com/chat/overview',\n setup: [\n 'Create a DeepInfra API token.',\n 'Use the OpenAI-compatible base URL ending in /v1/openai.',\n 'Tool support is model-dependent, so enable native tools only after testing.'\n ],\n description: 'DeepInfra hosted open-source models through OpenAI-compatible chat.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n apiKeyEnv: 'AZURE_OPENAI_API_KEY',\n docsUrl: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/reference',\n setup: [\n 'Enter your Azure OpenAI resource endpoint, for example https://resource-name.openai.azure.com.',\n 'Set Model to the Azure deployment name, or set deployment in advanced settings.',\n 'Azure requires an api-version query parameter; Asyncat defaults to 2024-10-21.'\n ],\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n setup: [\n 'Use this for any endpoint that implements OpenAI-compatible /chat/completions.',\n 'Set the exact base URL expected by the provider and a model ID that endpoint accepts.',\n 'Enable native tools only if the endpoint supports OpenAI tool_calls.'\n ],\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n return PROVIDER_CATALOG.find(p => p.id === providerId || p.providerId === providerId) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'openai-codex') return url.replace(/\\/+$/, '');\n if (providerId === 'codex-cli' || url.startsWith('runtime://')) return url;\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (providerId === 'perplexity') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|v\\d+\\/openai|api\\/v\\d+|v\\d+beta\\/openai|compatibility\\/v\\d+|inference\\/v\\d+)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const preset = getProviderPreset(row.provider_id);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: row.provider_id,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || row.provider_id || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, row.provider_id),\n provider_id: row.provider_id || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: row.provider_id === LLAMA_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "8390d339d860f0982057ba53cdda8ba4a63a4e71": {"t": 1785766739.1609762, "n": 10038, "h": "d741f090e5f1ae2e", "c": "import { resolveContextWindow } from './modelContextResolver.js';\n\nconst LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-local';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-4o',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n description: 'OpenAI platform API.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 200000,\n local: false,\n description: 'Claude through Anthropic OpenAI compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 1048576,\n local: false,\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 204800,\n local: false,\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 131072,\n local: false,\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 128000,\n local: false,\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n contextWindow: 128000,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n contextWindow: 8192,\n local: false,\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n return PROVIDER_CATALOG.find(p => p.id === providerId || p.providerId === providerId) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|api\\/v\\d+|v\\d+beta\\/openai)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const preset = getProviderPreset(row.provider_id);\n const settings = parseSettings(row.settings);\n const resolvedContext = resolveContextWindow({\n providerId: row.provider_id,\n model: row.model,\n settings,\n preset,\n });\n return {\n id: row.id,\n name: row.name || preset?.name || row.provider_id || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, row.provider_id),\n provider_id: row.provider_id || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n context_window: resolvedContext.contextWindow,\n context_window_source: resolvedContext.source,\n context_window_confidence: resolvedContext.confidence,\n local: providerIsLocal(row),\n managed: row.provider_id === LLAMA_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "41a24d4929670d66381f1f5faf69f214c971bf45": {"t": 1785766740.6750743, "n": 9432, "h": "cbee960dcf0c8ade", "c": "const LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nconst MLX_PORT = parseInt(process.env.MLX_SERVER_PORT ?? '8766', 10);\nexport const MLX_PROVIDER_ID = 'mlx-local';\nexport const MLX_BASE_URL = `http://127.0.0.1:${MLX_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: MLX_PROVIDER_ID,\n name: 'MLX (Apple Silicon)',\n providerType: 'local',\n providerId: MLX_PROVIDER_ID,\n baseUrl: MLX_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run HuggingFace MLX models natively on Apple Silicon via mlx_lm.server.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-4o',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'OpenAI platform API.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n local: false,\n description: 'Claude through Anthropic OpenAI compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n return PROVIDER_CATALOG.find(p => p.id === providerId || p.providerId === providerId) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|api\\/v\\d+|v\\d+beta\\/openai)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const preset = getProviderPreset(row.provider_id);\n const settings = parseSettings(row.settings);\n return {\n id: row.id,\n name: row.name || preset?.name || row.provider_id || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, row.provider_id),\n provider_id: row.provider_id || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n local: providerIsLocal(row),\n managed: row.provider_id === LLAMA_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "fdbb3fed4f697c3d73e6b27683b1af154403fbbf": {"t": 1785766741.9285197, "n": 8874, "h": "1ff9e8aa1544110e", "c": "const LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-4o',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'OpenAI platform API.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n local: false,\n description: 'Claude through Anthropic OpenAI compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n return PROVIDER_CATALOG.find(p => p.id === providerId || p.providerId === providerId) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|api\\/v\\d+|v\\d+beta\\/openai)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const preset = getProviderPreset(row.provider_id);\n const settings = parseSettings(row.settings);\n return {\n id: row.id,\n name: row.name || preset?.name || row.provider_id || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, row.provider_id),\n provider_id: row.provider_id || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n local: providerIsLocal(row),\n managed: row.provider_id === LLAMA_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n\n/**\n * Check if Ollama is installed and running on localhost:11434\n * Attempts both /api/tags (list models) and / (health check) endpoints\n */\nexport async function checkOllamaRunning() {\n const OLLAMA_BASE = 'http://localhost:11434';\n\n // Try /api/tags first - gives us model list too\n try {\n const res = await fetch(`${OLLAMA_BASE}/api/tags`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: data.models?.map(m => m.name) || [],\n };\n }\n } catch {}\n\n // Fallback: try root health endpoint\n try {\n const health = await fetch(`${OLLAMA_BASE}/`, {\n signal: AbortSignal.timeout(2000)\n });\n if (health.ok) {\n return {\n found: true,\n running: true,\n baseUrl: `${OLLAMA_BASE}/v1`,\n models: [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n\n/**\n * Check if LM Studio is installed and running on localhost:1234\n */\nexport async function checkLMStudioRunning() {\n const LM_STUDIO_BASE = 'http://localhost:1234';\n\n try {\n const res = await fetch(`${LM_STUDIO_BASE}/v1/models`, {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const data = await res.json().catch(() => ({}));\n return {\n found: true,\n running: true,\n baseUrl: `${LM_STUDIO_BASE}/v1`,\n models: data.data?.map(m => m.id) || [],\n };\n }\n } catch {}\n\n return {\n found: false,\n running: false,\n baseUrl: null,\n models: [],\n };\n}\n"}, "f826b6217fa6e1b4a05ff3ae3758b8acfba012c6": {"t": 1785766743.3063846, "n": 7183, "h": "dcf23f453bb9b311", "c": "const LLAMA_PORT = parseInt(process.env.LLAMA_SERVER_PORT ?? '8765', 10);\n\nexport const LLAMA_PROVIDER_ID = 'llamacpp-builtin';\nexport const LLAMA_BASE_URL = `http://127.0.0.1:${LLAMA_PORT}/v1`;\n\nexport const PROVIDER_CATALOG = [\n {\n id: LLAMA_PROVIDER_ID,\n name: 'Built-in llama.cpp',\n providerType: 'local',\n providerId: LLAMA_PROVIDER_ID,\n baseUrl: LLAMA_BASE_URL,\n model: '',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: false,\n local: true,\n managed: true,\n description: 'Run downloaded GGUF models with Asyncat managed llama.cpp.',\n },\n {\n id: 'ollama',\n name: 'Ollama',\n providerType: 'local',\n providerId: 'ollama',\n baseUrl: 'http://localhost:11434/v1',\n model: 'llama3.2',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use models served by a local Ollama instance.',\n },\n {\n id: 'lmstudio',\n name: 'LM Studio',\n providerType: 'local',\n providerId: 'lmstudio',\n baseUrl: 'http://localhost:1234/v1',\n model: 'local-model',\n requiresApiKey: false,\n supportsTools: false,\n supportsModelList: true,\n local: true,\n managed: false,\n description: 'Use an OpenAI-compatible LM Studio local server.',\n },\n {\n id: 'openai',\n name: 'OpenAI',\n providerType: 'cloud',\n providerId: 'openai',\n baseUrl: 'https://api.openai.com/v1',\n model: 'gpt-4o',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'OpenAI platform API.',\n },\n {\n id: 'anthropic',\n name: 'Anthropic',\n providerType: 'cloud',\n providerId: 'anthropic',\n baseUrl: 'https://api.anthropic.com/v1',\n model: 'claude-sonnet-4-6',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n local: false,\n description: 'Claude through Anthropic OpenAI compatibility.',\n },\n {\n id: 'gemini',\n name: 'Gemini',\n providerType: 'cloud',\n providerId: 'gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/',\n model: 'gemini-2.5-flash',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Google Gemini through OpenAI compatibility.',\n },\n {\n id: 'minimax',\n name: 'MiniMax Global',\n providerType: 'cloud',\n providerId: 'minimax',\n baseUrl: 'https://api.minimax.io/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'MiniMax international OpenAI-compatible API.',\n },\n {\n id: 'minimax-cn',\n name: 'MiniMax China',\n providerType: 'cloud',\n providerId: 'minimax-cn',\n baseUrl: 'https://api.minimaxi.com/v1',\n model: 'MiniMax-M2.7',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'MiniMax China OpenAI-compatible API.',\n },\n {\n id: 'groq',\n name: 'Groq',\n providerType: 'cloud',\n providerId: 'groq',\n baseUrl: 'https://api.groq.com/openai/v1',\n model: 'llama-3.3-70b-versatile',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Groq OpenAI-compatible API.',\n },\n {\n id: 'openrouter',\n name: 'OpenRouter',\n providerType: 'cloud',\n providerId: 'openrouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n model: 'openrouter/auto',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Route through OpenRouter with one API key.',\n },\n {\n id: 'azure',\n name: 'Azure OpenAI',\n providerType: 'cloud',\n providerId: 'azure',\n baseUrl: '',\n model: '',\n requiresApiKey: true,\n supportsTools: true,\n supportsModelList: false,\n local: false,\n settings: { apiVersion: '2024-10-21' },\n description: 'Azure OpenAI deployment endpoint.',\n },\n {\n id: 'custom',\n name: 'Custom',\n providerType: 'custom',\n providerId: 'custom',\n baseUrl: '',\n model: '',\n requiresApiKey: false,\n supportsTools: true,\n supportsModelList: true,\n local: false,\n description: 'Any OpenAI-compatible endpoint.',\n },\n];\n\nexport function getProviderPreset(providerId) {\n return PROVIDER_CATALOG.find(p => p.id === providerId || p.providerId === providerId) || null;\n}\n\nexport function parseSettings(value) {\n if (!value) return {};\n if (typeof value === 'object') return value;\n try {\n const parsed = JSON.parse(value);\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nexport function normalizeProviderType(providerType, providerId) {\n const preset = getProviderPreset(providerId);\n if (providerType === 'local' || providerType === 'cloud' || providerType === 'custom') return providerType;\n return preset?.providerType || 'custom';\n}\n\nexport function normalizeBaseUrl(baseUrl, providerId) {\n const preset = getProviderPreset(providerId);\n let url = String(baseUrl || preset?.baseUrl || '').trim();\n if (!url) return '';\n url = url.replace(/\\s+/g, '');\n if (providerId === 'azure') return url.replace(/\\/+$/, '');\n if (/\\/(v\\d+(?:beta)?|openai\\/v\\d+|api\\/v\\d+|v\\d+beta\\/openai)\\/?$/i.test(url)) {\n return url;\n }\n return `${url.replace(/\\/+$/, '')}/v1`;\n}\n\nexport function providerRequiresBuiltinServer(providerInfo = {}) {\n return providerInfo.provider_id === LLAMA_PROVIDER_ID || providerInfo.providerId === LLAMA_PROVIDER_ID;\n}\n\nexport function providerIsLocal(providerInfo = {}) {\n const providerType = providerInfo.provider_type || providerInfo.providerType;\n return providerType === 'local';\n}\n\nexport function providerSupportsTools(providerInfo = {}) {\n if (providerInfo.supports_tools !== undefined && providerInfo.supports_tools !== null) {\n return Boolean(Number(providerInfo.supports_tools));\n }\n if (providerInfo.supportsTools !== undefined) return Boolean(providerInfo.supportsTools);\n const preset = getProviderPreset(providerInfo.provider_id || providerInfo.providerId);\n return Boolean(preset?.supportsTools);\n}\n\nexport function publicProvider(row) {\n if (!row) return null;\n const preset = getProviderPreset(row.provider_id);\n const settings = parseSettings(row.settings);\n return {\n id: row.id,\n name: row.name || preset?.name || row.provider_id || row.provider_type,\n provider_type: normalizeProviderType(row.provider_type, row.provider_id),\n provider_id: row.provider_id || preset?.providerId || 'custom',\n base_url: row.base_url || '',\n model: row.model || '',\n api_key_set: Boolean(row.api_key),\n settings,\n supports_tools: providerSupportsTools(row),\n supports_model_list: Boolean(preset?.supportsModelList),\n local: providerIsLocal(row),\n managed: row.provider_id === LLAMA_PROVIDER_ID,\n last_test_status: row.last_test_status || null,\n last_test_message: row.last_test_message || null,\n last_test_at: row.last_test_at || null,\n created_at: row.created_at || null,\n updated_at: row.updated_at || null,\n };\n}\n"}}}}