mulby-cli 1.1.5

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.
Files changed (59) hide show
  1. package/PLUGIN_DEVELOP_PROMPT.md +1164 -0
  2. package/README.md +852 -0
  3. package/assets/default-icon.png +0 -0
  4. package/dist/commands/ai-session.js +44 -0
  5. package/dist/commands/build.js +111 -0
  6. package/dist/commands/config-ai.js +291 -0
  7. package/dist/commands/config.js +53 -0
  8. package/dist/commands/create/ai-create.js +183 -0
  9. package/dist/commands/create/assets.js +53 -0
  10. package/dist/commands/create/basic.js +72 -0
  11. package/dist/commands/create/index.js +73 -0
  12. package/dist/commands/create/react.js +136 -0
  13. package/dist/commands/create/templates/basic.js +383 -0
  14. package/dist/commands/create/templates/react/backend.js +72 -0
  15. package/dist/commands/create/templates/react/config.js +166 -0
  16. package/dist/commands/create/templates/react/docs.js +78 -0
  17. package/dist/commands/create/templates/react/hooks.js +469 -0
  18. package/dist/commands/create/templates/react/index.js +41 -0
  19. package/dist/commands/create/templates/react/types.js +1228 -0
  20. package/dist/commands/create/templates/react/ui.js +528 -0
  21. package/dist/commands/create/templates/react.js +1888 -0
  22. package/dist/commands/dev.js +141 -0
  23. package/dist/commands/pack.js +160 -0
  24. package/dist/commands/resume.js +97 -0
  25. package/dist/commands/test-ui.js +50 -0
  26. package/dist/index.js +71 -0
  27. package/dist/services/ai/PLUGIN_API.md +1102 -0
  28. package/dist/services/ai/PLUGIN_DEVELOP_PROMPT.md +1164 -0
  29. package/dist/services/ai/context-manager.js +639 -0
  30. package/dist/services/ai/index.js +88 -0
  31. package/dist/services/ai/knowledge.js +52 -0
  32. package/dist/services/ai/prompts.js +114 -0
  33. package/dist/services/ai/providers/base.js +38 -0
  34. package/dist/services/ai/providers/claude.js +284 -0
  35. package/dist/services/ai/providers/deepseek.js +28 -0
  36. package/dist/services/ai/providers/gemini.js +191 -0
  37. package/dist/services/ai/providers/glm.js +31 -0
  38. package/dist/services/ai/providers/minimax.js +27 -0
  39. package/dist/services/ai/providers/openai.js +177 -0
  40. package/dist/services/ai/tools.js +204 -0
  41. package/dist/services/ai-generator.js +968 -0
  42. package/dist/services/config-manager.js +117 -0
  43. package/dist/services/dependency-manager.js +236 -0
  44. package/dist/services/file-writer.js +66 -0
  45. package/dist/services/plan-adapter.js +244 -0
  46. package/dist/services/plan-command-handler.js +172 -0
  47. package/dist/services/plan-manager.js +502 -0
  48. package/dist/services/session-manager.js +113 -0
  49. package/dist/services/task-analyzer.js +136 -0
  50. package/dist/services/tui/index.js +57 -0
  51. package/dist/services/tui/store.js +123 -0
  52. package/dist/types/ai.js +172 -0
  53. package/dist/types/plan.js +2 -0
  54. package/dist/ui/Terminal.js +56 -0
  55. package/dist/ui/components/InputArea.js +176 -0
  56. package/dist/ui/components/LogArea.js +19 -0
  57. package/dist/ui/components/PlanPanel.js +69 -0
  58. package/dist/ui/components/SelectArea.js +13 -0
  59. package/package.json +45 -0
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIServiceFactory = void 0;
4
+ const config_manager_1 = require("../config-manager");
5
+ const openai_1 = require("./providers/openai");
6
+ const claude_1 = require("./providers/claude");
7
+ const deepseek_1 = require("./providers/deepseek");
8
+ const gemini_1 = require("./providers/gemini");
9
+ const glm_1 = require("./providers/glm");
10
+ const minimax_1 = require("./providers/minimax");
11
+ const ai_1 = require("../../types/ai");
12
+ class AIServiceFactory {
13
+ /**
14
+ * 创建 AI 服务实例
15
+ * @param providerName 供应商名称,如果不指定则使用默认配置
16
+ * @param modelOverride 临时覆盖模型名称
17
+ */
18
+ static create(providerName, modelOverride) {
19
+ const configManager = config_manager_1.ConfigManager.getInstance();
20
+ const aiConfig = configManager.get('ai');
21
+ if (!aiConfig || !aiConfig.providers || Object.keys(aiConfig.providers).length === 0) {
22
+ throw new Error('未配置 AI 服务。请使用 `mulby ai add <name>` 添加供应商配置。');
23
+ }
24
+ // 确定使用哪个供应商配置
25
+ const targetProviderName = providerName || aiConfig.default || Object.keys(aiConfig.providers)[0];
26
+ const providerConfig = aiConfig.providers[targetProviderName];
27
+ if (!providerConfig) {
28
+ throw new Error(`未找到供应商配置 "${targetProviderName}"。可用的配置: ${Object.keys(aiConfig.providers).join(', ')}`);
29
+ }
30
+ // 合并默认配置
31
+ const mergedConfig = {
32
+ ...ai_1.DEFAULT_PROVIDER_CONFIG,
33
+ ...providerConfig
34
+ };
35
+ // 如果指定了模型覆盖,则使用覆盖的模型
36
+ if (modelOverride) {
37
+ mergedConfig.model = modelOverride;
38
+ }
39
+ // 根据供应商类型创建实例
40
+ return this.createProvider(mergedConfig);
41
+ }
42
+ /**
43
+ * 根据配置创建供应商实例
44
+ */
45
+ static createProvider(config) {
46
+ switch (config.provider) {
47
+ case 'claude':
48
+ return new claude_1.ClaudeProvider(config);
49
+ case 'deepseek':
50
+ return new deepseek_1.DeepSeekProvider(config);
51
+ case 'gemini':
52
+ return new gemini_1.GeminiProvider(config);
53
+ case 'glm':
54
+ return new glm_1.GLMProvider(config);
55
+ case 'minimax':
56
+ return new minimax_1.MiniMaxProvider(config);
57
+ case 'openai':
58
+ case 'custom':
59
+ default:
60
+ return new openai_1.OpenAIProvider(config);
61
+ }
62
+ }
63
+ /**
64
+ * 获取所有已配置的供应商列表
65
+ */
66
+ static listProviders() {
67
+ const configManager = config_manager_1.ConfigManager.getInstance();
68
+ const aiConfig = configManager.get('ai');
69
+ return aiConfig?.providers ? Object.keys(aiConfig.providers) : [];
70
+ }
71
+ /**
72
+ * 获取当前默认供应商名称
73
+ */
74
+ static getDefaultProvider() {
75
+ const configManager = config_manager_1.ConfigManager.getInstance();
76
+ const aiConfig = configManager.get('ai');
77
+ return aiConfig?.default;
78
+ }
79
+ /**
80
+ * 获取指定供应商的配置
81
+ */
82
+ static getProviderConfig(providerName) {
83
+ const configManager = config_manager_1.ConfigManager.getInstance();
84
+ const aiConfig = configManager.get('ai');
85
+ return aiConfig?.providers?.[providerName];
86
+ }
87
+ }
88
+ exports.AIServiceFactory = AIServiceFactory;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getPluginDevelopGuide = getPluginDevelopGuide;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ function getPluginDevelopGuide() {
40
+ try {
41
+ // 直接从当前目录读取,因为 CLI 会打包发布到 npm
42
+ const filePath = path.join(__dirname, 'PLUGIN_DEVELOP_PROMPT.md');
43
+ if (fs.existsSync(filePath)) {
44
+ return fs.readFileSync(filePath, 'utf-8');
45
+ }
46
+ console.warn('PLUGIN_DEVELOP_PROMPT.md not found at:', filePath);
47
+ }
48
+ catch (e) {
49
+ console.warn('Failed to load PLUGIN_DEVELOP_PROMPT.md', e);
50
+ }
51
+ return '';
52
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.USER_GUIDE_PROMPT = exports.SYSTEM_PROMPT = void 0;
4
+ exports.buildSystemPrompt = buildSystemPrompt;
5
+ const knowledge_1 = require("./knowledge");
6
+ function buildSystemPrompt(templates, isScaffolded = false, fileMap) {
7
+ const guide = (0, knowledge_1.getPluginDevelopGuide)();
8
+ let templateSection = '';
9
+ if (templates && Object.keys(templates).length > 0) {
10
+ templateSection = `
11
+ ## Reference Templates (Usage Guide)
12
+ 1. **Structure Only**: You MUST use these templates as the base for file structure and config (manifest.json, package.json).
13
+ 2. **UI/Style Freedom**: For UI code (App.tsx, styles.css), treat these as EXAMPLES only. Do NOT copy the style. You are encouraged to design a better, more unique UI while keeping the structural correctness.
14
+
15
+ ${Object.entries(templates).map(([filename, content]) => `
16
+ ### ${filename}
17
+ \`\`\`${filename.endsWith('json') ? 'json' : 'typescript'}
18
+ ${content}
19
+ \`\`\`
20
+ `).join('\n')}
21
+ `;
22
+ }
23
+ const scaffoldInfo = isScaffolded
24
+ ? `
25
+ ## Current State: Scaffolded & Ready for Design 🏗️
26
+ The project structure has ALREADY been created (React + Vite + Mulby API).
27
+ **Stack**: React 18, Tailwind CSS v3, Vite.
28
+ **Your Goal**: Now act as a Product Consultant to define the *content* and *logic* within this structure.
29
+
30
+ **Resources Available**:
31
+ - You have the full file tree in context.
32
+ - You can read any file (like \`src/ui/App.tsx\` or \`manifest.json\`) to understand where to add code.
33
+ - You have the \`PLUGIN_DEVELOP_PROMPT.md\` guide.
34
+ `
35
+ : `
36
+ ## Current State: NOT Scaffolded ⚠️
37
+ **IMPORTANT**: The project directory is EMPTY. You CANNOT write code yet!
38
+ You MUST first complete Phase 1 (Product Consultant) to gather requirements.
39
+ Only after user confirms requirements, call \`scaffold_project\` tool to create the project structure.
40
+ `;
41
+ const fileMapSection = fileMap ? `
42
+ ## Current Project Structure
43
+ (Auto-updated file tree)
44
+ \`\`\`
45
+ ${fileMap}
46
+ \`\`\`
47
+ ` : '';
48
+ return `
49
+ # Role: Mulby 插件开发专家 (Interactive Agent)
50
+
51
+ 你是一位通过交互式代理模式工作的 Mulby 插件开发专家。
52
+ 你的目标不仅仅是写代码,而是**作为产品经理和高级工程师**,引导用户挖掘需求,设计出色的插件。
53
+
54
+ **重要提示**: Mulby 及其插件是基于 **Electron** 框架开发的。请在设计和编码时始终遵循 Electron 的多进程架构原则(渲染进程 vs 主进程),并合理使用 preload 脚本进行通信。
55
+
56
+ ## Core Knowledge & Guidelines
57
+ ${guide}
58
+
59
+ ${templateSection}
60
+
61
+ ${scaffoldInfo}
62
+
63
+ ${fileMapSection}
64
+
65
+ ## 🚨 CRITICAL WORKFLOW (You MUST follow this order)
66
+
67
+ ### Phase 1: Product Consultant (MANDATORY FIRST STEP)
68
+ **Your FIRST action MUST be calling \`ask_user\` to start requirements gathering.**
69
+
70
+ ${isScaffolded ? 'You SHOULD read existing files (e.g., `read_file src/ui/App.tsx`) to understand the base structure before proposing changes.' : 'DO NOT read files, DO NOT write files until scaffold is created.'}
71
+
72
+ Ask questions like:
73
+ 1. "这个插件具体要实现什么功能?" (Features)
74
+ 2. "你希望 UI 是什么风格?" (UI Design - *refer to existing App.tsx if applicable*)
75
+ 3. "触发方式是什么?" (Trigger - *check manifest.json*)
76
+ 4. "需要 Node.js 后端能力吗?" (System APIs)
77
+
78
+ **Repeat \`ask_user\` until you have a clear picture of user needs.**
79
+
80
+ ### Phase 2: Confirm & Planning
81
+ When requirements are clear:
82
+ 1. Summarize the requirements back to user.
83
+ 2. ${isScaffolded ? 'Explain how you will implement this in the current structure (e.g., "I will modify App.tsx to add...").' : 'Ask for confirmation to create the scaffold.'}
84
+ 3. Ask: "准备好开始开发了吗?" (Ready to code?)
85
+
86
+ ### Phase 3: Implementation
87
+ ${isScaffolded ? 'Once confirmed:' : 'After scaffolding:'}
88
+ 1. Read relevant files to get fresh context.
89
+ 2. Implement features using \`write_file\` and \`replace_in_file\`.
90
+ 3. Install dependencies if needed with \`run_command\`.
91
+ 4. **Always keep the user informed of what you are building.**
92
+
93
+ ### ⛔️ FORBIDDEN ACTIONS
94
+ 1. **NO HTML Previews**: NEVER create \`preview.html\`, \`demo.html\`, etc.
95
+ 2. **NO Junk files**: DO NOT create \`ICON_INSTRUCTIONS.md\`, \`README_TEMP.txt\`, etc.
96
+ 3. **NO UI Tests**: DO NOT create \`*.test.tsx\` or \`*.spec.ts\`
97
+ 4. **NO skipping Phase 1**: You MUST ask questions before writing code.
98
+ 5. **Use SVG for Icons**: DO NOT create \`icon.png\` or any raster images.
99
+ 6. **NO Dev Server**: DO NOT run \`npm run dev\`, \`vite\`, or any watch mode commands. Testing is done by the user in the host app.
100
+
101
+ If the user needs Node.js capabilities (fs, child_process, etc.), you MUST:
102
+ 1. Create \`preload.cjs\` (CommonJS format) if it doesn't exist.
103
+ 2. Configure \`"preload": "preload.cjs"\` in \`manifest.json\`.
104
+
105
+ **NOW START**: Your first action should be \`ask_user\` to greet the user and ask about the plugin's intended functionality.
106
+ `;
107
+ }
108
+ exports.SYSTEM_PROMPT = buildSystemPrompt();
109
+ exports.USER_GUIDE_PROMPT = `
110
+ 请描述你想开发的插件。例如:
111
+ - "PDF 合并工具"
112
+ - "Base64 编解码器"
113
+ - "批量图片压缩"
114
+ `;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseAIProvider = void 0;
4
+ const ai_1 = require("../../../types/ai");
5
+ class BaseAIProvider {
6
+ constructor(config) {
7
+ this.config = config;
8
+ }
9
+ /**
10
+ * Get the context window size for the current model
11
+ * @returns Context window size in tokens
12
+ */
13
+ getContextWindow() {
14
+ // 1. Use user-configured contextWindow if available
15
+ if (this.config.contextWindow) {
16
+ return this.config.contextWindow;
17
+ }
18
+ // 2. Infer from model name
19
+ const modelName = this.config.model || '';
20
+ const modelInfo = (0, ai_1.getModelContextWindow)(modelName, this.config.provider);
21
+ return modelInfo.contextWindow;
22
+ }
23
+ /**
24
+ * Get the recommended max output tokens for the current model
25
+ * @returns Max output tokens
26
+ */
27
+ getMaxOutputTokens() {
28
+ // 1. Use user-configured maxTokens if available
29
+ if (this.config.maxTokens) {
30
+ return this.config.maxTokens;
31
+ }
32
+ // 2. Infer from model name
33
+ const modelName = this.config.model || '';
34
+ const modelInfo = (0, ai_1.getModelContextWindow)(modelName, this.config.provider);
35
+ return modelInfo.maxOutput || 4096;
36
+ }
37
+ }
38
+ exports.BaseAIProvider = BaseAIProvider;
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ClaudeProvider = void 0;
7
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
8
+ const base_1 = require("./base");
9
+ class ClaudeProvider extends base_1.BaseAIProvider {
10
+ constructor(config) {
11
+ super(config);
12
+ this.client = new sdk_1.default({
13
+ apiKey: config.apiKey,
14
+ baseURL: config.apiEndpoint, // Optional custom endpoint
15
+ timeout: (config.timeout || 60) * 1000,
16
+ });
17
+ }
18
+ /**
19
+ * 将 OpenAI 格式的消息转换为 Anthropic API 格式
20
+ *
21
+ * OpenAI 格式:
22
+ * - { role: 'assistant', content: '...', tool_calls: [...] }
23
+ * - { role: 'tool', tool_call_id: '...', content: '...' }
24
+ *
25
+ * Anthropic 格式:
26
+ * - { role: 'assistant', content: [{ type: 'text', text: '...' }, { type: 'tool_use', id: '...', name: '...', input: {...} }] }
27
+ * - { role: 'user', content: [{ type: 'tool_result', tool_use_id: '...', content: '...' }] }
28
+ */
29
+ convertMessagesToAnthropicFormat(messages) {
30
+ const result = [];
31
+ let i = 0;
32
+ while (i < messages.length) {
33
+ const msg = messages[i];
34
+ // 跳过 system 消息(会作为顶层参数传递)
35
+ if (msg.role === 'system') {
36
+ i++;
37
+ continue;
38
+ }
39
+ if (msg.role === 'assistant') {
40
+ const contentBlocks = [];
41
+ // 添加文本内容
42
+ if (msg.content) {
43
+ const textContent = typeof msg.content === 'string' ? msg.content :
44
+ (Array.isArray(msg.content) ? msg.content : '');
45
+ if (typeof textContent === 'string' && textContent.trim()) {
46
+ contentBlocks.push({ type: 'text', text: textContent });
47
+ }
48
+ else if (Array.isArray(textContent)) {
49
+ // 如果是内容块数组,直接使用
50
+ contentBlocks.push(...textContent);
51
+ }
52
+ }
53
+ // 转换 tool_calls 为 tool_use blocks
54
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
55
+ for (const toolCall of msg.tool_calls) {
56
+ let input = {};
57
+ try {
58
+ input = typeof toolCall.function.arguments === 'string'
59
+ ? JSON.parse(toolCall.function.arguments)
60
+ : toolCall.function.arguments;
61
+ }
62
+ catch {
63
+ input = {};
64
+ }
65
+ contentBlocks.push({
66
+ type: 'tool_use',
67
+ id: toolCall.id,
68
+ name: toolCall.function.name,
69
+ input: input
70
+ });
71
+ }
72
+ }
73
+ // 如果没有任何内容,添加空文本
74
+ if (contentBlocks.length === 0) {
75
+ contentBlocks.push({ type: 'text', text: '' });
76
+ }
77
+ result.push({
78
+ role: 'assistant',
79
+ content: contentBlocks
80
+ });
81
+ i++;
82
+ }
83
+ else if (msg.role === 'tool') {
84
+ // 收集连续的 tool 消息,合并为一个 user 消息
85
+ const toolResultBlocks = [];
86
+ while (i < messages.length && messages[i].role === 'tool') {
87
+ const toolMsg = messages[i];
88
+ toolResultBlocks.push({
89
+ type: 'tool_result',
90
+ tool_use_id: toolMsg.tool_call_id,
91
+ content: typeof toolMsg.content === 'string' ? toolMsg.content : JSON.stringify(toolMsg.content)
92
+ });
93
+ i++;
94
+ }
95
+ result.push({
96
+ role: 'user',
97
+ content: toolResultBlocks
98
+ });
99
+ }
100
+ else if (msg.role === 'user') {
101
+ // 普通 user 消息
102
+ let content;
103
+ if (typeof msg.content === 'string' || msg.content === null) {
104
+ content = msg.content || '';
105
+ }
106
+ else if (Array.isArray(msg.content)) {
107
+ content = msg.content;
108
+ }
109
+ else {
110
+ content = '';
111
+ }
112
+ result.push({
113
+ role: 'user',
114
+ content: content
115
+ });
116
+ i++;
117
+ }
118
+ else {
119
+ i++;
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+ parseXMLToolCalls(content) {
125
+ const toolCalls = [];
126
+ // Regex to match <tool_name>... content ...</tool_name>
127
+ // Use [\s\S]*? for non-greedy match across newlines
128
+ // We look for patterns that look like tool usage
129
+ const toolRegex = /<([a-zA-Z0-9_]+)>\s*\n([\s\S]*?)\n\s*<\/\1>/g;
130
+ let match;
131
+ while ((match = toolRegex.exec(content)) !== null) {
132
+ const toolName = match[1];
133
+ const toolContent = match[2];
134
+ // Skip if it looks like a thinking block or other non-tool tags we know
135
+ if (toolName === 'think' || toolName === 'thought' || toolName === 'thinking')
136
+ continue;
137
+ // Simple key-value parser for arguments
138
+ // Assumes format: key: "value" or key: value
139
+ const args = {};
140
+ const lines = toolContent.split('\n');
141
+ for (const line of lines) {
142
+ const parts = line.split(':');
143
+ if (parts.length >= 2) {
144
+ const key = parts[0].trim();
145
+ let value = parts.slice(1).join(':').trim();
146
+ // Remove quotes if present
147
+ if ((value.startsWith('"') && value.endsWith('"')) ||
148
+ (value.startsWith("'") && value.endsWith("'"))) {
149
+ value = value.substring(1, value.length - 1);
150
+ }
151
+ if (key && value) {
152
+ args[key] = value;
153
+ }
154
+ }
155
+ }
156
+ toolCalls.push({
157
+ id: `call_${Math.random().toString(36).substring(2, 11)}`,
158
+ type: 'function',
159
+ function: {
160
+ name: toolName,
161
+ arguments: JSON.stringify(args)
162
+ }
163
+ });
164
+ }
165
+ return toolCalls;
166
+ }
167
+ async chat(messages, options) {
168
+ const model = options?.model || this.config.model || 'claude-3-5-sonnet-20241022';
169
+ // maxTokens 是最大输出 token 数,使用 getMaxOutputTokens() 获取模型默认值
170
+ let maxTokens = options?.maxTokens || this.config.maxTokens || this.getMaxOutputTokens();
171
+ maxTokens = Number(maxTokens);
172
+ if (isNaN(maxTokens))
173
+ maxTokens = 8192; // claude-3-5-sonnet 默认最大输出
174
+ // Convert messages to Anthropic format
175
+ // System message is a top-level parameter in Anthropic API
176
+ const systemMessage = messages.find(m => m.role === 'system');
177
+ const anthropicMessages = this.convertMessagesToAnthropicFormat(messages);
178
+ const params = {
179
+ model: model,
180
+ messages: anthropicMessages,
181
+ max_tokens: maxTokens,
182
+ temperature: options?.temperature,
183
+ system: typeof systemMessage?.content === 'string' ? systemMessage.content : undefined,
184
+ };
185
+ if (options?.tools && options.tools.length > 0) {
186
+ params.tools = options.tools.map(t => ({
187
+ name: t.function.name,
188
+ description: t.function.description,
189
+ input_schema: t.function.parameters
190
+ }));
191
+ }
192
+ const response = await this.client.messages.create(params);
193
+ const thinkingBlock = response.content.find((c) => c.type === 'thinking');
194
+ const contentBlock = response.content.find((c) => c.type === 'text');
195
+ const toolUseBlocks = response.content.filter((c) => c.type === 'tool_use');
196
+ let toolCalls;
197
+ if (toolUseBlocks.length > 0) {
198
+ toolCalls = toolUseBlocks.map((block) => ({
199
+ id: block.id,
200
+ function: {
201
+ name: block.name,
202
+ arguments: JSON.stringify(block.input)
203
+ },
204
+ type: 'function'
205
+ }));
206
+ }
207
+ const textContent = contentBlock && contentBlock.type === 'text' ? contentBlock.text : null;
208
+ // Fallback: If no structured tool calls, try parsing from text content
209
+ if ((!toolCalls || toolCalls.length === 0) && textContent) {
210
+ const parsedTools = this.parseXMLToolCalls(textContent);
211
+ if (parsedTools.length > 0) {
212
+ toolCalls = parsedTools;
213
+ }
214
+ }
215
+ return {
216
+ content: textContent,
217
+ reasoning_content: thinkingBlock && thinkingBlock.type === 'thinking' ? thinkingBlock.thinking : undefined,
218
+ toolCalls: toolCalls,
219
+ usage: {
220
+ promptTokens: response.usage.input_tokens,
221
+ completionTokens: response.usage.output_tokens,
222
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens
223
+ }
224
+ };
225
+ }
226
+ async chatStream(messages, onChunk, options) {
227
+ const model = options?.model || this.config.model || 'claude-3-5-sonnet-20241022';
228
+ // maxTokens 是最大输出 token 数,使用 getMaxOutputTokens() 获取模型默认值
229
+ let maxTokens = options?.maxTokens || this.config.maxTokens || this.getMaxOutputTokens();
230
+ maxTokens = Number(maxTokens);
231
+ if (isNaN(maxTokens))
232
+ maxTokens = 8192; // claude-3-5-sonnet 默认最大输出
233
+ const systemMessage = messages.find(m => m.role === 'system');
234
+ const anthropicMessages = this.convertMessagesToAnthropicFormat(messages);
235
+ const streamParams = {
236
+ model: model,
237
+ messages: anthropicMessages,
238
+ max_tokens: maxTokens,
239
+ temperature: options?.temperature,
240
+ system: typeof systemMessage?.content === 'string' ? systemMessage.content : undefined,
241
+ };
242
+ // 添加 tools 参数,确保流式模式下也能使用工具调用
243
+ if (options?.tools && options.tools.length > 0) {
244
+ streamParams.tools = options.tools.map(t => ({
245
+ name: t.function.name,
246
+ description: t.function.description,
247
+ input_schema: t.function.parameters
248
+ }));
249
+ }
250
+ const stream = this.client.messages.stream(streamParams);
251
+ let fullContent = '';
252
+ stream.on('text', (text) => {
253
+ fullContent += text;
254
+ onChunk(text);
255
+ });
256
+ const finalMessage = await stream.finalMessage();
257
+ const thinkingBlock = finalMessage.content.find((c) => c.type === 'thinking');
258
+ const toolUseBlocks = finalMessage.content.filter((c) => c.type === 'tool_use');
259
+ let toolCalls;
260
+ if (toolUseBlocks.length > 0) {
261
+ toolCalls = toolUseBlocks.map((block) => ({
262
+ id: block.id,
263
+ function: {
264
+ name: block.name,
265
+ arguments: JSON.stringify(block.input)
266
+ },
267
+ type: 'function'
268
+ }));
269
+ }
270
+ // Try parsing tool calls from the full content if available and no native tool calls found
271
+ if ((!toolCalls || toolCalls.length === 0) && fullContent) {
272
+ const parsedTools = this.parseXMLToolCalls(fullContent);
273
+ if (parsedTools.length > 0) {
274
+ toolCalls = parsedTools;
275
+ }
276
+ }
277
+ return {
278
+ content: fullContent,
279
+ reasoning_content: thinkingBlock && thinkingBlock.type === 'thinking' ? thinkingBlock.thinking : undefined,
280
+ toolCalls: toolCalls
281
+ };
282
+ }
283
+ }
284
+ exports.ClaudeProvider = ClaudeProvider;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeepSeekProvider = void 0;
4
+ const openai_1 = require("./openai");
5
+ /**
6
+ * DeepSeek Provider
7
+ * DeepSeek提供与OpenAI兼容的API接口
8
+ *
9
+ * 支持模型:
10
+ * - deepseek-chat: V3/V3.1 通用对话模型 (64K上下文, 8K输出)
11
+ * - deepseek-reasoner: R1 推理模型 (64K上下文, 32K输出)
12
+ */
13
+ class DeepSeekProvider extends openai_1.OpenAIProvider {
14
+ constructor(config) {
15
+ // DeepSeek使用OpenAI兼容接口
16
+ // 注意: maxTokens 是最大输出 token 数,不是上下文窗口
17
+ const deepseekConfig = {
18
+ ...config,
19
+ provider: 'deepseek',
20
+ apiEndpoint: config.apiEndpoint || 'https://api.deepseek.com',
21
+ model: config.model || 'deepseek-chat',
22
+ // deepseek-chat 默认最大输出 8K,deepseek-reasoner 可达 32K
23
+ // 不设置默认值,让 getMaxOutputTokens() 从模型推断
24
+ };
25
+ super(deepseekConfig);
26
+ }
27
+ }
28
+ exports.DeepSeekProvider = DeepSeekProvider;