my-ai-chat-framework 2.0.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"my-ai-chat-framework.browser.es.js","names":[],"sources":["../src/core/EventEmitter.js","../src/core/MessageStore.js","../src/core/ChatService.js","../src/adapters/openai.js","../src/plugins/tool-calling.js","../src/index.js"],"sourcesContent":["/**\n * 极简事件发射器\n */\nexport class EventEmitter {\n constructor() {\n this._events = new Map();\n }\n\n on(event, handler) {\n if (!this._events.has(event)) this._events.set(event, []);\n this._events.get(event).push(handler);\n return () => this.off(event, handler);\n }\n\n off(event, handler) {\n if (!this._events.has(event)) return;\n const handlers = this._events.get(event).filter(h => h !== handler);\n if (handlers.length) this._events.set(event, handlers);\n else this._events.delete(event);\n }\n\n emit(event, data) {\n if (!this._events.has(event)) return;\n for (const handler of this._events.get(event)) {\n try {\n handler(data);\n } catch (err) {\n console.error(`事件 ${event} 处理出错:`, err);\n }\n }\n }\n}\n","/**\n * 消息存储 - 极简实现\n * 每条消息格式:\n * {\n * id?: string,\n * role: 'user'|'assistant'|'system'|'tool',\n * content: string,\n * toolCalls?: Array,\n * toolCallId?: string,\n * timestamp?: number,\n * metadata?: any\n * }\n */\nexport class MessageStore {\n constructor() {\n this._messages = [];\n this._idCounter = 0;\n }\n\n add(message) {\n const msg = {\n id: message.id || `msg_${Date.now()}_${++this._idCounter}`,\n timestamp: message.timestamp || Date.now(),\n ...message\n };\n this._messages.push(msg);\n return msg;\n }\n\n addUser(content, metadata = {}) {\n return this.add({ role: 'user', content, metadata });\n }\n\n addAssistant(content, metadata = {}) {\n return this.add({ role: 'assistant', content, metadata });\n }\n\n addSystem(content, metadata = {}) {\n return this.add({ role: 'system', content, metadata });\n }\n\n addTool(content, toolCallId, metadata = {}) {\n return this.add({ role: 'tool', content, toolCallId, metadata });\n }\n\n getLast() {\n return this._messages[this._messages.length - 1] || null;\n }\n\n getAll() {\n return [...this._messages];\n }\n\n clear() {\n this._messages = [];\n }\n\n // 撤回最后一条助手消息及其后的所有消息(用于工具调用循环)\n undoToLastAssistant() {\n let lastAssistantIndex = -1;\n for (let i = this._messages.length - 1; i >= 0; i--) {\n if (this._messages[i].role === 'assistant') {\n lastAssistantIndex = i;\n break;\n }\n }\n if (lastAssistantIndex >= 0) {\n const removed = this._messages.splice(lastAssistantIndex + 1);\n return removed;\n }\n return [];\n }\n}\n","import { EventEmitter } from './EventEmitter.js';\nimport { MessageStore } from './MessageStore.js';\n\n/**\n * 核心聊天服务\n * 极简设计:只负责消息存储、事件发射、插件管理、请求委托\n */\nexport class ChatService extends EventEmitter {\n constructor(config = {}) {\n super();\n this.config = {\n apiKey: config.apiKey || '',\n model: config.model || 'gpt-3.5-turbo',\n ...config\n };\n this.messages = new MessageStore();\n this._plugins = [];\n this._adapter = null; // 当前使用的 API 适配器\n this._pendingRequest = null;\n }\n\n /**\n * 加载插件\n * @param {object} plugin - 必须包含 install 方法\n */\n use(plugin) {\n if (typeof plugin.install === 'function') {\n plugin.install(this);\n this._plugins.push(plugin);\n } else {\n throw new Error('插件必须提供 install 方法');\n }\n return this;\n }\n\n /**\n * 设置 API 适配器\n */\n setAdapter(adapter) {\n this._adapter = adapter;\n return this;\n }\n\n /**\n * 发送消息(非流式)\n * @param {string|object} input - 字符串或消息对象\n */\n async send(input) {\n const userMsg = typeof input === 'string'\n ? { role: 'user', content: input }\n : { role: 'user', ...input };\n\n this.messages.add(userMsg);\n this.emit('sending', userMsg);\n\n // 构建请求\n const requestBody = this._adapter.buildRequest(this.messages.getAll(), this.config);\n const response = await this._adapter.send(requestBody, this.config);\n const assistantMsg = this._adapter.parseResponse(response);\n\n this.messages.add(assistantMsg);\n this.emit('message', assistantMsg);\n return assistantMsg;\n }\n\n /**\n * 流式发送消息\n * @param {string|object} input\n * @param {function} onProgress - 每次收到数据块时调用,参数为累积的当前消息对象\n * @param {function} onDone - 完成时调用,参数为最终消息对象\n */\n async stream(input, onProgress, onDone) {\n const userMsg = typeof input === 'string'\n ? { role: 'user', content: input }\n : { role: 'user', ...input };\n\n this.messages.add(userMsg);\n this.emit('sending', userMsg);\n\n const requestBody = this._adapter.buildRequest(this.messages.getAll(), this.config);\n\n let accumulated = { role: 'assistant', content: '' };\n await this._adapter.stream(\n requestBody,\n this.config,\n (chunk) => {\n // 累积消息\n if (chunk.content) accumulated.content += chunk.content;\n if (chunk.toolCalls) accumulated.toolCalls = chunk.toolCalls;\n if (chunk.reasoningContent) accumulated.reasoningContent = chunk.reasoningContent;\n // 触发进度事件\n this.emit('stream-progress', { ...accumulated });\n if (onProgress) onProgress({ ...accumulated });\n },\n (finalMessage) => {\n this.messages.add(finalMessage);\n this.emit('message', finalMessage);\n if (onDone) onDone(finalMessage);\n }\n );\n }\n\n /**\n * 注册工具(由工具调用插件实现)\n * 这里只留一个空方法,插件会覆盖它\n */\n registerTool(name, description, executor) {\n throw new Error('工具调用插件未加载,请先使用 use(toolCallingPlugin)');\n }\n}\n","/**\n * OpenAI 兼容 API 适配器\n * 支持 DeepSeek 等完全兼容 OpenAI 接口的服务\n */\nexport const openaiAdapter = {\n name: 'openai',\n\n install(chatService) {\n // 将自身设置为适配器\n chatService.setAdapter(this);\n },\n\n buildRequest(messages, config) {\n // 将内部消息格式转为 OpenAI 格式\n const apiMessages = messages.map(msg => {\n const base = { role: msg.role, content: msg.content };\n if (msg.role === 'assistant' && msg.toolCalls) {\n base.tool_calls = msg.toolCalls;\n }\n if (msg.role === 'tool' && msg.toolCallId) {\n base.tool_call_id = msg.toolCallId;\n }\n return base;\n });\n\n return {\n model: config.model,\n messages: apiMessages,\n temperature: config.temperature ?? 0.7,\n max_tokens: config.maxTokens ?? 2000,\n stream: false // 流式请求会在 stream 方法中单独处理\n };\n },\n\n async send(requestBody, config) {\n const response = await fetch(config.apiUrl || 'https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`\n },\n body: JSON.stringify(requestBody)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`API 请求失败: ${response.status} ${error}`);\n }\n const data = await response.json();\n return data;\n },\n\n parseResponse(apiResponse) {\n const choice = apiResponse.choices[0];\n const msg = choice.message;\n // 转换为内部格式\n const internal = {\n role: msg.role,\n content: msg.content || '',\n };\n if (msg.tool_calls) {\n internal.toolCalls = msg.tool_calls;\n }\n if (msg.reasoning_content) {\n internal.reasoningContent = msg.reasoning_content;\n }\n return internal;\n },\n\n async stream(requestBody, config, onProgress, onDone) {\n // 流式请求需要设置 stream: true\n const streamBody = { ...requestBody, stream: true };\n const response = await fetch(config.apiUrl || 'https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n 'Accept': 'text/event-stream'\n },\n body: JSON.stringify(streamBody)\n });\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`流式请求失败: ${response.status} ${error}`);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let accumulated = { role: 'assistant', content: '' };\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(/\\n\\n/);\n buffer = lines.pop();\n\n for (const line of lines) {\n const dataLine = line.replace(/^data: /, '').trim();\n if (!dataLine || dataLine === '[DONE]') continue;\n try {\n const chunk = JSON.parse(dataLine);\n const delta = chunk.choices?.[0]?.delta;\n if (!delta) continue;\n\n const update = { role: delta.role || 'assistant', content: delta.content || '' };\n if (delta.tool_calls) update.toolCalls = delta.tool_calls;\n if (delta.reasoning_content) update.reasoningContent = delta.reasoning_content;\n\n if (update.content) accumulated.content += update.content;\n if (update.toolCalls) accumulated.toolCalls = update.toolCalls;\n if (update.reasoningContent) accumulated.reasoningContent = update.reasoningContent;\n\n onProgress({ ...accumulated });\n } catch (e) {\n console.warn('解析流式数据块失败:', e, dataLine);\n }\n }\n }\n\n // 流结束,最终消息\n const finalMessage = {\n role: 'assistant',\n content: accumulated.content,\n toolCalls: accumulated.toolCalls,\n reasoningContent: accumulated.reasoningContent\n };\n onDone(finalMessage);\n }\n};\n","/**\n * 工具调用插件\n * 拦截消息中的 tool_calls,执行注册的工具,并将结果插入对话,然后继续请求\n */\nexport const toolCallingPlugin = {\n name: 'tool-calling',\n maxIterations: 5, // 防止无限循环\n\n install(chatService) {\n this.chatService = chatService;\n this._tools = new Map(); // 工具名 -> { executor, definition }\n\n // 覆盖 registerTool 方法\n chatService.registerTool = (name, description, executor) => {\n this._tools.set(name, { executor, description });\n // 可选:同时生成工具定义,以便在请求中告知 AI\n // 这里简化,依赖适配器在请求中自动包含工具定义(如果需要)\n return chatService;\n };\n\n // 拦截发送流程:在发送前注入工具定义(如果需要)\n // 在流式和非流式响应后检测 tool_calls\n const originalSend = chatService.send;\n chatService.send = async (input) => {\n return this._handleWithTools(originalSend, input, false);\n };\n\n const originalStream = chatService.stream;\n chatService.stream = async (input, onProgress, onDone) => {\n return this._handleWithTools(originalStream, input, true, onProgress, onDone);\n };\n },\n\n async _handleWithTools(originalMethod, input, isStream, onProgress, onDone) {\n const self = this;\n const chat = this.chatService;\n let iteration = 0;\n\n // 包装回调,用于递归处理\n async function processResponse(initialResponse) {\n let lastResponse = initialResponse;\n while (iteration < self.maxIterations) {\n // 检查是否有工具调用\n const toolCalls = lastResponse?.toolCalls;\n if (!toolCalls || toolCalls.length === 0) break;\n\n iteration++;\n // 执行所有工具\n const toolResults = [];\n for (const call of toolCalls) {\n const toolName = call.function?.name;\n const args = JSON.parse(call.function?.arguments || '{}');\n const tool = self._tools.get(toolName);\n if (!tool) {\n console.warn(`未找到工具: ${toolName}`);\n toolResults.push({ tool_call_id: call.id, error: `工具 ${toolName} 未注册`, success: false });\n continue;\n }\n try {\n const result = await tool.executor(args);\n toolResults.push({\n tool_call_id: call.id,\n content: typeof result === 'string' ? result : JSON.stringify(result),\n success: true\n });\n } catch (err) {\n console.error(`工具 ${toolName} 执行失败:`, err);\n toolResults.push({ tool_call_id: call.id, error: err.message, success: false });\n }\n }\n\n // 添加工具结果消息\n for (const tr of toolResults) {\n chat.messages.addTool(tr.content || tr.error, tr.tool_call_id);\n }\n\n // 再次请求\n if (isStream) {\n // 流式重新请求:需要重新构建请求体\n const userMsg = chat.messages.getLast(); // 实际上应该重新生成请求\n // 这里简化:直接调用原 stream 方法,但需要避免循环\n // 实际实现需更细致,我们这里仅示意\n await new Promise((resolve) => {\n originalStream.call(chat, null, (chunk) => {\n if (onProgress) onProgress(chunk);\n lastResponse = chunk;\n }, (final) => {\n lastResponse = final;\n resolve();\n });\n });\n } else {\n const reqBody = chat._adapter.buildRequest(chat.messages.getAll(), chat.config);\n const apiResp = await chat._adapter.send(reqBody, chat.config);\n lastResponse = chat._adapter.parseResponse(apiResp);\n }\n }\n return lastResponse;\n }\n\n if (isStream) {\n // 流式模式\n let finalMessage;\n await originalMethod.call(chat, input,\n (chunk) => {\n if (onProgress) onProgress(chunk);\n },\n async (final) => {\n finalMessage = await processResponse(final);\n if (onDone) onDone(finalMessage);\n }\n );\n } else {\n const resp = await originalMethod.call(chat, input);\n const final = await processResponse(resp);\n return final;\n }\n }\n};\n","import { ChatService } from './core/ChatService.js';\nimport { MessageStore } from './core/MessageStore.js';\nimport { EventEmitter } from './core/EventEmitter.js';\nimport { openaiAdapter } from './adapters/openai.js';\nimport { toolCallingPlugin } from './plugins/tool-calling.js';\n\nexport { ChatService, MessageStore, EventEmitter, openaiAdapter, toolCallingPlugin };\n\nexport default {\n ChatService,\n MessageStore,\n EventEmitter,\n openaiAdapter,\n toolCallingPlugin\n};"],"mappings":"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"}
1
+ {"version":3,"file":"my-ai-chat-framework.browser.es.js","names":[],"sources":["../src/core/EventEmitter.js","../src/core/MessageStore.js","../src/core/SystemPromptStore.js","../src/core/Errors.js","../src/core/ChatService.js","../src/utils/url.js","../src/utils/typeCheck.js","../src/utils/MessageFormatter.js","../src/adapters/openai.js","../src/plugins/tool-calling.js","../src/plugins/model-registry.js"],"sourcesContent":["/**\n * 极简事件发射器\n * 支持同步/异步事件,handler 返回 Promise 时自动 await\n */\nexport class EventEmitter {\n constructor() {\n this._events = new Map();\n }\n\n on(event, handler) {\n if (!this._events.has(event)) this._events.set(event, []);\n this._events.get(event).push(handler);\n return () => this.off(event, handler);\n }\n\n off(event, handler) {\n if (!this._events.has(event)) return;\n const handlers = this._events.get(event).filter(h => h !== handler);\n if (handlers.length) this._events.set(event, handlers);\n else this._events.delete(event);\n }\n\n /**\n * 同步触发事件\n */\n emit(event, data) {\n if (!this._events.has(event)) return;\n for (const handler of this._events.get(event)) {\n try {\n handler(data);\n } catch (err) {\n console.error(`事件 ${event} 处理出错:`, err);\n }\n }\n }\n\n /**\n * 异步触发事件,逐个 await handler\n * handler 返回 Promise 时自动等待\n */\n async emitAsync(event, data) {\n if (!this._events.has(event)) return;\n for (const handler of this._events.get(event)) {\n try {\n await handler(data);\n } catch (err) {\n console.error(`事件 ${event} 处理出错:`, err);\n }\n }\n }\n}\n","/**\n * 消息存储 - 极简实现\n * 每条消息格式:\n * {\n * id?: string,\n * role: 'user'|'assistant'|'system'|'tool',\n * content: string,\n * images?: Array<string>, // 图片引用(URL / id),框架不存储图片数据\n * _ephemeral?: boolean, // 临时消息:仅底部连续时参与请求,续写后转正\n * _complete?: boolean, // 流式是否完成(false=未完成/中断)\n * prefix?: boolean, // 前缀续写标记\n * toolCalls?: Array,\n * toolCallId?: string,\n * timestamp?: number,\n * metadata?: any\n * }\n */\nexport class MessageStore {\n constructor() {\n this._messages = [];\n this._idCounter = 0;\n }\n\n add(message) {\n const msg = {\n id: message.id || `msg_${Date.now()}_${++this._idCounter}`,\n timestamp: message.timestamp || Date.now(),\n ...message\n };\n this._messages.push(msg);\n return msg;\n }\n\n addUser(content, metadata = {}) {\n return this.add({ role: 'user', content, metadata });\n }\n\n addAssistant(content, metadata = {}) {\n return this.add({ role: 'assistant', content, metadata });\n }\n\n addSystem(content, metadata = {}) {\n return this.add({ role: 'system', content, metadata });\n }\n\n addTool(content, toolCallId, metadata = {}) {\n return this.add({ role: 'tool', content, toolCallId, metadata });\n }\n\n /**\n * 快速添加一次性的、带续写标记的 assistant 消息\n * 适用于思维链引导、临时注入等场景\n * @param {string} content — 引导内容\n * @param {Object} [options] — { reasoningContent, prefix }\n * @returns {Object} 添加的消息\n */\n addOnceAssistant(content, options = {}) {\n return this.add({\n role: 'assistant',\n content,\n reasoningContent: options.reasoningContent,\n prefix: options.prefix !== false, // 默认开启续写\n _ephemeral: true\n });\n }\n\n getLast() {\n return this._messages[this._messages.length - 1] || null;\n }\n\n getAll() {\n return [...this._messages];\n }\n\n clear() {\n this._messages = [];\n }\n\n // 撤回最后一条助手消息及其后的所有消息(用于工具调用循环)\n undoToLastAssistant() {\n let lastAssistantIndex = -1;\n for (let i = this._messages.length - 1; i >= 0; i--) {\n if (this._messages[i].role === 'assistant') {\n lastAssistantIndex = i;\n break;\n }\n }\n if (lastAssistantIndex >= 0) {\n const removed = this._messages.splice(lastAssistantIndex + 1);\n return removed;\n }\n return [];\n }\n\n /**\n * 更新消息:按 id 查找并合并 changes,不新增消息\n * @param {string} id — 消息 id\n * @param {Object} changes — 要合并的字段\n * @returns {Object|null} 更新后的消息,未找到返回 null\n */\n update(id, changes) {\n for (const msg of this._messages) {\n if (msg.id === id) {\n Object.assign(msg, changes);\n // 更新 timestamp 便于排查\n msg.timestamp = changes.timestamp || Date.now();\n return msg;\n }\n }\n return null;\n }\n}\n","/**\r\n * SystemPromptStore — 系统提示词存储\r\n * \r\n * 职责:管理多条 system prompt 的增删改查与开关\r\n * 与 MessageStore 分离,因为 system prompt 是\"AI 行为准则\",不是对话事件\r\n * \r\n * 每条记录格式:\r\n * {\r\n * id: string,\r\n * content: string,\r\n * enabled: boolean,\r\n * timestamp: number\r\n * }\r\n */\r\n\r\nexport class SystemPromptStore {\r\n constructor() {\r\n this._prompts = [];\r\n this._idCounter = 0;\r\n }\r\n\r\n /**\r\n * 添加一条 system prompt\r\n * @param {string} content — 提示词内容\r\n * @param {boolean} [enabled=true] — 是否启用\r\n * @returns {Object} 添加的记录\r\n */\r\n add(content, enabled = true) {\r\n if (!content || typeof content !== 'string' || !content.trim()) {\r\n throw new Error('[SystemPromptStore] content 必须是非空字符串');\r\n }\r\n const record = {\r\n id: `sys_${Date.now()}_${++this._idCounter}`,\r\n content: content.trim(),\r\n enabled: Boolean(enabled),\r\n timestamp: Date.now()\r\n };\r\n this._prompts.push(record);\r\n return record;\r\n }\r\n\r\n /**\r\n * 删除指定索引的 system prompt\r\n * @param {number} index\r\n * @returns {Object} 被删除的记录\r\n */\r\n remove(index) {\r\n if (index < 0 || index >= this._prompts.length) {\r\n throw new Error(`[SystemPromptStore] 索引越界: ${index}`);\r\n }\r\n return this._prompts.splice(index, 1)[0];\r\n }\r\n\r\n /**\r\n * 切换指定索引的启用/禁用状态\r\n * @param {number} index\r\n * @returns {boolean} 切换后的状态\r\n */\r\n toggle(index) {\r\n if (index < 0 || index >= this._prompts.length) {\r\n throw new Error(`[SystemPromptStore] 索引越界: ${index}`);\r\n }\r\n this._prompts[index].enabled = !this._prompts[index].enabled;\r\n return this._prompts[index].enabled;\r\n }\r\n\r\n /**\r\n * 更新指定索引的 content\r\n * @param {number} index\r\n * @param {string} content\r\n */\r\n update(index, content) {\r\n if (index < 0 || index >= this._prompts.length) {\r\n throw new Error(`[SystemPromptStore] 索引越界: ${index}`);\r\n }\r\n if (!content || typeof content !== 'string' || !content.trim()) {\r\n throw new Error('[SystemPromptStore] content 必须是非空字符串');\r\n }\r\n this._prompts[index].content = content.trim();\r\n this._prompts[index].timestamp = Date.now();\r\n }\r\n\r\n /**\r\n * 清空并用一条 content 替换(便捷方法,常用于 config.system = '...')\r\n * @param {string} content\r\n */\r\n set(content) {\r\n this._prompts = [];\r\n if (content && typeof content === 'string' && content.trim()) {\r\n this.add(content, true);\r\n }\r\n }\r\n\r\n /**\r\n * 按 enabled 筛选后,转为适配器可用的格式\r\n * @returns {Array<{role:'system', content:string}>}\r\n */\r\n getEnabled() {\r\n return this._prompts\r\n .filter(p => p.enabled)\r\n .map(p => ({ role: 'system', content: p.content }));\r\n }\r\n\r\n /**\r\n * 返回所有记录(含 enabled 状态,用于 UI 展示)\r\n * @returns {Array}\r\n */\r\n getAll() {\r\n return [...this._prompts];\r\n }\r\n\r\n /**\r\n * 清空所有 system prompt\r\n */\r\n clear() {\r\n this._prompts = [];\r\n }\r\n}\r\n","/**\r\n * 自定义错误类\r\n * 用于区分不同类型的错误,方便用户通过 `error.name` 或 `instanceof` 处理\r\n */\r\n\r\n// API 错误 (如 401 认证失败、400 参数错误、429 限流等)\r\nexport class APIError extends Error {\r\n /**\r\n * @param {string} message - 错误消息(通常来自 API 响应)\r\n * @param {number} statusCode - HTTP 状态码\r\n * @param {any} originalError - 原始错误对象或相关信息\r\n * @param {string} responseText - 原始响应文本(如果有)\r\n */\r\n constructor(message, statusCode, originalError, responseText) {\r\n super(message)\r\n this.name = 'APIError'\r\n this.statusCode = statusCode\r\n this.originalError = originalError\r\n this.responseText = responseText\r\n }\r\n}\r\n\r\n// 网络错误 (fetch 失败、连接超时等)\r\nexport class NetworkError extends Error {\r\n /**\r\n * @param {string} message - 错误消息\r\n * @param {any} originalError - 原始错误对象或相关信息\r\n */\r\n constructor(message, originalError) {\r\n super(message)\r\n this.name = 'NetworkError'\r\n this.originalError = originalError\r\n }\r\n}\r\n\r\n// 配置错误 (缺少必要配置项、配置项类型错误等)\r\nexport class ConfigurationError extends Error {\r\n /**\r\n * @param {string} message - 错误消息\r\n */\r\n constructor(message) {\r\n super(message)\r\n this.name = 'ConfigurationError'\r\n }\r\n}\r\n\r\n// 解析错误 (解析 API 响应失败、响应格式不正确,不符合预期等)\r\nexport class ParsingError extends Error {\r\n /**\r\n * @param {string} message - 错误消息\r\n * @param {any} originalError - 原始错误对象或相关信息 \r\n * @param {string} responseText - 原始响应文本(如果有)\r\n **/\r\n constructor(message, originalError, responseText) {\r\n super(message)\r\n this.name = 'ParsingError'\r\n this.originalError = originalError\r\n this.responseText = responseText\r\n }\r\n}","import { EventEmitter } from './EventEmitter.js';\r\nimport { MessageStore } from './MessageStore.js';\r\nimport { SystemPromptStore } from './SystemPromptStore.js';\r\nimport { ConfigurationError, NetworkError } from './Errors.js';\r\n\r\nexport class ChatService extends EventEmitter {\r\n constructor(config = {}) {\r\n super();\r\n this.config = { ...config };\r\n this.messages = new MessageStore();\r\n this.systemPrompts = new SystemPromptStore();\r\n this._adapter = null;\r\n this._abortController = null;\r\n this._isGenerating = false;\r\n this._hooks = new EventEmitter();\r\n this._processResponse = null;\r\n\r\n const model = this.config.model || this.config.modelParams?.model;\r\n if (!model || typeof model !== 'string' || !model.trim()) {\r\n throw new ConfigurationError('缺少 model 配置');\r\n }\r\n\r\n const temperature = this.config.modelParams?.temperature ?? this.config.temperature;\r\n if (temperature !== undefined && (typeof temperature !== 'number' || temperature < 0 || temperature > 2)) {\r\n throw new ConfigurationError(`temperature 必须在 0-2 之间,当前值: ${temperature}`);\r\n }\r\n\r\n const maxTokens = this.config.modelParams?.maxTokens ?? this.config.maxTokens;\r\n if (maxTokens !== undefined && (typeof maxTokens !== 'number' || maxTokens < 1 || !Number.isInteger(maxTokens))) {\r\n throw new ConfigurationError(`maxTokens 必须为正整数,当前值: ${maxTokens}`);\r\n }\r\n\r\n if (typeof config.system === 'string' && config.system.trim()) {\r\n this.systemPrompts.set(config.system);\r\n }\r\n }\r\n\r\n use(plugin) {\r\n plugin.install(this);\r\n return this;\r\n }\r\n\r\n setAdapter(adapter) {\r\n this._adapter = adapter;\r\n }\r\n\r\n abort() {\r\n if (this._abortController) this._abortController.abort();\r\n }\r\n\r\n get isGenerating() { return this._isGenerating; }\r\n\r\n async continueLast() {\r\n const target = this._prepareContinue();\r\n this.messages.update(target.id, { prefix: true });\r\n try {\r\n await this._request({ addUser: false, isStream: false, mergeToEntry: target.id });\r\n this.messages.update(target.id, {\r\n _complete: true, prefix: undefined, _ephemeral: undefined\r\n });\r\n return target;\r\n } catch (err) {\r\n this.messages.update(target.id, { prefix: undefined });\r\n throw err;\r\n }\r\n }\r\n\r\n async continueLastStream(onProgress, onDone) {\r\n const target = this._prepareContinue();\r\n console.log('[DEBUG] continueLastStream target.id:', target.id);\r\n this.messages.update(target.id, { prefix: true });\r\n const baseLen = (target.content || '').length;\r\n\r\n try {\r\n await this._request({\r\n addUser: false, isStream: true, mergeToEntry: target.id,\r\n onProgress: (chunk) => {\r\n if (onProgress) {\r\n onProgress({ ...chunk, content: (chunk.content || '').slice(baseLen) });\r\n }\r\n },\r\n onDone: (final) => { if (onDone) onDone(final); }\r\n });\r\n this.messages.update(target.id, {\r\n _complete: true, prefix: undefined, _ephemeral: undefined\r\n });\r\n return target;\r\n } catch (err) {\r\n this.messages.update(target.id, { prefix: undefined });\r\n throw err;\r\n }\r\n }\r\n\r\n _prepareContinue() {\r\n const msgs = this.messages.getAll();\r\n const last = msgs[msgs.length - 1];\r\n if (last && (last.role === 'assistant' || last._ephemeral)) return last;\r\n throw new Error('最后一条消息不是 assistant,无法续写');\r\n }\r\n\r\n updateConfig(partial) {\r\n Object.assign(this.config, partial);\r\n if (typeof partial.system === 'string') this.systemPrompts.set(partial.system);\r\n this.emit('config-updated', { changes: partial, timestamp: Date.now() });\r\n }\r\n\r\n // ========== 编排角色 ==========\r\n async _request(options) {\r\n let { userInput, addUser = true, isStream = false, onProgress, onDone, mergeToEntry } = options;\r\n\r\n this.emit('sending', { addUser, userInput, timestamp: Date.now() });\r\n this._addUserMessage(userInput, addUser);\r\n\r\n // 发送前钩子:在 adapter 转换 messages 之前执行,可修改 messages/config\r\n await this._hooks.emitAsync('beforeRequest', {\r\n messages: this.messages,\r\n config: this.config,\r\n options\r\n });\r\n\r\n // 自动续写:启用 autoContinue 时,检测底部是否有 prefix 标记的消息,自动设为续写目标\r\n // 用于 ephemeral 注入场景:注入一条带 prefix 的引导消息后,自动合并结果回原消息\r\n // 不启用时 prefix 仅透传给 API,结果仍作为新消息追加\r\n if (this.config.ephemeralContinue && !mergeToEntry) {\r\n const msgs = this.messages.getAll();\r\n const last = msgs[msgs.length - 1];\r\n if (last && last.prefix && (last.role === 'assistant' || last._ephemeral)) {\r\n mergeToEntry = last.id;\r\n }\r\n }\r\n\r\n const body = this._adapter.buildRequest(\r\n this.messages.getAll(), this.config, this.systemPrompts.getEnabled()\r\n );\r\n\r\n const retryCfg = this.config.retry || {};\r\n try {\r\n return await this._withRetry(body, {\r\n isStream, onProgress, onDone, mergeToEntry,\r\n maxRetries: retryCfg.maxRetries ?? 0,\r\n retryDelay: retryCfg.retryDelay ?? 1000\r\n });\r\n } catch (error) {\r\n this.emit('error', { error, timestamp: Date.now() });\r\n throw error;\r\n }\r\n }\r\n\r\n _addUserMessage(userInput, addUser) {\r\n if (addUser && userInput !== undefined) {\r\n const msg = typeof userInput === 'string'\r\n ? { role: 'user', content: userInput }\r\n : { role: 'user', ...userInput };\r\n this.messages.add(msg);\r\n }\r\n }\r\n\r\n /** retry 循环,占位消息只 push 一次 */\r\n async _withRetry(body, { isStream, onProgress, onDone, mergeToEntry, maxRetries, retryDelay }) {\r\n let lastError = null;\r\n const adapterOptions = { signal: this._abortController?.signal };\r\n\r\n let placeholder = null;\r\n let base = null;\r\n if (isStream) {\r\n if (mergeToEntry) {\r\n placeholder = this.messages.getAll().find(m => m.id === mergeToEntry);\r\n if (placeholder) base = { content: placeholder.content || '', reasoning: placeholder.reasoningContent || '' };\r\n }\r\n if (!placeholder) {\r\n placeholder = this.messages.add({ role: 'assistant', content: '', _complete: false });\r\n }\r\n }\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n if (attempt > 0) {\r\n this.emit('retry', { attempt, maxRetries, lastError, timestamp: Date.now() });\r\n await new Promise(r => setTimeout(r, retryDelay));\r\n }\r\n try {\r\n this._isGenerating = true;\r\n if (isStream) {\r\n return await this._stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone });\r\n } else {\r\n const resp = await this._adapter.send(body, this.config, adapterOptions);\r\n let result = this._handleResult(this._adapter.parseResponse(resp), mergeToEntry);\r\n // 响应后处理钩子(tool-calling 用),仅非续写时\r\n if (this._processResponse && !mergeToEntry) {\r\n result = await this._processResponse(result, { isStream: false });\r\n }\r\n return result;\r\n }\r\n } catch (error) {\r\n lastError = error;\r\n if (error.name === 'AbortError' || this._abortController?.signal.aborted) {\r\n this._isGenerating = false; throw error;\r\n }\r\n if (error instanceof NetworkError && attempt < maxRetries) continue;\r\n this._isGenerating = false; throw error;\r\n }\r\n }\r\n this._isGenerating = false;\r\n throw lastError;\r\n }\r\n\r\n /** 流式:ChatService 维护占位,adapter 只管解析 */\r\n async _stream(body, adapterOptions, { placeholder, base, mergeToEntry, onProgress, onDone }) {\r\n let finalMsg = null;\r\n\r\n await this._adapter.stream(body, this.config,\r\n (snap) => {\r\n placeholder.content = snap.content || '';\r\n if (snap.reasoningContent) placeholder.reasoningContent = snap.reasoningContent;\r\n if (snap.toolCalls) placeholder.toolCalls = [...snap.toolCalls];\r\n this.emit('stream-progress', snap);\r\n if (onProgress) onProgress(snap);\r\n },\r\n async (final) => {\r\n finalMsg = final;\r\n\r\n if (this._processResponse && !mergeToEntry) {\r\n finalMsg = await this._processResponse(finalMsg, {\r\n isStream: true, onProgress, onDone\r\n });\r\n }\r\n\r\n if (mergeToEntry && base) {\r\n const newContent = base.content + (final.content || '');\r\n const changes = { content: newContent, _complete: true };\r\n if (final.reasoningContent) changes.reasoningContent = base.reasoning + final.reasoningContent;\r\n if (final.toolCalls) changes.toolCalls = final.toolCalls;\r\n this.messages.update(mergeToEntry, changes);\r\n const updated = this.messages.getAll().find(m => m.id === mergeToEntry);\r\n this.emit('message', updated);\r\n if (onDone) onDone(updated);\r\n } else {\r\n placeholder._complete = true;\r\n this.emit('message', finalMsg);\r\n if (onDone) onDone(finalMsg);\r\n }\r\n },\r\n adapterOptions\r\n );\r\n return mergeToEntry || finalMsg;\r\n }\r\n\r\n /** 非流式结果落位 */\r\n _handleResult(assistantMsg, mergeToEntry) {\r\n if (mergeToEntry) {\r\n const entry = this.messages.getAll().find(m => m.id === mergeToEntry);\r\n if (!entry) {\r\n this.messages.add(assistantMsg);\r\n this.emit('message', assistantMsg);\r\n return assistantMsg;\r\n }\r\n const changes = {\r\n content: (entry.content || '') + (assistantMsg.content || ''),\r\n _complete: true\r\n };\r\n if (assistantMsg.reasoningContent) {\r\n changes.reasoningContent = (entry.reasoningContent || '') + assistantMsg.reasoningContent;\r\n }\r\n if (assistantMsg.toolCalls) changes.toolCalls = assistantMsg.toolCalls;\r\n this.messages.update(mergeToEntry, changes);\r\n const updated = this.messages.getAll().find(m => m.id === mergeToEntry);\r\n this.emit('message', updated);\r\n return updated;\r\n }\r\n this.messages.add(assistantMsg);\r\n this.emit('message', assistantMsg);\r\n return assistantMsg;\r\n }\r\n\r\n // ========== 公共 API ==========\r\n async send(userInput) {\r\n this._abortController = new AbortController();\r\n try { return await this._request({ userInput, addUser: true, isStream: false }); }\r\n finally { this._abortController = null; this._isGenerating = false; }\r\n }\r\n\r\n async stream(userInput, onProgress, onDone) {\r\n this._abortController = new AbortController();\r\n try { return await this._request({ userInput, addUser: true, isStream: true, onProgress, onDone }); }\r\n finally { this._abortController = null; this._isGenerating = false; }\r\n }\r\n\r\n async sendExisting() {\r\n this._abortController = new AbortController();\r\n try { return await this._request({ addUser: false, isStream: false }); }\r\n finally { this._abortController = null; this._isGenerating = false; }\r\n }\r\n\r\n async sendExistingStream(onProgress, onDone) {\r\n this._abortController = new AbortController();\r\n try { return await this._request({ addUser: false, isStream: true, onProgress, onDone }); }\r\n finally { this._abortController = null; this._isGenerating = false; }\r\n }\r\n}","/**\r\n * URL 工具函数\r\n */\r\n\r\n/**\r\n * 拼接 baseUrl 和 path\r\n * 注意:path 应以 '/' 开头,否则会替换 baseUrl 的最后一段\r\n * @param {string} baseUrl - 基础 URL(如 https://api.deepseek.com)\r\n * @param {string} path - 路径(如 /v1/chat/completions)\r\n * @returns {string} 完整的 URL\r\n */\r\nexport function joinUrl(baseUrl, path) {\r\n // 移除 baseUrl 末尾的斜杠(如果有)\r\n const base = baseUrl.replace(/\\/$/, '');\r\n // 确保 path 以斜杠开头\r\n const p = path.startsWith('/') ? path : '/' + path;\r\n return base + p;\r\n}","/**\r\n * 类型判断工具\r\n */\r\n\r\n/**\r\n * 判断一个值是否为字符串\r\n * @param {any} value - 要检查的值\r\n * @returns {boolean} 如果是字符串则返回 true\r\n */\r\nexport function isString(value) {\r\n return typeof value === 'string';\r\n}","/**\r\n * MessageFormatter — 可注册的消息格式转换器\r\n *\r\n * 职责:\r\n * 1. 内置 OpenA I兼容格式的转换逻辑\r\n * 2. 支持 register(name, fn) 注册自定义格式(如 Anthropic、Gemini 等)\r\n * 3. 统一处理 capabilities 过滤(reasoning、vision 等)\r\n * 4. 所有适配器通过此工具获取 API 消息数组,消除重复代码\r\n *\r\n * 用法:\r\n * import { MessageFormatter } from 'my-ai-chat-framework';\r\n *\r\n * // 使用内置格式\r\n * const msgs = MessageFormatter.format({\r\n * messages, systemPrompts, capabilities, resolveImage\r\n * }); // 默认 'openai'\r\n *\r\n * // 注册自定义格式\r\n * MessageFormatter.register('anthropic', ({ messages, systemPrompts, capabilities }) => {\r\n * // 返回 Anthropic 格式的消息数组\r\n * });\r\n */\r\n\r\nimport { isString } from './typeCheck.js';\r\n\r\n// ====== 内置格式 ======\r\n\r\nconst IS_URL = /^https?:\\/\\//i;\r\n\r\nfunction defaultResolveImage(imageId) {\r\n if (IS_URL.test(imageId)) return { url: imageId };\r\n return null;\r\n}\r\n\r\nfunction buildMultimodalContent(textContent, images, resolveImage) {\r\n const content = [{ type: 'text', text: textContent }];\r\n for (const ref of images) {\r\n const resolved = resolveImage(ref);\r\n if (!resolved) continue;\r\n if (resolved.url) {\r\n content.push({ type: 'image_url', image_url: { url: resolved.url } });\r\n } else if (resolved.data) {\r\n const mime = resolved.mimeType || 'image/png';\r\n content.push({ type: 'image_url', image_url: { url: `data:${mime};base64,${resolved.data}` } });\r\n }\r\n }\r\n return content;\r\n}\r\n\r\n/** OpenAI 兼容格式 */\r\nfunction toOpenAI({ messages, systemPrompts, capabilities, resolveImage }) {\r\n const rImg = typeof resolveImage === 'function' ? resolveImage : defaultResolveImage;\r\n const result = [];\r\n\r\n // 1) system 置顶\r\n for (const sp of systemPrompts) {\r\n if (sp?.content && isString(sp.content) && sp.content.trim()) {\r\n result.push({ role: 'system', content: sp.content.trim() });\r\n }\r\n }\r\n\r\n // 2) 识别底部连续 ephemeral(只保留这些临时消息,其他 ephemeral 忽略)\r\n let ephemEnd = messages.length;\r\n for (let i = messages.length - 1; i >= 0; i--) {\r\n if (messages[i]._ephemeral) ephemEnd = i;\r\n else break;\r\n }\r\n\r\n // 3) 过滤 + 字段映射\r\n for (let i = 0; i < messages.length; i++) {\r\n const msg = messages[i];\r\n\r\n // ephemeral 消息:仅底部连续的保留,其余跳过\r\n if (msg._ephemeral) {\r\n if (i < ephemEnd) continue;\r\n // 底部 ephemeral 按原始 role 处理,不加 _ephemeral 标记\r\n }\r\n\r\n // 非底部 ephemeral 的正常消息仍保留原有过滤\r\n if (msg.role === 'system' && !msg._ephemeral) continue;\r\n\r\n if (msg.role === 'tool') {\r\n result.push({ role: 'tool', content: msg.content || '', tool_call_id: msg.toolCallId });\r\n continue;\r\n }\r\n\r\n if (msg.role === 'assistant' && msg.toolCalls?.length) {\r\n const entry = { role: 'assistant', content: msg.content || '', tool_calls: msg.toolCalls };\r\n if (msg.prefix) entry.prefix = true;\r\n if (capabilities?.reasoning && msg.reasoningContent) {\r\n entry.reasoning_content = msg.reasoningContent;\r\n }\r\n result.push(entry);\r\n continue;\r\n }\r\n\r\n // 常规消息\r\n const hasText = msg.content && isString(msg.content) && msg.content.trim();\r\n const hasImages = msg.images && Array.isArray(msg.images) && msg.images.length > 0;\r\n if (!hasText && !hasImages) {\r\n if (msg.role === 'assistant' && msg.prefix) {\r\n const prefixEntry = { role: 'assistant', content: '', prefix: true };\r\n if (capabilities?.reasoning && msg.reasoningContent) {\r\n prefixEntry.reasoning_content = msg.reasoningContent;\r\n }\r\n result.push(prefixEntry);\r\n }\r\n continue;\r\n }\r\n\r\n const entry = { role: msg.role };\r\n\r\n if (hasImages) {\r\n if (!capabilities?.vision) {\r\n throw new Error(\r\n '[MessageFormatter] 消息包含图片但模型不支持视觉(capabilities.vision=false)。' +\r\n '请切换模型或移除图片。'\r\n );\r\n }\r\n entry.content = buildMultimodalContent(msg.content || '', msg.images, rImg);\r\n } else {\r\n entry.content = msg.content;\r\n }\r\n\r\n if (msg.prefix) entry.prefix = true;\r\n // 常规 assistant 不回传 reasoningContent——API 自己会重新推理\r\n result.push(entry);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n// ====== 格式化器 ======\r\n\r\nexport const MessageFormatter = {\r\n _formats: new Map([\r\n ['openai', toOpenAI]\r\n ]),\r\n\r\n /**\r\n * 注册自定义格式\r\n * @param {string} name — 格式名称(如 'anthropic', 'gemini')\r\n * @param {Function} fn — 转换函数,接收 { messages, systemPrompts, capabilities, resolveImage },返回消息数组\r\n */\r\n register(name, fn) {\r\n if (!name || typeof name !== 'string' || !name.trim()) {\r\n throw new Error('[MessageFormatter] 格式名称必须是非空字符串');\r\n }\r\n if (typeof fn !== 'function') {\r\n throw new Error('[MessageFormatter] 转换函数必须是 function');\r\n }\r\n this._formats.set(name.trim(), fn);\r\n },\r\n\r\n /**\r\n * 移除自定义格式(内置格式不可移除)\r\n * @param {string} name\r\n */\r\n unregister(name) {\r\n if (name === 'openai') {\r\n throw new Error('[MessageFormatter] 内置格式 \"openai\" 不可移除');\r\n }\r\n this._formats.delete(name);\r\n },\r\n\r\n /**\r\n * 列出所有已注册的格式名称\r\n * @returns {Array<string>}\r\n */\r\n listFormats() {\r\n return [...this._formats.keys()];\r\n },\r\n\r\n /**\r\n * 按指定格式转换消息\r\n * @param {Object} options\r\n * @param {Array} options.messages — 内部消息列表\r\n * @param {Array} [options.systemPrompts=[]] — 启用的 system prompt\r\n * @param {Object} [options.capabilities={}] — 模型能力标签\r\n * @param {Function} [options.resolveImage] — 图片解析钩子\r\n * @param {string} [options.format='openai'] — 目标格式名称\r\n * @returns {Array<Object>}\r\n */\r\n format(options = {}) {\r\n const formatName = options.format || 'openai';\r\n const fn = this._formats.get(formatName);\r\n if (!fn) {\r\n throw new Error(\r\n `[MessageFormatter] 未知格式 \"${formatName}\"。可用格式: ${[...this._formats.keys()].join(', ')}`\r\n );\r\n }\r\n return fn({\r\n messages: options.messages || [],\r\n systemPrompts: options.systemPrompts || [],\r\n capabilities: options.capabilities || {},\r\n resolveImage: options.resolveImage\r\n });\r\n }\r\n};\r\n\r\n// 向后兼容:保留 assembleMessages 函数\r\nexport function assembleMessages(options = {}) {\r\n return MessageFormatter.format({ ...options, format: 'openai' });\r\n}\r\n","/**\n * OpenAI 兼容 API 适配器\n * 支持 DeepSeek 等完全兼容 OpenAI 接口的服务\n */\n\nimport { joinUrl } from \"../utils/url.js\";\nimport { isString } from \"../utils/typeCheck.js\";\nimport { APIError, NetworkError, ParsingError } from '../core/Errors.js';\nimport { MessageFormatter } from '../utils/MessageFormatter.js';\n\nexport const openaiAdapter = {\n name: 'openai',\n\n install(chatService) {\n chatService.setAdapter(this);\n },\n\n /**\n * 构建 OpenAI 格式的请求体\n * @param {Array} messages - 内部消息列表\n * @param {Object} config - 完整配置对象\n * @param {Array} [systemPrompts=[]] - 启用的 system prompt 列表\n * @returns {Object} 请求体\n */\n buildRequest(messages, config, systemPrompts = []) {\n const model = config.model || config.modelParams?.model;\n if (!model) {\n throw new Error('Missing required config: model (either at top level or in modelParams)');\n }\n\n const temperature = config.modelParams?.temperature ?? config.temperature ?? 0.7;\n const maxTokens = config.modelParams?.maxTokens ?? config.maxTokens ?? 2000;\n const reasoningEffort = config.modelParams?.reasoningEffort ?? config.reasoningEffort;\n\n // 用 MessageFormatter 统一处理:system 置顶、消息过滤、字段映射、图片解析\n // format 由 config.messageFormat 指定(默认 'openai')\n const apiMessages = MessageFormatter.format({\n messages,\n systemPrompts,\n capabilities: config.capabilities || {},\n resolveImage: config.resolveImage,\n format: config.messageFormat\n });\n\n const requestBody = {\n model,\n messages: apiMessages,\n temperature,\n max_tokens: maxTokens,\n stream: false\n };\n\n if (config.tools && Array.isArray(config.tools) && config.tools.length > 0) {\n requestBody.tools = config.tools;\n requestBody.tool_choice = 'auto';\n }\n\n if (reasoningEffort && model === 'deepseek-reasoner') {\n requestBody.reasoning_effort = reasoningEffort;\n }\n\n return requestBody;\n },\n\n /**\n * 处理URL相关配置,将其转换为完整请求URL\n * @param {Object} config - 配置参数\n * @returns {String} 实际请求地址\n */\n _getUrl(config) {\n config = config || {};\n const { apiUrl, baseUrl, path } = config;\n // 默认 path 没有 v1 因为 baseUrl 一般包括版本号\n const defaultPath = '/chat/completions';\n\n if (apiUrl && isString(apiUrl)) {\n return apiUrl;\n }\n\n if (baseUrl && isString(baseUrl)) {\n const finalPath = (path && isString(path)) ? path : defaultPath;\n return joinUrl(baseUrl, finalPath);\n }\n\n return 'https://api.openai.com/v1/chat/completions';\n },\n\n /**\n * 非流式发送\n * @param {Object} requestBody\n * @param {Object} config\n * @param {Object} [options] — { signal, headers: {...} }\n */\n async send(requestBody, config, options = {}) {\n try {\n const url = this._getUrl(config);\n\n const headers = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n ...(config.headers || {}),\n ...(options.headers || {})\n };\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(requestBody),\n signal: options.signal\n });\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n const data = await response.json();\n return data;\n\n } catch (error) {\n if (error instanceof APIError) throw error;\n if (error.name === 'AbortError') throw error;\n\n if (error.name === 'TypeError' && error.message.includes('fetch')) {\n throw new NetworkError(`Request failed: ${error.message}`, error);\n }\n\n throw error;\n }\n },\n\n /**\n * 解析 API 响应为内部消息格式\n */\n parseResponse(apiResponse) {\n // 检查响应结构是否有效\n if (!apiResponse || !apiResponse.choices || !apiResponse.choices[0] || !apiResponse.choices[0].message) {\n throw new ParsingError('Invalid API response: missing choices or message', apiResponse, JSON.stringify(apiResponse));\n }\n // 将 API 响应转换为内部消息格式\n const msg = apiResponse.choices[0].message;\n const internal = {\n role: msg.role,\n content: msg.content || '',\n };\n if (msg.tool_calls) internal.toolCalls = msg.tool_calls;\n if (msg.reasoning_content) internal.reasoningContent = msg.reasoning_content;\n return internal;\n },\n\n /**\n * 流式发送\n * @param {Object} requestBody\n * @param {Object} config\n * @param {Function} onProgress — 接收累积快照 { content, reasoningContent, toolCalls }\n * @param {Function} onDone — 接收最终消息对象\n * @param {Object} [options] — { signal, headers: {...} }\n */\n async stream(requestBody, config, onProgress, onDone, options = {}) {\n const streamBody = { ...requestBody, stream: true };\n const url = this._getUrl(config);\n let response;\n\n const headers = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n 'Accept': 'text/event-stream',\n ...(config.headers || {}),\n ...(options.headers || {})\n };\n\n try {\n response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(streamBody),\n signal: options.signal\n });\n\n } catch (error) {\n if (error.name === 'AbortError') throw error;\n\n if (error.name === 'TypeError' && error.message.includes('fetch')) {\n throw new NetworkError(`Stream request failed: ${error.message}`, error);\n }\n\n throw error;\n }\n\n // 处理非 2xx 响应\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n // 解析流式响应\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let accumulated = { role: 'assistant', content: '', reasoningContent: '' };\n let reachedDone = false; // 是否收到 [DONE]\n\n while (true) {\n // 用户调用 abort() 后 signal.aborted 为 true,跳出循环\n if (options.signal?.aborted) break;\n\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(/\\n\\n/);\n buffer = lines.pop();\n\n for (const line of lines) {\n const dataLine = line.replace(/^data: /, '').trim();\n if (!dataLine) continue;\n if (dataLine === '[DONE]') { reachedDone = true; break; }\n try {\n const chunk = JSON.parse(dataLine);\n const delta = chunk.choices?.[0]?.delta;\n if (!delta) continue;\n\n if (delta.content) accumulated.content += delta.content;\n if (delta.tool_calls) {\n if (!accumulated.toolCalls) accumulated.toolCalls = [];\n for (const toolCallDelta of delta.tool_calls) {\n const index = toolCallDelta.index;\n if (!accumulated.toolCalls[index]) {\n accumulated.toolCalls[index] = {\n id: '',\n type: 'function',\n function: { name: '', arguments: '' }\n };\n }\n if (toolCallDelta.id) accumulated.toolCalls[index].id = toolCallDelta.id;\n if (toolCallDelta.type) accumulated.toolCalls[index].type = toolCallDelta.type;\n if (toolCallDelta.function?.name) {\n accumulated.toolCalls[index].function.name += toolCallDelta.function.name;\n }\n if (toolCallDelta.function?.arguments) {\n accumulated.toolCalls[index].function.arguments += toolCallDelta.function.arguments;\n }\n }\n }\n if (delta.reasoning_content) accumulated.reasoningContent += delta.reasoning_content;\n\n // 传出累积快照,ChatService 自行处理占位消息更新\n onProgress({ ...accumulated });\n } catch (e) {\n console.warn('stream parsing failed :', e, dataLine);\n }\n }\n }\n\n const finalMessage = {\n role: 'assistant',\n content: accumulated.content,\n toolCalls: accumulated.toolCalls,\n reasoningContent: accumulated.reasoningContent\n };\n onDone(finalMessage);\n },\n\n /**\n * 处理对话请求的非 2xx 响应,抛出 APIError,包含状态码、错误消息和原始响应文本\n * @param {Response} response - fetch API 的 Response 对象\n * @returns {Promise<never>} 抛出错误 \n */\n async handleErrorResponse(response) {\n let errorText = await response.text();\n let errorMessage = `HTTP ${response.status}`;\n // 尝试从响应体中解析 JSON 格式的错误信息\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error?.message || errorJson.message || errorText.slice(0, 200);\n } catch (e) {\n // 如果解析失败,使用原始文本(已截断)\n errorMessage = errorText.length > 200 ? errorText.slice(0, 200) + '...' : errorText;\n }\n throw new APIError(errorMessage, response.status, null, errorText);\n }\n\n};","/**\n * 工具调用插件\n * 功能:拦截助手消息中的 tool_calls,执行对应的工具,将结果作为 tool 消息加入对话,\n * 然后自动继续对话(通过 sendExisting / sendExistingStream),直到没有新的工具调用。\n * \n * 设计要点:\n * - 支持普通请求和流式请求(通过 isStream 标志区分)\n * - 支持多次工具调用循环(maxIterations 防止无限循环)\n * - 串行执行工具(可后续升级为并行)\n * - 工具执行失败时,仍然返回错误信息给 AI,而不是中断整个流程\n * - 触发 tool-error 事件,方便用户监听工具执行异常\n */\nexport const toolCallingPlugin = {\n name: 'tool-calling',\n maxIterations: 5, // 最大工具调用循环次数,防止无限循环(例如 AI 反复请求同一工具)\n\n /**\n * 安装插件,覆盖 ChatService 的 registerTool、send、stream 方法\n * @param {ChatService} chatService - ChatService 实例\n */\n install(chatService) {\n this.chatService = chatService;\n this._tools = new Map(); // 存储工具名 -> { executor, description }\n\n // 初始化 config.tools 数组(用于存放 OpenAI 格式的工具定义)\n if (!chatService.config.tools) {\n chatService.config.tools = [];\n }\n\n /**\n * 注册工具\n * @param {string} name - 工具名称(唯一标识)\n * @param {string} description - 工具描述(告诉 AI 何时调用)\n * @param {Function} executor - 异步执行函数,接收参数对象,返回结果(字符串或对象)\n * @param {Object} parameters - JSON Schema 参数定义(可选,默认为空对象)\n * @returns {ChatService} 返回 chatService 实例,支持链式调用\n */\n chatService.registerTool = (name, description, executor, parameters = {}) => {\n // 保存执行器\n this._tools.set(name, { executor, description });\n\n // 构建 OpenAI 兼容的工具定义\n const toolDefinition = {\n type: 'function',\n function: {\n name,\n description,\n parameters: {\n type: 'object',\n properties: parameters,\n required: Object.keys(parameters).filter(key => parameters[key]?.required)\n }\n }\n };\n // 避免重复添加相同工具\n const existing = chatService.config.tools.find(t => t.function.name === name);\n if (!existing) {\n chatService.config.tools.push(toolDefinition);\n }\n return chatService;\n };\n\n // 注册响应后处理钩子,不再覆盖 send/stream\n chatService._processResponse = async (response, { isStream, onProgress, onDone }) => {\n return this._handleWithTools(response, isStream, onProgress, onDone);\n };\n },\n\n /**\n * 核心处理逻辑:检查响应 → 执行工具 → 继续请求 → 循环\n * @param {Object} initialResponse - 初始 AI 响应\n * @param {boolean} isStream - 是否流式\n * @param {Function} onProgress - 流式进度回调\n * @param {Function} onDone - 流式完成回调\n * @returns {Promise<Object>} 最终 AI 响应\n */\n async _handleWithTools(initialResponse, isStream, onProgress, onDone) {\n const self = this;\n const chat = this.chatService;\n let iteration = 0;\n\n /**\n * 递归处理工具调用\n * @param {Object} initialResponse - 初始 AI 响应(可能是第一次请求的响应)\n * @returns {Promise<Object>} 最终 AI 响应(不含 tool_calls)\n */\n async function processResponse(initialResponse) {\n let lastResponse = initialResponse;\n while (iteration < self.maxIterations) {\n // 检查 AI 返回的消息中是否包含工具调用请求\n const toolCalls = lastResponse?.toolCalls;\n if (!toolCalls || toolCalls.length === 0) break;\n\n iteration++;\n // 并行执行所有工具调用(彼此独立,不会因一个失败而阻塞其他)\n const toolResults = await Promise.all(\n toolCalls.map(async (call) => {\n const toolName = call.function?.name;\n\n // 解析参数(JSON.parse 可能失败,需单独 try/catch)\n let args;\n try {\n args = JSON.parse(call.function?.arguments || '{}');\n } catch (parseErr) {\n chat.emit('tool-error', {\n toolName: toolName || 'unknown',\n error: parseErr,\n toolCallId: call.id,\n stage: 'parse',\n timestamp: Date.now()\n });\n return {\n tool_call_id: call.id,\n error: `参数解析失败: ${parseErr.message}`,\n success: false\n };\n }\n\n const tool = self._tools.get(toolName);\n if (!tool) {\n chat.emit('tool-error', {\n toolName,\n error: new Error(`Tool not registered: ${toolName}`),\n toolCallId: call.id,\n stage: 'lookup',\n timestamp: Date.now()\n });\n return {\n tool_call_id: call.id,\n error: `工具 ${toolName} 未注册`,\n success: false\n };\n }\n\n try {\n // 工具执行超时控制\n const toolTimeout = chat.config.toolTimeout;\n let execPromise = tool.executor(args);\n if (toolTimeout && typeof toolTimeout === 'number' && toolTimeout > 0) {\n const timeoutErr = new Error(`工具 ${toolName} 执行超时 (${toolTimeout}ms)`);\n timeoutErr.name = 'ToolTimeoutError';\n const timeoutPromise = new Promise((_, reject) =>\n setTimeout(() => reject(timeoutErr), toolTimeout)\n );\n execPromise = Promise.race([execPromise, timeoutPromise]);\n }\n\n const result = await execPromise;\n chat.emit('tool-success', {\n toolName,\n result,\n toolCallId: call.id,\n timestamp: Date.now()\n });\n return {\n tool_call_id: call.id,\n content: typeof result === 'string' ? result : JSON.stringify(result),\n success: true\n };\n } catch (err) {\n const stage = err.name === 'ToolTimeoutError' ? 'timeout' : 'execute';\n chat.emit('tool-error', {\n toolName,\n error: err,\n toolCallId: call.id,\n stage,\n timestamp: Date.now()\n });\n return {\n tool_call_id: call.id,\n error: err.message,\n success: false\n };\n }\n })\n );\n\n // 将工具执行结果作为 tool 消息添加到对话历史\n for (const tr of toolResults) {\n // 注意:addTool 的第二个参数是 tool_call_id\n // 如果工具执行失败且没有内容,则传递错误信息作为 content\n const content = tr.success ? tr.content : (tr.error || '工具执行失败');\n chat.messages.addTool(content, tr.tool_call_id);\n }\n\n // 再次请求 AI,获取基于工具结果的回复\n if (isStream) {\n // 流式继续:使用 sendExistingStream\n await new Promise((resolve) => {\n chat.sendExistingStream(\n (chunk) => {\n if (onProgress) onProgress(chunk);\n lastResponse = chunk;\n },\n (final) => {\n lastResponse = final;\n resolve();\n }\n );\n });\n } else {\n // 非流式继续:使用 sendExisting\n lastResponse = await chat.sendExisting();\n }\n }\n return lastResponse;\n }\n\n // 初始响应已由 ChatService 发出,直接进入工具调用循环\n const final = await processResponse(initialResponse);\n return final;\n }\n};","/**\r\n * model-registry 插件\r\n * \r\n * 职责:\r\n * 1. 内置常用模型的能力标签表(model → capabilities 映射)\r\n * 2. 安装时自动根据 config.model 查表,写入 config.capabilities\r\n * 3. 监听 config-updated 事件,切换模型时自动同步能力\r\n * 4. 提供 chat.registerModel(),允许用户追加/覆盖自定义模型\r\n * \r\n * 能力标签字段(全部可选,缺失视为 false / 未知):\r\n * {\r\n * reasoning: boolean — 是否返回 reasoning_content(思维链)\r\n * vision: boolean — 是否支持图片输入\r\n * toolCalling: boolean — 是否支持 function calling\r\n * streaming: boolean — 是否支持流式传输\r\n * maxInputTokens: number — 最大输入 token 数\r\n * maxOutputTokens: number — 最大输出 token 数\r\n * }\r\n * \r\n * 使用:\r\n * chat.use(modelRegistryPlugin);\r\n * // config.capabilities 现在已自动填充\r\n * chat.registerModel('my-custom-model', { toolCalling: true });\r\n */\r\n\r\n// ====== 内置模型能力表 ======\r\n// 数据来源:各厂商官方文档(2026-05 快照),用户可通过 registerModel() 覆盖\r\nconst BUILTIN_MODELS = {\r\n // ---- DeepSeek ----\r\n 'deepseek-chat': {\r\n reasoning: false,\r\n vision: false,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 128000,\r\n maxOutputTokens: 8192\r\n },\r\n 'deepseek-reasoner': {\r\n reasoning: true,\r\n vision: false,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 128000,\r\n maxOutputTokens: 8192\r\n },\r\n\r\n // ---- OpenAI ----\r\n 'gpt-4o': {\r\n reasoning: false,\r\n vision: true,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 128000,\r\n maxOutputTokens: 16384\r\n },\r\n 'gpt-4o-mini': {\r\n reasoning: false,\r\n vision: true,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 128000,\r\n maxOutputTokens: 16384\r\n },\r\n 'gpt-3.5-turbo': {\r\n reasoning: false,\r\n vision: false,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 16385,\r\n maxOutputTokens: 4096\r\n },\r\n 'o1': {\r\n reasoning: true,\r\n vision: false,\r\n toolCalling: false,\r\n streaming: false,\r\n maxInputTokens: 200000,\r\n maxOutputTokens: 100000\r\n },\r\n 'o3-mini': {\r\n reasoning: true,\r\n vision: false,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 200000,\r\n maxOutputTokens: 100000\r\n },\r\n\r\n // ---- Anthropic ----\r\n 'claude-3.5-sonnet': {\r\n reasoning: false,\r\n vision: true,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 200000,\r\n maxOutputTokens: 8192\r\n },\r\n 'claude-3.5-haiku': {\r\n reasoning: false,\r\n vision: true,\r\n toolCalling: true,\r\n streaming: true,\r\n maxInputTokens: 200000,\r\n maxOutputTokens: 8192\r\n }\r\n};\r\n\r\nconst DEFAULT_CAPABILITIES = {\r\n streaming: true,\r\n toolCalling: false,\r\n reasoning: false,\r\n vision: false\r\n};\r\n\r\nexport const modelRegistryPlugin = {\r\n name: 'model-registry',\r\n\r\n install(chatService) {\r\n this.chat = chatService;\r\n // 合并内置表 + 用户通过 registerModel 追加的\r\n this._registry = new Map(Object.entries(BUILTIN_MODELS));\r\n\r\n /**\r\n * 注册/覆盖一个模型的能力标签\r\n * @param {string} name — 模型名称\r\n * @param {Object} capabilities — 能力标签对象(部分字段即可,未提供的取默认值)\r\n * @returns {ChatService}\r\n */\r\n chatService.registerModel = (name, capabilities = {}) => {\r\n if (!name || typeof name !== 'string' || !name.trim()) {\r\n throw new Error('[model-registry] 模型名称必须是非空字符串');\r\n }\r\n const merged = { ...DEFAULT_CAPABILITIES, ...capabilities };\r\n this._registry.set(name.trim(), merged);\r\n // 如果注册的模型与当前使用的模型同名,立即同步\r\n if (chatService.config.model === name.trim()) {\r\n this._syncCapabilities();\r\n }\r\n return chatService;\r\n };\r\n\r\n /**\r\n * 列出所有已注册的模型名称\r\n * @returns {Array<string>}\r\n */\r\n chatService.listModels = () => {\r\n return [...this._registry.keys()];\r\n };\r\n\r\n // 安装时立即同步一次\r\n this._syncCapabilities();\r\n\r\n // 监听配置变更:model 变了就重新同步\r\n chatService.on('config-updated', ({ changes }) => {\r\n if ('model' in changes) {\r\n this._syncCapabilities();\r\n }\r\n // 如果用户手动改了 capabilities,用 merge 保留未指定的字段\r\n if ('capabilities' in changes && !('model' in changes)) {\r\n const current = this._lookupCapabilities();\r\n if (current) {\r\n chatService.config.capabilities = { ...current, ...changes.capabilities };\r\n }\r\n }\r\n });\r\n },\r\n\r\n // ---- 内部方法 ----\r\n\r\n /** 查表获取模型能力,未注册返回 null */\r\n _lookupCapabilities() {\r\n const model = this.chat.config.model;\r\n if (!model) return null;\r\n return this._registry.get(model) || null;\r\n },\r\n\r\n /** 将查到的能力写入 config.capabilities */\r\n _syncCapabilities() {\r\n const caps = this._lookupCapabilities();\r\n if (caps) {\r\n // 若用户已手动设了 capabilities,用模型的合并(用户值优先?还是模型优先?)\r\n // 策略:模型优先,因为换了模型能力应该重置\r\n this.chat.config.capabilities = { ...caps };\r\n }\r\n // 未注册的模型:不清除已有 capabilities,保留用户手动设的值\r\n }\r\n};\r\n"],"mappings":"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"}