UNPKG

@cherrystudio/ai-core

Version:

Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK

790 lines (751 loc) 27 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_plugins = require('../../plugins-Dxz_fOwT.cjs'); const require_initialization = require('../../initialization-CThbOFZ0.cjs'); //#region src/core/options/factory.ts const isPlainObject = (value) => { return typeof value === "object" && value !== null && !Array.isArray(value); }; function deepMergeObjects(target, source) { const result = { ...target }; Object.entries(source).forEach(([key, value]) => { if (isPlainObject(value) && isPlainObject(result[key])) result[key] = deepMergeObjects(result[key], value); else result[key] = value; }); return result; } /** * Deep-merge multiple provider-specific options. * Nested objects are recursively merged; primitive values are overwritten. * * When the same key appears in multiple options: * - If both values are plain objects: they are deeply merged (recursive merge) * - If values are primitives/arrays: the later value overwrites the earlier one * * @example * mergeProviderOptions( * { openrouter: { reasoning: { enabled: true, effort: 'low' }, user: 'user-123' } }, * { openrouter: { reasoning: { effort: 'high', max_tokens: 500 }, models: ['gpt-4'] } } * ) * // Result: { * // openrouter: { * // reasoning: { enabled: true, effort: 'high', max_tokens: 500 }, * // user: 'user-123', * // models: ['gpt-4'] * // } * // } * * @param optionsMap Objects containing options for multiple providers * @returns Fully merged TypedProviderOptions */ function mergeProviderOptions(...optionsMap) { return optionsMap.reduce((acc, options) => { if (!options) return acc; Object.entries(options).forEach(([providerId, providerOptions]) => { if (!providerOptions) return; if (acc[providerId]) acc[providerId] = deepMergeObjects(acc[providerId], providerOptions); else acc[providerId] = providerOptions; }); return acc; }, {}); } //#endregion //#region src/core/plugins/built-in/providerToolPlugin.ts /** * 通用 provider 工具注入插件 * * 查找 extensionRegistry 中声明的 toolFactory, * 将返回的 ToolFactoryPatch(tools / providerOptions)合并到 params。 */ const providerToolPlugin = (capability, config = {}) => require_plugins.definePlugin({ name: capability, enforce: "pre", transformParams: async (params, context) => { const { providerId } = context; const modelProvider = context.model && typeof context.model !== "string" && "provider" in context.model ? context.model.provider : void 0; const resolved = await require_initialization.extensionRegistry.resolveToolCapability(providerId, capability, modelProvider); if (!resolved) return params; const userConfig = config[providerId] ?? {}; const patch = resolved.factory(resolved.provider)(userConfig); if (patch.tools) params.tools = { ...params.tools, ...patch.tools }; if (patch.providerOptions) params.providerOptions = mergeProviderOptions(params.providerOptions, patch.providerOptions); return params; } }); //#endregion //#region src/core/plugins/built-in/toolUsePlugin/StreamEventManager.ts /** * 类型守卫:检查对象是否是有效的流结果(包含 ReadableStream 类型的 fullStream) */ function hasFullStream(obj) { return typeof obj === "object" && obj !== null && "fullStream" in obj && obj.fullStream instanceof ReadableStream; } /** * 类型守卫:检查 usage 是否是 LanguageModelUsage * LanguageModelUsage 包含 totalTokens, inputTokens, outputTokens 等字段 */ function isLanguageModelUsage(usage) { return typeof usage === "object" && usage !== null && ("totalTokens" in usage || "inputTokens" in usage || "outputTokens" in usage); } /** * 类型守卫:检查 usage 是否是 ImageModelUsage * ImageModelUsage 包含 inputTokens, outputTokens, totalTokens 字段 * but lacks inputTokenDetails/outputTokenDetails which are present in LanguageModelUsage */ function isImageModelUsage(usage) { return typeof usage === "object" && usage !== null && "inputTokens" in usage && "outputTokens" in usage && !("inputTokenDetails" in usage) && !("outputTokenDetails" in usage); } /** * 类型守卫:检查 usage 是否是 EmbeddingModelUsage * EmbeddingModelUsage 只包含 tokens 字段 */ function isEmbeddingModelUsage(usage) { return typeof usage === "object" && usage !== null && "tokens" in usage && !("inputTokens" in usage) && !("outputTokens" in usage); } /** * 流事件管理器类 */ var StreamEventManager = class { /** * 发送工具调用步骤开始事件 */ sendStepStartEvent(controller) { controller.enqueue({ type: "start-step", request: {}, warnings: [] }); } /** * 发送步骤完成事件 */ sendStepFinishEvent(controller, chunk, context, finishReason = "stop") { if (chunk.usage && context.accumulatedUsage) this.accumulateUsage(context.accumulatedUsage, chunk.usage); controller.enqueue({ type: "finish-step", finishReason, response: chunk.response, usage: chunk.usage, providerMetadata: chunk.providerMetadata }); } /** * 处理递归调用并将结果流接入当前流 */ async handleRecursiveCall(controller, recursiveParams, context) { context.hasExecutedToolsInCurrentStep = false; const recursiveResult = await context.recursiveCall(recursiveParams); if (hasFullStream(recursiveResult)) await this.pipeRecursiveStream(controller, recursiveResult.fullStream); else console.warn("[MCP Prompt] No fullstream found in recursive result:", recursiveResult); } /** * 将递归流的数据传递到当前流 */ async pipeRecursiveStream(controller, recursiveStream) { const reader = recursiveStream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; if (value.type === "start") continue; if (value.type === "finish") break; controller.enqueue(value); } } finally { reader.releaseLock(); } } /** * 构建递归调用的参数 */ buildRecursiveParams(context, textBuffer, toolResultsText, tools) { const params = context.originalParams; const newMessages = [ ...params.messages || [], ...textBuffer ? [{ role: "assistant", content: textBuffer }] : [], { role: "user", content: toolResultsText } ]; return { ...params, messages: newMessages, tools }; } /** * 累加 usage 数据 * * 使用类型守卫来处理不同类型的 usage(LanguageModelUsage, ImageModelUsage, EmbeddingModelUsage) * - LanguageModelUsage: inputTokens, outputTokens, totalTokens * - ImageModelUsage: inputTokens, outputTokens, totalTokens * - EmbeddingModelUsage: tokens */ accumulateUsage(target, source) { if (!target || !source) return; if (isLanguageModelUsage(target) && isLanguageModelUsage(source)) { target.totalTokens = (target.totalTokens || 0) + (source.totalTokens || 0); target.inputTokens = (target.inputTokens || 0) + (source.inputTokens || 0); target.outputTokens = (target.outputTokens || 0) + (source.outputTokens || 0); if (source.inputTokenDetails) { if (!target.inputTokenDetails) target.inputTokenDetails = { noCacheTokens: void 0, cacheReadTokens: void 0, cacheWriteTokens: void 0 }; target.inputTokenDetails.cacheReadTokens = (target.inputTokenDetails.cacheReadTokens || 0) + (source.inputTokenDetails.cacheReadTokens || 0); target.inputTokenDetails.cacheWriteTokens = (target.inputTokenDetails.cacheWriteTokens || 0) + (source.inputTokenDetails.cacheWriteTokens || 0); target.inputTokenDetails.noCacheTokens = (target.inputTokenDetails.noCacheTokens || 0) + (source.inputTokenDetails.noCacheTokens || 0); } if (source.outputTokenDetails) { if (!target.outputTokenDetails) target.outputTokenDetails = { textTokens: void 0, reasoningTokens: void 0 }; target.outputTokenDetails.reasoningTokens = (target.outputTokenDetails.reasoningTokens || 0) + (source.outputTokenDetails.reasoningTokens || 0); target.outputTokenDetails.textTokens = (target.outputTokenDetails.textTokens || 0) + (source.outputTokenDetails.textTokens || 0); } return; } if (isImageModelUsage(target) && isImageModelUsage(source)) { target.totalTokens = (target.totalTokens || 0) + (source.totalTokens || 0); target.inputTokens = (target.inputTokens || 0) + (source.inputTokens || 0); target.outputTokens = (target.outputTokens || 0) + (source.outputTokens || 0); return; } if (isEmbeddingModelUsage(target) && isEmbeddingModelUsage(source)) { target.tokens = (target.tokens || 0) + (source.tokens || 0); return; } console.warn("[StreamEventManager] Unable to accumulate usage - type mismatch or unknown type", { target, source }); } }; //#endregion //#region src/core/plugins/built-in/toolUsePlugin/tagExtraction.ts /** * Returns the index of the start of the searchedText in the text, or null if it * is not found. */ function getPotentialStartIndex(text, searchedText) { if (searchedText.length === 0) return null; const directIndex = text.indexOf(searchedText); if (directIndex !== -1) return directIndex; for (let i = text.length - 1; i >= 0; i--) { const suffix = text.substring(i); if (searchedText.startsWith(suffix)) return i; } return null; } /** * 通用标签提取处理器 * 可以处理各种形式的标签对,如 <think>...</think>, <tool_use>...</tool_use> 等 */ var TagExtractor = class { constructor(config) { this.config = config; this.state = { textBuffer: "", isInsideTag: false, isFirstTag: true, isFirstText: true, afterSwitch: false, accumulatedTagContent: "", hasTagContent: false }; } /** * 处理文本块,返回处理结果 */ processText(newText) { this.state.textBuffer += newText; const results = []; while (true) { const nextTag = this.state.isInsideTag ? this.config.closingTag : this.config.openingTag; const startIndex = getPotentialStartIndex(this.state.textBuffer, nextTag); if (startIndex == null) { const content = this.state.textBuffer; if (content.length > 0) { results.push({ content: this.addPrefix(content), isTagContent: this.state.isInsideTag, complete: false }); if (this.state.isInsideTag) { this.state.accumulatedTagContent += this.addPrefix(content); this.state.hasTagContent = true; } } this.state.textBuffer = ""; break; } const contentBeforeTag = this.state.textBuffer.slice(0, startIndex); if (contentBeforeTag.length > 0) { results.push({ content: this.addPrefix(contentBeforeTag), isTagContent: this.state.isInsideTag, complete: false }); if (this.state.isInsideTag) { this.state.accumulatedTagContent += this.addPrefix(contentBeforeTag); this.state.hasTagContent = true; } } if (startIndex + nextTag.length <= this.state.textBuffer.length) { this.state.textBuffer = this.state.textBuffer.slice(startIndex + nextTag.length); if (this.state.isInsideTag && this.state.hasTagContent) { results.push({ content: "", isTagContent: false, complete: true, tagContentExtracted: this.state.accumulatedTagContent }); this.state.accumulatedTagContent = ""; this.state.hasTagContent = false; } this.state.isInsideTag = !this.state.isInsideTag; this.state.afterSwitch = true; if (this.state.isInsideTag) this.state.isFirstTag = false; else this.state.isFirstText = false; } else { this.state.textBuffer = this.state.textBuffer.slice(startIndex); break; } } return results; } /** * 完成处理,返回任何剩余的标签内容 */ finalize() { if (this.state.hasTagContent && this.state.accumulatedTagContent) { const result = { content: "", isTagContent: false, complete: true, tagContentExtracted: this.state.accumulatedTagContent }; this.state.accumulatedTagContent = ""; this.state.hasTagContent = false; return result; } return null; } addPrefix(text) { const prefix = this.state.afterSwitch && (this.state.isInsideTag ? !this.state.isFirstTag : !this.state.isFirstText) && this.config.separator ? this.config.separator : ""; this.state.afterSwitch = false; return prefix + text; } /** * 重置状态 */ reset() { this.state = { textBuffer: "", isInsideTag: false, isFirstTag: true, isFirstText: true, afterSwitch: false, accumulatedTagContent: "", hasTagContent: false }; } }; //#endregion //#region src/core/plugins/built-in/toolUsePlugin/ToolExecutor.ts /** * 工具执行器类 */ var ToolExecutor = class { /** * 执行多个工具调用 */ async executeTools(toolUses, tools, controller) { const executedResults = []; for (const toolUse of toolUses) try { const tool = tools[toolUse.toolName]; if (!tool || typeof tool.execute !== "function") throw new Error(`Tool "${toolUse.toolName}" has no execute method`); controller.enqueue({ type: "tool-call", toolCallId: toolUse.id, toolName: toolUse.toolName, input: toolUse.arguments }); const result = await tool.execute(toolUse.arguments, { toolCallId: toolUse.id, messages: [], abortSignal: new AbortController().signal }); controller.enqueue({ type: "tool-result", toolCallId: toolUse.id, toolName: toolUse.toolName, input: toolUse.arguments, output: result }); executedResults.push({ toolCallId: toolUse.id, toolName: toolUse.toolName, result, isError: false }); } catch (error) { console.error(`[MCP Prompt Stream] Tool execution failed: ${toolUse.toolName}`, error); const errorResult = this.handleToolError(toolUse, error, controller); executedResults.push(errorResult); } return executedResults; } /** * 格式化工具结果为 Cherry Studio 标准格式 */ formatToolResults(executedResults) { return executedResults.map((tr) => { if (!tr.isError) return `<tool_use_result>\n <name>${tr.toolName}</name>\n <result>${JSON.stringify(tr.result)}</result>\n</tool_use_result>`; else { const error = tr.result || "Unknown error"; return `<tool_use_result>\n <name>${tr.toolName}</name>\n <error>${error}</error>\n</tool_use_result>`; } }).join("\n\n"); } /** * 发送工具调用开始相关事件 */ /** * 处理工具执行错误 */ handleToolError(toolUse, error, controller) { const toolError = { type: "tool-error", toolCallId: toolUse.id, toolName: toolUse.toolName, input: toolUse.arguments, error }; controller.enqueue(toolError); return { toolCallId: toolUse.id, toolName: toolUse.toolName, result: error, isError: true }; } }; //#endregion //#region src/core/plugins/built-in/toolUsePlugin/promptToolUsePlugin.ts /** * 工具使用标签配置 */ const TOOL_USE_TAG_CONFIG = { openingTag: "<tool_use>", closingTag: "</tool_use>", separator: "\n" }; const DEFAULT_SYSTEM_PROMPT = `In this environment you have access to a set of tools you can use to answer the user's question. \ You can use one or more tools per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. ## Tool Use Formatting Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: <tool_use> <name>{tool_name}</name> <arguments>{json_arguments}</arguments> </tool_use> The tool name should be the exact name of the tool you are using, and the arguments should be a JSON object containing the parameters required by that tool. IMPORTANT: When writing JSON inside the <arguments> tag, any double quotes inside string values must be escaped with a backslash ("). For example: <tool_use> <name>search</name> <arguments>{ "query": "browser,fetch" }</arguments> </tool_use> <tool_use> <name>exec</name> <arguments>{ "code": "const page = await CherryBrowser_fetch({ url: \\"https://example.com\\" })\nreturn page" }</arguments> </tool_use> The user will respond with the result of the tool use, which should be formatted as follows: <tool_use_result> <name>{tool_name}</name> <result>{result}</result> </tool_use_result> The result should be a string, which can represent a file or any other output type. You can use this result as input for the next action. For example, if the result of the tool use is an image file, you can use it in the next action like this: <tool_use> <name>image_transformer</name> <arguments>{"image": "image_1.jpg"}</arguments> </tool_use> Always adhere to this format for the tool use to ensure proper parsing and execution. ## Tool Use Rules Here are the rules you should always follow to solve your task: 1. Always use the right arguments for the tools. Never use variable names as the action arguments, use the value instead. 2. Call a tool only when needed: do not call the search agent if you do not need information, try to solve the task yourself. 3. If no tool call is needed, just answer the question directly. 4. Never re-do a tool call that you previously did with the exact same parameters. 5. For tool use, MAKE SURE use XML tag format as shown in the examples above. Do not use any other format. {{ TOOLS_INFO }} ## Response rules Respond in the language of the user's query, unless the user instructions specify additional requirements for the language to be used. # User Instructions {{ USER_SYSTEM_PROMPT }} `; /** * 默认工具使用示例(提取自 Cherry Studio) */ const DEFAULT_TOOL_USE_EXAMPLES = ` Here are a few examples using notional tools: --- User: Generate an image of the oldest person in this document. A: I can use the document_qa tool to find out who the oldest person is in the document. <tool_use> <name>document_qa</name> <arguments>{"document": "document.pdf", "question": "Who is the oldest person mentioned?"}</arguments> </tool_use> User: <tool_use_result> <name>document_qa</name> <result>John Doe, a 55 year old lumberjack living in Newfoundland.</result> </tool_use_result> A: I can use the image_generator tool to create a portrait of John Doe. <tool_use> <name>image_generator</name> <arguments>{"prompt": "A portrait of John Doe, a 55-year-old man living in Canada."}</arguments> </tool_use> User: <tool_use_result> <name>image_generator</name> <result>image.png</result> </tool_use_result> A: the image is generated as image.png --- User: "What is the result of the following operation: 5 + 3 + 1294.678?" A: I can use the python_interpreter tool to calculate the result of the operation. <tool_use> <name>python_interpreter</name> <arguments>{"code": "5 + 3 + 1294.678"}</arguments> </tool_use> User: <tool_use_result> <name>python_interpreter</name> <result>1302.678</result> </tool_use_result> A: The result of the operation is 1302.678. --- User: "Which city has the highest population , Guangzhou or Shanghai?" A: I can use the search tool to find the population of Guangzhou. <tool_use> <name>search</name> <arguments>{"query": "Population Guangzhou"}</arguments> </tool_use> User: <tool_use_result> <name>search</name> <result>Guangzhou has a population of 15 million inhabitants as of 2021.</result> </tool_use_result> A: I can use the search tool to find the population of Shanghai. <tool_use> <name>search</name> <arguments>{"query": "Population Shanghai"}</arguments> </tool_use> User: <tool_use_result> <name>search</name> <result>26 million (2019)</result> </tool_use_result> A: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population.`; /** * 构建可用工具部分(提取自 Cherry Studio) */ function buildAvailableTools(tools) { const availableTools = Object.keys(tools); if (availableTools.length === 0) return null; return `<tools> ${availableTools.map((toolName) => { const tool = tools[toolName]; return ` <tool> <name>${toolName}</name> <description>${tool.description || ""}</description> <arguments> ${tool.inputSchema ? JSON.stringify(tool.inputSchema) : ""} </arguments> </tool> `; }).join("\n")} </tools>`; } /** * 默认的系统提示符构建函数(提取自 Cherry Studio) */ function defaultBuildSystemPrompt(userSystemPrompt, tools, mcpMode) { const availableTools = buildAvailableTools(tools); if (availableTools === null) return userSystemPrompt; if (mcpMode == "auto") return DEFAULT_SYSTEM_PROMPT.replace("{{ TOOLS_INFO }}", "").replace("{{ USER_SYSTEM_PROMPT }}", userSystemPrompt || ""); const toolsInfo = ` ## Tool Use Examples {{ TOOL_USE_EXAMPLES }} ## Tool Use Available Tools Above example were using notional tools that might not exist for you. You only have access to these tools: {{ AVAILABLE_TOOLS }}`.replace("{{ TOOL_USE_EXAMPLES }}", DEFAULT_TOOL_USE_EXAMPLES).replace("{{ AVAILABLE_TOOLS }}", availableTools); return DEFAULT_SYSTEM_PROMPT.replace("{{ TOOLS_INFO }}", toolsInfo).replace("{{ USER_SYSTEM_PROMPT }}", userSystemPrompt || ""); } /** * 默认工具解析函数(提取自 Cherry Studio) * 解析 XML 格式的工具调用 */ function defaultParseToolUse(content, tools) { if (!content || !tools || Object.keys(tools).length === 0) return { results: [], content }; let contentToProcess = content; if (!content.includes("<tool_use>")) contentToProcess = `<tool_use>\n${content}\n</tool_use>`; const toolUsePattern = /<tool_use>([\s\S]*?)<name>([\s\S]*?)<\/name>([\s\S]*?)<arguments>([\s\S]*?)<\/arguments>([\s\S]*?)<\/tool_use>/g; const results = []; let match; let idx = 0; while ((match = toolUsePattern.exec(contentToProcess)) !== null) { const fullMatch = match[0]; let toolName = match[2].trim(); switch (toolName.toLowerCase()) { case "search": toolName = "mcp__CherryHub__search"; break; case "exec": toolName = "mcp__CherryHub__exec"; break; default: break; } const toolArgs = match[4].trim(); let parsedArgs; try { parsedArgs = JSON.parse(toolArgs); } catch (error) { parsedArgs = toolArgs; } if (!tools[toolName]) { console.warn(`Tool "${toolName}" not found in available tools`); continue; } results.push({ id: `${toolName}-${idx++}`, toolName, arguments: parsedArgs, status: "pending" }); contentToProcess = contentToProcess.replace(fullMatch, ""); } return { results, content: contentToProcess }; } const createPromptToolUsePlugin = (config = {}) => { const { enabled = true, buildSystemPrompt = defaultBuildSystemPrompt, parseToolUse = defaultParseToolUse, mcpMode } = config; return require_plugins.definePlugin({ name: "built-in:prompt-tool-use", transformParams: (params, context) => { if (!enabled || !params.tools || typeof params.tools !== "object") return params; const providerDefinedTools = {}; const promptTools = {}; for (const [toolName, tool] of Object.entries(params.tools)) if (tool.type === "provider") providerDefinedTools[toolName] = tool; else promptTools[toolName] = tool; if (Object.keys(promptTools).length > 0) context.mcpTools = promptTools; if (context.isRecursiveCall) { const transformedParams = { ...params, tools: Object.keys(providerDefinedTools).length > 0 ? providerDefinedTools : void 0 }; context.originalParams = transformedParams; return transformedParams; } const systemPrompt = buildSystemPrompt(typeof params.system === "string" ? params.system : "", promptTools, mcpMode); const transformedParams = { ...params, ...systemPrompt ? { system: systemPrompt } : {}, tools: Object.keys(providerDefinedTools).length > 0 ? providerDefinedTools : void 0 }; context.originalParams = transformedParams; return transformedParams; }, transformStream: (_, context) => () => { let textBuffer = ""; if (!context.mcpTools) return new TransformStream(); if (!context.accumulatedUsage) context.accumulatedUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, reasoningTokens: 0, cachedInputTokens: 0 }; if (context.hasExecutedToolsInCurrentStep === void 0) context.hasExecutedToolsInCurrentStep = false; const toolExecutor = new ToolExecutor(); const streamEventManager = new StreamEventManager(); const tagExtractor = new TagExtractor(TOOL_USE_TAG_CONFIG); let pendingTextStart = null; let hasStartedText = false; return new TransformStream({ async transform(chunk, controller) { if (chunk.type === "text-start") { pendingTextStart = chunk; return; } if (chunk.type === "text-delta") { textBuffer += chunk.text || ""; const extractionResults = tagExtractor.processText(chunk.text || ""); for (const result of extractionResults) if (!result.isTagContent && result.content) { if (!hasStartedText && pendingTextStart) { controller.enqueue(pendingTextStart); hasStartedText = true; pendingTextStart = null; } const filteredChunk = { ...chunk, text: result.content }; controller.enqueue(filteredChunk); } return; } if (chunk.type === "text-end") { if (hasStartedText) controller.enqueue(chunk); return; } if (chunk.type === "finish-step") { const tools = context.mcpTools; if (tools && Object.keys(tools).length > 0 && !context.hasExecutedToolsInCurrentStep) { const { results: parsedTools } = parseToolUse(textBuffer, tools); const validToolUses = parsedTools.filter((t) => t.status === "pending"); if (validToolUses.length > 0) { context.hasExecutedToolsInCurrentStep = true; const executedResults = await toolExecutor.executeTools(validToolUses, tools, controller); streamEventManager.sendStepFinishEvent(controller, chunk, context, "tool-calls"); const toolResultsText = toolExecutor.formatToolResults(executedResults); const recursiveParams = streamEventManager.buildRecursiveParams(context, textBuffer, toolResultsText, tools); await streamEventManager.handleRecursiveCall(controller, recursiveParams, context); return; } } if (chunk.usage && context.accumulatedUsage) streamEventManager.accumulateUsage(context.accumulatedUsage, chunk.usage); controller.enqueue(chunk); textBuffer = ""; return; } if (chunk.type === "finish") { controller.enqueue({ ...chunk, totalUsage: context.accumulatedUsage }); return; } if (chunk.type !== "text-start") controller.enqueue(chunk); }, flush() { pendingTextStart = null; hasStartedText = false; } }); } }); }; //#endregion exports.DEFAULT_SYSTEM_PROMPT = DEFAULT_SYSTEM_PROMPT; exports.createPromptToolUsePlugin = createPromptToolUsePlugin; exports.providerToolPlugin = providerToolPlugin;