{"repo": "chenm4/voicechatassistant", "files": {"lobehub-main/packages/model-runtime/src/providers/infiniai/index.test.ts": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785595477.6051762, "n": 2244, "h": "665fe1ada3ed5e4f", "c": "// @vitest-environment node\nimport { ModelProvider } from 'model-bank';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { testProvider } from '../../providerTestUtils';\nimport { LobeInfiniAI } from './index';\n\ntestProvider({\n Runtime: LobeInfiniAI,\n provider: ModelProvider.InfiniAI,\n defaultBaseURL: 'https://cloud.infini-ai.com/maas/v1',\n chatDebugEnv: 'DEBUG_INFINIAI_CHAT_COMPLETION',\n chatModel: 'gpt-3.5-turbo',\n invalidErrorType: 'InvalidProviderAPIKey',\n bizErrorType: 'ProviderBizError',\n test: {\n skipAPICall: true,\n skipErrorHandle: true,\n },\n});\n\ndescribe('LobeInfiniAI - custom features', () => {\n let instance: InstanceType;\n\n beforeEach(() => {\n instance = new LobeInfiniAI({ apiKey: 'test_api_key' });\n vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(\n new ReadableStream() as any,\n );\n });\n\n describe('handlePayload', () => {\n it('should add enable_thinking when thinking is enabled', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: 'deepseek-v3',\n thinking: {\n type: 'enabled',\n budget_tokens: 1000,\n },\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.enable_thinking).toBe(true);\n });\n\n it('should not add enable_thinking when thinking is disabled', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: 'deepseek-v3',\n thinking: {\n budget_tokens: 1000,\n type: 'disabled',\n },\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.enable_thinking).toBe(false);\n });\n\n it('should set enable_thinking to false when thinking is undefined', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: 'deepseek-v3',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.enable_thinking).toBe(false);\n });\n });\n});\n"}}, "lobehub-main/src/envs/llm.ts": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785595562.2722452, "n": 14664, "h": "270ae96448c34ee7", "c": "/* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */\nimport { createEnv } from '@t3-oss/env-nextjs';\nimport { z } from 'zod';\n\nexport const getLLMConfig = () => {\n return createEnv({\n server: {\n API_KEY_SELECT_MODE: z.string().optional(),\n\n ENABLED_OPENAI: z.boolean(),\n OPENAI_API_KEY: z.string().optional(),\n\n ENABLED_AZURE_OPENAI: z.boolean(),\n AZURE_API_KEY: z.string().optional(),\n AZURE_API_VERSION: z.string().optional(),\n AZURE_ENDPOINT: z.string().optional(),\n\n ENABLED_AZUREAI: z.boolean(),\n AZUREAI_ENDPOINT: z.string().optional(),\n AZUREAI_ENDPOINT_KEY: z.string().optional(),\n\n ENABLED_ZHIPU: z.boolean(),\n ZHIPU_API_KEY: z.string().optional(),\n\n ENABLED_DEEPSEEK: z.boolean(),\n DEEPSEEK_API_KEY: z.string().optional(),\n\n ENABLED_GOOGLE: z.boolean(),\n GOOGLE_API_KEY: z.string().optional(),\n\n ENABLED_VERTEXAI: z.boolean(),\n\n ENABLED_MOONSHOT: z.boolean(),\n MOONSHOT_API_KEY: z.string().optional(),\n\n ENABLED_PERPLEXITY: z.boolean(),\n PERPLEXITY_API_KEY: z.string().optional(),\n\n ENABLED_ANTHROPIC: z.boolean(),\n ANTHROPIC_API_KEY: z.string().optional(),\n\n ENABLED_MINIMAX: z.boolean(),\n MINIMAX_API_KEY: z.string().optional(),\n\n ENABLED_MISTRAL: z.boolean(),\n MISTRAL_API_KEY: z.string().optional(),\n\n ENABLED_GROQ: z.boolean(),\n GROQ_API_KEY: z.string().optional(),\n\n ENABLED_GITHUB: z.boolean(),\n GITHUB_TOKEN: z.string().optional(),\n\n ENABLED_OPENROUTER: z.boolean(),\n OPENROUTER_API_KEY: z.string().optional(),\n\n ENABLED_ZEROONE: z.boolean(),\n ZEROONE_API_KEY: z.string().optional(),\n\n ENABLED_TOGETHERAI: z.boolean(),\n TOGETHERAI_API_KEY: z.string().optional(),\n\n ENABLED_FIREWORKSAI: z.boolean(),\n FIREWORKSAI_API_KEY: z.string().optional(),\n\n ENABLED_AWS_BEDROCK: z.boolean(),\n AWS_REGION: z.string().optional(),\n AWS_ACCESS_KEY_ID: z.string().optional(),\n AWS_SECRET_ACCESS_KEY: z.string().optional(),\n AWS_SESSION_TOKEN: z.string().optional(),\n\n ENABLED_WENXIN: z.boolean(),\n WENXIN_API_KEY: z.string().optional(),\n\n ENABLED_OLLAMA: z.boolean(),\n ENABLED_OLLAMA_CLOUD: z.boolean(),\n OLLAMA_CLOUD_API_KEY: z.string().optional(),\n\n ENABLED_VLLM: z.boolean(),\n VLLM_API_KEY: z.string().optional(),\n\n ENABLED_XINFERENCE: z.boolean(),\n XINFERENCE_API_KEY: z.string().optional(),\n\n ENABLED_QINIU: z.boolean(),\n QINIU_API_KEY: z.string().optional(),\n\n ENABLED_QWEN: z.boolean(),\n QWEN_API_KEY: z.string().optional(),\n\n ENABLED_STEPFUN: z.boolean(),\n STEPFUN_API_KEY: z.string().optional(),\n\n ENABLED_NEBIUS: z.boolean(),\n NEBIUS_API_KEY: z.string().optional(),\n\n ENABLED_NOVITA: z.boolean(),\n NOVITA_API_KEY: z.string().optional(),\n\n ENABLED_NVIDIA: z.boolean(),\n NVIDIA_API_KEY: z.string().optional(),\n\n ENABLED_BAICHUAN: z.boolean(),\n BAICHUAN_API_KEY: z.string().optional(),\n\n ENABLED_TAICHU: z.boolean(),\n TAICHU_API_KEY: z.string().optional(),\n\n ENABLED_CLOUDFLARE: z.boolean(),\n CLOUDFLARE_API_KEY: z.string().optional(),\n CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID: z.string().optional(),\n\n ENABLED_AI360: z.boolean(),\n AI360_API_KEY: z.string().optional(),\n\n ENABLED_SILICONCLOUD: z.boolean(),\n SILICONCLOUD_API_KEY: z.string().optional(),\n\n ENABLED_GITEE_AI: z.boolean(),\n GITEE_AI_API_KEY: z.string().optional(),\n\n ENABLED_UPSTAGE: z.boolean(),\n UPSTAGE_API_KEY: z.string().optional(),\n\n ENABLED_SPARK: z.boolean(),\n SPARK_API_KEY: z.string().optional(),\n\n ENABLED_AI21: z.boolean(),\n AI21_API_KEY: z.string().optional(),\n\n ENABLED_HUNYUAN: z.boolean(),\n HUNYUAN_API_KEY: z.string().optional(),\n\n ENABLED_HUGGINGFACE: z.boolean(),\n HUGGINGFACE_API_KEY: z.string().optional(),\n\n ENABLED_SENSENOVA: z.boolean(),\n SENSENOVA_API_KEY: z.string().optional(),\n\n ENABLED_XAI: z.boolean(),\n XAI_API_KEY: z.string().optional(),\n\n ENABLED_INTERNLM: z.boolean(),\n INTERNLM_API_KEY: z.string().optional(),\n\n ENABLED_HIGRESS: z.boolean(),\n HIGRESS_API_KEY: z.string().optional(),\n\n ENABLED_VOLCENGINE: z.boolean(),\n VOLCENGINE_API_KEY: z.string().optional(),\n\n ENABLED_TENCENT_CLOUD: z.boolean(),\n TENCENT_CLOUD_API_KEY: z.string().optional(),\n\n ENABLED_JINA: z.boolean(),\n JINA_API_KEY: z.string().optional(),\n\n ENABLED_SAMBANOVA: z.boolean(),\n SAMBANOVA_API_KEY: z.string().optional(),\n\n ENABLED_PPIO: z.boolean(),\n PPIO_API_KEY: z.string().optional(),\n\n ENABLED_SEARCH1API: z.boolean(),\n SEARCH1API_API_KEY: z.string().optional(),\n\n ENABLED_COHERE: z.boolean(),\n COHERE_API_KEY: z.string().optional(),\n\n ENABLED_INFINIAI: z.boolean(),\n INFINIAI_API_KEY: z.string().optional(),\n\n ENABLED_FAL: z.boolean(),\n FAL_API_KEY: z.string().optional(),\n\n ENABLED_BFL: z.boolean(),\n BFL_API_KEY: z.string().optional(),\n\n ENABLED_COMFYUI: z.boolean(),\n COMFYUI_BASE_URL: z.string().optional(),\n COMFYUI_AUTH_TYPE: z.string().optional(),\n COMFYUI_API_KEY: z.string().optional(),\n COMFYUI_USERNAME: z.string().optional(),\n COMFYUI_PASSWORD: z.string().optional(),\n COMFYUI_CUSTOM_HEADERS: z.string().optional(),\n\n ENABLED_MODELSCOPE: z.boolean(),\n MODELSCOPE_API_KEY: z.string().optional(),\n\n ENABLED_V0: z.boolean(),\n V0_API_KEY: z.string().optional(),\n\n ENABLED_VERCELAIGATEWAY: z.boolean(),\n VERCELAIGATEWAY_API_KEY: z.string().optional(),\n\n ENABLED_AI302: z.boolean(),\n AI302_API_KEY: z.string().optional(),\n\n ENABLED_AKASHCHAT: z.boolean(),\n AKASHCHAT_API_KEY: z.string().optional(),\n\n ENABLED_COMETAPI: z.boolean(),\n COMETAPI_KEY: z.string().optional(),\n\n ENABLED_AIHUBMIX: z.boolean(),\n AIHUBMIX_API_KEY: z.string().optional(),\n\n ENABLED_NEWAPI: z.boolean(),\n NEWAPI_API_KEY: z.string().optional(),\n NEWAPI_PROXY_URL: z.string().optional(),\n\n ENABLED_CEREBRAS: z.boolean(),\n CEREBRAS_API_KEY: z.string().optional(),\n\n ENABLED_ZENMUX: z.boolean(),\n ZENMUX_API_KEY: z.string().optional(),\n\n ENABLED_LOBEHUB: z.boolean(),\n\n ENABLED_XIAOMIMIMO: z.boolean(),\n XIAOMIMIMO_API_KEY: z.string().optional(),\n },\n runtimeEnv: {\n API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,\n\n ENABLED_OPENAI: process.env.ENABLED_OPENAI !== '0',\n OPENAI_API_KEY: process.env.OPENAI_API_KEY,\n\n ENABLED_AZURE_OPENAI: !!process.env.AZURE_API_KEY,\n AZURE_API_KEY: process.env.AZURE_API_KEY,\n AZURE_API_VERSION: process.env.AZURE_API_VERSION,\n AZURE_ENDPOINT: process.env.AZURE_ENDPOINT,\n\n ENABLED_AZUREAI: !!process.env.AZUREAI_ENDPOINT_KEY,\n AZUREAI_ENDPOINT_KEY: process.env.AZUREAI_ENDPOINT_KEY,\n AZUREAI_ENDPOINT: process.env.AZUREAI_ENDPOINT,\n\n ENABLED_ZHIPU: !!process.env.ZHIPU_API_KEY,\n ZHIPU_API_KEY: process.env.ZHIPU_API_KEY,\n\n ENABLED_DEEPSEEK: !!process.env.DEEPSEEK_API_KEY,\n DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,\n\n ENABLED_GOOGLE: process.env.ENABLED_GOOGLE !== '0',\n GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,\n\n ENABLED_VERTEXAI: !!process.env.VERTEXAI_CREDENTIALS,\n\n ENABLED_VOLCENGINE: !!process.env.VOLCENGINE_API_KEY,\n VOLCENGINE_API_KEY: process.env.VOLCENGINE_API_KEY,\n\n ENABLED_PERPLEXITY: !!process.env.PERPLEXITY_API_KEY,\n PERPLEXITY_API_KEY: process.env.PERPLEXITY_API_KEY,\n\n ENABLED_ANTHROPIC: process.env.ENABLED_ANTHROPIC !== '0',\n ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,\n\n ENABLED_MINIMAX: !!process.env.MINIMAX_API_KEY,\n MINIMAX_API_KEY: process.env.MINIMAX_API_KEY,\n\n ENABLED_MISTRAL: !!process.env.MISTRAL_API_KEY,\n MISTRAL_API_KEY: process.env.MISTRAL_API_KEY,\n\n ENABLED_OPENROUTER: !!process.env.OPENROUTER_API_KEY,\n OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,\n\n ENABLED_TOGETHERAI: !!process.env.TOGETHERAI_API_KEY,\n TOGETHERAI_API_KEY: process.env.TOGETHERAI_API_KEY,\n\n ENABLED_FIREWORKSAI: !!process.env.FIREWORKSAI_API_KEY,\n FIREWORKSAI_API_KEY: process.env.FIREWORKSAI_API_KEY,\n\n ENABLED_MOONSHOT: !!process.env.MOONSHOT_API_KEY,\n MOONSHOT_API_KEY: process.env.MOONSHOT_API_KEY,\n\n ENABLED_GROQ: !!process.env.GROQ_API_KEY,\n GROQ_API_KEY: process.env.GROQ_API_KEY,\n\n ENABLED_GITHUB: !!process.env.GITHUB_TOKEN,\n GITHUB_TOKEN: process.env.GITHUB_TOKEN,\n\n ENABLED_ZEROONE: !!process.env.ZEROONE_API_KEY,\n ZEROONE_API_KEY: process.env.ZEROONE_API_KEY,\n\n ENABLED_AWS_BEDROCK: process.env.ENABLED_AWS_BEDROCK === '1',\n AWS_REGION: process.env.AWS_REGION,\n AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,\n AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,\n AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN,\n\n ENABLED_WENXIN: !!process.env.WENXIN_API_KEY,\n WENXIN_API_KEY: process.env.WENXIN_API_KEY,\n\n ENABLED_OLLAMA: process.env.ENABLED_OLLAMA !== '0',\n ENABLED_OLLAMA_CLOUD: !!process.env.OLLAMA_CLOUD_API_KEY,\n OLLAMA_CLOUD_API_KEY: process.env.OLLAMA_CLOUD_API_KEY,\n\n ENABLED_VLLM: !!process.env.VLLM_API_KEY,\n VLLM_API_KEY: process.env.VLLM_API_KEY,\n\n ENABLED_XINFERENCE: !!process.env.XINFERENCE_API_KEY,\n XINFERENCE_API_KEY: process.env.XINFERENCE_API_KEY,\n\n ENABLED_QINIU: !!process.env.QINIU_API_KEY,\n QINIU_API_KEY: process.env.QINIU_API_KEY,\n\n ENABLED_QWEN: !!process.env.QWEN_API_KEY,\n QWEN_API_KEY: process.env.QWEN_API_KEY,\n\n ENABLED_STEPFUN: !!process.env.STEPFUN_API_KEY,\n STEPFUN_API_KEY: process.env.STEPFUN_API_KEY,\n\n ENABLED_NOVITA: !!process.env.NOVITA_API_KEY,\n NOVITA_API_KEY: process.env.NOVITA_API_KEY,\n\n ENABLED_NVIDIA: !!process.env.NVIDIA_API_KEY,\n NVIDIA_API_KEY: process.env.NVIDIA_API_KEY,\n\n ENABLED_BAICHUAN: !!process.env.BAICHUAN_API_KEY,\n BAICHUAN_API_KEY: process.env.BAICHUAN_API_KEY,\n\n ENABLED_TAICHU: !!process.env.TAICHU_API_KEY,\n TAICHU_API_KEY: process.env.TAICHU_API_KEY,\n\n ENABLED_CLOUDFLARE:\n !!process.env.CLOUDFLARE_API_KEY && !!process.env.CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID,\n CLOUDFLARE_API_KEY: process.env.CLOUDFLARE_API_KEY,\n CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID: process.env.CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID,\n\n ENABLED_AI360: !!process.env.AI360_API_KEY,\n AI360_API_KEY: process.env.AI360_API_KEY,\n\n ENABLED_SILICONCLOUD: !!process.env.SILICONCLOUD_API_KEY,\n SILICONCLOUD_API_KEY: process.env.SILICONCLOUD_API_KEY,\n\n ENABLED_GITEE_AI: !!process.env.GITEE_AI_API_KEY,\n GITEE_AI_API_KEY: process.env.GITEE_AI_API_KEY,\n\n ENABLED_UPSTAGE: !!process.env.UPSTAGE_API_KEY,\n UPSTAGE_API_KEY: process.env.UPSTAGE_API_KEY,\n\n ENABLED_SPARK: !!process.env.SPARK_API_KEY,\n SPARK_API_KEY: process.env.SPARK_API_KEY,\n\n ENABLED_AI21: !!process.env.AI21_API_KEY,\n AI21_API_KEY: process.env.AI21_API_KEY,\n\n ENABLED_HUNYUAN: !!process.env.HUNYUAN_API_KEY,\n HUNYUAN_API_KEY: process.env.HUNYUAN_API_KEY,\n\n ENABLED_HUGGINGFACE: !!process.env.HUGGINGFACE_API_KEY,\n HUGGINGFACE_API_KEY: process.env.HUGGINGFACE_API_KEY,\n\n ENABLED_SENSENOVA: !!process.env.SENSENOVA_API_KEY,\n SENSENOVA_API_KEY: process.env.SENSENOVA_API_KEY,\n\n ENABLED_XAI: !!process.env.XAI_API_KEY,\n XAI_API_KEY: process.env.XAI_API_KEY,\n\n ENABLED_INTERNLM: !!process.env.INTERNLM_API_KEY,\n INTERNLM_API_KEY: process.env.INTERNLM_API_KEY,\n\n ENABLED_HIGRESS: !!process.env.HIGRESS_API_KEY,\n HIGRESS_API_KEY: process.env.HIGRESS_API_KEY,\n\n ENABLED_TENCENT_CLOUD: !!process.env.TENCENT_CLOUD_API_KEY,\n TENCENT_CLOUD_API_KEY: process.env.TENCENT_CLOUD_API_KEY,\n\n ENABLED_JINA: !!process.env.JINA_API_KEY,\n JINA_API_KEY: process.env.JINA_API_KEY,\n\n ENABLED_SAMBANOVA: !!process.env.SAMBANOVA_API_KEY,\n SAMBANOVA_API_KEY: process.env.SAMBANOVA_API_KEY,\n\n ENABLED_PPIO: !!process.env.PPIO_API_KEY,\n PPIO_API_KEY: process.env.PPIO_API_KEY,\n\n ENABLED_SEARCH1API: !!process.env.SEARCH1API_API_KEY,\n SEARCH1API_API_KEY: process.env.SEARCH1API_API_KEY,\n\n ENABLED_COHERE: !!process.env.COHERE_API_KEY,\n COHERE_API_KEY: process.env.COHERE_API_KEY,\n\n ENABLED_INFINIAI: !!process.env.INFINIAI_API_KEY,\n INFINIAI_API_KEY: process.env.INFINIAI_API_KEY,\n\n ENABLED_FAL: process.env.ENABLED_FAL !== '0',\n FAL_API_KEY: process.env.FAL_API_KEY,\n\n ENABLED_BFL: !!process.env.BFL_API_KEY,\n BFL_API_KEY: process.env.BFL_API_KEY,\n\n ENABLED_COMFYUI: process.env.ENABLED_COMFYUI !== '0',\n COMFYUI_BASE_URL: process.env.COMFYUI_BASE_URL,\n COMFYUI_AUTH_TYPE: process.env.COMFYUI_AUTH_TYPE,\n COMFYUI_API_KEY: process.env.COMFYUI_API_KEY,\n COMFYUI_USERNAME: process.env.COMFYUI_USERNAME,\n COMFYUI_PASSWORD: process.env.COMFYUI_PASSWORD,\n COMFYUI_CUSTOM_HEADERS: process.env.COMFYUI_CUSTOM_HEADERS,\n\n ENABLED_MODELSCOPE: !!process.env.MODELSCOPE_API_KEY,\n MODELSCOPE_API_KEY: process.env.MODELSCOPE_API_KEY,\n\n ENABLED_V0: !!process.env.V0_API_KEY,\n V0_API_KEY: process.env.V0_API_KEY,\n\n ENABLED_VERCELAIGATEWAY: !!process.env.VERCELAIGATEWAY_API_KEY,\n VERCELAIGATEWAY_API_KEY: process.env.VERCELAIGATEWAY_API_KEY,\n\n ENABLED_AI302: !!process.env.AI302_API_KEY,\n AI302_API_KEY: process.env.AI302_API_KEY,\n\n ENABLED_AKASHCHAT: !!process.env.AKASHCHAT_API_KEY,\n AKASHCHAT_API_KEY: process.env.AKASHCHAT_API_KEY,\n\n ENABLED_COMETAPI: !!process.env.COMETAPI_KEY,\n COMETAPI_KEY: process.env.COMETAPI_KEY,\n\n ENABLED_AIHUBMIX: !!process.env.AIHUBMIX_API_KEY,\n AIHUBMIX_API_KEY: process.env.AIHUBMIX_API_KEY,\n\n ENABLED_NEWAPI: !!process.env.NEWAPI_API_KEY,\n NEWAPI_API_KEY: process.env.NEWAPI_API_KEY,\n NEWAPI_PROXY_URL: process.env.NEWAPI_PROXY_URL,\n\n ENABLED_NEBIUS: !!process.env.NEBIUS_API_KEY,\n NEBIUS_API_KEY: process.env.NEBIUS_API_KEY,\n\n ENABLED_CEREBRAS: !!process.env.CEREBRAS_API_KEY,\n CEREBRAS_API_KEY: process.env.CEREBRAS_API_KEY,\n\n ENABLED_ZENMUX: !!process.env.ZENMUX_API_KEY,\n ZENMUX_API_KEY: process.env.ZENMUX_API_KEY,\n\n ENABLED_LOBEHUB: !!process.env.ENABLED_LOBEHUB,\n\n ENABLED_XIAOMIMIMO: !!process.env.XIAOMIMIMO_API_KEY,\n XIAOMIMIMO_API_KEY: process.env.XIAOMIMIMO_API_KEY,\n },\n });\n};\n\nexport const llmEnv = getLLMConfig();\n"}}, "lobehub-main/Dockerfile": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785595569.9529452, "n": 11420, "h": "994bbfb4dbeca3d8", "c": "## Set global build ENV\nARG NODEJS_VERSION=\"24\"\n\n## Base image for all building stages\nFROM node:${NODEJS_VERSION}-slim AS base\n\nARG USE_CN_MIRROR\n\nENV DEBIAN_FRONTEND=\"noninteractive\"\n\nRUN set -e && \\\n if [ \"${USE_CN_MIRROR:-false}\" = \"true\" ]; then \\\n sed -i \"s/deb.debian.org/mirrors.ustc.edu.cn/g\" \"/etc/apt/sources.list.d/debian.sources\"; \\\n fi && \\\n apt update && \\\n apt install ca-certificates proxychains-ng -qy && \\\n mkdir -p /distroless/bin /distroless/etc /distroless/etc/ssl/certs /distroless/lib && \\\n cp /usr/lib/$(arch)-linux-gnu/libproxychains.so.4 /distroless/lib/libproxychains.so.4 && \\\n cp /usr/lib/$(arch)-linux-gnu/libdl.so.2 /distroless/lib/libdl.so.2 && \\\n cp /usr/bin/proxychains4 /distroless/bin/proxychains && \\\n cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf && \\\n cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6 && \\\n cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1 && \\\n cp /usr/local/bin/node /distroless/bin/node && \\\n cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt && \\\n rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*\n\n## Builder image, install all the dependencies and build the app\nFROM base AS builder\n\nARG USE_CN_MIRROR\nARG NEXT_PUBLIC_BASE_PATH\nARG NEXT_PUBLIC_SENTRY_DSN\nARG NEXT_PUBLIC_ANALYTICS_POSTHOG\nARG NEXT_PUBLIC_POSTHOG_HOST\nARG NEXT_PUBLIC_POSTHOG_KEY\nARG NEXT_PUBLIC_ANALYTICS_UMAMI\nARG NEXT_PUBLIC_UMAMI_SCRIPT_URL\nARG NEXT_PUBLIC_UMAMI_WEBSITE_ID\nARG FEATURE_FLAGS\n\nENV NEXT_PUBLIC_BASE_PATH=\"${NEXT_PUBLIC_BASE_PATH}\" \\\n FEATURE_FLAGS=\"${FEATURE_FLAGS}\"\n\nENV APP_URL=\"http://app.com\" \\\n DATABASE_DRIVER=\"node\" \\\n DATABASE_URL=\"postgres://postgres:password@localhost:5432/postgres\" \\\n KEY_VAULTS_SECRET=\"use-for-build\" \\\n AUTH_SECRET=\"use-for-build\"\n\n# Sentry\nENV NEXT_PUBLIC_SENTRY_DSN=\"${NEXT_PUBLIC_SENTRY_DSN}\" \\\n SENTRY_ORG=\"\" \\\n SENTRY_PROJECT=\"\"\n\n# Posthog\nENV NEXT_PUBLIC_ANALYTICS_POSTHOG=\"${NEXT_PUBLIC_ANALYTICS_POSTHOG}\" \\\n NEXT_PUBLIC_POSTHOG_HOST=\"${NEXT_PUBLIC_POSTHOG_HOST}\" \\\n NEXT_PUBLIC_POSTHOG_KEY=\"${NEXT_PUBLIC_POSTHOG_KEY}\"\n\n# Umami\nENV NEXT_PUBLIC_ANALYTICS_UMAMI=\"${NEXT_PUBLIC_ANALYTICS_UMAMI}\" \\\n NEXT_PUBLIC_UMAMI_SCRIPT_URL=\"${NEXT_PUBLIC_UMAMI_SCRIPT_URL}\" \\\n NEXT_PUBLIC_UMAMI_WEBSITE_ID=\"${NEXT_PUBLIC_UMAMI_WEBSITE_ID}\"\n\n# Node\nENV NODE_OPTIONS=\"--max-old-space-size=8192\"\n\nWORKDIR /app\n\nCOPY package.json pnpm-workspace.yaml ./\nCOPY .npmrc ./\nCOPY packages ./packages\nCOPY patches ./patches\n# bring in desktop workspace manifest so pnpm can resolve it\nCOPY apps/desktop/src/main/package.json ./apps/desktop/src/main/package.json\n\nRUN set -e && \\\n if [ \"${USE_CN_MIRROR:-false}\" = \"true\" ]; then \\\n export SENTRYCLI_CDNURL=\"https://npmmirror.com/mirrors/sentry-cli\"; \\\n npm config set registry \"https://registry.npmmirror.com/\"; \\\n echo 'canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas' >> .npmrc; \\\n fi && \\\n export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\\/$//') && \\\n npm i -g corepack@latest && \\\n corepack enable && \\\n corepack use $(sed -n 's/.*\"packageManager\": \"\\(.*\\)\".*/\\1/p' package.json) && \\\n pnpm i && \\\n mkdir -p /deps && \\\n cd /deps && \\\n pnpm init && \\\n pnpm add pg drizzle-orm\n\nCOPY . .\n\n# run build standalone for docker version\nRUN npm run build:docker\n\n# Prepare desktop export assets for Electron packaging (if generated)\nRUN set -e && \\\n if [ -d \"/app/out\" ]; then \\\n mkdir -p /app/apps/desktop/dist/next && \\\n cp -a /app/out/. /app/apps/desktop/dist/next/ && \\\n echo \"Copied Next export output into /app/apps/desktop/dist/next\"; \\\n else \\\n echo \"No Next export output found at /app/out, creating empty directory\" && \\\n mkdir -p /app/apps/desktop/dist/next; \\\n fi\n\n## Application image, copy all the files for production\nFROM busybox:latest AS app\n\nCOPY --from=base /distroless/ /\n\n# Automatically leverage output traces to reduce image size\n# https://nextjs.org/docs/advanced-features/output-file-tracing\nCOPY --from=builder /app/.next/standalone /app/\n# Copy Next export output for desktop renderer\nCOPY --from=builder /app/apps/desktop/dist/next /app/apps/desktop/dist/next\n\n# Copy database migrations\nCOPY --from=builder /app/packages/database/migrations /app/migrations\nCOPY --from=builder /app/scripts/migrateServerDB/docker.cjs /app/docker.cjs\nCOPY --from=builder /app/scripts/migrateServerDB/errorHint.js /app/errorHint.js\n\n# copy dependencies\nCOPY --from=builder /deps/node_modules/.pnpm /app/node_modules/.pnpm\nCOPY --from=builder /deps/node_modules/pg /app/node_modules/pg\nCOPY --from=builder /deps/node_modules/drizzle-orm /app/node_modules/drizzle-orm\n\n# Copy server launcher and shared scripts\nCOPY --from=builder /app/scripts/serverLauncher/startServer.js /app/startServer.js\nCOPY --from=builder /app/scripts/_shared /app/scripts/_shared\n\nRUN set -e && \\\n addgroup -S -g 1001 nodejs && \\\n adduser -D -G nodejs -H -S -h /app -u 1001 nextjs && \\\n chown -R nextjs:nodejs /app /etc/proxychains4.conf\n\n## Production image, copy all the files and run next\nFROM scratch\n\n# Copy all the files from app, set the correct permission for prerender cache\nCOPY --from=app / /\n\nENV NODE_ENV=\"production\" \\\n NODE_OPTIONS=\"--dns-result-order=ipv4first --use-openssl-ca\" \\\n NODE_EXTRA_CA_CERTS=\"\" \\\n NODE_TLS_REJECT_UNAUTHORIZED=\"\" \\\n SSL_CERT_FILE=\"/etc/ssl/certs/ca-certificates.crt\"\n\n# Make the middleware rewrite through local as default\n# refs: https://github.com/lobehub/lobe-chat/issues/5876\nENV MIDDLEWARE_REWRITE_THROUGH_LOCAL=\"1\"\n\n# set hostname to localhost\nENV HOSTNAME=\"0.0.0.0\" \\\n PORT=\"3210\"\n\n# General Variables\nENV ACCESS_CODE=\"\" \\\n APP_URL=\"\" \\\n API_KEY_SELECT_MODE=\"\" \\\n DEFAULT_AGENT_CONFIG=\"\" \\\n SYSTEM_AGENT=\"\" \\\n FEATURE_FLAGS=\"\" \\\n PROXY_URL=\"\" \\\n ENABLE_AUTH_PROTECTION=\"\"\n\n# Database\nENV KEY_VAULTS_SECRET=\"\" \\\n DATABASE_DRIVER=\"node\" \\\n DATABASE_URL=\"\"\n\n# Better Auth\nENV AUTH_SECRET=\"\" \\\n AUTH_SSO_PROVIDERS=\"\" \\\n AUTH_ALLOWED_EMAILS=\"\" \\\n # Google\n AUTH_GOOGLE_ID=\"\" \\\n AUTH_GOOGLE_SECRET=\"\" \\\n # GitHub\n AUTH_GITHUB_ID=\"\" \\\n AUTH_GITHUB_SECRET=\"\" \\\n # Microsoft\n AUTH_MICROSOFT_ID=\"\" \\\n AUTH_MICROSOFT_SECRET=\"\"\n\n# Redis\nENV REDIS_URL=\"\" \\\n REDIS_PREFIX=\"\" \\\n REDIS_TLS=\"\"\n\n# Email\nENV EMAIL_SERVICE_PROVIDER=\"\" \\\n SMTP_HOST=\"\" \\\n SMTP_PORT=\"\" \\\n SMTP_SECURE=\"\" \\\n SMTP_USER=\"\" \\\n SMTP_PASS=\"\" \\\n SMTP_FROM=\"\" \\\n RESEND_API_KEY=\"\" \\\n RESEND_FROM=\"\"\n\n# S3\nENV NEXT_PUBLIC_S3_DOMAIN=\"\" \\\n S3_PUBLIC_DOMAIN=\"\" \\\n S3_ACCESS_KEY_ID=\"\" \\\n S3_BUCKET=\"\" \\\n S3_ENDPOINT=\"\" \\\n S3_SECRET_ACCESS_KEY=\"\" \\\n S3_ENABLE_PATH_STYLE=\"\" \\\n S3_SET_ACL=\"\"\n\n# Model Variables\nENV \\\n # AI21\n AI21_API_KEY=\"\" AI21_MODEL_LIST=\"\" \\\n # Ai360\n AI360_API_KEY=\"\" AI360_MODEL_LIST=\"\" \\\n # AiHubMix\n AIHUBMIX_API_KEY=\"\" AIHUBMIX_MODEL_LIST=\"\" \\\n # Anthropic\n ANTHROPIC_API_KEY=\"\" ANTHROPIC_MODEL_LIST=\"\" ANTHROPIC_PROXY_URL=\"\" \\\n # Amazon Bedrock\n ENABLED_AWS_BEDROCK=\"\" AWS_ACCESS_KEY_ID=\"\" AWS_SECRET_ACCESS_KEY=\"\" AWS_REGION=\"\" AWS_BEDROCK_MODEL_LIST=\"\" \\\n # Azure OpenAI\n AZURE_API_KEY=\"\" AZURE_API_VERSION=\"\" AZURE_ENDPOINT=\"\" AZURE_MODEL_LIST=\"\" \\\n # Baichuan\n BAICHUAN_API_KEY=\"\" BAICHUAN_MODEL_LIST=\"\" \\\n # Cloudflare\n CLOUDFLARE_API_KEY=\"\" CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID=\"\" CLOUDFLARE_MODEL_LIST=\"\" \\\n # Cohere\n COHERE_API_KEY=\"\" COHERE_MODEL_LIST=\"\" COHERE_PROXY_URL=\"\" \\\n # ComfyUI\n ENABLED_COMFYUI=\"\" COMFYUI_BASE_URL=\"\" COMFYUI_AUTH_TYPE=\"\" \\\n COMFYUI_API_KEY=\"\" COMFYUI_USERNAME=\"\" COMFYUI_PASSWORD=\"\" COMFYUI_CUSTOM_HEADERS=\"\" \\\n # DeepSeek\n DEEPSEEK_API_KEY=\"\" DEEPSEEK_MODEL_LIST=\"\" \\\n # Fireworks AI\n FIREWORKSAI_API_KEY=\"\" FIREWORKSAI_MODEL_LIST=\"\" \\\n # Gitee AI\n GITEE_AI_API_KEY=\"\" GITEE_AI_MODEL_LIST=\"\" \\\n # GitHub\n GITHUB_TOKEN=\"\" GITHUB_MODEL_LIST=\"\" \\\n # Google\n GOOGLE_API_KEY=\"\" GOOGLE_MODEL_LIST=\"\" GOOGLE_PROXY_URL=\"\" \\\n # Vertex AI\n VERTEXAI_CREDENTIALS=\"\" VERTEXAI_PROJECT=\"\" VERTEXAI_LOCATION=\"\" VERTEXAI_MODEL_LIST=\"\" \\\n # Groq\n GROQ_API_KEY=\"\" GROQ_MODEL_LIST=\"\" GROQ_PROXY_URL=\"\" \\\n # Higress\n HIGRESS_API_KEY=\"\" HIGRESS_MODEL_LIST=\"\" HIGRESS_PROXY_URL=\"\" \\\n # HuggingFace\n HUGGINGFACE_API_KEY=\"\" HUGGINGFACE_MODEL_LIST=\"\" HUGGINGFACE_PROXY_URL=\"\" \\\n # Hunyuan\n HUNYUAN_API_KEY=\"\" HUNYUAN_MODEL_LIST=\"\" \\\n # InternLM\n INTERNLM_API_KEY=\"\" INTERNLM_MODEL_LIST=\"\" \\\n # Jina\n JINA_API_KEY=\"\" JINA_MODEL_LIST=\"\" JINA_PROXY_URL=\"\" \\\n # Minimax\n MINIMAX_API_KEY=\"\" MINIMAX_MODEL_LIST=\"\" \\\n # Mistral\n MISTRAL_API_KEY=\"\" MISTRAL_MODEL_LIST=\"\" \\\n # ModelScope\n MODELSCOPE_API_KEY=\"\" MODELSCOPE_MODEL_LIST=\"\" MODELSCOPE_PROXY_URL=\"\" \\\n # Moonshot\n MOONSHOT_API_KEY=\"\" MOONSHOT_MODEL_LIST=\"\" MOONSHOT_PROXY_URL=\"\" \\\n # Nebius\n NEBIUS_API_KEY=\"\" NEBIUS_MODEL_LIST=\"\" NEBIUS_PROXY_URL=\"\" \\\n # NewAPI\n NEWAPI_API_KEY=\"\" NEWAPI_PROXY_URL=\"\" \\\n # Novita\n NOVITA_API_KEY=\"\" NOVITA_MODEL_LIST=\"\" \\\n # Nvidia NIM\n NVIDIA_API_KEY=\"\" NVIDIA_MODEL_LIST=\"\" NVIDIA_PROXY_URL=\"\" \\\n # Ollama\n ENABLED_OLLAMA=\"\" OLLAMA_MODEL_LIST=\"\" OLLAMA_PROXY_URL=\"\" \\\n # OpenAI\n ENABLED_OPENAI=\"\" OPENAI_API_KEY=\"\" OPENAI_MODEL_LIST=\"\" OPENAI_PROXY_URL=\"\" \\\n # OpenRouter\n OPENROUTER_API_KEY=\"\" OPENROUTER_MODEL_LIST=\"\" \\\n # Perplexity\n PERPLEXITY_API_KEY=\"\" PERPLEXITY_MODEL_LIST=\"\" PERPLEXITY_PROXY_URL=\"\" \\\n # PPIO\n PPIO_API_KEY=\"\" PPIO_MODEL_LIST=\"\" \\\n # Qiniu\n QINIU_API_KEY=\"\" QINIU_MODEL_LIST=\"\" QINIU_PROXY_URL=\"\" \\\n # Qwen\n QWEN_API_KEY=\"\" QWEN_MODEL_LIST=\"\" QWEN_PROXY_URL=\"\" \\\n # SambaNova\n SAMBANOVA_API_KEY=\"\" SAMBANOVA_MODEL_LIST=\"\" \\\n # Search1API\n SEARCH1API_API_KEY=\"\" SEARCH1API_MODEL_LIST=\"\" \\\n # SenseNova\n SENSENOVA_API_KEY=\"\" SENSENOVA_MODEL_LIST=\"\" \\\n # SiliconCloud\n SILICONCLOUD_API_KEY=\"\" SILICONCLOUD_MODEL_LIST=\"\" SILICONCLOUD_PROXY_URL=\"\" \\\n # Spark\n SPARK_API_KEY=\"\" SPARK_MODEL_LIST=\"\" SPARK_PROXY_URL=\"\" SPARK_SEARCH_MODE=\"\" \\\n # Stepfun\n STEPFUN_API_KEY=\"\" STEPFUN_MODEL_LIST=\"\" \\\n # Taichu\n TAICHU_API_KEY=\"\" TAICHU_MODEL_LIST=\"\" \\\n # TogetherAI\n TOGETHERAI_API_KEY=\"\" TOGETHERAI_MODEL_LIST=\"\" \\\n # Upstage\n UPSTAGE_API_KEY=\"\" UPSTAGE_MODEL_LIST=\"\" \\\n # v0 (Vercel)\n V0_API_KEY=\"\" V0_MODEL_LIST=\"\" \\\n # vLLM\n VLLM_API_KEY=\"\" VLLM_MODEL_LIST=\"\" VLLM_PROXY_URL=\"\" \\\n # Wenxin\n WENXIN_API_KEY=\"\" WENXIN_MODEL_LIST=\"\" \\\n # xAI\n XAI_API_KEY=\"\" XAI_MODEL_LIST=\"\" XAI_PROXY_URL=\"\" \\\n # Xinference\n XINFERENCE_API_KEY=\"\" XINFERENCE_MODEL_LIST=\"\" XINFERENCE_PROXY_URL=\"\" \\\n # 01.AI\n ZEROONE_API_KEY=\"\" ZEROONE_MODEL_LIST=\"\" \\\n # Zhipu\n ZHIPU_API_KEY=\"\" ZHIPU_MODEL_LIST=\"\" \\\n # Tencent Cloud\n TENCENT_CLOUD_API_KEY=\"\" TENCENT_CLOUD_MODEL_LIST=\"\" \\\n # Infini-AI\n INFINIAI_API_KEY=\"\" INFINIAI_MODEL_LIST=\"\" \\\n # 302.AI\n AI302_API_KEY=\"\" AI302_MODEL_LIST=\"\" \\\n # FAL\n ENABLED_FAL=\"\" FAL_API_KEY=\"\" FAL_MODEL_LIST=\"\" \\\n # BFL\n BFL_API_KEY=\"\" BFL_MODEL_LIST=\"\" \\\n # Vercel AI Gateway\n VERCELAIGATEWAY_API_KEY=\"\" VERCELAIGATEWAY_MODEL_LIST=\"\" \\\n # Cerebras\n CEREBRAS_API_KEY=\"\" CEREBRAS_MODEL_LIST=\"\"\n\nUSER nextjs\n\nEXPOSE 3210/tcp\n\nENTRYPOINT [\"/bin/node\"]\n\nCMD [\"/app/startServer.js\"]\n"}, "ed7bb85c532110d96ffd2405ed94dc8f6677e760": {"t": 1785769786.1887548, "n": 11420, "h": "994bbfb4dbeca3d8", "c": "## Set global build ENV\nARG NODEJS_VERSION=\"24\"\n\n## Base image for all building stages\nFROM node:${NODEJS_VERSION}-slim AS base\n\nARG USE_CN_MIRROR\n\nENV DEBIAN_FRONTEND=\"noninteractive\"\n\nRUN set -e && \\\n if [ \"${USE_CN_MIRROR:-false}\" = \"true\" ]; then \\\n sed -i \"s/deb.debian.org/mirrors.ustc.edu.cn/g\" \"/etc/apt/sources.list.d/debian.sources\"; \\\n fi && \\\n apt update && \\\n apt install ca-certificates proxychains-ng -qy && \\\n mkdir -p /distroless/bin /distroless/etc /distroless/etc/ssl/certs /distroless/lib && \\\n cp /usr/lib/$(arch)-linux-gnu/libproxychains.so.4 /distroless/lib/libproxychains.so.4 && \\\n cp /usr/lib/$(arch)-linux-gnu/libdl.so.2 /distroless/lib/libdl.so.2 && \\\n cp /usr/bin/proxychains4 /distroless/bin/proxychains && \\\n cp /etc/proxychains4.conf /distroless/etc/proxychains4.conf && \\\n cp /usr/lib/$(arch)-linux-gnu/libstdc++.so.6 /distroless/lib/libstdc++.so.6 && \\\n cp /usr/lib/$(arch)-linux-gnu/libgcc_s.so.1 /distroless/lib/libgcc_s.so.1 && \\\n cp /usr/local/bin/node /distroless/bin/node && \\\n cp /etc/ssl/certs/ca-certificates.crt /distroless/etc/ssl/certs/ca-certificates.crt && \\\n rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/*\n\n## Builder image, install all the dependencies and build the app\nFROM base AS builder\n\nARG USE_CN_MIRROR\nARG NEXT_PUBLIC_BASE_PATH\nARG NEXT_PUBLIC_SENTRY_DSN\nARG NEXT_PUBLIC_ANALYTICS_POSTHOG\nARG NEXT_PUBLIC_POSTHOG_HOST\nARG NEXT_PUBLIC_POSTHOG_KEY\nARG NEXT_PUBLIC_ANALYTICS_UMAMI\nARG NEXT_PUBLIC_UMAMI_SCRIPT_URL\nARG NEXT_PUBLIC_UMAMI_WEBSITE_ID\nARG FEATURE_FLAGS\n\nENV NEXT_PUBLIC_BASE_PATH=\"${NEXT_PUBLIC_BASE_PATH}\" \\\n FEATURE_FLAGS=\"${FEATURE_FLAGS}\"\n\nENV APP_URL=\"http://app.com\" \\\n DATABASE_DRIVER=\"node\" \\\n DATABASE_URL=\"postgres://postgres:password@localhost:5432/postgres\" \\\n KEY_VAULTS_SECRET=\"use-for-build\" \\\n AUTH_SECRET=\"use-for-build\"\n\n# Sentry\nENV NEXT_PUBLIC_SENTRY_DSN=\"${NEXT_PUBLIC_SENTRY_DSN}\" \\\n SENTRY_ORG=\"\" \\\n SENTRY_PROJECT=\"\"\n\n# Posthog\nENV NEXT_PUBLIC_ANALYTICS_POSTHOG=\"${NEXT_PUBLIC_ANALYTICS_POSTHOG}\" \\\n NEXT_PUBLIC_POSTHOG_HOST=\"${NEXT_PUBLIC_POSTHOG_HOST}\" \\\n NEXT_PUBLIC_POSTHOG_KEY=\"${NEXT_PUBLIC_POSTHOG_KEY}\"\n\n# Umami\nENV NEXT_PUBLIC_ANALYTICS_UMAMI=\"${NEXT_PUBLIC_ANALYTICS_UMAMI}\" \\\n NEXT_PUBLIC_UMAMI_SCRIPT_URL=\"${NEXT_PUBLIC_UMAMI_SCRIPT_URL}\" \\\n NEXT_PUBLIC_UMAMI_WEBSITE_ID=\"${NEXT_PUBLIC_UMAMI_WEBSITE_ID}\"\n\n# Node\nENV NODE_OPTIONS=\"--max-old-space-size=8192\"\n\nWORKDIR /app\n\nCOPY package.json pnpm-workspace.yaml ./\nCOPY .npmrc ./\nCOPY packages ./packages\nCOPY patches ./patches\n# bring in desktop workspace manifest so pnpm can resolve it\nCOPY apps/desktop/src/main/package.json ./apps/desktop/src/main/package.json\n\nRUN set -e && \\\n if [ \"${USE_CN_MIRROR:-false}\" = \"true\" ]; then \\\n export SENTRYCLI_CDNURL=\"https://npmmirror.com/mirrors/sentry-cli\"; \\\n npm config set registry \"https://registry.npmmirror.com/\"; \\\n echo 'canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas' >> .npmrc; \\\n fi && \\\n export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\\/$//') && \\\n npm i -g corepack@latest && \\\n corepack enable && \\\n corepack use $(sed -n 's/.*\"packageManager\": \"\\(.*\\)\".*/\\1/p' package.json) && \\\n pnpm i && \\\n mkdir -p /deps && \\\n cd /deps && \\\n pnpm init && \\\n pnpm add pg drizzle-orm\n\nCOPY . .\n\n# run build standalone for docker version\nRUN npm run build:docker\n\n# Prepare desktop export assets for Electron packaging (if generated)\nRUN set -e && \\\n if [ -d \"/app/out\" ]; then \\\n mkdir -p /app/apps/desktop/dist/next && \\\n cp -a /app/out/. /app/apps/desktop/dist/next/ && \\\n echo \"Copied Next export output into /app/apps/desktop/dist/next\"; \\\n else \\\n echo \"No Next export output found at /app/out, creating empty directory\" && \\\n mkdir -p /app/apps/desktop/dist/next; \\\n fi\n\n## Application image, copy all the files for production\nFROM busybox:latest AS app\n\nCOPY --from=base /distroless/ /\n\n# Automatically leverage output traces to reduce image size\n# https://nextjs.org/docs/advanced-features/output-file-tracing\nCOPY --from=builder /app/.next/standalone /app/\n# Copy Next export output for desktop renderer\nCOPY --from=builder /app/apps/desktop/dist/next /app/apps/desktop/dist/next\n\n# Copy database migrations\nCOPY --from=builder /app/packages/database/migrations /app/migrations\nCOPY --from=builder /app/scripts/migrateServerDB/docker.cjs /app/docker.cjs\nCOPY --from=builder /app/scripts/migrateServerDB/errorHint.js /app/errorHint.js\n\n# copy dependencies\nCOPY --from=builder /deps/node_modules/.pnpm /app/node_modules/.pnpm\nCOPY --from=builder /deps/node_modules/pg /app/node_modules/pg\nCOPY --from=builder /deps/node_modules/drizzle-orm /app/node_modules/drizzle-orm\n\n# Copy server launcher and shared scripts\nCOPY --from=builder /app/scripts/serverLauncher/startServer.js /app/startServer.js\nCOPY --from=builder /app/scripts/_shared /app/scripts/_shared\n\nRUN set -e && \\\n addgroup -S -g 1001 nodejs && \\\n adduser -D -G nodejs -H -S -h /app -u 1001 nextjs && \\\n chown -R nextjs:nodejs /app /etc/proxychains4.conf\n\n## Production image, copy all the files and run next\nFROM scratch\n\n# Copy all the files from app, set the correct permission for prerender cache\nCOPY --from=app / /\n\nENV NODE_ENV=\"production\" \\\n NODE_OPTIONS=\"--dns-result-order=ipv4first --use-openssl-ca\" \\\n NODE_EXTRA_CA_CERTS=\"\" \\\n NODE_TLS_REJECT_UNAUTHORIZED=\"\" \\\n SSL_CERT_FILE=\"/etc/ssl/certs/ca-certificates.crt\"\n\n# Make the middleware rewrite through local as default\n# refs: https://github.com/lobehub/lobe-chat/issues/5876\nENV MIDDLEWARE_REWRITE_THROUGH_LOCAL=\"1\"\n\n# set hostname to localhost\nENV HOSTNAME=\"0.0.0.0\" \\\n PORT=\"3210\"\n\n# General Variables\nENV ACCESS_CODE=\"\" \\\n APP_URL=\"\" \\\n API_KEY_SELECT_MODE=\"\" \\\n DEFAULT_AGENT_CONFIG=\"\" \\\n SYSTEM_AGENT=\"\" \\\n FEATURE_FLAGS=\"\" \\\n PROXY_URL=\"\" \\\n ENABLE_AUTH_PROTECTION=\"\"\n\n# Database\nENV KEY_VAULTS_SECRET=\"\" \\\n DATABASE_DRIVER=\"node\" \\\n DATABASE_URL=\"\"\n\n# Better Auth\nENV AUTH_SECRET=\"\" \\\n AUTH_SSO_PROVIDERS=\"\" \\\n AUTH_ALLOWED_EMAILS=\"\" \\\n # Google\n AUTH_GOOGLE_ID=\"\" \\\n AUTH_GOOGLE_SECRET=\"\" \\\n # GitHub\n AUTH_GITHUB_ID=\"\" \\\n AUTH_GITHUB_SECRET=\"\" \\\n # Microsoft\n AUTH_MICROSOFT_ID=\"\" \\\n AUTH_MICROSOFT_SECRET=\"\"\n\n# Redis\nENV REDIS_URL=\"\" \\\n REDIS_PREFIX=\"\" \\\n REDIS_TLS=\"\"\n\n# Email\nENV EMAIL_SERVICE_PROVIDER=\"\" \\\n SMTP_HOST=\"\" \\\n SMTP_PORT=\"\" \\\n SMTP_SECURE=\"\" \\\n SMTP_USER=\"\" \\\n SMTP_PASS=\"\" \\\n SMTP_FROM=\"\" \\\n RESEND_API_KEY=\"\" \\\n RESEND_FROM=\"\"\n\n# S3\nENV NEXT_PUBLIC_S3_DOMAIN=\"\" \\\n S3_PUBLIC_DOMAIN=\"\" \\\n S3_ACCESS_KEY_ID=\"\" \\\n S3_BUCKET=\"\" \\\n S3_ENDPOINT=\"\" \\\n S3_SECRET_ACCESS_KEY=\"\" \\\n S3_ENABLE_PATH_STYLE=\"\" \\\n S3_SET_ACL=\"\"\n\n# Model Variables\nENV \\\n # AI21\n AI21_API_KEY=\"\" AI21_MODEL_LIST=\"\" \\\n # Ai360\n AI360_API_KEY=\"\" AI360_MODEL_LIST=\"\" \\\n # AiHubMix\n AIHUBMIX_API_KEY=\"\" AIHUBMIX_MODEL_LIST=\"\" \\\n # Anthropic\n ANTHROPIC_API_KEY=\"\" ANTHROPIC_MODEL_LIST=\"\" ANTHROPIC_PROXY_URL=\"\" \\\n # Amazon Bedrock\n ENABLED_AWS_BEDROCK=\"\" AWS_ACCESS_KEY_ID=\"\" AWS_SECRET_ACCESS_KEY=\"\" AWS_REGION=\"\" AWS_BEDROCK_MODEL_LIST=\"\" \\\n # Azure OpenAI\n AZURE_API_KEY=\"\" AZURE_API_VERSION=\"\" AZURE_ENDPOINT=\"\" AZURE_MODEL_LIST=\"\" \\\n # Baichuan\n BAICHUAN_API_KEY=\"\" BAICHUAN_MODEL_LIST=\"\" \\\n # Cloudflare\n CLOUDFLARE_API_KEY=\"\" CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID=\"\" CLOUDFLARE_MODEL_LIST=\"\" \\\n # Cohere\n COHERE_API_KEY=\"\" COHERE_MODEL_LIST=\"\" COHERE_PROXY_URL=\"\" \\\n # ComfyUI\n ENABLED_COMFYUI=\"\" COMFYUI_BASE_URL=\"\" COMFYUI_AUTH_TYPE=\"\" \\\n COMFYUI_API_KEY=\"\" COMFYUI_USERNAME=\"\" COMFYUI_PASSWORD=\"\" COMFYUI_CUSTOM_HEADERS=\"\" \\\n # DeepSeek\n DEEPSEEK_API_KEY=\"\" DEEPSEEK_MODEL_LIST=\"\" \\\n # Fireworks AI\n FIREWORKSAI_API_KEY=\"\" FIREWORKSAI_MODEL_LIST=\"\" \\\n # Gitee AI\n GITEE_AI_API_KEY=\"\" GITEE_AI_MODEL_LIST=\"\" \\\n # GitHub\n GITHUB_TOKEN=\"\" GITHUB_MODEL_LIST=\"\" \\\n # Google\n GOOGLE_API_KEY=\"\" GOOGLE_MODEL_LIST=\"\" GOOGLE_PROXY_URL=\"\" \\\n # Vertex AI\n VERTEXAI_CREDENTIALS=\"\" VERTEXAI_PROJECT=\"\" VERTEXAI_LOCATION=\"\" VERTEXAI_MODEL_LIST=\"\" \\\n # Groq\n GROQ_API_KEY=\"\" GROQ_MODEL_LIST=\"\" GROQ_PROXY_URL=\"\" \\\n # Higress\n HIGRESS_API_KEY=\"\" HIGRESS_MODEL_LIST=\"\" HIGRESS_PROXY_URL=\"\" \\\n # HuggingFace\n HUGGINGFACE_API_KEY=\"\" HUGGINGFACE_MODEL_LIST=\"\" HUGGINGFACE_PROXY_URL=\"\" \\\n # Hunyuan\n HUNYUAN_API_KEY=\"\" HUNYUAN_MODEL_LIST=\"\" \\\n # InternLM\n INTERNLM_API_KEY=\"\" INTERNLM_MODEL_LIST=\"\" \\\n # Jina\n JINA_API_KEY=\"\" JINA_MODEL_LIST=\"\" JINA_PROXY_URL=\"\" \\\n # Minimax\n MINIMAX_API_KEY=\"\" MINIMAX_MODEL_LIST=\"\" \\\n # Mistral\n MISTRAL_API_KEY=\"\" MISTRAL_MODEL_LIST=\"\" \\\n # ModelScope\n MODELSCOPE_API_KEY=\"\" MODELSCOPE_MODEL_LIST=\"\" MODELSCOPE_PROXY_URL=\"\" \\\n # Moonshot\n MOONSHOT_API_KEY=\"\" MOONSHOT_MODEL_LIST=\"\" MOONSHOT_PROXY_URL=\"\" \\\n # Nebius\n NEBIUS_API_KEY=\"\" NEBIUS_MODEL_LIST=\"\" NEBIUS_PROXY_URL=\"\" \\\n # NewAPI\n NEWAPI_API_KEY=\"\" NEWAPI_PROXY_URL=\"\" \\\n # Novita\n NOVITA_API_KEY=\"\" NOVITA_MODEL_LIST=\"\" \\\n # Nvidia NIM\n NVIDIA_API_KEY=\"\" NVIDIA_MODEL_LIST=\"\" NVIDIA_PROXY_URL=\"\" \\\n # Ollama\n ENABLED_OLLAMA=\"\" OLLAMA_MODEL_LIST=\"\" OLLAMA_PROXY_URL=\"\" \\\n # OpenAI\n ENABLED_OPENAI=\"\" OPENAI_API_KEY=\"\" OPENAI_MODEL_LIST=\"\" OPENAI_PROXY_URL=\"\" \\\n # OpenRouter\n OPENROUTER_API_KEY=\"\" OPENROUTER_MODEL_LIST=\"\" \\\n # Perplexity\n PERPLEXITY_API_KEY=\"\" PERPLEXITY_MODEL_LIST=\"\" PERPLEXITY_PROXY_URL=\"\" \\\n # PPIO\n PPIO_API_KEY=\"\" PPIO_MODEL_LIST=\"\" \\\n # Qiniu\n QINIU_API_KEY=\"\" QINIU_MODEL_LIST=\"\" QINIU_PROXY_URL=\"\" \\\n # Qwen\n QWEN_API_KEY=\"\" QWEN_MODEL_LIST=\"\" QWEN_PROXY_URL=\"\" \\\n # SambaNova\n SAMBANOVA_API_KEY=\"\" SAMBANOVA_MODEL_LIST=\"\" \\\n # Search1API\n SEARCH1API_API_KEY=\"\" SEARCH1API_MODEL_LIST=\"\" \\\n # SenseNova\n SENSENOVA_API_KEY=\"\" SENSENOVA_MODEL_LIST=\"\" \\\n # SiliconCloud\n SILICONCLOUD_API_KEY=\"\" SILICONCLOUD_MODEL_LIST=\"\" SILICONCLOUD_PROXY_URL=\"\" \\\n # Spark\n SPARK_API_KEY=\"\" SPARK_MODEL_LIST=\"\" SPARK_PROXY_URL=\"\" SPARK_SEARCH_MODE=\"\" \\\n # Stepfun\n STEPFUN_API_KEY=\"\" STEPFUN_MODEL_LIST=\"\" \\\n # Taichu\n TAICHU_API_KEY=\"\" TAICHU_MODEL_LIST=\"\" \\\n # TogetherAI\n TOGETHERAI_API_KEY=\"\" TOGETHERAI_MODEL_LIST=\"\" \\\n # Upstage\n UPSTAGE_API_KEY=\"\" UPSTAGE_MODEL_LIST=\"\" \\\n # v0 (Vercel)\n V0_API_KEY=\"\" V0_MODEL_LIST=\"\" \\\n # vLLM\n VLLM_API_KEY=\"\" VLLM_MODEL_LIST=\"\" VLLM_PROXY_URL=\"\" \\\n # Wenxin\n WENXIN_API_KEY=\"\" WENXIN_MODEL_LIST=\"\" \\\n # xAI\n XAI_API_KEY=\"\" XAI_MODEL_LIST=\"\" XAI_PROXY_URL=\"\" \\\n # Xinference\n XINFERENCE_API_KEY=\"\" XINFERENCE_MODEL_LIST=\"\" XINFERENCE_PROXY_URL=\"\" \\\n # 01.AI\n ZEROONE_API_KEY=\"\" ZEROONE_MODEL_LIST=\"\" \\\n # Zhipu\n ZHIPU_API_KEY=\"\" ZHIPU_MODEL_LIST=\"\" \\\n # Tencent Cloud\n TENCENT_CLOUD_API_KEY=\"\" TENCENT_CLOUD_MODEL_LIST=\"\" \\\n # Infini-AI\n INFINIAI_API_KEY=\"\" INFINIAI_MODEL_LIST=\"\" \\\n # 302.AI\n AI302_API_KEY=\"\" AI302_MODEL_LIST=\"\" \\\n # FAL\n ENABLED_FAL=\"\" FAL_API_KEY=\"\" FAL_MODEL_LIST=\"\" \\\n # BFL\n BFL_API_KEY=\"\" BFL_MODEL_LIST=\"\" \\\n # Vercel AI Gateway\n VERCELAIGATEWAY_API_KEY=\"\" VERCELAIGATEWAY_MODEL_LIST=\"\" \\\n # Cerebras\n CEREBRAS_API_KEY=\"\" CEREBRAS_MODEL_LIST=\"\"\n\nUSER nextjs\n\nEXPOSE 3210/tcp\n\nENTRYPOINT [\"/bin/node\"]\n\nCMD [\"/app/startServer.js\"]\n"}}, "lobehub-main/.env.example": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785595579.9230576, "n": 12438, "h": "70cc9684c577939a", "c": "# add a access code to lock your lobe-chat application, you can set a long password to avoid leaking. If this value contains a comma, it is a password array.\n# ACCESS_CODE=lobe66\n\n# Specify your API Key selection method, currently supporting `random` and `turn`.\n# API_KEY_SELECT_MODE=random\n\n# #######################################\n# ########## Security Settings ###########\n# #######################################\n\n# Control Content Security Policy headers\n# Set to '1' to enable X-Frame-Options and Content-Security-Policy headers\n# Default is '0' (enabled)\n# ENABLED_CSP=1\n\n# SSRF Protection Settings\n# Set to '1' to allow connections to private IP addresses (disable SSRF protection)\n# WARNING: Only enable this in trusted environments\n# Default is '0' (SSRF protection enabled)\n# SSRF_ALLOW_PRIVATE_IP_ADDRESS=0\n\n# Whitelist of allowed private IP addresses (comma-separated)\n# Only takes effect when SSRF_ALLOW_PRIVATE_IP_ADDRESS is '0'\n# Example: Allow specific internal servers while keeping SSRF protection\n# SSRF_ALLOW_IP_ADDRESS_LIST=192.168.1.100,10.0.0.50\n\n# #######################################\n# ########### Redis Settings ############\n# #######################################\n\n# Connection string for self-hosted Redis (Docker/K8s/managed). Use container hostname when running via docker-compose.\n# REDIS_URL=redis://localhost:6379\n\n# Optional database index.\n# REDIS_DATABASE=0\n\n# Optional authentication for managed Redis.\n# REDIS_USERNAME=default\n# REDIS_PASSWORD=yourpassword\n\n# Set to '1' to enforce TLS when connecting to managed Redis or rediss:// endpoints.\n# REDIS_TLS=0\n\n# Namespace prefix for cache/queue keys.\n# REDIS_PREFIX=lobechat\n\n# #######################################\n# ######### AI Provider Service #########\n# #######################################\n\n# ## OpenAI ###\n\n# you openai api key\nOPENAI_API_KEY=sk-xxxxxxxxx\n\n# use a proxy to connect to the OpenAI API\n# OPENAI_PROXY_URL=https://api.openai.com/v1\n\n# add your custom model name, multi model separate by comma. for example gpt-3.5-1106,gpt-4-1106\n# OPENAI_MODEL_LIST=gpt-3.5-turbo\n\n\n# ## Azure OpenAI ###\n\n# you can learn azure OpenAI Service on https://learn.microsoft.com/en-us/azure/ai-services/openai/overview\n# use Azure OpenAI Service by uncomment the following line\n\n# The API key you applied for on the Azure OpenAI account page, which can be found in the \"Keys and Endpoints\" section.\n# AZURE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# The endpoint you applied for on the Azure OpenAI account page, which can be found in the \"Keys and Endpoints\" section.\n# AZURE_ENDPOINT=https://docs-test-001.openai.azure.com\n\n# Azure's API version, follows the YYYY-MM-DD format\n# AZURE_API_VERSION=2024-10-21\n\n\n# ## Anthropic Service ####\n\n# ANTHROPIC_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# use a proxy to connect to the Anthropic API\n# ANTHROPIC_PROXY_URL=https://api.anthropic.com\n\n\n# ## Google AI ####\n\n# GOOGLE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n\n# ## AWS Bedrock ###\n\n# AWS_REGION=us-east-1\n# AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxx\n# AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n\n# ## Ollama AI ####\n\n# You can use ollama to get and run LLM locally, learn more about it via https://github.com/ollama/ollama\n\n# The local/remote ollama service url\n# OLLAMA_PROXY_URL=http://127.0.0.1:11434\n\n# OLLAMA_MODEL_LIST=your_ollama_model_names\n\n\n# ## OpenRouter Service ###\n\n# OPENROUTER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n# OPENROUTER_MODEL_LIST=model1,model2,model3\n\n\n# ## Mistral AI ###\n\n# MISTRAL_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## Perplexity Service ###\n\n# PERPLEXITY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## Groq Service ####\n\n# GROQ_API_KEY=gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ### 01.AI Service ####\n\n# ZEROONE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## TogetherAI Service ###\n\n# TOGETHERAI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## ZhiPu AI ###\n\n# ZHIPU_API_KEY=xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxx\n\n# ## Moonshot AI ####\n\n# MOONSHOT_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## Minimax AI ####\n\n# MINIMAX_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## DeepSeek AI ####\n\n# DEEPSEEK_PROXY_URL=https://api.deepseek.com/v1\n# DEEPSEEK_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## Qiniu AI ####\n\n# QINIU_PROXY_URL=https://api.qnaigc.com/v1\n# QINIU_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## Qwen AI ####\n\n# QWEN_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## Cloudflare Workers AI ####\n\n# CLOUDFLARE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n# CLOUDFLARE_BASE_URL_OR_ACCOUNT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## SiliconCloud AI ####\n\n# SILICONCLOUD_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n\n# ## TencentCloud AI ####\n\n# TENCENT_CLOUD_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## PPIO ####\n\n# PPIO_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## INFINI-AI ###\n\n# INFINIAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n\n# ## 302.AI ###\n\n# AI302_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## ModelScope ###\n\n# MODELSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## AiHubMix ###\n\n# AIHUBMIX_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## BFL ###\n\n# BFL_API_KEY=bfl-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## FAL ###\n\n# FAL_API_KEY=fal-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# #######################################\n# ######## AI Image Settings ############\n# #######################################\n\n# Default image generation count (range: 1-20, default: 4)\n# AI_IMAGE_DEFAULT_IMAGE_NUM=4\n\n# ## Nebius ###\n\n# NEBIUS_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# ## NewAPI Service ###\n\n# NEWAPI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n# NEWAPI_PROXY_URL=https://your-newapi-server.com\n\n# ## Vercel AI Gateway ###\n\n# VERCELAIGATEWAY_API_KEY=your_vercel_ai_gateway_api_key\n\n\n# #######################################\n# ########### Market Service ############\n# #######################################\n\n# The LobeChat agents market index url\n# AGENTS_INDEX_URL=https://chat-agents.lobehub.com\n\n# #######################################\n# ########### Plugin Service ############\n# #######################################\n\n# The LobeChat plugins store index url\n# PLUGINS_INDEX_URL=https://chat-plugins.lobehub.com\n\n# set the plugin settings\n# the format is `plugin-identifier:key1=value1;key2=value2`, multiple settings fields are separated by semicolons `;`, multiple plugin settings are separated by commas `,`.\n# PLUGIN_SETTINGS=search-engine:SERPAPI_API_KEY=xxxxx\n\n# #######################################\n# ###### Doc / Changelog Service ########\n# #######################################\n\n# Use in Changelog / Document service cdn url prefix\n# DOC_S3_PUBLIC_DOMAIN=https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# Use in dev cdn workflow\n# DOC_S3_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n# DOC_S3_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n\n# #######################################\n# #### S3 Object Storage Service ########\n# #######################################\n\n# S3 keys\n# S3_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n# S3_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# Bucket name\n# S3_BUCKET=lobechat\n\n# Bucket request endpoint\n# S3_ENDPOINT=https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxx.r2.cloudflarestorage.com\n\n# Public access domain for the bucket\n# S3_PUBLIC_DOMAIN=https://s3-for-lobechat.your-domain.com\n\n# Bucket region, such as us-west-1, generally not needed to add\n# but some service providers may require configuration\n# S3_REGION=us-west-1\n\n\n# #######################################\n# ########### Auth Service ##############\n# #######################################\n\n# Auth Secret (use `openssl rand -base64 32` to generate)\n# AUTH_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# Require email verification before allowing users to sign in (default: false)\n# Set to '1' to force users to verify their email before signing in\n# AUTH_EMAIL_VERIFICATION=0\n\n# SSO Providers Configuration (for Better-Auth)\n# Comma-separated list of enabled OAuth providers\n# Supported providers: auth0, authelia, authentik, casdoor, cloudflare-zero-trust, cognito, generic-oidc, github, google, keycloak, logto, microsoft, microsoft-entra-id, okta, zitadel\n# Example: AUTH_SSO_PROVIDERS=google,github,auth0,microsoft-entra-id\n# AUTH_SSO_PROVIDERS=\n\n# Email whitelist for registration (comma-separated)\n# Supports full email (user@example.com) or domain (example.com)\n# Leave empty to allow all emails\n# AUTH_ALLOWED_EMAILS=example.com,admin@other.com\n\n# Google OAuth Configuration (for Better-Auth)\n# Get credentials from: https://console.cloud.google.com/apis/credentials\n# Authorized redirect URIs:\n# - Development: http://localhost:3210/api/auth/callback/google\n# - Production: https://yourdomain.com/api/auth/callback/google\n# GOOGLE_CLIENT_ID=xxxxx.apps.googleusercontent.com\n# GOOGLE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxxxxxx\n\n# GitHub OAuth Configuration (for Better-Auth)\n# Get credentials from: https://github.com/settings/developers\n# Create a new OAuth App with:\n# Authorized callback URL:\n# - Development: http://localhost:3210/api/auth/callback/github\n# - Production: https://yourdomain.com/api/auth/callback/github\n# GITHUB_CLIENT_ID=Ov23xxxxxxxxxxxxx\n# GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# AWS Cognito OAuth Configuration (for Better-Auth)\n# Get credentials from: https://console.aws.amazon.com/cognito\n# Setup steps:\n# 1. Create a User Pool with App Client\n# 2. Configure Hosted UI domain\n# 3. Enable \"Authorization code grant\" OAuth flow\n# 4. Set OAuth scopes: openid, profile, email\n# Authorized callback URL:\n# - Development: http://localhost:3210/api/auth/callback/cognito\n# - Production: https://yourdomain.com/api/auth/callback/cognito\n# COGNITO_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxx\n# COGNITO_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n# COGNITO_DOMAIN=your-app.auth.us-east-1.amazoncognito.com\n# COGNITO_REGION=us-east-1\n# COGNITO_USERPOOL_ID=us-east-1_xxxxxxxxx\n\n# Microsoft OAuth Configuration (for Better-Auth)\n# Get credentials from: https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\n# Create a new App Registration in Microsoft Entra ID (Azure AD)\n# Authorized redirect URL:\n# - Development: http://localhost:3210/api/auth/callback/microsoft\n# - Production: https://yourdomain.com/api/auth/callback/microsoft\n# MICROSOFT_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n# MICROSOFT_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# #######################################\n# ########## Email Service ##############\n# #######################################\n\n# SMTP Server Configuration (required for email verification with Better-Auth)\n\n# SMTP server hostname (e.g., smtp.gmail.com, smtp.office365.com)\n# SMTP_HOST=smtp.example.com\n\n# SMTP server port (usually 587 for TLS, or 465 for SSL)\n# SMTP_PORT=587\n\n# Use secure connection (set to 'true' for port 465, 'false' for port 587)\n# SMTP_SECURE=false\n\n# SMTP authentication username (usually your email address)\n# SMTP_USER=your-email@example.com\n\n# SMTP authentication password (use app-specific password for Gmail)\n# SMTP_PASS=your-password-or-app-specific-password\n\n# Sender email address (optional, defaults to SMTP_USER)\n# Required for AWS SES where SMTP_USER is not a valid email address\n# SMTP_FROM=noreply@example.com\n\n# #######################################\n# ######### Server Database #############\n# #######################################\n\n# Postgres database URL\n# DATABASE_URL=postgres://username:password@host:port/database\n\n# use `openssl rand -base64 32` to generate a key for the encryption of the database\n# we use this key to encrypt the user api key and proxy url\n# KEY_VAULTS_SECRET=xxxxx/xxxxxxxxxxxxxx=\n\n# Specify the Embedding model and Reranker model(unImplemented)\n# DEFAULT_FILES_CONFIG=\"embedding_model=openai/embedding-text-3-small,reranker_model=cohere/rerank-english-v3.0,query_mode=full_text\"\n\n# #######################################\n# ######### MCP Service Config ##########\n# #######################################\n\n# MCP tool call timeout (milliseconds)\n# MCP_TOOL_TIMEOUT=60000\n\n# #######################################\n# ######### Klavis Service ##############\n# #######################################\n\n# Klavis API Key for accessing Strata hosted MCP servers\n# Get your API key from: https://klavis.io\n# IMPORTANT: This key is stored server-side only and NEVER exposed to the client\n# When this key is set, Klavis integration will be automatically enabled\n# KLAVIS_API_KEY=your_klavis_api_key_here\n"}}, "lobehub-main/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785595580.543389, "n": 17209, "h": "465b8c33c69fa2f7", "c": "---\ntitle: LobeHub 模型服务商相关环境变量配置指南\ndescription: 了解如何配置各个模型服务商的环境变量以在 LobeHub 中使用不同的AI模型服务。\ntags:\n - LobeHub\n - 模型服务商\n - 环境变量\n - AI模型\n - 配置指南\n---\n\n# 模型服务商\n\nLobeHub 在部署时提供了丰富的模型服务商相关的环境变量,你可以使用这些环境变量轻松定义需要在 LobeHub 中开启的模型服务商。\n\n## OpenAI\n\n### `ENABLED_OPENAI`\n\n- 类型:可选\n- 描述:默认启用 OpenAI 作为模型供应商,当设为 0 时关闭 OpenAI 服务\n- 默认值:`1`\n- 示例:`0`\n\n### `OPENAI_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 OpenAI 账户页面申请的 API 密钥,可以前往 [这里](https://platform.openai.com/api-keys) 查看\n- 默认值:-\n- 示例:`sk-xxxxxx...xxxxxx`\n\n### `OPENAI_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 OpenAI 接口代理,可以使用此配置项来覆盖默认的 OpenAI API 请求基础 URL\n- 默认值:`https://api.openai.com/v1`\n- 示例:`https://api.chatanywhere.cn` 或 `https://aihubmix.com/v1`\n\n\n 请检查你的代理服务商的请求后缀,有的代理服务商会在请求后缀添加\n `/v1`,有的则不会。如果你在测试时发现 AI 返回的消息为空,请尝试添加 `/v1` 后缀后重试。\n\n\n\n 是否填写 `/v1` 跟模型服务商有很大关系,比如 openai 的默认地址是 `api.openai.com/v1`\n 。如果你的代理商转发了 `/v1` 这个接口,那么直接填 `proxy.com` 即可。 但如果模型服务商是直接转发了\n `api.openai.com` 域名,那么你就要自己加上 `/v1` 这个 url。\n\n\n相关讨论:\n\n- [Docker 安装,配置好环境变量后,为何返回值是空白?](https://github.com/lobehub/lobe-chat/discussions/623)\n- [使用第三方接口报错的原因](https://github.com/lobehub/lobe-chat/discussions/734)\n- [代理服务器地址填了聊天没任何反应](https://github.com/lobehub/lobe-chat/discussions/1065)\n\n### `OPENAI_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo`\n\n上面示例表示增加 `qwen-7b-chat` 和 `glm-6b` 到模型列表,而从列表中删除 `gpt-3.5-turbo`,并将 `gpt-4-0125-preview` 模型名字展示为 `gpt-4-turbo`。如果你想先禁用所有模型,再启用指定模型,可以使用 `-all,+gpt-3.5-turbo`,则表示仅启用 `gpt-3.5-turbo`。\n\n你可以在 [modelProviders](https://github.com/lobehub/lobe-chat/tree/main/src/config/modelProviders) 查找到当前的所有模型名。\n\n## Azure OpenAI\n\n如果你需要使用 Azure OpenAI 来提供模型服务,可以查阅 [使用 Azure OpenAI 部署](/zh/docs/self-hosting/examples/azure-openai) 章节查看详细步骤,这里将列举和 Azure OpenAI 相关的环境变量。\n\n### `AZURE_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Azure OpenAI 账户页面申请的 API 密钥\n- 默认值:-\n- 示例:`c55168be3874490ef0565d9779ecd5a6`\n\n### `AZURE_ENDPOINT`\n\n- 类型:必选\n- 描述:Azure API 地址,从 Azure 门户检查资源时,可在 “密钥和终结点” 部分中找到此值\n- 默认值:`-`\n- 示例:`https://docs-test-001.openai.azure.com`\n\n### `AZURE_API_VERSION`\n\n- 类型:必选\n- 描述:Azure 的 API 版本,遵循 YYYY-MM-DD 格式\n- 默认值:`-`\n- 示例:`2024-02-01`,查阅 [最新版本](https://docs.microsoft.com/zh-cn/azure/developer/javascript/api-reference/es-modules/azure-sdk/ai-translation/translationconfiguration?view=azure-node-latest#api-version)\n\n### `AZURE_MODEL_LIST`\n\n- 类型:必选\n- 描述:用来控制模型列表,使用 `模型名->部署名=展示名` 来自定义模型的展示名,用英文逗号隔开。支持扩展能力,其余语法规则详见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`gpt-35-turbo->my-deploy=GPT 3.5 Turbo` 或 `gpt-4-turbo->my-gpt4=GPT 4 Turbo<128000:vision:fc>`\n\n## Google AI\n\n### `GOOGLE_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Google AI Platform 申请的 API 密钥,用于访问 Google AI 服务\n- 默认值:-\n- 示例:`AIraDyDwcw254kwJaGjI9wwaHcdDCS__Vt3xQE`\n\n### `GOOGLE_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Google 接口代理,可以使用此配置项来覆盖默认的 Google API 请求基础 URL\n- 默认值:`https://generativelanguage.googleapis.com`\n- 示例:`https://api.genai.gd.edu.kg/google`\n\n### `GOOGLE_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`\n\n## Vertex AI\n\n### `VERTEXAI_CREDENTIALS`\n\n- 类型:必选\n- 描述:Google Cloud 服务账号密钥的 JSON 字符串。用于认证和授权访问 Vertex AI 服务,获取方法请参考 [这里](/zh/docs/usage/providers/vertexai)\n- 默认值:-\n- 示例:`{\"type\": \"service_account\", \"project_id\": \"your-gcp-project-id\", ...}`\n\n### `VERTEXAI_PROJECT`\n\n- 类型:可选\n- 描述:你的 Google Cloud 项目 ID。如果未设置,将从 `VERTEXAI_CREDENTIALS` 中的 `project_id` 字段获取。\n- 默认值:-\n- 示例:`your-gcp-project-id`\n\n### `VERTEXAI_LOCATION`\n\n- 类型:可选\n- 描述:你的 Vertex AI 模型所在的区域。\n- 默认值:`global`\n- 示例:`us-central1`\n\n### `VERTEXAI_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`\n\n## Anthropic AI\n\n### `ANTHROPIC_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Anthropic AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`sk-ant-apixx-xxxxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxx`\n\n### `ANTHROPIC_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Anthropic 接口代理,可以使用此配置项来覆盖默认的 Anthropic API 请求基础 URL\n- 默认值:`https://api.anthropic.com`\n- 示例:`https://my-anthropic-proxy.com`\n\n## AWS Bedrock\n\n### `ENABLED_AWS_BEDROCK`\n\n- 类型:可选\n- 描述:当设为 1 时启用 AWS Bedrock 服务\n- 默认值:`0`\n- 示例:`1`\n\n### `AWS_ACCESS_KEY_ID`\n\n- 类型:必选\n- 描述:用于 AWS 服务认证的访问键 ID\n- 默认值:-\n- 示例:`AKIA5STVRLFSB4S9HWBR`\n\n### `AWS_SECRET_ACCESS_KEY`\n\n- 类型:必选\n- 描述:用于 AWS 服务认证的密钥\n- 默认值:-\n- 示例:`Th3vXxLYpuKcv2BARktPSTPxx+jbSiFT6/0w7oEC`\n\n### `AWS_REGION`\n\n- 类型:可选\n- 描述:AWS 服务的区域设置\n- 默认值:`us-east-1`\n- 示例:`us-east-1`\n\n## ComfyUI\n\n### `ENABLED_COMFYUI`\n\n- 类型:可选\n- 描述:默认启用 ComfyUI 作为模型供应商,当设为 0 时关闭 ComfyUI 服务\n- 默认值:`1`\n- 示例:`0`\n\n### `COMFYUI_BASE_URL`\n\n- 类型:可选\n- 描述:ComfyUI 服务的基础 URL 地址\n- 默认值:`http://localhost:8000`\n- 示例:`http://192.168.1.100:8000` 或 `https://my-comfyui-server.com`\n\n### `COMFYUI_AUTH_TYPE`\n\n- 类型:可选\n- 描述:ComfyUI 的认证类型,支持 4 种认证方式\n - `none`: 无认证(默认)\n - `basic`: 基础认证(用户名 + 密码)\n - `bearer`: Bearer Token 认证(API 密钥)\n - `custom`: 自定义请求头认证\n- 默认值:`none`\n- 示例:`basic`\n\n### `COMFYUI_API_KEY`\n\n- 类型:可选\n- 描述:当认证类型为 `bearer` 时使用的 API 密钥\n- 默认值:-\n- 示例:`sk-xxxxxx...xxxxxx`\n\n### `COMFYUI_USERNAME`\n\n- 类型:可选\n- 描述:当认证类型为 `basic` 时使用的用户名\n- 默认值:-\n- 示例:`admin`\n\n### `COMFYUI_PASSWORD`\n\n- 类型:可选\n- 描述:当认证类型为 `basic` 时使用的密码\n- 默认值:-\n- 示例:`password123`\n\n### `COMFYUI_CUSTOM_HEADERS`\n\n- 类型:可选\n- 描述:当认证类型为 `custom` 时使用的自定义请求头,需要使用 JSON 格式字符串\n- 默认值:-\n- 示例:`{\"X-Auth-Token\": \"your-token\", \"X-Custom-Header\": \"value\"}`\n\n\n ComfyUI 支持多种认证方式,请根据您的 ComfyUI 服务配置选择合适的认证类型和相应的认证参数。如果您的 ComfyUI 服务没有设置认证,可以不配置认证相关的环境变量。\n\n\n## DeepSeek AI\n\n### `DEEPSEEK_PROXY_URL`\n\n- 类型:可选\n- 描述:如果您手动配置了 DeepSeek API 代理,可以使用此配置项覆盖默认的 DeepSeek API 请求基础 URL\n- 默认值:`https://api.deepseek.com`\n- 示例:`https://my-deepseek-proxy.com`\n\n### `DEEPSEEK_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 DeepSeek AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`sk-xxxxxx...xxxxxx`\n\n### `DEEPSEEK_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+deepseek-reasoner`\n\n## XAI\n\n### `XAI_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 XAI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xai-xxxxxx...xxxxxx`\n\n## 文心一言\n\n### `WENXIN_API_KEY`\n\n- 类型:必选\n- 描述:这是你在百度智能云平台申请的文心一言 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## OpenRouter AI\n\n### `OPENROUTER_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 OpenRouter AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`sk-or-v1-xxxxxx...xxxxxx=`\n\n### `OPENROUTER_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+01-ai/yi-34b-chat,+huggingfaceh4/zephyr-7b-beta`\n\n## PPIO\n\n### `PPIO_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 PPIO 网站申请的 API 密钥\n- 默认值:-\n- 示例:`sk_xxxxxxxxxxxx`\n\n### `PPIO_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+deepseek/deepseek-v3/community,+deepseek/deepseek-r1-distill-llama-70b`\n\n## Github\n\n### `GITHUB_TOKEN`\n\n- 类型:必选\n- 描述:这是你在 Github 申请的 Personal access tokens\n- 默认值:-\n- 示例:`ghp_xxxxxx...xxxxxx=`\n\n### `GITHUB_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+gpt-4o,+gpt-4o-mini`\n\n## TogetherAI\n\n### `TOGETHERAI_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 TogetherAI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n### `TOGETHERAI_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo,+meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo`\n\n## Fireworks AI\n\n### `FIREWORKSAI_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Fireworks AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n### `FIREWORKSAI_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+accounts/fireworks/models/firefunction-v2,+accounts/fireworks/models/firefunction-v1`\n\n## Ollama\n\n### `ENABLED_OLLAMA`\n\n- 类型:可选\n- 描述:默认启用 Ollama 作为模型供应商,当设为 0 时关闭 Ollama 服务\n- 默认值:`1`\n- 示例:`0`\n\n### `OLLAMA_PROXY_URL`\n\n- 类型:必选\n- 描述:用于启用 Ollama 服务,设置后可在语言模型列表内展示可选开源语言模型,也可以指定自定义语言模型\n- 默认值:-\n- 示例:`http://127.0.0.1:11434`\n\n### `OLLAMA_MODEL_LIST`\n\n- 类型:可选\n- 描述:用于指定自定义 Ollama 语言模型。模型定义语法规则见 [模型列表][model-list]\n- 默认值:-\n- 示例:`qwen:32B`\n\n## Moonshot AI\n\n### `MOONSHOT_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Moonshot AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`Y2xpdGhpMzNhZXNoYjVtdnZjMWc6bXNrLWIxQlk3aDNPaXpBWnc0V1RaMDhSRmRFVlpZUWY=`\n\n### `MOONSHOT_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Moonshot 接口代理,可以使用此配置项来覆盖默认的 Moonshot API 请求基础 URL\n- 默认值:`https://api.moonshot.cn/v1`\n- 示例:`https://my-moonshot-proxy.com/v1`\n\n## Perplexity AI\n\n### `PERPLEXITY_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Perplexity AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`pplx-xxxxxx...xxxxxx`\n\n### `PERPLEXITY_MODEL_LIST`\n\n- 类型:可选\n- 描述:用于指定自定义 Perplexity 语言模型。模型定义语法规则见 [模型列表][model-list]\n- 默认值:-\n- 示例: `-all,+llama-3.1-sonar-small-128k-online,+llama-3.1-sonar-small-128k-chat`\n\n### `PERPLEXITY_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Perplexity 接口代理,可以使用此配置项来覆盖默认的 Perplexity API 请求基础 URL\n- 默认值:`https://api.Perplexity.ai`\n- 示例:`https://my-Perplexity-proxy.com`\n\n## Minimax AI\n\n### `MINIMAX_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Minimax AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## Mistral AI\n\n### `MISTRAL_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Mistral AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx=`\n\n## Groq AI\n\n### `GROQ_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Groq AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`gsk_xxxxxx...xxxxxx`\n\n### `GROQ_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+gemma2-9b-it,+llama-3.1-8b-instant`\n\n### `GROQ_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Groq 接口代理,可以使用此配置项来覆盖默认的 Groq API 请求基础 URL\n- 默认值:`https://api.groq.com/openai/v1`\n- 示例:`https://my-groq-proxy.com/v1`\n\n## 智谱 AI\n\n### `ZHIPU_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 智谱 AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`4582d332441a313f5c2ed9824d1798ca.rC8EcTAhgbOuAuVT`\n\n### `ZHIPU_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+glm-4-alltools,+glm-4-plus`\n\n## 01 AI\n\n### `ZEROONE_API_KEY`\n\n- 类型:必选\n- 描述:这是你在零一万物服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n### `ZEROONE_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+yi-large,+yi-large-rag`\n\n## 七牛云\n\n### `QINIU_API_KEY`\n\n- 类型:必选\n- 描述:这是你在七牛云上获取的 API 密钥\n- 默认值:-\n- 示例:`sk-xxxxx...xxxxx`\n\n### `QINIU_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+deepseek-r1,+deepseek-v3`\n\n### `QINIU_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Qiniu 接口代理,可以使用此配置项来覆盖默认的 Qiniu API 请求基础 URL\n- 默认值:`https://api.qnaigc.com/v1`\n- 示例:`https://my-qnaigc.com/v1`\n\n## 通义千问\n\n### `QWEN_API_KEY`\n\n- 类型:必选\n- 描述:这是你在阿里云百炼平台上获取的 DashScope API 密钥\n- 默认值:-\n- 示例:`sk-xxxxx...xxxxx`\n\n### `QWEN_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名->部署名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。部署名`->部署名`可以省略,默认为最新版本的模型。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+qwen-turbo,+qwen-plus,+qwen-max->qwen-max-2025-01-25`\n\n### `QWEN_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Qwen 接口代理,可以使用此配置项来覆盖默认的 Qwen API 请求基础 URL\n- 默认值:`https://dashscope.aliyuncs.com/compatible-mode/v1`\n- 示例:`https://my-qwen-proxy.com/v1`\n\n## Stepfun AI\n\n### `STEPFUN_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Stepfun AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## Novita AI\n\n### `NOVITA_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Novita AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n### `NOVITA_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+meta-llama/llama-3.1-8b-instruct,+meta-llama/llama-3.1-70b-instruct`\n\n## 百川\n\n### `BAICHUAN_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 百川智能 服务平台申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## 紫东太初\n\n### `TAICHU_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 紫东太初 服务平台申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## 360 AI\n\n### `AI360_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 360 智脑 服务平台申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## Siliconflow\n\n### `SILICONCLOUD_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Siliconflow 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n### `SILICONCLOUD_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+deepseek-ai/DeepSeek-V2.5,+Qwen/Qwen2.5-7B-Instruct`\n\n### `SILICONCLOUD_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Siliconflow 接口代理,可以使用此配置项来覆盖默认的 Siliconflow API 请求基础 URL\n- 默认值:`https://api.siliconflow.cn/v1`\n- 示例:`https://my-siliconflow-proxy.com/v1`\n\n## Upstage AI\n\n### `UPSTAGE_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Upstage AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## Spark AI\n\n### `SPARK_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Spark AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## A21 AI\n\n### `AI21_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 A21 AI 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n## 腾讯混元\n\n### `HUNYUAN_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 腾讯混元 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`xxxxxx...xxxxxx`\n\n### `HUNYUAN_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+hunyuan-lite,+hunyuan-standard`\n\n## 火山引擎\n\n### `VOLCENGINE_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 火山引擎 服务中申请的 API 密钥,可以前往 [这里](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint) 查看\n- 默认值:-\n- 示例:`ad925b3d-aaaa-bbbb-cccc-51fde2f50547`\n\n### `VOLCENGINE_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名->部署名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。部署名`->部署名`可以省略,默认为最新版本的模型。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+deepseek-r1,+deepseek-v3->deepseek-v3-250324,+doubao-1.5-pro-256k,+doubao-1.5-pro-32k->doubao-1-5-pro-32k-250115,+doubao-1.5-lite-32k`\n\n### `VOLCENGINE_PROXY_URL`\n\n- 类型:可选\n- 描述:如果你手动配置了 Volcengine 接口代理,可以使用此配置项来覆盖默认的 Volcengine API 请求基础 URL\n- 默认值:`https://ark.cn-beijing.volces.com/api/v3`\n- 示例:`https://my-volcengine-proxy.com/v1`\n\n## InfiniAI\n\n### `INFINIAI_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 [Infini-AI](https://cloud.infini-ai.com) 申请的 API 密钥。\n- 默认值:-\n- 示例:`sk-xxxxxx...xxxxxx`\n\n### `INFINIAI_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]\n- 默认值:`-`\n- 示例:`-all,+qwq-32b,+deepseek-r1`\n\n## FAL\n\n### `ENABLED_FAL`\n\n- 类型:可选\n- 描述:默认启用 FAL 作为模型供应商,当设为 0 时关闭 FAL 服务\n- 默认值:`1`\n- 示例:`0`\n\n### `FAL_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 FAL 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`fal-xxxxxx...xxxxxx`\n\n### `FAL_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制 FAL 模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则与其他 provider 保持一致。\n- 默认值:`-`\n- 示例:`-all,+fal-ai/flux/schnell,+fal-ai/flux-pro/kontext=FLUX.1 Kontext [pro]`\n\n上述示例表示先禁用所有模型,再启用 `fal-ai/flux/schnell` 和 `fal-ai/flux-pro/kontext`(显示名为 `FLUX.1 Kontext [pro]`)。\n\n## BFL\n\n### `BFL_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 BFL 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`12345678-1234-1234-1234-123456789abc`\n\n### `BFL_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制 BFL 模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则与其他 provider 保持一致。\n- 默认值:`-`\n- 示例:`-all,+flux-pro-1.1,+flux-kontext-pro=FLUX.1 Kontext [pro]`\n\n上述示例表示先禁用所有模型,再启用 `flux-pro-1.1` 和 `flux-kontext-pro`(显示名为 `FLUX.1 Kontext [pro]`)。\n\n## NewAPI\n\n### `NEWAPI_API_KEY`\n\n- 类型:可选\n- 描述:这是你的 NewAPI 服务实例的 API 密钥。NewAPI 是一个多供应商模型聚合服务,提供对各种 AI 模型 API 的统一访问。\n- 默认值:-\n- 示例:`sk-xxxxxx...xxxxxx`\n\n### `NEWAPI_PROXY_URL`\n\n- 类型:可选\n- 描述:你的 NewAPI 服务器实例的基础 URL。这应该指向你部署的 NewAPI 服务端点。\n- 默认值:-\n- 示例:`https://your-newapi-server.com`\n\n\n NewAPI\n 是一个多供应商模型聚合服务,支持基于供应商检测的自动模型路由。它提供成本管理功能,并为访问包括\n OpenAI、Anthropic、Google 等多个供应商的模型提供单一端点。了解更多关于 NewAPI 的信息请访问\n [https://github.com/Calcium-Ion/new-api](https://github.com/Calcium-Ion/new-api)。\n\n\n## Vercel AI Gateway\n\n### `VERCELAIGATEWAY_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Vercel AI Gateway 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`vck_xxxxxx...xxxxxx`\n\n### `VERCELAIGATEWAY_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制 Vercel AI Gateway 模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则与其他 provider 保持一致。\n- 默认值:`-`\n- 示例:`-all,+vercel-model-1,+vercel-model-2=vercel-special`\n\n## Cerebras\n\n### `CEREBRAS_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 Cerebras 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`csk-xxxxxx...xxxxxx`\n\n### `CEREBRAS_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制 Cerebras 模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则与其他 provider 保持一致。\n- 默认值:`-`\n- 示例:`-all,+cerebras-model-1,+cerebras-model-2=cerebras-special`\n\n## AiHubMix\n\n### `AIHUBMIX_API_KEY`\n\n- 类型:必选\n- 描述:这是你在 AiHubMix 服务中申请的 API 密钥\n- 默认值:-\n- 示例:`sk-xxxxxx...xxxxxx`\n\n### `AIHUBMIX_MODEL_LIST`\n\n- 类型:可选\n- 描述:用来控制 AiHubMix 模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则与其他 provider 保持一致。\n- 默认值:`-`\n- 示例:`-all,+claude-opus-4-1-20250805,+claude-opus-4-20250514=claude-opus-4`\n\n[model-list]: /zh/docs/self-hosting/advanced/model-list\n"}}, "lobehub-main/docs/self-hosting/environment-variables/model-provider.mdx": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785595580.5528963, "n": 28452, "h": "f2661e3cf73e8403", "c": "---\ntitle: LobeHub Model Service Providers - Environment Variables and Configuration\ndescription: >-\n Learn about the environment variables and configuration settings for various\n model service providers like OpenAI, Google AI, AWS Bedrock, Ollama,\n Perplexity AI, Anthropic AI, Mistral AI, Groq AI, OpenRouter AI, and 01.AI.\ntags:\n - Model Service Providers\n - Environment Variables\n---\n\n# Model Providers\n\nWhen deploying LobeHub, a rich set of environment variables related to model service providers is provided, allowing you to easily define the model service providers to be enabled in LobeHub.\n\n## OpenAI\n\n### `ENABLED_OPENAI`\n\n- Type:Optional\n- Description:Enables OpenAI as a model provider by default, turns off the OpenAI service when set to `0`\n- Default:`1`\n- Example:`0`\n\n### `OPENAI_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for on the OpenAI account page, you can check it out [here](https://platform.openai.com/api-keys)\n- Default: -\n- Example: `sk-xxxxxx...xxxxxx`\n\n### `OPENAI_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the OpenAI interface proxy, you can use this configuration item to override the default OpenAI API request base URL\n- Default: `https://api.openai.com/v1`\n- Example: `https://api.chatanywhere.cn` or `https://aihubmix.com/v1`\n\n\n Please check the request suffix of your proxy service provider. Some proxy service providers may\n add `/v1` to the request suffix, while others may not. If you find that the AI returns an empty\n message during testing, try adding the `/v1` suffix and retry.\n\n\n\n Whether to fill in `/v1` is closely related to the model service provider. For example, the\n default address of openai is `api.openai.com/v1`. If your proxy forwards the `/v1` interface, you\n can simply fill in `proxy.com`. However, if the model service provider directly forwards the\n `api.openai.com` domain, then you need to add `/v1` to the URL yourself.\n\n\nRelated discussions:\n\n- [Why is the return value blank after installing Docker, configuring environment variables?](https://github.com/lobehub/lobe-chat/discussions/623)\n- [Reasons for errors when using third-party interfaces](https://github.com/lobehub/lobe-chat/discussions/734)\n- [No response in chat after filling in the proxy server address](https://github.com/lobehub/lobe-chat/discussions/1065)\n\n### `OPENAI_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo`\n\nThe above example adds `qwen-7b-chat` and `glm-6b` to the model list, removes `gpt-3.5-turbo` from the list, and displays the name of `gpt-4-0125-preview` as `gpt-4-turbo`. If you want to disable all models first and then enable specific models, you can use `-all,+gpt-3.5-turbo`, which means only `gpt-3.5-turbo` will be enabled.\n\nYou can find all current model names in [modelProviders](https://github.com/lobehub/lobe-chat/tree/main/src/config/modelProviders).\n\n## Azure OpenAI\n\nIf you need to use Azure OpenAI to provide model services, you can refer to the [Deploying with Azure OpenAI](/docs/self-hosting/examples/azure-openai) section for detailed steps. Here, we will list the environment variables related to Azure OpenAI.\n\n### `AZURE_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for on the Azure OpenAI account page\n- Default: -\n- Example: `c55168be3874490ef0565d9779ecd5a6`\n\n### `AZURE_ENDPOINT`\n\n- Type: Required\n- Description: The endpoint of Azure OpenAI, you can find it in the Azure OpenAI account page\n- Default: `-`\n- Example: `https://docs-test-001.openai.azure.com`\n\n### `AZURE_API_VERSION`\n\n- Type: Required\n- Description: The API version of Azure, following the format YYYY-MM-DD\n- Default: `-`\n- Example: `2023-02-01`, refer to [latest version](https://docs.microsoft.com/zh-cn/azure/developer/javascript/api-reference/es-modules/azure-sdk/ai-translation/translationconfiguration?view=azure-node-latest#api-version)\n\n### `AZURE_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `id->deploymentName=displayName` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `gpt-35-turbo->my-deploy=GPT 3.5 Turbo` 或 `gpt-4-turbo->my-gpt4=GPT 4 Turbo<128000:vision:fc>`\n\n## Google AI\n\n### `GOOGLE_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Google AI Platform to access Google AI services\n- Default: -\n- Example: `AIraDyDwcw254kwJaGjI9wwaHcdDCS__Vt3xQE`\n\n### `GOOGLE_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Google API proxy, you can use this configuration item to override the default Google API request base URL\n- Default: `https://generativelanguage.googleapis.com`\n- Example: `https://api.genai.gd.edu.kg/google`\n\n### `GOOGLE_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`\n\n## Vertex AI\n\n### `VERTEXAI_CREDENTIALS`\n\n- Type: Required\n- Description: A JSON string of your Google Cloud service account key, you can get the key from [here](/docs/usage/providers/vertexai).\n- Default: -\n- Example: `{\"type\": \"service_account\", \"project_id\": \"your-gcp-project-id\", ...}`\n\n### `VERTEXAI_PROJECT`\n\n- Type: Optional\n- Description: Your Google Cloud project ID. If not set, it will be obtained from the `project_id` field in `VERTEXAI_CREDENTIALS`.\n- Default: -\n- Example: `your-gcp-project-id`\n\n### `VERTEXAI_LOCATION`\n\n- Type: Optional\n- Description: The region where your Vertex AI model is located.\n- Default: `global`\n- Example: `us-central1`\n\n### `VERTEXAI_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`\n\n## Anthropic AI\n\n### `ANTHROPIC_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Anthropic AI\n- Default: -\n- Example: `sk-ant-apixx-xxxxxxxxx-xxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxx`\n\n### `ANTHROPIC_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Anthropic API proxy, you can use this configuration item to override the default Anthropic API request base URL\n- Default: `https://api.anthropic.com`\n- Example: `https://my-anthropic-proxy.com`\n\n## AWS Bedrock\n\n### `ENABLED_AWS_BEDROCK`\n\n- Type:Optional\n- Description:Enables AWS Bedrock as a model provider by default, turns on the AWS Bedrock service when set to `1`\n- Default:`0`\n- Example:`1`\n\n### `AWS_ACCESS_KEY_ID`\n\n- Type: Required\n- Description: Access key ID for AWS service authentication\n- Default: -\n- Example: `AKIA5STVRLFSB4S9HWBR`\n\n### `AWS_SECRET_ACCESS_KEY`\n\n- Type: Required\n- Description: Key for AWS service authentication\n- Default: -\n- Example: `Th3vXxLYpuKcv2BARktPSTPxx+jbSiFT6/0w7oEC`\n\n### `AWS_REGION`\n\n- Type: Optional\n- Description: Region setting for AWS services\n- Default: `us-east-1`\n- Example: `us-east-1`\n\n## DeepSeek AI\n\n### `DEEPSEEK_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the DeepSeek API proxy, you can use this configuration item to override the default DeepSeek API request base URL\n- Default: `https://api.deepseek.com`\n- Example: `https://my-deepseek-proxy.com`\n\n### `DEEPSEEK_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the DeepSeek AI service\n- Default: -\n- Example: `sk-xxxxxx...xxxxxx`\n\n### `DEEPSEEK_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=displayName` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+deepseek-reasoner`\n\n## XAI\n\n### `XAI_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the XAI service\n- Default: -\n- Example: `xai-xxxxxx...xxxxxx`\n\n## Wenxin\n\n### `WENXIN_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Baidu Wenxin service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n## OpenRouter AI\n\n### `OPENROUTER_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the OpenRouter AI service\n- Default: -\n- Example: `sk-or-v1-xxxxxx...xxxxxx=`\n\n### `OPENROUTER_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to specify a custom OpenRouter model list. Model definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+01-ai/yi-34b-chat,+huggingfaceh4/zephyr-7b-beta`\n\n## PPIO\n\n### `PPIO_API_KEY`\n\n- Type: Required\n- Description: This your PPIO API Key.\n- Default: -\n- Example: `sk_xxxxxxxxxx`\n\n### `PPIO_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+deepseek/deepseek-v3/community,+deepseek/deepseek-r1-distill-llama-70b`\n\n## Github\n\n### `GITHUB_TOKEN`\n\n- Type: Required\n- Description: This is your Personal access tokens you got for in the Github\n- Default: `-`\n- Example:`ghp_xxxxxx...xxxxxx=`\n\n### `GITHUB_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+gpt-4o,+gpt-4o-mini`\n\n## TogetherAI\n\n### `TOGETHERAI_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the TogetherAI service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n### `TOGETHERAI_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to specify a custom TogetherAI model list. Model definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo,+meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo`\n\n## Fireworks AI\n\n### `FIREWORKSAI_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Fireworks AI service\n- Default: `-`\n- Example:`xxxxxx...xxxxxx`\n\n### `FIREWORKSAI_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+accounts/fireworks/models/firefunction-v2,+accounts/fireworks/models/firefunction-v1`\n\n## Ollama\n\n### `ENABLED_OLLAMA`\n\n- Type:Optional\n- Description:Enables Ollama as a model provider by default, turns off the Ollama service when set to `0`\n- Default:`1`\n- Example:`0`\n\n### `OLLAMA_PROXY_URL`\n\n- Type: Required\n- Description: Used to enable the Ollama service, setting this will display optional open-source language models in the language model list and can also specify custom language models\n- Default: -\n- Example: `http://127.0.0.1:11434`\n\n### `OLLAMA_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to specify a custom Ollama language model. Model definition syntax rules see [model-list][model-list]\n- Default: -\n- Example: `llama2:7B`\n\n## Moonshot AI\n\n### `MOONSHOT_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Moonshot AI service\n- Default: -\n- Example: `Y2xpdGhpMzNhZXNoYjVtdnZjMWc6bXNrLWIxQlk3aDNPaXpBWnc0V1RaMDhSRmRFVlpZUWY=`\n\n### `MOONSHOT_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Moonshot API proxy, you can use this configuration item to override the default Moonshot API request base URL\n- Default: `https://api.moonshot.cn/v1`\n- Example: `https://my-moonshot-proxy.com/v1`\n\n## Perplexity AI\n\n### `PERPLEXITY_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Perplexity AI\n- Default: -\n- Example: `pplx-xxxxxx...xxxxxx`\n\n### `PERPLEXITY_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Perplexity API proxy, you can use this configuration item to override the default Perplexity API request base URL\n- Default: `https://api.Perplexity.ai`\n- Example: `https://my-Perplexity-proxy.com`\n\n### `PERPLEXITY_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see\n- Default: `-`\n- Example: `-all,+llama-3.1-sonar-small-128k-online,+llama-3.1-sonar-small-128k-chat`\n\n## Minimax AI\n\n### `MINIMAX_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Minimax AI service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n## Mistral AI\n\n### `MISTRAL_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Mistral AI service\n- Default: -\n- Example: `xxxxxx...xxxxxx=`\n\n## Groq AI\n\n### `GROQ_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Groq AI\n- Default: -\n- Example: `gsk_xxxxxx...xxxxxx`\n\n### `GROQ_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Groq API proxy, you can use this configuration item to override the default Groq API request base URL\n- Default: `https://api.groq.com/openai/v1`\n- Example: `https://my-groq-proxy.com/v1`\n\n### `GROQ_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+gemma2-9b-it,+llama-3.1-8b-instant`\n\n## ZHIPU AI\n\n### `ZHIPU_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the ZHIPU AI service\n- Default: -\n- Example: `4582d332441a313f5c2ed9824d1798ca.rC8EcTAhgbOuAuVT`\n\n### `ZHIPU_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+glm-4-alltools,+glm-4-plus`\n\n## 01.AI\n\n### `ZEROONE_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the 01.AI service\n- Default: -\n- Example:`xxxxxx...xxxxxx`\n\n### `ZEROONE_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+yi-large,+yi-large-rag`\n\n## Qiniu\n\n### `QINIU_API_KEY`\n\n- Type: Required\n- Description: This is the API key you can obtain from Qiniu AI service\n- Default: -\n- Example:`sk-xxxxx...xxxxx`\n\n### `QINIU_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+deepseek-r1,+deepseek-v3`\n\n### `QINIU_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Qiniu API proxy, you can use this configuration item to override the default Qiniu API request base URL\n- Default: `https://api.qnaigc.com/v1`\n- Example: `https://my-qnaigc.com/v1`\n\n## Qwen\n\n### `QWEN_API_KEY`\n\n- Type: Required\n- Description: This is the DashScope API key you can obtain from Alibaba Cloud\n- Default: -\n- Example:`sk-xxxxx...xxxxx`\n\n### `QWEN_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name->deploymentName=display_name` to customize the display name of a model, separated by commas. The deploymentName `->deploymentName` can be omitted, and it defaults to the latest model version. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+qwen-turbo,+qwen-plus,+qwen-max->qwen-max-2025-01-25`\n\n### `QWEN_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Qwen API proxy, you can use this configuration item to override the default Qwen API request base URL\n- Default: `https://dashscope.aliyuncs.com/compatible-mode/v1`\n- Example: `https://my-qwen-proxy.com/v1`\n\n## Stepfun AI\n\n### `STEPFUN_API_KEY`\n\n- Type: Required\n- Description: This is the DashScope API key you can obtain from Stepfun AI service\n- Default: -\n- Example:`sk-xxxxx...xxxxx`\n\n## Novita AI\n\n### `NOVITA_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Novita AI service\n- Default: -\n- Example:`xxxxxx...xxxxxx`\n\n### `NOVITA_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+meta-llama/llama-3.1-8b-instruct,+meta-llama/llama-3.1-70b-instruct`\n\n## BAICHUAN\n\n### `BAICHUAN_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the BAICHUAN service\n- Default: -\n- Example:`xxxxxx...xxxxxx`\n\n## TAICHU\n\n### `TAICHU_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the TAICHU service\n- Default: -\n- Example:`xxxxxx...xxxxxx`\n\n## 360 AI\n\n### `AI360_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the 360 AI service\n- Default: -\n- Example:`xxxxxx...xxxxxx`\n\n## Siliconflow\n\n### `SILICONCLOUD_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Siliconflow service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n### `SILICONCLOUD_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Siliconflow API proxy, you can use this configuration item to override the default Siliconflow API request base URL\n- Default: `https://api.siliconflow.cn/v1`\n- Example: `https://my-siliconflow-proxy.com/v1`\n\n### `SILICONCLOUD_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+deepseek-ai/DeepSeek-V2.5,+Qwen/Qwen2.5-7B-Instruct`\n\n## Upstage AI\n\n### `UPSTAGE_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Upstage AI service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n## Spark AI\n\n### `SPARK_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Spark AI service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n## A21 AI\n\n### `AI21_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from `AI21_API_KEY` service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n## HUNYUAN\n\n### `HUNYUAN_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from HUNYUAN service\n- Default: -\n- Example: `xxxxxx...xxxxxx`\n\n### `HUNYUAN_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+hunyuan-lite,+hunyuan-standard`\n\n## Volcengine\n\n### `VOLCENGINE_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Volcengine service, you can check it out [here](https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint)\n- Default: -\n- Example: `ad925b3d-aaaa-bbbb-cccc-51fde2f50547`\n\n### `VOLCENGINE_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name->deploymentName=display_name` to customize the display name of a model, separated by commas. The deploymentName `->deploymentName` can be omitted, and it defaults to the latest model version. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+deepseek-r1,+deepseek-v3->deepseek-v3-250324,+doubao-1.5-pro-256k,+doubao-1.5-pro-32k->doubao-1-5-pro-32k-250115,+doubao-1.5-lite-32k`\n\n### `VOLCENGINE_PROXY_URL`\n\n- Type: Optional\n- Description: If you manually configure the Volcengine API proxy, you can use this configuration item to override the default Volcengine API request base URL\n- Default: `https://ark.cn-beijing.volces.com/api/v3`\n- Example: `https://my-volcengine-proxy.com/v1`\n\n## InfiniAI\n\n### `INFINIAI_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied from Infini-AI, you can check it out [here](https://cloud.infini-ai.com)\n- Default: -\n- Example: `sk-xxxxxx...xxxxxx`\n\n### `INFINIAI_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]\n- Default: `-`\n- Example: `-all,+qwq-32b,+deepseek-r1`\n\n## FAL\n\n### `ENABLED_FAL`\n\n- Type: Optional\n- Description: Enables FAL as a model provider by default. Set to `0` to disable the FAL service.\n- Default: `1`\n- Example: `0`\n\n### `FAL_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the FAL service.\n- Default: -\n- Example: `fal-xxxxxx...xxxxxx`\n\n### `FAL_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the FAL model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model. Separate multiple entries with commas. The definition syntax follows the same rules as other providers' model lists.\n- Default: `-`\n- Example: `-all,+fal-ai/flux/schnell,+fal-ai/flux-pro/kontext=FLUX.1 Kontext [pro]`\n\nThe above example disables all models first, then enables `fal-ai/flux/schnell` and `fal-ai/flux-pro/kontext` (displayed as `FLUX.1 Kontext [pro]`).\n\n## ComfyUI\n\n### `ENABLED_COMFYUI`\n\n- Type: Optional\n- Description: Enables ComfyUI as a model provider by default. Set to `0` to disable the ComfyUI service.\n- Default: `1`\n- Example: `0`\n\n### `COMFYUI_BASE_URL`\n\n- Type: Optional\n- Description: The base URL address of the ComfyUI service\n- Default: `http://localhost:8188`\n- Example: `http://192.168.1.100:8188` or `https://my-comfyui-server.com`\n\n### `COMFYUI_AUTH_TYPE`\n\n- Type: Optional\n- Description: The authentication type for ComfyUI, supporting 4 authentication methods\n - `none`: No authentication (default)\n - `basic`: Basic authentication (username + password)\n - `bearer`: Bearer Token authentication (API key)\n - `custom`: Custom request header authentication\n- Default: `none`\n- Example: `basic`\n\n### `COMFYUI_API_KEY`\n\n- Type: Optional\n- Description: The API key used when the authentication type is `bearer`\n- Default: -\n- Example: `sk-xxxxxx...xxxxxx`\n\n### `COMFYUI_USERNAME`\n\n- Type: Optional\n- Description: The username used when the authentication type is `basic`\n- Default: -\n- Example: `admin`\n\n### `COMFYUI_PASSWORD`\n\n- Type: Optional\n- Description: The password used when the authentication type is `basic`\n- Default: -\n- Example: `password123`\n\n### `COMFYUI_CUSTOM_HEADERS`\n\n- Type: Optional\n- Description: Custom request headers used when the authentication type is `custom`, requires JSON format string\n- Default: -\n- Example: `{\"X-Auth-Token\": \"your-token\", \"X-Custom-Header\": \"value\"}`\n\n\n ComfyUI supports multiple authentication methods. Please choose the appropriate authentication type and corresponding authentication parameters according to your ComfyUI service configuration. If your ComfyUI service has no authentication set up, you can skip configuring authentication-related environment variables.\n\n\n## BFL\n\n### `BFL_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the BFL service.\n- Default: -\n- Example: `12345678-1234-1234-1234-123456789abc`\n\n### `BFL_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the BFL model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model. Separate multiple entries with commas. The definition syntax follows the same rules as other providers' model lists.\n- Default: `-`\n- Example: `-all,+flux-pro-1.1,+flux-kontext-pro=FLUX.1 Kontext [pro]`\n\nThe above example disables all models first, then enables `flux-pro-1.1` and `flux-kontext-pro` (displayed as `FLUX.1 Kontext [pro]`).\n\n## NewAPI\n\n### `NEWAPI_API_KEY`\n\n- Type: Optional\n- Description: This is the API key for your NewAPI service instance. NewAPI is a multi-provider model aggregation service that provides unified access to various AI model APIs.\n- Default: -\n- Example: `sk-xxxxxx...xxxxxx`\n\n### `NEWAPI_PROXY_URL`\n\n- Type: Optional\n- Description: The base URL for your NewAPI server instance. This should point to your deployed NewAPI service endpoint.\n- Default: -\n- Example: `https://your-newapi-server.com/`\n\nNewAPI is a multi-provider model aggregation service that supports automatic model routing based on provider detection. It offers cost management features and provides a single endpoint for accessing models from multiple providers including OpenAI, Anthropic, Google, and more. Learn more about NewAPI at [https://github.com/Calcium-Ion/new-api](https://github.com/Calcium-Ion/new-api).\n\n## Vercel AI Gateway\n\n### `VERCELAIGATEWAY_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Vercel AI Gateway service.\n- Default: -\n- Example: `vck_xxxxxx...xxxxxx`\n\n### `VERCELAIGATEWAY_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the Vercel AI Gateway model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model. Separate multiple entries with commas. The definition syntax follows the same rules as other providers' model lists.\n- Default: `-`\n- Example: `-all,+vercel-model-1,+vercel-model-2=vercel-special`\n\n## Cerebras\n\n### `CEREBRAS_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the Cerebras service.\n- Default: -\n- Example: `csk-xxxxxx...xxxxxx`\n\n### `CEREBRAS_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the Cerebras model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model. Separate multiple entries with commas. The definition syntax follows the same rules as other providers' model lists.\n- Default: `-`\n- Example: `-all,+cerebras-model-1,+cerebras-model-2=cerebras-special`\n\n## AiHubMix\n\n### `AIHUBMIX_API_KEY`\n\n- Type: Required\n- Description: This is the API key you applied for in the AiHubMix service.\n- Default: -\n- Example: `sk-xxxxxx...xxxxxx`\n\n### `AIHUBMIX_MODEL_LIST`\n\n- Type: Optional\n- Description: Used to control the AiHubMix model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model. Separate multiple entries with commas. The definition syntax follows the same rules as other providers' model lists.\n- Default: `-`\n- Example: `-all,+claude-opus-4-1-20250805,+claude-opus-4-20250514=claude-opus-4`\n\n[model-list]: /docs/self-hosting/advanced/model-list\n"}}, "lobehub-main/packages/model-runtime/src/providers/ai360/index.test.ts": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785809835.461105, "n": 37886, "h": "2664eab91e8bff11", "c": "// @vitest-environment node\nimport { ModelProvider } from 'model-bank';\nimport { beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport { testProvider } from '../../providerTestUtils';\nimport { LobeAi360AI, params } from './index';\n\ntestProvider({\n Runtime: LobeAi360AI,\n provider: ModelProvider.Ai360,\n defaultBaseURL: 'https://api.360.cn/v1',\n chatDebugEnv: 'DEBUG_AI360_CHAT_COMPLETION',\n chatModel: 'deepseek-r1',\n invalidErrorType: 'InvalidProviderAPIKey',\n bizErrorType: 'ProviderBizError',\n test: {\n skipAPICall: true,\n skipErrorHandle: true,\n },\n});\n\ndescribe('LobeAi360AI - custom features', () => {\n let instance: InstanceType;\n\n beforeEach(() => {\n instance = new LobeAi360AI({ apiKey: 'test_api_key' });\n vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(\n new ReadableStream() as any,\n );\n });\n\n describe('params.debug', () => {\n it('should disable debug mode by default', () => {\n delete process.env.DEBUG_AI360_CHAT_COMPLETION;\n const result = params.debug.chatCompletion();\n expect(result).toBe(false);\n });\n\n it('should enable debug mode when DEBUG_AI360_CHAT_COMPLETION is set to 1', () => {\n process.env.DEBUG_AI360_CHAT_COMPLETION = '1';\n const result = params.debug.chatCompletion();\n expect(result).toBe(true);\n delete process.env.DEBUG_AI360_CHAT_COMPLETION;\n });\n\n it('should disable debug mode when DEBUG_AI360_CHAT_COMPLETION is not 1', () => {\n process.env.DEBUG_AI360_CHAT_COMPLETION = '0';\n const result = params.debug.chatCompletion();\n expect(result).toBe(false);\n delete process.env.DEBUG_AI360_CHAT_COMPLETION;\n });\n });\n\n describe('params.chatCompletion.handlePayload', () => {\n it('should add web_search tool when enabledSearch is true', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.tools).toBeDefined();\n expect(result.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);\n expect(result.tools?.find((tool: any) => tool.type === 'web_search')).toEqual({\n type: 'web_search',\n web_search: {\n search_mode: 'auto',\n },\n });\n });\n\n it('should correctly set web_search properties', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n const webSearchTool = result.tools?.find((tool: any) => tool.type === 'web_search');\n\n expect(webSearchTool).toBeDefined();\n expect(webSearchTool.type).toBe('web_search');\n expect(webSearchTool.web_search).toBeDefined();\n expect(webSearchTool.web_search.search_mode).toBe('auto');\n });\n\n it('should not add web_search tool when enabledSearch is false', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: false,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.tools).toBeUndefined();\n });\n\n it('should not add web_search tool when enabledSearch is not present', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.tools).toBeUndefined();\n });\n\n it('should disable stream when tools are present', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n tools: [{ type: 'function', function: { name: 'test' } }],\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.stream).toBe(false);\n });\n\n it('should disable stream when enabledSearch is true', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.stream).toBe(false);\n });\n\n it('should not set stream to false when no tools', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.stream).not.toBe(false);\n });\n\n it('should merge tools with web_search when enabledSearch is true', () => {\n const existingTool = { type: 'function', function: { name: 'existing_tool' } };\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n tools: [existingTool],\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.tools).toHaveLength(2);\n expect(result.tools).toContainEqual(existingTool);\n expect(result.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);\n });\n\n it('should remove enabledSearch from payload', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.enabledSearch).toBeUndefined();\n });\n\n it('should preserve other payload properties', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0.7,\n max_tokens: 1024,\n top_p: 0.9,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.messages).toEqual(payload.messages);\n expect(result.model).toBe(payload.model);\n expect(result.temperature).toBe(payload.temperature);\n expect(result.max_tokens).toBe(payload.max_tokens);\n expect(result.top_p).toBe(payload.top_p);\n });\n\n it('should handle empty tools array when enabledSearch is true', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n tools: [],\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.tools).toHaveLength(1);\n expect(result.tools?.[0].type).toBe('web_search');\n });\n\n it('should handle multiple existing tools when enabledSearch is true', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n tools: [\n { type: 'function', function: { name: 'tool1' } },\n { type: 'function', function: { name: 'tool2' } },\n ],\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n\n expect(result.tools).toHaveLength(3);\n expect(result.tools?.[0].function.name).toBe('tool1');\n expect(result.tools?.[1].function.name).toBe('tool2');\n expect(result.tools?.[2].type).toBe('web_search');\n });\n });\n\n describe('params.models', () => {\n it('should fetch and process models successfully', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n {\n id: '360gpt2-o1',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n\n expect(models).toHaveLength(2);\n expect(mockClient.models.list).toHaveBeenCalled();\n });\n\n it('should correctly map model properties', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.id).toBe('360gpt-pro');\n expect(model.contextWindowTokens).toBe(8192);\n expect(model.maxOutput).toBe(4096);\n });\n\n it('should detect 360gpt-pro as function call capable', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.functionCall).toBe(true);\n });\n\n it('should detect reasoning models with 360gpt2-o1 keyword', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt2-o1',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.reasoning).toBe(true);\n });\n\n it('should detect reasoning models with 360zhinao2-o1 keyword', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360zhinao2-o1-preview',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.reasoning).toBe(true);\n });\n\n it('should handle case-insensitive reasoning keyword matching', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360GPT2-O1',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.reasoning).toBe(true);\n });\n\n it('should merge with LOBE_DEFAULT_MODEL_LIST for known models', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n // Known models from LOBE_DEFAULT_MODEL_LIST should have displayName and enabled flag\n expect(model.displayName).toBeDefined();\n expect(model.enabled).toBeDefined();\n });\n\n it('should handle models not in LOBE_DEFAULT_MODEL_LIST', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'unknown-model',\n max_tokens: 2048,\n total_tokens: 4096,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.id).toBe('unknown-model');\n expect(model.displayName).toBeUndefined();\n expect(model.enabled).toBe(false);\n });\n\n it('should inherit abilities from LOBE_DEFAULT_MODEL_LIST', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n // Check that abilities are inherited from known model\n expect(typeof model.functionCall).toBe('boolean');\n expect(typeof model.vision).toBe('boolean');\n expect(typeof model.reasoning).toBe('boolean');\n });\n\n it('should handle models with non-number max_tokens', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'test-model',\n max_tokens: 'unlimited' as any,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.maxOutput).toBeUndefined();\n });\n\n it('should handle models with null max_tokens', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'test-model',\n max_tokens: null as any,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.maxOutput).toBeUndefined();\n });\n\n it('should handle empty model list', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n\n expect(models).toEqual([]);\n });\n\n it('should handle model list with valid models only', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'model1',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n {\n id: 'model2',\n max_tokens: 2048,\n total_tokens: 4096,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n\n expect(models).toHaveLength(2);\n expect(models.every((m) => m !== null && m.id)).toBe(true);\n });\n\n it('should handle API errors gracefully', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockRejectedValue(new Error('API Error')),\n },\n };\n\n await expect(params.models!({ client: mockClient as any })).rejects.toThrow('API Error');\n });\n\n it('should set reasoning to false for non-reasoning models', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-base',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.reasoning).toBe(false);\n });\n\n it('should set functionCall to false for non-360gpt-pro models without functionCall ability', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'test-model',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.functionCall).toBe(false);\n });\n\n it('should set vision to false for models without vision ability', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'test-model',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.vision).toBe(false);\n });\n\n it('should handle case-insensitive model ID matching with LOBE_DEFAULT_MODEL_LIST', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360GPT-PRO',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n // Should match case-insensitively with LOBE_DEFAULT_MODEL_LIST\n expect(model.id).toBe('360GPT-PRO');\n expect(model.displayName).toBeDefined();\n });\n\n it('should handle models with all properties present', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model).toHaveProperty('id');\n expect(model).toHaveProperty('contextWindowTokens');\n expect(model).toHaveProperty('maxOutput');\n expect(model).toHaveProperty('functionCall');\n expect(model).toHaveProperty('vision');\n expect(model).toHaveProperty('reasoning');\n expect(model).toHaveProperty('enabled');\n });\n\n it('should detect all reasoning model variants correctly', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt2-o1',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n {\n id: '360gpt2-o1-preview',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n {\n id: '360zhinao2-o1',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n {\n id: '360zhinao2-o1-mini',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n\n expect(models).toHaveLength(4);\n expect(models.every((m) => m.reasoning === true)).toBe(true);\n });\n\n it('should handle models with zero context window', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'test-model',\n max_tokens: 0,\n total_tokens: 0,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.contextWindowTokens).toBe(0);\n expect(model.maxOutput).toBe(0);\n });\n\n it('should handle very large context window values', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: 'large-context-model',\n max_tokens: 128000,\n total_tokens: 1000000,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.contextWindowTokens).toBe(1000000);\n expect(model.maxOutput).toBe(128000);\n });\n\n it('should handle models with special characters in ID', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro-v2.0-beta',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.id).toBe('360gpt-pro-v2.0-beta');\n });\n\n it('should detect 360gpt-pro as functionCall capable', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.id).toBe('360gpt-pro');\n expect(model.functionCall).toBe(true);\n });\n\n it('should prioritize reasoning from keyword over knownModel', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt2-o1-custom',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n // Should detect reasoning from keyword even if not in LOBE_DEFAULT_MODEL_LIST\n expect(model.reasoning).toBe(true);\n });\n\n it('should prioritize functionCall from model ID check over knownModel', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt-pro',\n max_tokens: 4096,\n total_tokens: 8192,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.functionCall).toBe(true);\n });\n\n it('should merge multiple capabilities correctly', async () => {\n const mockClient = {\n models: {\n list: vi.fn().mockResolvedValue({\n data: [\n {\n id: '360gpt2-o1',\n max_tokens: 8192,\n total_tokens: 16384,\n },\n ],\n }),\n },\n };\n\n const models = await params.models!({ client: mockClient as any });\n const model = models[0];\n\n expect(model.reasoning).toBe(true);\n expect(typeof model.functionCall).toBe('boolean');\n expect(typeof model.vision).toBe('boolean');\n });\n });\n\n describe('LobeAi360AI instance - integration tests', () => {\n it('should add web_search tool when enabledSearch is true', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.tools).toBeDefined();\n expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);\n });\n\n it('should disable stream when tools are present', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n tools: [{ type: 'function', function: { name: 'test' } }],\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.stream).toBe(false);\n });\n\n it('should enable stream when no tools', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.stream).not.toBe(false);\n });\n\n it('should merge tools with web_search when enabledSearch is true', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n tools: [{ type: 'function', function: { name: 'existing_tool' } }],\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.tools).toHaveLength(2);\n expect(calledPayload.tools?.some((tool: any) => tool.type === 'function')).toBe(true);\n expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);\n });\n\n it('should preserve temperature without normalization', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0.7,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(0.7);\n });\n\n it('should preserve top_p without modification', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n top_p: 0.9,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.top_p).toBe(0.9);\n });\n\n it('should preserve max_tokens without modification', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n max_tokens: 1024,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.max_tokens).toBe(1024);\n });\n\n it('should preserve messages as-is', async () => {\n const messages = [\n { content: 'Hello', role: 'user' as const },\n { content: 'Hi there!', role: 'assistant' as const },\n { content: 'How are you?', role: 'user' as const },\n ];\n\n await instance.chat({\n messages,\n model: '360gpt-pro',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.messages).toEqual(messages);\n });\n\n it('should preserve model name', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt2-o1',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.model).toBe('360gpt2-o1');\n });\n\n it('should handle combined parameters correctly', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0.8,\n top_p: 0.85,\n max_tokens: 2048,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.model).toBe('360gpt-pro');\n expect(calledPayload.temperature).toBe(0.8);\n expect(calledPayload.top_p).toBe(0.85);\n expect(calledPayload.max_tokens).toBe(2048);\n });\n\n it('should not include undefined parameters in payload', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n });\n\n const callArgs = vi.mocked(instance['client'].chat.completions.create).mock.calls[0][0];\n expect(callArgs).not.toHaveProperty('temperature');\n expect(callArgs).not.toHaveProperty('top_p');\n expect(callArgs).not.toHaveProperty('max_tokens');\n });\n });\n\n describe('exports', () => {\n it('should export params object', () => {\n expect(params).toBeDefined();\n expect(params.provider).toBe(ModelProvider.Ai360);\n expect(params.baseURL).toBe('https://api.360.cn/v1');\n });\n\n it('should export LobeAi360AI class', () => {\n expect(LobeAi360AI).toBeDefined();\n expect(typeof LobeAi360AI).toBe('function');\n });\n\n it('should export params with all required properties', () => {\n expect(params).toHaveProperty('provider');\n expect(params).toHaveProperty('baseURL');\n expect(params).toHaveProperty('chatCompletion');\n expect(params).toHaveProperty('debug');\n expect(params).toHaveProperty('models');\n });\n\n it('should have chatCompletion.handlePayload function', () => {\n expect(params.chatCompletion).toBeDefined();\n expect(params.chatCompletion.handlePayload).toBeDefined();\n expect(typeof params.chatCompletion.handlePayload).toBe('function');\n });\n\n it('should have debug.chatCompletion function', () => {\n expect(params.debug).toBeDefined();\n expect(params.debug.chatCompletion).toBeDefined();\n expect(typeof params.debug.chatCompletion).toBe('function');\n });\n\n it('should have models function', () => {\n expect(params.models).toBeDefined();\n expect(typeof params.models).toBe('function');\n });\n });\n\n describe('edge cases and comprehensive coverage', () => {\n it('should handle payload with undefined tools', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n tools: undefined,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n expect(result.tools).toBeUndefined();\n });\n\n it('should handle payload with null tools', () => {\n const payload = {\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n tools: null as any,\n } as any;\n\n const result = params.chatCompletion.handlePayload!(payload);\n expect(result.tools).toBeNull();\n });\n\n it('should handle empty messages array', async () => {\n await instance.chat({\n messages: [],\n model: '360gpt-pro',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.messages).toEqual([]);\n });\n\n it('should handle very small temperature values', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0.01,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(0.01);\n });\n\n it('should handle temperature of 0', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(0);\n });\n\n it('should handle temperature of 1', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 1,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(1);\n });\n\n it('should handle high temperature values', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 2.0,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(2.0);\n });\n\n it('should handle top_p of 0', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n top_p: 0,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.top_p).toBe(0);\n });\n\n it('should handle top_p of 1', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n top_p: 1,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.top_p).toBe(1);\n });\n\n it('should handle very small top_p values', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n top_p: 0.01,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.top_p).toBe(0.01);\n });\n\n it('should handle large max_tokens values', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n max_tokens: 100000,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.max_tokens).toBe(100000);\n });\n\n it('should handle complex message history with multiple roles', async () => {\n const messages = [\n { content: 'You are a helpful assistant', role: 'system' as const },\n { content: 'Hello', role: 'user' as const },\n { content: 'Hi! How can I help?', role: 'assistant' as const },\n { content: 'What is 2+2?', role: 'user' as const },\n { content: '2+2 equals 4', role: 'assistant' as const },\n { content: 'Thanks', role: 'user' as const },\n ];\n\n await instance.chat({\n messages,\n model: '360gpt-pro',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.messages).toEqual(messages);\n });\n\n it('should handle combined parameters with web search', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0.8,\n top_p: 0.85,\n max_tokens: 2048,\n enabledSearch: true,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(0.8);\n expect(calledPayload.top_p).toBe(0.85);\n expect(calledPayload.max_tokens).toBe(2048);\n expect(calledPayload.stream).toBe(false);\n expect(calledPayload.tools?.some((tool: any) => tool.type === 'web_search')).toBe(true);\n });\n\n it('should handle combined parameters with tools and no web search', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n temperature: 0.9,\n top_p: 0.95,\n max_tokens: 4096,\n tools: [{ type: 'function', function: { name: 'test_tool' } }],\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.temperature).toBe(0.9);\n expect(calledPayload.top_p).toBe(0.95);\n expect(calledPayload.max_tokens).toBe(4096);\n expect(calledPayload.stream).toBe(false);\n expect(calledPayload.tools).toHaveLength(1);\n });\n\n it('should preserve all model parameters', async () => {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360zhinao2-o1-preview',\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.model).toBe('360zhinao2-o1-preview');\n });\n\n it('should handle multiple function tools', async () => {\n const tools = [\n { type: 'function' as const, function: { name: 'tool1', description: 'First tool' } },\n { type: 'function' as const, function: { name: 'tool2', description: 'Second tool' } },\n { type: 'function' as const, function: { name: 'tool3', description: 'Third tool' } },\n ];\n\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n tools,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.tools).toHaveLength(3);\n expect(calledPayload.stream).toBe(false);\n });\n\n it('should handle enabledSearch with multiple function tools', async () => {\n const tools = [\n { type: 'function' as const, function: { name: 'tool1', description: 'First tool' } },\n { type: 'function' as const, function: { name: 'tool2', description: 'Second tool' } },\n ];\n\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-pro',\n enabledSearch: true,\n tools,\n });\n\n const calledPayload = (instance['client'].chat.completions.create as any).mock.calls[0][0];\n expect(calledPayload.tools).toHaveLength(3);\n expect(calledPayload.tools?.filter((t: any) => t.type === 'function')).toHaveLength(2);\n expect(calledPayload.tools?.filter((t: any) => t.type === 'web_search')).toHaveLength(1);\n expect(calledPayload.stream).toBe(false);\n });\n });\n});\n"}}, "lobehub-main/packages/model-bank/src/modelProviders/sensenova.ts": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1785809866.3708057, "n": 816, "h": "1132f8db2f9ae526", "c": "import { type ModelProviderCard } from '@/types/llm';\n\n// ref https://platform.sensenova.cn/pricing\n// ref https://platform.sensenova.cn/release?path=/release-202409.md\nconst SenseNova: ModelProviderCard = {\n chatModels: [],\n checkModel: 'SenseChat-Turbo',\n description:\n 'SenseNova delivers efficient, easy-to-use full-stack LLM services backed by SenseTime infrastructure.',\n disableBrowserRequest: true,\n id: 'sensenova',\n modelList: { showModelFetcher: true },\n modelsUrl: 'https://platform.sensenova.cn/pricing',\n name: 'SenseNova',\n settings: {\n disableBrowserRequest: true,\n proxyUrl: {\n placeholder: 'https://api.sensenova.cn/compatible-mode/v1',\n },\n sdkType: 'openai',\n showModelFetcher: true,\n },\n url: 'https://platform.sensenova.cn/home',\n};\n\nexport default SenseNova;\n"}}, "lobehub-main/src/server/modules/ModelRuntime/index.test.ts": {"b875db7b63fece38345de91c137194f846fbad9a": {"t": 1786068275.7003071, "n": 28719, "h": "69fc16aa7cf519fa", "c": "// @vitest-environment node\nimport {\n LobeAnthropicAI,\n LobeAzureOpenAI,\n LobeBedrockAI,\n LobeComfyUI,\n LobeDeepSeekAI,\n LobeGoogleAI,\n LobeGroq,\n LobeMinimaxAI,\n LobeMistralAI,\n LobeMoonshotAI,\n LobeOllamaAI,\n LobeOpenAI,\n LobeOpenRouterAI,\n LobePerplexityAI,\n LobeQwenAI,\n LobeStepfunAI,\n LobeTogetherAI,\n LobeZeroOneAI,\n LobeZhipuAI,\n ModelRuntime,\n} from '@lobechat/model-runtime';\nimport { LobeVertexAI } from '@lobechat/model-runtime/vertexai';\nimport { ClientSecretPayload } from '@lobechat/types';\nimport { ModelProvider } from 'model-bank';\nimport { describe, expect, it, vi } from 'vitest';\n\nimport { buildPayloadFromKeyVaults, initModelRuntimeWithUserPayload } from './index';\n\n// 模拟依赖项\nvi.mock('@/envs/llm', () => ({\n getLLMConfig: vi.fn(() => ({\n // 确保为每个provider提供必要的配置信息\n OPENAI_API_KEY: 'test-openai-key',\n GOOGLE_API_KEY: 'test-google-key',\n\n AZURE_API_KEY: 'test-azure-key',\n AZURE_ENDPOINT: 'endpoint',\n\n ZHIPU_API_KEY: 'test.zhipu-key',\n MOONSHOT_API_KEY: 'test-moonshot-key',\n AWS_SECRET_ACCESS_KEY: 'test-aws-secret',\n AWS_ACCESS_KEY_ID: 'test-aws-id',\n AWS_REGION: 'test-aws-region',\n AWS_SESSION_TOKEN: 'test-aws-session-token',\n OLLAMA_PROXY_URL: 'https://test-ollama-url.local',\n PERPLEXITY_API_KEY: 'test-perplexity-key',\n DEEPSEEK_API_KEY: 'test-deepseek-key',\n ANTHROPIC_API_KEY: 'test-anthropic-key',\n MINIMAX_API_KEY: 'test-minimax-key',\n MISTRAL_API_KEY: 'test-mistral-key',\n OPENROUTER_API_KEY: 'test-openrouter-key',\n TOGETHERAI_API_KEY: 'test-togetherai-key',\n QINIU_API_KEY: 'test-qiniu-key',\n QWEN_API_KEY: 'test-qwen-key',\n STEPFUN_API_KEY: 'test-stepfun-key',\n })),\n}));\n\n/**\n * Test cases for function initModelRuntimeWithUserPayload\n * this method will use ModelRuntime from `@lobechat/model-runtime`\n * and method `getLlmOptionsFromPayload` to initialize runtime\n * with user payload. Test case below will test both the methods\n */\ndescribe('initModelRuntimeWithUserPayload method', () => {\n describe('should initialize with options correctly', () => {\n it('OpenAI provider: with apikey and endpoint', async () => {\n const jwtPayload: ClientSecretPayload = {\n apiKey: 'user-openai-key',\n baseURL: 'user-endpoint',\n };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.OpenAI, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);\n expect(runtime['_runtime'].baseURL).toBe(jwtPayload.baseURL);\n });\n\n it('Azure AI provider: with apikey, endpoint and apiversion', async () => {\n const jwtPayload: ClientSecretPayload = {\n apiKey: 'user-azure-key',\n baseURL: 'user-azure-endpoint',\n azureApiVersion: '2024-06-01',\n };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Azure, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeAzureOpenAI);\n expect(runtime['_runtime'].baseURL).toBe(jwtPayload.baseURL);\n });\n\n it('Custom provider should use runtimeProvider to init', async () => {\n const jwtPayload: ClientSecretPayload = {\n apiKey: 'user-azure-key',\n azureApiVersion: '2024-06-01',\n baseURL: 'user-azure-endpoint',\n runtimeProvider: ModelProvider.Azure,\n };\n const runtime = await initModelRuntimeWithUserPayload('custom-provider', jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeAzureOpenAI);\n expect(runtime['_runtime'].baseURL).toBe(jwtPayload.baseURL);\n });\n\n it('ZhiPu AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'zhipu.user-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.ZhiPu, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeZhipuAI);\n });\n\n it('Google provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-google-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Google, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeGoogleAI);\n });\n\n it('Moonshot AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-moonshot-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Moonshot, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeMoonshotAI);\n });\n\n it('Qwen AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-qwen-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Qwen, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);\n });\n\n it('Vertex AI provider: with service account json', async () => {\n const credentials = {\n client_email: 'vertex@test-project.iam.gserviceaccount.com',\n private_key: '-----BEGIN PRIVATE KEY-----\\nTEST\\n-----END PRIVATE KEY-----\\n',\n project_id: 'test-project',\n type: 'service_account',\n };\n const payload: ClientSecretPayload = { apiKey: JSON.stringify(credentials) };\n const initSpy = vi\n .spyOn(LobeVertexAI, 'initFromVertexAI')\n .mockImplementation((options: any) => {\n expect(options.project).toBe('test-project');\n expect(options.googleAuthOptions?.credentials?.private_key).toContain('TEST');\n\n return new LobeGoogleAI({\n apiKey: 'avoid-error',\n client: {} as any,\n isVertexAi: true,\n });\n });\n\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.VertexAI, payload);\n\n expect(initSpy).toHaveBeenCalledTimes(1);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeGoogleAI);\n\n initSpy.mockRestore();\n });\n\n it('Bedrock AI provider: with apikey, awsAccessKeyId, awsSecretAccessKey, awsRegion', async () => {\n const jwtPayload: ClientSecretPayload = {\n apiKey: 'user-bedrock-key',\n awsAccessKeyId: 'user-aws-id',\n awsSecretAccessKey: 'user-aws-secret',\n awsRegion: 'user-aws-region',\n };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Bedrock, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeBedrockAI);\n });\n\n it('Ollama provider: with endpoint', async () => {\n const jwtPayload: ClientSecretPayload = { baseURL: 'http://user-ollama-url' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Ollama, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeOllamaAI);\n expect(runtime['_runtime']['baseURL']).toEqual(jwtPayload.baseURL);\n });\n\n it('Perplexity AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-perplexity-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Perplexity, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobePerplexityAI);\n });\n\n it('Anthropic AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-anthropic-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Anthropic, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeAnthropicAI);\n });\n\n it('Minimax AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-minimax-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Minimax, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeMinimaxAI);\n });\n\n it('Mistral AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-mistral-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Mistral, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeMistralAI);\n });\n\n it('OpenRouter AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-openrouter-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.OpenRouter, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenRouterAI);\n });\n\n it('DeepSeek AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-deepseek-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.DeepSeek, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeDeepSeekAI);\n });\n\n it('Together AI provider: with apikey', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-togetherai-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.TogetherAI, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeTogetherAI);\n });\n\n it('ZeroOne AI provider: with apikey', async () => {\n const jwtPayload = { apiKey: 'user-zeroone-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.ZeroOne, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeZeroOneAI);\n });\n\n it('Groq AI provider: with apikey', async () => {\n const jwtPayload = { apiKey: 'user-zeroone-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Groq, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeGroq);\n });\n\n it('Stepfun AI provider: with apikey', async () => {\n const jwtPayload = { apiKey: 'user-stepfun-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Stepfun, jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeStepfunAI);\n });\n\n it('ComfyUI provider: with multiple auth types', async () => {\n // Test basic auth\n const basicAuthPayload: ClientSecretPayload = {\n authType: 'basic',\n username: 'test-user',\n password: 'test-pass',\n baseURL: 'http://localhost:8188',\n };\n let runtime = await initModelRuntimeWithUserPayload(ModelProvider.ComfyUI, basicAuthPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeComfyUI);\n expect(runtime['_runtime'].baseURL).toBe(basicAuthPayload.baseURL);\n\n // Test bearer auth\n const bearerAuthPayload: ClientSecretPayload = {\n authType: 'bearer',\n apiKey: 'test-token',\n baseURL: 'http://localhost:8188',\n };\n runtime = await initModelRuntimeWithUserPayload(ModelProvider.ComfyUI, bearerAuthPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeComfyUI);\n\n // Test custom auth\n const customAuthPayload: ClientSecretPayload = {\n authType: 'custom',\n customHeaders: { 'X-API-Key': 'secret123' },\n baseURL: 'http://localhost:8188',\n };\n runtime = await initModelRuntimeWithUserPayload(ModelProvider.ComfyUI, customAuthPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeComfyUI);\n\n // Test none auth\n const noAuthPayload: ClientSecretPayload = {\n authType: 'none',\n baseURL: 'http://localhost:8188',\n };\n runtime = await initModelRuntimeWithUserPayload(ModelProvider.ComfyUI, noAuthPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeComfyUI);\n });\n\n it('Unknown Provider: with apikey and endpoint, should initialize to OpenAi', async () => {\n const jwtPayload: ClientSecretPayload = {\n apiKey: 'user-unknown-key',\n baseURL: 'user-unknown-endpoint',\n };\n const runtime = await initModelRuntimeWithUserPayload('unknown', jwtPayload);\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);\n expect(runtime['_runtime'].baseURL).toBe(jwtPayload.baseURL);\n });\n });\n\n describe('should initialize without some options', () => {\n it('OpenAI provider: without apikey', async () => {\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.OpenAI, jwtPayload);\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);\n });\n\n it('Azure AI Provider: without apikey', async () => {\n const jwtPayload: ClientSecretPayload = {\n azureApiVersion: 'test-azure-api-version',\n };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Azure, jwtPayload);\n\n expect(runtime['_runtime']).toBeInstanceOf(LobeAzureOpenAI);\n });\n\n it('ZhiPu AI provider: without apikey', async () => {\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.ZhiPu, jwtPayload);\n\n // 假设 LobeZhipuAI 是 ZhiPu 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeZhipuAI);\n });\n\n it('Google provider: without apikey', async () => {\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Google, {});\n\n // 假设 LobeGoogleAI 是 Google 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeGoogleAI);\n });\n\n it('Moonshot AI provider: without apikey', async () => {\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Moonshot, jwtPayload);\n\n // 假设 LobeMoonshotAI 是 Moonshot 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeMoonshotAI);\n });\n\n it('Qwen AI provider: without apikey', async () => {\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Qwen, jwtPayload);\n\n // 假设 LobeQwenAI 是 Qwen 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);\n });\n\n it('Qwen AI provider: without endpoint', async () => {\n const jwtPayload: ClientSecretPayload = { apiKey: 'user-qwen-key' };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Qwen, jwtPayload);\n\n // 假设 LobeQwenAI 是 Qwen 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);\n // endpoint 不存在,应返回 DEFAULT_BASE_URL\n expect(runtime['_runtime'].baseURL).toBe('https://dashscope.aliyuncs.com/compatible-mode/v1');\n });\n\n it('Bedrock AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Bedrock, jwtPayload);\n\n // 假设 LobeBedrockAI 是 Bedrock 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeBedrockAI);\n });\n\n it('Ollama provider: without endpoint', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Ollama, jwtPayload);\n\n // 假设 LobeOllamaAI 是 Ollama 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeOllamaAI);\n });\n\n it('Perplexity AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Perplexity, jwtPayload);\n\n // 假设 LobePerplexityAI 是 Perplexity 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobePerplexityAI);\n });\n\n it('Anthropic AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Anthropic, jwtPayload);\n\n // 假设 LobeAnthropicAI 是 Anthropic 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeAnthropicAI);\n });\n\n it('Minimax AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Minimax, jwtPayload);\n\n // 假设 LobeMistralAI 是 Mistral 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeMinimaxAI);\n });\n\n it('Mistral AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Mistral, jwtPayload);\n\n // 假设 LobeMistralAI 是 Mistral 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeMistralAI);\n });\n\n it('OpenRouter AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.OpenRouter, jwtPayload);\n\n // 假设 LobeOpenRouterAI 是 OpenRouter 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenRouterAI);\n });\n\n it('DeepSeek AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.DeepSeek, jwtPayload);\n\n // 假设 LobeDeepSeekAI 是 DeepSeek 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeDeepSeekAI);\n });\n\n it('Stepfun AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Stepfun, jwtPayload);\n\n // 假设 LobeDeepSeekAI 是 DeepSeek 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeStepfunAI);\n });\n\n it('Together AI provider: without apikey', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.TogetherAI, jwtPayload);\n\n // 假设 LobeTogetherAI 是 TogetherAI 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeTogetherAI);\n });\n\n it('OpenAI provider: without apikey with OPENAI_PROXY_URL', async () => {\n process.env.OPENAI_PROXY_URL = 'https://proxy.example.com/v1';\n\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.OpenAI, jwtPayload);\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);\n // 应返回 OPENAI_PROXY_URL\n expect(runtime['_runtime'].baseURL).toBe('https://proxy.example.com/v1');\n });\n\n it('Qwen AI provider: without apiKey and endpoint with OPENAI_PROXY_URL', async () => {\n process.env.OPENAI_PROXY_URL = 'https://proxy.example.com/v1';\n\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.Qwen, jwtPayload);\n\n // 假设 LobeQwenAI 是 Qwen 提供者的实现类\n expect(runtime['_runtime']).toBeInstanceOf(LobeQwenAI);\n // endpoint 不存在,应返回 DEFAULT_BASE_URL\n expect(runtime['_runtime'].baseURL).toBe('https://dashscope.aliyuncs.com/compatible-mode/v1');\n });\n\n it('ComfyUI provider: without user payload (using environment variables)', async () => {\n const jwtPayload: ClientSecretPayload = {};\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.ComfyUI, jwtPayload);\n\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeComfyUI);\n // Should use environment variable defaults\n expect(runtime['_runtime'].baseURL).toBe('http://127.0.0.1:8000');\n });\n\n it('ComfyUI provider: partial payload (mixed with env vars)', async () => {\n const jwtPayload: ClientSecretPayload = {\n baseURL: 'http://custom-comfyui:8188',\n // authType, username, password will come from env vars\n };\n const runtime = await initModelRuntimeWithUserPayload(ModelProvider.ComfyUI, jwtPayload);\n\n expect(runtime).toBeInstanceOf(ModelRuntime);\n expect(runtime['_runtime']).toBeInstanceOf(LobeComfyUI);\n expect(runtime['_runtime'].baseURL).toBe('http://custom-comfyui:8188');\n });\n\n it('Unknown Provider', async () => {\n const jwtPayload = {};\n const runtime = await initModelRuntimeWithUserPayload('unknown', jwtPayload);\n\n // 根据实际实现,你可能需要检查是否返回了默认的 runtime 实例,或者是否抛出了异常\n // 例如,如果默认使用 OpenAI:\n expect(runtime['_runtime']).toBeInstanceOf(LobeOpenAI);\n });\n });\n});\n\n/**\n * Test cases for buildPayloadFromKeyVaults function\n * This function builds ClientSecretPayload based on runtimeProvider (sdkType)\n * to ensure provider-specific fields are correctly forwarded\n */\ndescribe('buildPayloadFromKeyVaults', () => {\n describe('should build payload with correct fields based on runtimeProvider', () => {\n it('OpenAI compatible: returns apiKey, baseURL and runtimeProvider', () => {\n const keyVaults = {\n apiKey: 'test-api-key',\n baseURL: 'https://custom-endpoint.com/v1',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.OpenAI);\n\n expect(payload).toEqual({\n apiKey: 'test-api-key',\n baseURL: 'https://custom-endpoint.com/v1',\n runtimeProvider: ModelProvider.OpenAI,\n });\n });\n\n it('Azure: returns apiKey, baseURL, azureApiVersion and runtimeProvider', () => {\n const keyVaults = {\n apiKey: 'azure-api-key',\n baseURL: 'https://my-azure.openai.azure.com',\n apiVersion: '2024-06-01',\n endpoint: 'https://fallback-endpoint.com',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Azure);\n\n expect(payload).toEqual({\n apiKey: 'azure-api-key',\n azureApiVersion: '2024-06-01',\n baseURL: 'https://my-azure.openai.azure.com',\n runtimeProvider: ModelProvider.Azure,\n });\n });\n\n it('Azure: uses endpoint as fallback when baseURL is not provided', () => {\n const keyVaults = {\n apiKey: 'azure-api-key',\n endpoint: 'https://fallback-endpoint.com',\n apiVersion: '2024-06-01',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Azure);\n\n expect(payload.baseURL).toBe('https://fallback-endpoint.com');\n });\n\n it('Cloudflare: returns apiKey, cloudflareBaseURLOrAccountID and runtimeProvider', () => {\n const keyVaults = {\n apiKey: 'cloudflare-api-key',\n baseURLOrAccountID: 'my-account-id',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Cloudflare);\n\n expect(payload).toEqual({\n apiKey: 'cloudflare-api-key',\n cloudflareBaseURLOrAccountID: 'my-account-id',\n runtimeProvider: ModelProvider.Cloudflare,\n });\n });\n\n it('Bedrock: returns AWS credentials and runtimeProvider', () => {\n const keyVaults = {\n accessKeyId: 'aws-access-key',\n secretAccessKey: 'aws-secret-key',\n region: 'us-east-1',\n sessionToken: 'session-token',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Bedrock);\n\n expect(payload).toEqual({\n apiKey: 'aws-secret-keyaws-access-key',\n awsAccessKeyId: 'aws-access-key',\n awsRegion: 'us-east-1',\n awsSecretAccessKey: 'aws-secret-key',\n awsSessionToken: 'session-token',\n runtimeProvider: ModelProvider.Bedrock,\n });\n });\n\n it('Ollama: returns baseURL and runtimeProvider', () => {\n const keyVaults = {\n baseURL: 'http://localhost:11434',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Ollama);\n\n expect(payload).toEqual({\n baseURL: 'http://localhost:11434',\n runtimeProvider: ModelProvider.Ollama,\n });\n });\n\n it('VertexAI: returns apiKey, baseURL, vertexAIRegion and runtimeProvider', () => {\n const keyVaults = {\n apiKey: 'vertex-credentials-json',\n baseURL: 'https://vertex-endpoint.com',\n region: 'us-central1',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.VertexAI);\n\n expect(payload).toEqual({\n apiKey: 'vertex-credentials-json',\n baseURL: 'https://vertex-endpoint.com',\n runtimeProvider: ModelProvider.VertexAI,\n vertexAIRegion: 'us-central1',\n });\n });\n\n it('ComfyUI: returns all auth fields and runtimeProvider', () => {\n const keyVaults = {\n apiKey: 'comfyui-api-key',\n authType: 'bearer',\n baseURL: 'http://localhost:8188',\n customHeaders: { 'X-Custom': 'header' },\n password: 'pass',\n username: 'user',\n } as const;\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.ComfyUI);\n\n expect(payload).toEqual({\n apiKey: 'comfyui-api-key',\n authType: 'bearer',\n baseURL: 'http://localhost:8188',\n customHeaders: { 'X-Custom': 'header' },\n password: 'pass',\n runtimeProvider: ModelProvider.ComfyUI,\n username: 'user',\n });\n });\n\n it('Unknown provider: falls back to default with apiKey, baseURL and runtimeProvider', () => {\n const keyVaults = {\n apiKey: 'unknown-api-key',\n baseURL: 'https://unknown-endpoint.com',\n };\n const payload = buildPayloadFromKeyVaults(keyVaults, 'unknown-provider');\n\n expect(payload).toEqual({\n apiKey: 'unknown-api-key',\n baseURL: 'https://unknown-endpoint.com',\n runtimeProvider: 'unknown-provider',\n });\n });\n });\n\n describe('custom provider with sdkType should include provider-specific fields', () => {\n it('custom provider with Azure sdkType includes azureApiVersion', () => {\n const keyVaults = {\n apiKey: 'custom-azure-key',\n baseURL: 'https://custom-azure.openai.azure.com',\n apiVersion: '2024-06-01',\n };\n // Simulates a custom provider where runtimeProvider is resolved to 'azure'\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Azure);\n\n expect(payload.azureApiVersion).toBe('2024-06-01');\n expect(payload.runtimeProvider).toBe(ModelProvider.Azure);\n });\n\n it('custom provider with Cloudflare sdkType includes cloudflareBaseURLOrAccountID', () => {\n const keyVaults = {\n apiKey: 'custom-cloudflare-key',\n baseURLOrAccountID: 'custom-account-id',\n };\n // Simulates a custom provider where runtimeProvider is resolved to 'cloudflare'\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Cloudflare);\n\n expect(payload.cloudflareBaseURLOrAccountID).toBe('custom-account-id');\n expect(payload.runtimeProvider).toBe(ModelProvider.Cloudflare);\n });\n\n it('custom provider with Bedrock sdkType includes AWS credentials', () => {\n const keyVaults = {\n accessKeyId: 'custom-aws-id',\n secretAccessKey: 'custom-aws-secret',\n region: 'eu-west-1',\n };\n // Simulates a custom provider where runtimeProvider is resolved to 'bedrock'\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Bedrock);\n\n expect(payload.awsAccessKeyId).toBe('custom-aws-id');\n expect(payload.awsSecretAccessKey).toBe('custom-aws-secret');\n expect(payload.awsRegion).toBe('eu-west-1');\n expect(payload.runtimeProvider).toBe(ModelProvider.Bedrock);\n });\n\n it('custom provider with Ollama sdkType includes baseURL', () => {\n const keyVaults = {\n baseURL: 'http://custom-ollama:11434',\n };\n // Simulates a custom provider where runtimeProvider is resolved to 'ollama'\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.Ollama);\n\n expect(payload.baseURL).toBe('http://custom-ollama:11434');\n expect(payload.runtimeProvider).toBe(ModelProvider.Ollama);\n });\n\n it('custom provider with VertexAI sdkType includes vertexAIRegion', () => {\n const keyVaults = {\n apiKey: 'custom-vertex-creds',\n region: 'asia-northeast1',\n };\n // Simulates a custom provider where runtimeProvider is resolved to 'vertexai'\n const payload = buildPayloadFromKeyVaults(keyVaults, ModelProvider.VertexAI);\n\n expect(payload.vertexAIRegion).toBe('asia-northeast1');\n expect(payload.runtimeProvider).toBe(ModelProvider.VertexAI);\n });\n });\n});\n"}}}}