diff --git a/.env.example b/.env.example index ed061ff..966983a 100644 --- a/.env.example +++ b/.env.example @@ -19,8 +19,9 @@ MAX_UPLOAD_SIZE_MB=5 # DEFAULT_NUM_CTX=6144 # Consumes 24GB of VRAM DEFAULT_NUM_CTX= -# LLM Configuration Options -# Enabled model providers, currently supporting Anthropic, Cohere, DeepSeek, Google, Groq, HuggingFace, Hyperbolic, Mistral, Ollama, OpenAI, OpenRouter, Perplexity, xAI, Together, LMStudio, AmazonBedrock, Github +# Enabled model providers, currently supporting Anthropic, Cohere, Deepseek, DouBao, Ernie, Google, Groq, +# HuggingFace, Hyperbolic, Kimi, Mistral, Ollama, OpenAI, OpenRouter, OpenAILike, Perplexity, Qwen, xAI, +# ZhiPu, Together, LMStudio, AmazonBedrock, Github LLM_PROVIDER= # BASE URL of the current model provider, some providers require this to be set, such as OpenAI, Ollama, LMStudio diff --git a/app/lib/.server/logger.server.ts b/app/lib/.server/logger.server.ts index 5dd6be9..7e150b4 100644 --- a/app/lib/.server/logger.server.ts +++ b/app/lib/.server/logger.server.ts @@ -22,7 +22,7 @@ let currentLevel: DebugLevel = (process.env.LOG_LEVEL as DebugLevel | undefined) || (import.meta.env.DEV ? 'debug' : 'info'); // 文件日志配置 -const enableFileLogging = process.env.USAGE_LOG_FILE === 'true' || import.meta.env.DEV; +const enableFileLogging = process.env.USAGE_LOG_FILE !== 'false'; const logDir = path.join(process.cwd(), 'logs'); // 确保日志目录存在 diff --git a/app/lib/.server/logger.ts b/app/lib/.server/logger.ts index 5dd6be9..7e150b4 100644 --- a/app/lib/.server/logger.ts +++ b/app/lib/.server/logger.ts @@ -22,7 +22,7 @@ let currentLevel: DebugLevel = (process.env.LOG_LEVEL as DebugLevel | undefined) || (import.meta.env.DEV ? 'debug' : 'info'); // 文件日志配置 -const enableFileLogging = process.env.USAGE_LOG_FILE === 'true' || import.meta.env.DEV; +const enableFileLogging = process.env.USAGE_LOG_FILE !== 'false'; const logDir = path.join(process.cwd(), 'logs'); // 确保日志目录存在 diff --git a/app/lib/modules/llm/providers/doubao.ts b/app/lib/modules/llm/providers/doubao.ts new file mode 100644 index 0000000..afcd64c --- /dev/null +++ b/app/lib/modules/llm/providers/doubao.ts @@ -0,0 +1,57 @@ +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; +import type { LanguageModel } from 'ai'; +import { BaseProvider } from '~/lib/modules/llm/base-provider'; +import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { IProviderSetting } from '~/types/model'; + +export default class DouBaoProvider extends BaseProvider { + name = 'DouBao'; + getApiKeyLink = undefined; + + staticModels: ModelInfo[] = []; + + async getDynamicModels(settings?: IProviderSetting): Promise { + const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings); + const baseUrl = fetchBaseUrl || 'https://ark.cn-beijing.volces.com/api/v3'; + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const response = await fetch(`${baseUrl}/models`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + const res = (await response.json()) as any; + + const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat); + + return data.map((m: any) => ({ + name: m.id, + label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`, + provider: this.name, + maxTokenAllowed: m.context_length || 8000, + })); + } + + getModelInstance(options: { model: string; providerSettings?: Record }): LanguageModel { + const { model, providerSettings } = options; + + const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]); + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const provider = createOpenAICompatible({ + name: this.name, + baseURL: 'https://ark.cn-beijing.volces.com/api/v3', + apiKey, + includeUsage: true, + }); + + return provider(model); + } +} diff --git a/app/lib/modules/llm/providers/ernie.ts b/app/lib/modules/llm/providers/ernie.ts new file mode 100644 index 0000000..d111f02 --- /dev/null +++ b/app/lib/modules/llm/providers/ernie.ts @@ -0,0 +1,57 @@ +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; +import type { LanguageModel } from 'ai'; +import { BaseProvider } from '~/lib/modules/llm/base-provider'; +import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { IProviderSetting } from '~/types/model'; + +export default class ErnieProvider extends BaseProvider { + name = 'Ernie'; + getApiKeyLink = undefined; + + staticModels: ModelInfo[] = []; + + async getDynamicModels(settings?: IProviderSetting): Promise { + const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings); + const baseUrl = fetchBaseUrl || 'https://qianfan.baidubce.com/v2'; + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const response = await fetch(`${baseUrl}/models`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + const res = (await response.json()) as any; + + const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat); + + return data.map((m: any) => ({ + name: m.id, + label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`, + provider: this.name, + maxTokenAllowed: m.context_length || 8000, + })); + } + + getModelInstance(options: { model: string; providerSettings?: Record }): LanguageModel { + const { model, providerSettings } = options; + + const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]); + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const provider = createOpenAICompatible({ + name: this.name, + baseURL: 'https://qianfan.baidubce.com/v2', + apiKey, + includeUsage: true, + }); + + return provider(model); + } +} diff --git a/app/lib/modules/llm/providers/kimi.ts b/app/lib/modules/llm/providers/kimi.ts new file mode 100644 index 0000000..03c1f45 --- /dev/null +++ b/app/lib/modules/llm/providers/kimi.ts @@ -0,0 +1,57 @@ +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; +import type { LanguageModel } from 'ai'; +import { BaseProvider } from '~/lib/modules/llm/base-provider'; +import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { IProviderSetting } from '~/types/model'; + +export default class KimiProvider extends BaseProvider { + name = 'Kimi'; + getApiKeyLink = undefined; + + staticModels: ModelInfo[] = []; + + async getDynamicModels(settings?: IProviderSetting): Promise { + const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings); + const baseUrl = fetchBaseUrl || 'https://api.moonshot.cn/v1'; + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const response = await fetch(`${baseUrl}/models`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + const res = (await response.json()) as any; + + const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat); + + return data.map((m: any) => ({ + name: m.id, + label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`, + provider: this.name, + maxTokenAllowed: m.context_length || 8000, + })); + } + + getModelInstance(options: { model: string; providerSettings?: Record }): LanguageModel { + const { model, providerSettings } = options; + + const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]); + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const provider = createOpenAICompatible({ + name: this.name, + baseURL: 'https://api.moonshot.cn/v1', + apiKey, + includeUsage: true, + }); + + return provider(model); + } +} diff --git a/app/lib/modules/llm/providers/openai.ts b/app/lib/modules/llm/providers/openai.ts index 731de82..83c0dcc 100644 --- a/app/lib/modules/llm/providers/openai.ts +++ b/app/lib/modules/llm/providers/openai.ts @@ -1,4 +1,5 @@ import { createOpenAI } from '@ai-sdk/openai'; +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; import type { LanguageModel } from 'ai'; import { BaseProvider } from '~/lib/modules/llm/base-provider'; import type { ModelInfo } from '~/lib/modules/llm/types'; @@ -42,13 +43,19 @@ export default class OpenAILikeProvider extends BaseProvider { throw new Error(`Missing configuration for ${this.name} provider`); } - let openaiBaseUrl = baseUrl; - if (!baseUrl) { - openaiBaseUrl = undefined; + if (!!baseUrl) { + const provider = createOpenAICompatible({ + name: this.name, + baseURL: baseUrl, + apiKey, + includeUsage: true, + }); + + return provider(model); } const openai = createOpenAI({ - baseURL: openaiBaseUrl, + baseURL: baseUrl, apiKey, }); diff --git a/app/lib/modules/llm/providers/qwen.ts b/app/lib/modules/llm/providers/qwen.ts new file mode 100644 index 0000000..5b436f6 --- /dev/null +++ b/app/lib/modules/llm/providers/qwen.ts @@ -0,0 +1,57 @@ +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; +import type { LanguageModel } from 'ai'; +import { BaseProvider } from '~/lib/modules/llm/base-provider'; +import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { IProviderSetting } from '~/types/model'; + +export default class QwenProvider extends BaseProvider { + name = 'Qwen'; + getApiKeyLink = undefined; + + staticModels: ModelInfo[] = []; + + async getDynamicModels(settings?: IProviderSetting): Promise { + const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings); + const baseUrl = fetchBaseUrl || 'https://dashscope.aliyuncs.com/compatible-mode/v1'; + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const response = await fetch(`${baseUrl}/models`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + const res = (await response.json()) as any; + + const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat); + + return data.map((m: any) => ({ + name: m.id, + label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`, + provider: this.name, + maxTokenAllowed: m.context_length || 8000, + })); + } + + getModelInstance(options: { model: string; providerSettings?: Record }): LanguageModel { + const { model, providerSettings } = options; + + const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]); + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const provider = createOpenAICompatible({ + name: this.name, + baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + apiKey, + includeUsage: true, + }); + + return provider(model); + } +} diff --git a/app/lib/modules/llm/providers/zhipu.ts b/app/lib/modules/llm/providers/zhipu.ts new file mode 100644 index 0000000..49b9525 --- /dev/null +++ b/app/lib/modules/llm/providers/zhipu.ts @@ -0,0 +1,57 @@ +import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; +import type { LanguageModel } from 'ai'; +import { BaseProvider } from '~/lib/modules/llm/base-provider'; +import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { IProviderSetting } from '~/types/model'; + +export default class ZhiPuProvider extends BaseProvider { + name = 'ZhiPu'; + getApiKeyLink = undefined; + + staticModels: ModelInfo[] = []; + + async getDynamicModels(settings?: IProviderSetting): Promise { + const { baseUrl: fetchBaseUrl, apiKey } = this.getProviderBaseUrlAndKey(settings); + const baseUrl = fetchBaseUrl || 'https://open.bigmodel.cn/api/paas/v4'; + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const response = await fetch(`${baseUrl}/models`, { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + const res = (await response.json()) as any; + + const data = res.data.filter((model: any) => model.object === 'model' && model.supports_chat); + + return data.map((m: any) => ({ + name: m.id, + label: `${m.id} - context ${m.context_length ? Math.floor(m.context_length / 1000) + 'k' : 'N/A'}`, + provider: this.name, + maxTokenAllowed: m.context_length || 8000, + })); + } + + getModelInstance(options: { model: string; providerSettings?: Record }): LanguageModel { + const { model, providerSettings } = options; + + const { apiKey } = this.getProviderBaseUrlAndKey(providerSettings?.[this.name]); + + if (!apiKey) { + throw `Missing Api Key configuration for ${this.name} provider`; + } + + const openai = createOpenAICompatible({ + name: this.name, + baseURL: 'https://open.bigmodel.cn/api/paas/v4', + apiKey, + includeUsage: true, + }); + + return openai(model); + } +} diff --git a/app/lib/modules/llm/registry.ts b/app/lib/modules/llm/registry.ts index 8d1122d..ceb33ad 100644 --- a/app/lib/modules/llm/registry.ts +++ b/app/lib/modules/llm/registry.ts @@ -2,24 +2,34 @@ import AmazonBedrockProvider from './providers/amazon-bedrock'; import AnthropicProvider from './providers/anthropic'; import CohereProvider from './providers/cohere'; import DeepseekProvider from './providers/deepseek'; +import DouBaoProvider from './providers/doubao'; +import ErnieProvider from './providers/ernie'; import GithubProvider from './providers/github'; import GoogleProvider from './providers/google'; import GroqProvider from './providers/groq'; import HuggingFaceProvider from './providers/huggingface'; import HyperbolicProvider from './providers/hyperbolic'; +import KimiProvider from './providers/kimi'; import LMStudioProvider from './providers/lmstudio'; import MistralProvider from './providers/mistral'; import OllamaProvider from './providers/ollama'; import OpenRouterProvider from './providers/open-router'; import OpenAIProvider from './providers/openai'; import PerplexityProvider from './providers/perplexity'; +import QwenProvider from './providers/qwen'; import TogetherProvider from './providers/together'; import XAIProvider from './providers/xai'; +import ZhiPuProvider from './providers/zhipu'; export { AnthropicProvider, CohereProvider, DeepseekProvider, + DouBaoProvider, + ErnieProvider, + KimiProvider, + QwenProvider, + ZhiPuProvider, GoogleProvider, GroqProvider, HuggingFaceProvider, diff --git a/docs/content/configuration.md b/docs/content/configuration.md index 270cbca..beef18d 100644 --- a/docs/content/configuration.md +++ b/docs/content/configuration.md @@ -37,7 +37,7 @@ UPage 支持多种 AI 提供商,您需要配置一个 AI 提供商才能使用 | 环境变量 | 描述 | 默认值 | 必填 | | --- | --- | --- | --- | -| `LLM_PROVIDER` | LLM 提供商,按照下述配置项配置一个 | - | 是 | +| `LLM_PROVIDER` | LLM 提供商,**按照下述配置项配置一个** | - | 是 | | `PROVIDER_BASE_URL` | LLM 提供商的 API 基础 URL,部分提供商需要设置此项,例如 Ollama, LMStudio。 OpenAI 可选此项 | - | 否,部分提供商不需要设置此项 | | `PROVIDER_API_KEY` | LLM 提供商的 API 密钥,大部分提供商需要设置此项 | - | 否,部分提供商不需要设置此项 | | `LLM_DEFAULT_MODEL` | 生成页面所使用的模型 | - | 是 | @@ -45,6 +45,61 @@ UPage 支持多种 AI 提供商,您需要配置一个 AI 提供商才能使用 以下是常见的 AI 提供商配置: +### 豆包(DouBao) + +| 环境变量 | 描述 | 默认值 | 必填 | +| --- | --- | --- | --- | +| `LLM_PROVIDER` | DouBao 提供商名称 | DouBao | 是 | +| `PROVIDER_API_KEY` | DouBao API 密钥 | - | 是(如果使用 DouBao) | + +:::info +前往 [DouBao](https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey) 获取 API 密钥。 +::: + +### 文心一言(Ernie) + +| 环境变量 | 描述 | 默认值 | 必填 | +| --- | --- | --- | --- | +| `LLM_PROVIDER` | Ernie 提供商名称 | Ernie | 是 | +| `PROVIDER_API_KEY` | Ernie API 密钥 | - | 是(如果使用 Ernie) | + +:::info +前往 [Ernie](https://cloud.baidu.com/doc/WENXINWORKSHOP/s/wm9cvs292) 获取 API 密钥。 +::: + +### 月之暗面(Kimi) + +| 环境变量 | 描述 | 默认值 | 必填 | +| --- | --- | --- | --- | +| `LLM_PROVIDER` | Kimi 提供商名称 | Kimi | 是 | +| `PROVIDER_API_KEY` | Kimi API 密钥 | - | 是(如果使用 Kimi) | + +:::info +前往 [Kimi](https://platform.moonshot.cn/console/api-keys) 获取 API 密钥。 +::: + +### 通义千问(Qwen) + +| 环境变量 | 描述 | 默认值 | 必填 | +| --- | --- | --- | --- | +| `LLM_PROVIDER` | Qwen 提供商名称 | Qwen | 是 | +| `PROVIDER_API_KEY` | Qwen API 密钥 | - | 是(如果使用 Qwen) | + +:::info +前往 [Qwen](https://bailian.console.aliyun.com/?spm=5176.29597918.J_SEsSjsNv72yRuRFS2VknO.2.624f7b08yj3vyX&tab=api#/api/?type=model&url=2712195) 获取 API 密钥。 +::: + +### 智谱 AI(ZhiPu) + +| 环境变量 | 描述 | 默认值 | 必填 | +| --- | --- | --- | --- | +| `LLM_PROVIDER` | ZhiPu 提供商名称 | ZhiPu | 是 | +| `PROVIDER_API_KEY` | ZhiPu API 密钥 | - | 是(如果使用 ZhiPu) | + +:::info +前往 [ZhiPu](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) 获取 API 密钥。 +::: + ### Amazon Bedrock | 环境变量 | 描述 | 默认值 | 必填 | @@ -206,11 +261,18 @@ UPage 支持多种 AI 提供商,您需要配置一个 AI 提供商才能使用 | 环境变量 | 描述 | 默认值 | 必填 | | --- | --- | --- | --- | | `LLM_PROVIDER` | OpenAI 提供商名称 | OpenAI | 是 | -| `PROVIDER_BASE_URL` | API 基础 URL | - | 否(不填写时,使用 OpenAI 官方 API) | +| `PROVIDER_BASE_URL` | 兼容 OpenAI 的 API 接口的地址 | - | 否(不填写时,使用 OpenAI 官方 API) | | `PROVIDER_API_KEY` | OpenAI API 密钥 | - | 是(如果使用 OpenAI) | :::info -前往 [OpenAI](https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key) 获取 API 密钥。 +此供应商也支持任何 OpenAI 兼容的 API 接口,只需要额外配置 `PROVIDER_BASE_URL` 为兼容 OpenAI 的 API 接口的地址。 + +在一些第三方软件或平台输入自定义 URL 时,可能需要追加 /v1 或 /v1/chat/completions 等后缀。 + +如: +- https://your-api-base-url +- https://your-api-base-url/v1 (目前最常见) +- https://your-api-base-url/v1/chat/completions ::: ### Perplexity diff --git a/package.json b/package.json index 1da8636..15a3afb 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@ai-sdk/google": "^2.0.15", "@ai-sdk/mistral": "^2.0.15", "@ai-sdk/openai": "^2.0.32", + "@ai-sdk/openai-compatible": "^1.0.19", "@ai-sdk/react": "^2.0.49", "@floating-ui/react": "^0.27.16", "@headlessui/react": "^2.2.8", @@ -84,6 +85,7 @@ "lodash": "^4.17.21", "morgan": "^1.10.1", "nanostores": "^1.0.1", + "node-fetch": "^3.3.2", "ollama-ai-provider-v2": "^1.3.1", "path-browserify": "^1.0.1", "prettier": "^3.6.2", @@ -108,8 +110,7 @@ "unist-util-visit": "^5.0.0", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0", - "zod": "^4.1.11", - "node-fetch": "^3.3.2" + "zod": "^4.1.11" }, "devDependencies": { "@biomejs/biome": "2.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2afe6ee..e077036 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@ai-sdk/openai': specifier: ^2.0.32 version: 2.0.32(zod@4.1.11) + '@ai-sdk/openai-compatible': + specifier: ^1.0.19 + version: 1.0.19(zod@4.1.11) '@ai-sdk/react': specifier: ^2.0.49 version: 2.0.49(react@18.3.1)(zod@4.1.11) @@ -481,12 +484,24 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 + '@ai-sdk/openai-compatible@1.0.19': + resolution: {integrity: sha512-hnsqPCCSNKgpZRNDOAIXZs7OcUDM4ut5ggWxj2sjB4tNL/aBn/xrM7pJkqu+WuPowyrE60wPVSlw0LvtXAlMXQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/openai@2.0.32': resolution: {integrity: sha512-p7giSkCs66Q1qYO/NPYI41CrSg65mcm8R2uAdF86+Y1D1/q4mUrWMyf5UTOJ0bx/z4jIPiNgGDCg2Kabi5zrKQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 + '@ai-sdk/provider-utils@3.0.10': + resolution: {integrity: sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@3.0.9': resolution: {integrity: sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ==} engines: {node: '>=18'} @@ -9990,12 +10005,25 @@ snapshots: '@ai-sdk/provider-utils': 3.0.9(zod@4.1.11) zod: 4.1.11 + '@ai-sdk/openai-compatible@1.0.19(zod@4.1.11)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.10(zod@4.1.11) + zod: 4.1.11 + '@ai-sdk/openai@2.0.32(zod@4.1.11)': dependencies: '@ai-sdk/provider': 2.0.0 '@ai-sdk/provider-utils': 3.0.9(zod@4.1.11) zod: 4.1.11 + '@ai-sdk/provider-utils@3.0.10(zod@4.1.11)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@standard-schema/spec': 1.0.0 + eventsource-parser: 3.0.6 + zod: 4.1.11 + '@ai-sdk/provider-utils@3.0.9(zod@4.1.11)': dependencies: '@ai-sdk/provider': 2.0.0