Files
Phantom 5167c927be fix: preserve openrouter reasoning with web search (#11505)
* feat(options): implement deep merging for provider options

Add deep merge functionality to preserve nested properties when combining provider options. The new implementation handles object merging recursively while maintaining type safety.

* refactor(tsconfig): reorganize include paths in tsconfig files

Clean up and reorder include paths for better maintainability and consistency between tsconfig.node.json and tsconfig.web.json

* test: add aiCore test configuration and script

Add new test configuration for aiCore package and corresponding test script in package.json to enable running tests specifically for the aiCore module.

* fix: format

* fix(aiCore): resolve test failures and update test infrastructure

- Add vitest setup file with global mocks for @cherrystudio/ai-sdk-provider
- Fix context assertions: use 'model' instead of 'modelId' in plugin tests
- Fix error handling tests: update expected error messages to match actual behavior
- Fix streamText tests: use 'maxOutputTokens' instead of 'maxTokens'
- Fix schemas test: update expected provider list to match actual implementation
- Fix mock-responses: use AI SDK v5 format (inputTokens/outputTokens)
- Update vi.mock to use importOriginal for preserving jsonSchema export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(aiCore): add alias mock for @cherrystudio/ai-sdk-provider in tests

The vi.mock in setup file doesn't work for source code imports.
Use vitest resolve.alias to mock the external package properly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(aiCore): disable unused-vars warnings in mock file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(aiCore): use import.meta.url for ESM compatibility in vitest config

__dirname is not available in ESM modules, use fileURLToPath instead.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(aiCore): use absolute paths in vitest config for workspace compatibility

- Use path.resolve for setupFiles and all alias paths
- Extend aiCore vitest.config.ts from root workspace config
- Change aiCore test environment to 'node' instead of 'jsdom'

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(factory): improve mergeProviderOptions documentation

Add detailed explanation of merge behavior with examples

* test(factory): add tests for mergeProviderOptions behavior

Add test cases to verify mergeProviderOptions correctly handles primitive values, arrays, and nested objects during merging

* refactor(tests): clean up mock responses test fixtures

Remove unused mock streaming chunks and error responses to simplify test fixtures
Update warning details structure in mock complete responses

* docs(test): clarify comment in generateImage test

Update comment to use consistent 'model id' terminology instead of 'modelId'

* test(factory): verify array replacement in mergeProviderOptions

---------

Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-28 13:56:46 +08:00
..
2025-11-19 18:13:33 +08:00
2025-09-04 14:03:04 +08:00
2025-09-04 14:03:04 +08:00

@cherrystudio/ai-core

Cherry Studio AI Core 是一个基于 Vercel AI SDK 的统一 AI Provider 接口包,为 AI 应用提供强大的抽象层和插件化架构。

核心亮点

🏗️ 优雅的架构设计

  • 简化分层models(模型层)→ runtime(运行时层),清晰的职责分离
  • 函数式优先:避免过度抽象,提供简洁直观的 API
  • 类型安全:完整的 TypeScript 支持,直接复用 AI SDK 类型系统
  • 最小包装:直接使用 AI SDK 的接口,避免重复定义和性能损耗

🔌 强大的插件系统

  • 生命周期钩子:支持请求全生命周期的扩展点
  • 流转换支持:基于 AI SDK 的 experimental_transform 实现流处理
  • 插件分类First、Sequential、Parallel 三种钩子类型,满足不同场景
  • 内置插件webSearch、logging、toolUse 等开箱即用的功能

🌐 统一多 Provider 接口

  • 扩展注册:支持自定义 Provider 注册,无限扩展能力
  • 配置统一:统一的配置接口,简化多 Provider 管理

🚀 多种使用方式

  • 函数式调用:适合简单场景的直接函数调用
  • 执行器实例:适合复杂场景的可复用执行器
  • 静态工厂:便捷的静态创建方法
  • 原生兼容:完全兼容 AI SDK 原生 Provider Registry

🔮 面向未来

  • Agent 就绪:为 OpenAI Agents SDK 集成预留架构空间
  • 模块化设计:独立包结构,支持跨项目复用
  • 渐进式迁移:可以逐步从现有 AI SDK 代码迁移

特性

  • 🚀 统一的 AI Provider 接口
  • 🔄 动态导入支持
  • 🛠️ TypeScript 支持
  • 📦 强大的插件系统
  • 🌍 内置webSearch(Openai,Google,Anthropic,xAI)
  • 🎯 多种使用模式(函数式/实例式/静态工厂)
  • 🔌 可扩展的 Provider 注册系统
  • 🧩 完整的中间件支持
  • 📊 插件统计和调试功能

支持的 Providers

基于 AI SDK 官方支持的 providers

核心 Providers内置支持:

  • OpenAI
  • Anthropic
  • Google Generative AI
  • OpenAI-Compatible
  • xAI (Grok)
  • Azure OpenAI
  • DeepSeek

扩展 Providers通过注册API支持:

  • Google Vertex AI
  • ...
  • 自定义 Provider

安装

npm install @cherrystudio/ai-core ai @ai-sdk/google @ai-sdk/openai

React Native

如果你在 React Native 项目中使用此包,需要在 metro.config.js 中添加以下配置:

// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')

const config = getDefaultConfig(__dirname)

// 添加对 @cherrystudio/ai-core 的支持
config.resolver.resolverMainFields = ['react-native', 'browser', 'main']
config.resolver.platforms = ['ios', 'android', 'native', 'web']

module.exports = config

还需要安装你要使用的 AI SDK provider:

npm install @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google

使用示例

基础用法

import { AiCore } from '@cherrystudio/ai-core'

// 创建 OpenAI executor
const executor = AiCore.create('openai', {
  apiKey: 'your-api-key'
})

// 流式生成
const result = await executor.streamText('gpt-4', {
  messages: [{ role: 'user', content: 'Hello!' }]
})

// 非流式生成
const response = await executor.generateText('gpt-4', {
  messages: [{ role: 'user', content: 'Hello!' }]
})

便捷函数

import { createOpenAIExecutor } from '@cherrystudio/ai-core'

// 快速创建 OpenAI executor
const executor = createOpenAIExecutor({
  apiKey: 'your-api-key'
})

// 使用 executor
const result = await executor.streamText('gpt-4', {
  messages: [{ role: 'user', content: 'Hello!' }]
})

多 Provider 支持

import { AiCore } from '@cherrystudio/ai-core'

// 支持多种 AI providers
const openaiExecutor = AiCore.create('openai', { apiKey: 'openai-key' })
const anthropicExecutor = AiCore.create('anthropic', { apiKey: 'anthropic-key' })
const googleExecutor = AiCore.create('google', { apiKey: 'google-key' })
const xaiExecutor = AiCore.create('xai', { apiKey: 'xai-key' })

扩展 Provider 注册

对于非内置的 providers可以通过注册 API 扩展支持:

import { registerProvider, AiCore } from '@cherrystudio/ai-core'

// 方式一:导入并注册第三方 provider
import { createGroq } from '@ai-sdk/groq'

registerProvider({
  id: 'groq',
  name: 'Groq',
  creator: createGroq,
  supportsImageGeneration: false
})

// 现在可以使用 Groq
const groqExecutor = AiCore.create('groq', { apiKey: 'groq-key' })

// 方式二:动态导入方式注册
registerProvider({
  id: 'mistral',
  name: 'Mistral AI',
  import: () => import('@ai-sdk/mistral'),
  creatorFunctionName: 'createMistral'
})

const mistralExecutor = AiCore.create('mistral', { apiKey: 'mistral-key' })

🔌 插件系统

AI Core 提供了强大的插件系统,支持请求全生命周期的扩展。

内置插件

webSearchPlugin - 网络搜索插件

为不同 AI Provider 提供统一的网络搜索能力:

import { webSearchPlugin } from '@cherrystudio/ai-core/built-in/plugins'

const executor = AiCore.create('openai', { apiKey: 'your-key' }, [
  webSearchPlugin({
    openai: {
      /* OpenAI 搜索配置 */
    },
    anthropic: { maxUses: 5 },
    google: {
      /* Google 搜索配置 */
    },
    xai: {
      mode: 'on',
      returnCitations: true,
      maxSearchResults: 5,
      sources: [{ type: 'web' }, { type: 'x' }, { type: 'news' }]
    }
  })
])

loggingPlugin - 日志插件

提供详细的请求日志记录:

import { createLoggingPlugin } from '@cherrystudio/ai-core/built-in/plugins'

const executor = AiCore.create('openai', { apiKey: 'your-key' }, [
  createLoggingPlugin({
    logLevel: 'info',
    includeParams: true,
    includeResult: false
  })
])

promptToolUsePlugin - 提示工具使用插件

为不支持原生 Function Call 的模型提供 prompt 方式的工具调用:

import { createPromptToolUsePlugin } from '@cherrystudio/ai-core/built-in/plugins'

// 对于不支持 function call 的模型
const executor = AiCore.create(
  'providerId',
  {
    apiKey: 'your-key',
    baseURL: 'https://your-model-endpoint'
  },
  [
    createPromptToolUsePlugin({
      enabled: true,
      // 可选:自定义系统提示符构建
      buildSystemPrompt: (userPrompt, tools) => {
        return `${userPrompt}\n\nAvailable tools: ${Object.keys(tools).join(', ')}`
      }
    })
  ]
)

自定义插件

创建自定义插件非常简单:

import { definePlugin } from '@cherrystudio/ai-core'

const customPlugin = definePlugin({
  name: 'custom-plugin',
  enforce: 'pre', // 'pre' | 'post' | undefined

  // 在请求开始时记录日志
  onRequestStart: async (context) => {
    console.log(`Starting request for model: ${context.modelId}`)
  },

  // 转换请求参数
  transformParams: async (params, context) => {
    // 添加自定义系统消息
    if (params.messages) {
      params.messages.unshift({
        role: 'system',
        content: 'You are a helpful assistant.'
      })
    }
    return params
  },

  // 处理响应结果
  transformResult: async (result, context) => {
    // 添加元数据
    if (result.text) {
      result.metadata = {
        processedAt: new Date().toISOString(),
        modelId: context.modelId
      }
    }
    return result
  }
})

// 使用自定义插件
const executor = AiCore.create('openai', { apiKey: 'your-key' }, [customPlugin])

使用 AI SDK 原生 Provider 注册表

https://ai-sdk.dev/docs/reference/ai-sdk-core/provider-registry

除了使用内建的 provider 管理,你还可以使用 AI SDK 原生的 createProviderRegistry 来构建自己的 provider 注册表。

基本用法示例

import { createClient } from '@cherrystudio/ai-core'
import { createProviderRegistry } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'

// 1. 创建 AI SDK 原生注册表
export const registry = createProviderRegistry({
  // register provider with prefix and default setup:
  anthropic,

  // register provider with prefix and custom setup:
  openai: createOpenAI({
    apiKey: process.env.OPENAI_API_KEY
  })
})

// 2. 创建client,'openai'可以传空或者传providerId(内建的provider)
const client = PluginEnabledAiClient.create('openai', {
  apiKey: process.env.OPENAI_API_KEY
})

// 3. 方式1使用内建逻辑传统方式
const result1 = await client.streamText('gpt-4', {
  messages: [{ role: 'user', content: 'Hello with built-in logic!' }]
})

// 4. 方式2使用自定义注册表灵活方式
const result2 = await client.streamText({
  model: registry.languageModel('openai:gpt-4'),
  messages: [{ role: 'user', content: 'Hello with custom registry!' }]
})

// 5. 支持的重载方法
await client.generateObject({
  model: registry.languageModel('openai:gpt-4'),
  schema: z.object({ name: z.string() }),
  messages: [{ role: 'user', content: 'Generate a user' }]
})

await client.streamObject({
  model: registry.languageModel('anthropic:claude-3-opus-20240229'),
  schema: z.object({ items: z.array(z.string()) }),
  messages: [{ role: 'user', content: 'Generate a list' }]
})

与插件系统配合使用

更强大的是,你还可以将自定义注册表与 Cherry Studio 的插件系统结合使用:

import { PluginEnabledAiClient } from '@cherrystudio/ai-core'
import { createProviderRegistry } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'

// 1. 创建带插件的客户端
const client = PluginEnabledAiClient.create(
  'openai',
  {
    apiKey: process.env.OPENAI_API_KEY
  },
  [LoggingPlugin, RetryPlugin]
)

// 2. 创建自定义注册表
const registry = createProviderRegistry({
  openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
  anthropic: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
})

// 3. 方式1使用内建逻辑 + 完整插件系统
await client.streamText('gpt-4', {
  messages: [{ role: 'user', content: 'Hello with plugins!' }]
})

// 4. 方式2使用自定义注册表 + 有限插件支持
await client.streamText({
  model: registry.languageModel('anthropic:claude-3-opus-20240229'),
  messages: [{ role: 'user', content: 'Hello from Claude!' }]
})

// 5. 支持的方法
await client.generateObject({
  model: registry.languageModel('openai:gpt-4'),
  schema: z.object({ name: z.string() }),
  messages: [{ role: 'user', content: 'Generate a user' }]
})

await client.streamObject({
  model: registry.languageModel('openai:gpt-4'),
  schema: z.object({ items: z.array(z.string()) }),
  messages: [{ role: 'user', content: 'Generate a list' }]
})

混合使用的优势

  • 灵活性:可以根据需要选择使用内建逻辑或自定义注册表
  • 兼容性:完全兼容 AI SDK 的 createProviderRegistry API
  • 渐进式:可以逐步迁移现有代码,无需一次性重构
  • 插件支持:自定义注册表仍可享受插件系统的部分功能
  • 最佳实践:结合两种方式的优点,既有动态加载的性能优势,又有统一注册表的便利性

📚 相关资源

未来版本

  • 🔮 多 Agent 编排
  • 🔮 可视化插件配置
  • 🔮 实时监控和分析
  • 🔮 云端插件同步

📄 License

MIT License - 详见 LICENSE 文件


Cherry Studio AI Core - 让 AI 开发更简单、更强大、更灵活 🚀