Files

1 line
23 KiB
JSON

{"repo": "btccusdt/lobe-chat", "files": {"Dockerfile": {"e92fcc68aa43091f902ee7a2a33718b0c32c3547": {"t": 1785747004.0523582, "n": 6247, "h": "5ca1060ddd989df3", "c": "## Base image for all the stages\nFROM node:20-alpine AS base\n\nARG USE_CN_MIRROR\n\nRUN \\\n # If you want to build docker in China, build with --build-arg USE_CN_MIRROR=true\n if [ \"${USE_CN_MIRROR:-false}\" = \"true\" ]; then \\\n sed -i \"s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g\" \"/etc/apk/repositories\"; \\\n fi \\\n # Add required package & update base package\n && apk update \\\n && apk add --no-cache bind-tools proxychains-ng sudo \\\n && apk upgrade --no-cache \\\n # Add user nextjs to run the app\n && addgroup --system --gid 1001 nodejs \\\n && adduser --system --uid 1001 nextjs \\\n && chown -R nextjs:nodejs \"/etc/proxychains\" \\\n && echo \"nextjs ALL=(ALL) NOPASSWD: /bin/chmod * /etc/resolv.conf\" >> /etc/sudoers \\\n && rm -rf /tmp/* /var/cache/apk/*\n\n## Builder image, install all the dependencies and build the app\nFROM base AS builder\n\nARG USE_CN_MIRROR\n\nENV NEXT_PUBLIC_BASE_PATH=\"\"\n\n# Sentry\nENV NEXT_PUBLIC_SENTRY_DSN=\"\" \\\n SENTRY_ORG=\"\" \\\n SENTRY_PROJECT=\"\"\n\n# Posthog\nENV NEXT_PUBLIC_ANALYTICS_POSTHOG=\"\" \\\n NEXT_PUBLIC_POSTHOG_HOST=\"\" \\\n NEXT_PUBLIC_POSTHOG_KEY=\"\"\n\n# Umami\nENV NEXT_PUBLIC_ANALYTICS_UMAMI=\"\" \\\n NEXT_PUBLIC_UMAMI_SCRIPT_URL=\"\" \\\n NEXT_PUBLIC_UMAMI_WEBSITE_ID=\"\"\n\n# Node\nENV NODE_OPTIONS=\"--max-old-space-size=8192\"\n\nWORKDIR /app\n\nCOPY package.json ./\nCOPY .npmrc ./\n\nRUN \\\n # If you want to build docker in China, build with --build-arg USE_CN_MIRROR=true\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 fi \\\n # Set the registry for corepack\n && export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\\/$//') \\\n # Enable corepack\n && corepack enable \\\n # Use pnpm for corepack\n && corepack use pnpm \\\n # Install the dependencies\n && pnpm i \\\n # Add sharp dependencies\n && mkdir -p /deps \\\n && pnpm add sharp --prefix /deps\n\nCOPY . .\n\n# run build standalone for docker version\nRUN npm run build:docker\n\n## Application image, copy all the files for production\nFROM scratch AS app\n\nCOPY --from=builder /app/public /app/public\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/\nCOPY --from=builder /app/.next/static /app/.next/static\nCOPY --from=builder /deps/node_modules/.pnpm /app/node_modules/.pnpm\n\n## Production image, copy all the files and run next\nFROM base\n\n# Copy all the files from app, set the correct permission for prerender cache\nCOPY --from=app --chown=nextjs:nodejs /app /app\n\nENV NODE_ENV=\"production\"\n\n# set hostname to localhost\nENV HOSTNAME=\"0.0.0.0\" \\\n PORT=\"3210\"\n\n# General Variables\nENV ACCESS_CODE=\"\" \\\n API_KEY_SELECT_MODE=\"\" \\\n DEFAULT_AGENT_CONFIG=\"\" \\\n SYSTEM_AGENT=\"\" \\\n FEATURE_FLAGS=\"\" \\\n PROXY_URL=\"\"\n\n# Model Variables\nENV \\\n # Ai360\n AI360_API_KEY=\"\" \\\n # Anthropic\n ANTHROPIC_API_KEY=\"\" ANTHROPIC_PROXY_URL=\"\" \\\n # Amazon Bedrock\n 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=\"\" \\\n # DeepSeek\n DEEPSEEK_API_KEY=\"\" \\\n # Fireworks AI\n FIREWORKSAI_API_KEY=\"\" FIREWORKSAI_MODEL_LIST=\"\" \\\n # Google\n GOOGLE_API_KEY=\"\" GOOGLE_PROXY_URL=\"\" \\\n # Groq\n GROQ_API_KEY=\"\" GROQ_MODEL_LIST=\"\" GROQ_PROXY_URL=\"\" \\\n # Minimax\n MINIMAX_API_KEY=\"\" \\\n # Mistral\n MISTRAL_API_KEY=\"\" \\\n # Moonshot\n MOONSHOT_API_KEY=\"\" MOONSHOT_PROXY_URL=\"\" \\\n # Novita\n NOVITA_API_KEY=\"\" NOVITA_MODEL_LIST=\"\" \\\n # Ollama\n OLLAMA_MODEL_LIST=\"\" OLLAMA_PROXY_URL=\"\" \\\n # OpenAI\n 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_PROXY_URL=\"\" \\\n # Qwen\n QWEN_API_KEY=\"\" QWEN_MODEL_LIST=\"\" \\\n # SiliconCloud\n SILICONCLOUD_API_KEY=\"\" SILICONCLOUD_MODEL_LIST=\"\" SILICONCLOUD_PROXY_URL=\"\" \\\n # Spark\n SPARK_API_KEY=\"\" \\\n # Stepfun\n STEPFUN_API_KEY=\"\" \\\n # Taichu\n TAICHU_API_KEY=\"\" \\\n # TogetherAI\n TOGETHERAI_API_KEY=\"\" TOGETHERAI_MODEL_LIST=\"\" \\\n # Upstage\n UPSTAGE_API_KEY=\"\" \\\n # 01.AI\n ZEROONE_API_KEY=\"\" ZEROONE_MODEL_LIST=\"\" \\\n # Zhipu\n ZHIPU_API_KEY=\"\" ZHIPU_MODEL_LIST=\"\"\n\nUSER nextjs\n\nEXPOSE 3210/tcp\n\nCMD \\\n if [ -n \"$PROXY_URL\" ]; then \\\n # Set regex for IPv4\n IP_REGEX=\"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$\"; \\\n # Set proxychains command\n PROXYCHAINS=\"proxychains -q\"; \\\n # Parse the proxy URL\n host_with_port=\"${PROXY_URL#*//}\"; \\\n host=\"${host_with_port%%:*}\"; \\\n port=\"${PROXY_URL##*:}\"; \\\n protocol=\"${PROXY_URL%%://*}\"; \\\n # Resolve to IP address if the host is a domain\n if ! [[ \"$host\" =~ \"$IP_REGEX\" ]]; then \\\n nslookup=$(nslookup -q=\"A\" \"$host\" | tail -n +3 | grep 'Address:'); \\\n if [ -n \"$nslookup\" ]; then \\\n host=$(echo \"$nslookup\" | tail -n 1 | awk '{print $2}'); \\\n fi; \\\n fi; \\\n # Generate proxychains configuration file\n printf \"%s\\n\" \\\n 'localnet 127.0.0.0/255.0.0.0' \\\n 'localnet ::1/128' \\\n 'proxy_dns' \\\n 'remote_dns_subnet 224' \\\n 'strict_chain' \\\n 'tcp_connect_time_out 8000' \\\n 'tcp_read_time_out 15000' \\\n '[ProxyList]' \\\n \"$protocol $host $port\" \\\n > \"/etc/proxychains/proxychains.conf\"; \\\n fi; \\\n # Fix DNS resolving issue in Docker Compose, ref https://github.com/lobehub/lobe-chat/pull/3837\n if [ -f \"/etc/resolv.conf\" ]; then \\\n sudo chmod 666 \"/etc/resolv.conf\"; \\\n resolv_conf=$(grep '^nameserver' \"/etc/resolv.conf\" | awk '{print \"nameserver \" $2}'); \\\n printf \"%s\\n\" \\\n \"$resolv_conf\" \\\n > \"/etc/resolv.conf\"; \\\n fi; \\\n # Run the server\n ${PROXYCHAINS} node \"/app/server.js\";\n"}}, "Dockerfile.database": {"e92fcc68aa43091f902ee7a2a33718b0c32c3547": {"t": 1785747010.4177814, "n": 7313, "h": "e246e462de562851", "c": "## Base image for all the stages\nFROM node:20-alpine AS base\n\nARG USE_CN_MIRROR\n\nRUN \\\n # If you want to build docker in China, build with --build-arg USE_CN_MIRROR=true\n if [ \"${USE_CN_MIRROR:-false}\" = \"true\" ]; then \\\n sed -i \"s/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g\" \"/etc/apk/repositories\"; \\\n fi \\\n # Add required package & update base package\n && apk update \\\n && apk add --no-cache bind-tools proxychains-ng sudo \\\n && apk upgrade --no-cache \\\n # Add user nextjs to run the app\n && addgroup --system --gid 1001 nodejs \\\n && adduser --system --uid 1001 nextjs \\\n && chown -R nextjs:nodejs \"/etc/proxychains\" \\\n && echo \"nextjs ALL=(ALL) NOPASSWD: /bin/chmod * /etc/resolv.conf\" >> /etc/sudoers \\\n && rm -rf /tmp/* /var/cache/apk/*\n\n## Builder image, install all the dependencies and build the app\nFROM base AS builder\n\nARG USE_CN_MIRROR\n\nENV NEXT_PUBLIC_SERVICE_MODE=\"server\" \\\n 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\n# Sentry\nENV NEXT_PUBLIC_SENTRY_DSN=\"\" \\\n SENTRY_ORG=\"\" \\\n SENTRY_PROJECT=\"\"\n\n# Posthog\nENV NEXT_PUBLIC_ANALYTICS_POSTHOG=\"\" \\\n NEXT_PUBLIC_POSTHOG_HOST=\"\" \\\n NEXT_PUBLIC_POSTHOG_KEY=\"\"\n\n# Umami\nENV NEXT_PUBLIC_ANALYTICS_UMAMI=\"\" \\\n NEXT_PUBLIC_UMAMI_SCRIPT_URL=\"\" \\\n NEXT_PUBLIC_UMAMI_WEBSITE_ID=\"\"\n\n# Node\nENV NODE_OPTIONS=\"--max-old-space-size=8192\"\n\nWORKDIR /app\n\nCOPY package.json ./\nCOPY .npmrc ./\n\nRUN \\\n # If you want to build docker in China, build with --build-arg USE_CN_MIRROR=true\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 fi \\\n # Set the registry for corepack\n && export COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\\/$//') \\\n # Enable corepack\n && corepack enable \\\n # Use pnpm for corepack\n && corepack use pnpm \\\n # Install the dependencies\n && pnpm i \\\n # Add sharp and db migration dependencies\n && mkdir -p /deps \\\n && pnpm add sharp pg drizzle-orm --prefix /deps\n\nCOPY . .\n\n# run build standalone for docker version\nRUN npm run build:docker\n\n## Application image, copy all the files for production\nFROM scratch AS app\n\nCOPY --from=builder /app/public /app/public\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/\nCOPY --from=builder /app/.next/static /app/.next/static\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 database migrations\nCOPY --from=builder /app/src/database/server/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## Production image, copy all the files and run next\nFROM base\n\n# Copy all the files from app, set the correct permission for prerender cache\nCOPY --from=app --chown=nextjs:nodejs /app /app\n\nENV NODE_ENV=\"production\"\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\n# Database\nENV KEY_VAULTS_SECRET=\"\" \\\n DATABASE_DRIVER=\"node\" \\\n DATABASE_URL=\"\"\n\n# Next Auth\nENV NEXT_AUTH_SECRET=\"\" \\\n NEXT_AUTH_SSO_PROVIDERS=\"\" \\\n NEXTAUTH_URL=\"\"\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\n# Model Variables\nENV \\\n # Ai360\n AI360_API_KEY=\"\" \\\n # Anthropic\n ANTHROPIC_API_KEY=\"\" ANTHROPIC_PROXY_URL=\"\" \\\n # Amazon Bedrock\n 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=\"\" \\\n # DeepSeek\n DEEPSEEK_API_KEY=\"\" \\\n # Fireworks AI\n FIREWORKSAI_API_KEY=\"\" FIREWORKSAI_MODEL_LIST=\"\" \\\n # Google\n GOOGLE_API_KEY=\"\" GOOGLE_PROXY_URL=\"\" \\\n # Groq\n GROQ_API_KEY=\"\" GROQ_MODEL_LIST=\"\" GROQ_PROXY_URL=\"\" \\\n # Minimax\n MINIMAX_API_KEY=\"\" \\\n # Mistral\n MISTRAL_API_KEY=\"\" \\\n # Moonshot\n MOONSHOT_API_KEY=\"\" MOONSHOT_PROXY_URL=\"\" \\\n # Novita\n NOVITA_API_KEY=\"\" NOVITA_MODEL_LIST=\"\" \\\n # Ollama\n OLLAMA_MODEL_LIST=\"\" OLLAMA_PROXY_URL=\"\" \\\n # OpenAI\n 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_PROXY_URL=\"\" \\\n # Qwen\n QWEN_API_KEY=\"\" QWEN_MODEL_LIST=\"\" \\\n # SiliconCloud\n SILICONCLOUD_API_KEY=\"\" SILICONCLOUD_MODEL_LIST=\"\" SILICONCLOUD_PROXY_URL=\"\" \\\n # Spark\n SPARK_API_KEY=\"\" \\\n # Stepfun\n STEPFUN_API_KEY=\"\" \\\n # Taichu\n TAICHU_API_KEY=\"\" \\\n # TogetherAI\n TOGETHERAI_API_KEY=\"\" TOGETHERAI_MODEL_LIST=\"\" \\\n # Upstage\n UPSTAGE_API_KEY=\"\" \\\n # 01.AI\n ZEROONE_API_KEY=\"\" ZEROONE_MODEL_LIST=\"\" \\\n # Zhipu\n ZHIPU_API_KEY=\"\"\n\nUSER nextjs\n\nEXPOSE 3210/tcp\n\nCMD \\\n if [ -n \"$PROXY_URL\" ]; then \\\n # Set regex for IPv4\n IP_REGEX=\"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$\"; \\\n # Set proxychains command\n PROXYCHAINS=\"proxychains -q\"; \\\n # Parse the proxy URL\n host_with_port=\"${PROXY_URL#*//}\"; \\\n host=\"${host_with_port%%:*}\"; \\\n port=\"${PROXY_URL##*:}\"; \\\n protocol=\"${PROXY_URL%%://*}\"; \\\n # Resolve to IP address if the host is a domain\n if ! [[ \"$host\" =~ \"$IP_REGEX\" ]]; then \\\n nslookup=$(nslookup -q=\"A\" \"$host\" | tail -n +3 | grep 'Address:'); \\\n if [ -n \"$nslookup\" ]; then \\\n host=$(echo \"$nslookup\" | tail -n 1 | awk '{print $2}'); \\\n fi; \\\n fi; \\\n # Generate proxychains configuration file\n printf \"%s\\n\" \\\n 'localnet 127.0.0.0/255.0.0.0' \\\n 'localnet ::1/128' \\\n 'proxy_dns' \\\n 'remote_dns_subnet 224' \\\n 'strict_chain' \\\n 'tcp_connect_time_out 8000' \\\n 'tcp_read_time_out 15000' \\\n '[ProxyList]' \\\n \"$protocol $host $port\" \\\n > \"/etc/proxychains/proxychains.conf\"; \\\n fi; \\\n # Fix DNS resolving issue in Docker Compose, ref https://github.com/lobehub/lobe-chat/pull/3837\n if [ -f \"/etc/resolv.conf\" ]; then \\\n sudo chmod 666 \"/etc/resolv.conf\"; \\\n resolv_conf=$(grep '^nameserver' \"/etc/resolv.conf\" | awk '{print \"nameserver \" $2}'); \\\n printf \"%s\\n\" \\\n \"$resolv_conf\" \\\n > \"/etc/resolv.conf\"; \\\n fi; \\\n # Run migration\n node \"/app/docker.cjs\"; \\\n if [ \"$?\" -eq \"0\" ]; then \\\n # Run the server\n ${PROXYCHAINS} node \"/app/server.js\"; \\\n fi;\n"}}, "src/libs/agent-runtime/ai360/index.test.ts": {"e92fcc68aa43091f902ee7a2a33718b0c32c3547": {"t": 1785809837.6702602, "n": 7744, "h": "0a90fc2c380d8604", "c": "// @vitest-environment node\nimport OpenAI from 'openai';\nimport { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';\n\nimport {\n ChatStreamCallbacks,\n LobeOpenAICompatibleRuntime,\n ModelProvider,\n} from '@/libs/agent-runtime';\n\nimport * as debugStreamModule from '../utils/debugStream';\nimport { LobeAi360AI } from './index';\n\nconst provider = ModelProvider.Ai360;\nconst defaultBaseURL = 'https://ai.360.cn/v1';\n\nconst bizErrorType = 'ProviderBizError';\nconst invalidErrorType = 'InvalidProviderAPIKey';\n\n// Mock the console.error to avoid polluting test output\nvi.spyOn(console, 'error').mockImplementation(() => {});\n\nlet instance: LobeOpenAICompatibleRuntime;\n\nbeforeEach(() => {\n instance = new LobeAi360AI({ apiKey: 'test' });\n\n // 使用 vi.spyOn 来模拟 chat.completions.create 方法\n vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(\n new ReadableStream() as any,\n );\n});\n\nafterEach(() => {\n vi.clearAllMocks();\n});\n\ndescribe('LobeAi360AI', () => {\n describe('init', () => {\n it('should correctly initialize with an API key', async () => {\n const instance = new LobeAi360AI({ apiKey: 'test_api_key' });\n expect(instance).toBeInstanceOf(LobeAi360AI);\n expect(instance.baseURL).toEqual(defaultBaseURL);\n });\n });\n\n describe('chat', () => {\n describe('Error', () => {\n it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {\n // Arrange\n const apiError = new OpenAI.APIError(\n 400,\n {\n status: 400,\n error: {\n message: 'Bad Request',\n },\n },\n 'Error message',\n {},\n );\n\n vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);\n\n // Act\n try {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-turbo',\n temperature: 0,\n });\n } catch (e) {\n expect(e).toEqual({\n endpoint: defaultBaseURL,\n error: {\n error: { message: 'Bad Request' },\n status: 400,\n },\n errorType: bizErrorType,\n provider,\n });\n }\n });\n\n it('should throw AgentRuntimeError with NoOpenAIAPIKey if no apiKey is provided', async () => {\n try {\n new LobeAi360AI({});\n } catch (e) {\n expect(e).toEqual({ errorType: invalidErrorType });\n }\n });\n\n it('should return OpenAIBizError with the cause when OpenAI.APIError is thrown with cause', async () => {\n // Arrange\n const errorInfo = {\n stack: 'abc',\n cause: {\n message: 'api is undefined',\n },\n };\n const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});\n\n vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);\n\n // Act\n try {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-turbo',\n temperature: 0,\n });\n } catch (e) {\n expect(e).toEqual({\n endpoint: defaultBaseURL,\n error: {\n cause: { message: 'api is undefined' },\n stack: 'abc',\n },\n errorType: bizErrorType,\n provider,\n });\n }\n });\n\n it('should return OpenAIBizError with an cause response with desensitize Url', async () => {\n // Arrange\n const errorInfo = {\n stack: 'abc',\n cause: { message: 'api is undefined' },\n };\n const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});\n\n instance = new LobeAi360AI({\n apiKey: 'test',\n\n baseURL: 'https://api.abc.com/v1',\n });\n\n vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);\n\n // Act\n try {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-turbo',\n temperature: 0,\n });\n } catch (e) {\n expect(e).toEqual({\n endpoint: 'https://api.***.com/v1',\n error: {\n cause: { message: 'api is undefined' },\n stack: 'abc',\n },\n errorType: bizErrorType,\n provider,\n });\n }\n });\n\n it('should throw an InvalidAi360APIKey error type on 401 status code', async () => {\n // Mock the API call to simulate a 401 error\n const error = new Error('Unauthorized') as any;\n error.status = 401;\n vi.mocked(instance['client'].chat.completions.create).mockRejectedValue(error);\n\n try {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-turbo',\n temperature: 0,\n });\n } catch (e) {\n // Expect the chat method to throw an error with InvalidAi360APIKey\n expect(e).toEqual({\n endpoint: defaultBaseURL,\n error: new Error('Unauthorized'),\n errorType: invalidErrorType,\n provider,\n });\n }\n });\n\n it('should return AgentRuntimeError for non-OpenAI errors', async () => {\n // Arrange\n const genericError = new Error('Generic Error');\n\n vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(genericError);\n\n // Act\n try {\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-turbo',\n temperature: 0,\n });\n } catch (e) {\n expect(e).toEqual({\n endpoint: defaultBaseURL,\n errorType: 'AgentRuntimeError',\n provider,\n error: {\n name: genericError.name,\n cause: genericError.cause,\n message: genericError.message,\n stack: genericError.stack,\n },\n });\n }\n });\n });\n\n describe('DEBUG', () => {\n it('should call debugStream and return StreamingTextResponse when DEBUG_AI360_CHAT_COMPLETION is 1', async () => {\n // Arrange\n const mockProdStream = new ReadableStream() as any; // 模拟的 prod 流\n const mockDebugStream = new ReadableStream({\n start(controller) {\n controller.enqueue('Debug stream content');\n controller.close();\n },\n }) as any;\n mockDebugStream.toReadableStream = () => mockDebugStream; // 添加 toReadableStream 方法\n\n // 模拟 chat.completions.create 返回值,包括模拟的 tee 方法\n (instance['client'].chat.completions.create as Mock).mockResolvedValue({\n tee: () => [mockProdStream, { toReadableStream: () => mockDebugStream }],\n });\n\n // 保存原始环境变量值\n const originalDebugValue = process.env.DEBUG_AI360_CHAT_COMPLETION;\n\n // 模拟环境变量\n process.env.DEBUG_AI360_CHAT_COMPLETION = '1';\n vi.spyOn(debugStreamModule, 'debugStream').mockImplementation(() => Promise.resolve());\n\n // 执行测试\n // 运行你的测试函数,确保它会在条件满足时调用 debugStream\n // 假设的测试函数调用,你可能需要根据实际情况调整\n await instance.chat({\n messages: [{ content: 'Hello', role: 'user' }],\n model: '360gpt-turbo',\n stream: true,\n temperature: 0,\n });\n\n // 验证 debugStream 被调用\n expect(debugStreamModule.debugStream).toHaveBeenCalled();\n\n // 恢复原始环境变量值\n process.env.DEBUG_AI360_CHAT_COMPLETION = originalDebugValue;\n });\n });\n });\n});\n"}}}}