@huyooo/ai-chat-core 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1563 @@
1
+ 'use strict';
2
+
3
+ var genai = require('@google/genai');
4
+
5
+ // src/agent.ts
6
+
7
+ // src/types.ts
8
+ var AVAILABLE_MODELS = [
9
+ {
10
+ provider: "doubao",
11
+ model: "doubao-seed-1-6-251015",
12
+ displayName: "\u8C46\u5305 Seed",
13
+ supportsTools: true,
14
+ supportsWebSearch: true,
15
+ supportedThinkingModes: ["enabled", "disabled"]
16
+ },
17
+ {
18
+ provider: "deepseek",
19
+ model: "deepseek-v3-1-terminus",
20
+ displayName: "DeepSeek V3",
21
+ supportsTools: true,
22
+ supportsWebSearch: true,
23
+ // 注意:火山引擎托管的 DeepSeek V3.1 不支持 auto 模式
24
+ supportedThinkingModes: ["enabled", "disabled"]
25
+ },
26
+ // 通义千问 - 混合思考模式(默认不开启)
27
+ {
28
+ provider: "qwen",
29
+ model: "qwen3-max-preview",
30
+ displayName: "\u901A\u4E49\u5343\u95EE Max",
31
+ supportsTools: true,
32
+ supportsWebSearch: true,
33
+ supportedThinkingModes: ["enabled", "disabled"]
34
+ },
35
+ {
36
+ provider: "qwen",
37
+ model: "qwen-plus",
38
+ displayName: "\u901A\u4E49\u5343\u95EE Plus",
39
+ supportsTools: true,
40
+ supportsWebSearch: true,
41
+ supportedThinkingModes: ["enabled", "disabled"]
42
+ },
43
+ // Gemini 3 - Google 最新推理模型
44
+ {
45
+ provider: "gemini",
46
+ model: "gemini-3-pro-preview",
47
+ displayName: "Gemini 3 Pro",
48
+ supportsTools: true,
49
+ supportsWebSearch: true,
50
+ // 支持 Google Search grounding
51
+ supportedThinkingModes: ["enabled", "disabled"]
52
+ // thinking_level: high/low
53
+ },
54
+ // OpenRouter - Claude Opus 4.5
55
+ {
56
+ provider: "openrouter",
57
+ model: "anthropic/claude-opus-4.5",
58
+ displayName: "Claude Opus 4.5",
59
+ supportsTools: true,
60
+ supportsWebSearch: true,
61
+ // 通过 :online 后缀或 plugins 支持
62
+ supportedThinkingModes: ["enabled", "disabled"]
63
+ // Claude 扩展思考
64
+ }
65
+ ];
66
+
67
+ // src/tools.ts
68
+ var DEFAULT_WORKING_DIR = process.cwd();
69
+ function createToolDefinitions(workingDir = DEFAULT_WORKING_DIR) {
70
+ return [
71
+ {
72
+ type: "function",
73
+ function: {
74
+ name: "execute_command",
75
+ description: `\u6267\u884C shell \u547D\u4EE4\u6765\u5B8C\u6210\u6587\u4EF6\u7BA1\u7406\u4EFB\u52A1\u3002\u652F\u6301\uFF1Als\u3001find\u3001cat\u3001head\u3001tail\u3001du\u3001file \u7B49\u547D\u4EE4\u3002\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55\uFF1A${workingDir}`,
76
+ parameters: {
77
+ type: "object",
78
+ properties: {
79
+ command: { type: "string", description: "\u8981\u6267\u884C\u7684 shell \u547D\u4EE4" },
80
+ workingDir: { type: "string", description: "\u5DE5\u4F5C\u76EE\u5F55\uFF08\u53EF\u9009\uFF09" }
81
+ },
82
+ required: ["command"]
83
+ }
84
+ }
85
+ },
86
+ {
87
+ type: "function",
88
+ function: {
89
+ name: "analyze_image",
90
+ description: `\u5206\u6790\u56FE\u7247\u5185\u5BB9\u3002\u53EF\u4EE5\u8BC6\u522B\u56FE\u7247\u4E2D\u7684\u7269\u4F53\u3001\u573A\u666F\u3001\u6587\u5B57\u3001\u4EBA\u7269\u7B49\u3002\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F\uFF1Ajpg\u3001jpeg\u3001png\u3001gif\u3001webp`,
91
+ parameters: {
92
+ type: "object",
93
+ properties: {
94
+ imagePath: { type: "string", description: "\u56FE\u7247\u6587\u4EF6\u7684\u5B8C\u6574\u8DEF\u5F84" },
95
+ question: { type: "string", description: "\u5173\u4E8E\u56FE\u7247\u7684\u95EE\u9898\uFF08\u53EF\u9009\uFF09" }
96
+ },
97
+ required: ["imagePath"]
98
+ }
99
+ }
100
+ },
101
+ {
102
+ type: "function",
103
+ function: {
104
+ name: "generate_image",
105
+ description: `\u6839\u636E\u6587\u5B57\u63CF\u8FF0\u751F\u6210\u56FE\u7247\u3002\u53EF\u4EE5\u751F\u6210\u5404\u79CD\u98CE\u683C\u7684\u56FE\u7247\u3002`,
106
+ parameters: {
107
+ type: "object",
108
+ properties: {
109
+ prompt: { type: "string", description: "\u56FE\u7247\u63CF\u8FF0" },
110
+ outputPath: { type: "string", description: "\u8F93\u51FA\u56FE\u7247\u7684\u4FDD\u5B58\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09" }
111
+ },
112
+ required: ["prompt"]
113
+ }
114
+ }
115
+ },
116
+ {
117
+ type: "function",
118
+ function: {
119
+ name: "analyze_video",
120
+ description: `\u5206\u6790\u89C6\u9891\u5185\u5BB9\u3002\u53EF\u4EE5\u8BC6\u522B\u89C6\u9891\u4E2D\u7684\u573A\u666F\u3001\u52A8\u4F5C\u3001\u7269\u4F53\u7B49\u3002\u652F\u6301\u7684\u89C6\u9891\u683C\u5F0F\uFF1Amp4\u3001mov\u3001avi\u3001webm`,
121
+ parameters: {
122
+ type: "object",
123
+ properties: {
124
+ videoPath: { type: "string", description: "\u89C6\u9891\u6587\u4EF6\u7684\u5B8C\u6574\u8DEF\u5F84" },
125
+ question: { type: "string", description: "\u5173\u4E8E\u89C6\u9891\u7684\u95EE\u9898\uFF08\u53EF\u9009\uFF09" }
126
+ },
127
+ required: ["videoPath"]
128
+ }
129
+ }
130
+ }
131
+ ];
132
+ }
133
+ var DANGEROUS_COMMANDS = [
134
+ "rm -rf /",
135
+ "rm -rf ~",
136
+ "format",
137
+ "mkfs",
138
+ "dd if=",
139
+ "shutdown",
140
+ "reboot",
141
+ "sudo rm",
142
+ "sudo format"
143
+ ];
144
+ function isDangerousCommand(command) {
145
+ const lowerCommand = command.toLowerCase().trim();
146
+ return DANGEROUS_COMMANDS.some(
147
+ (dangerous) => lowerCommand.includes(dangerous.toLowerCase())
148
+ );
149
+ }
150
+ function createDefaultToolExecutor(workingDir = DEFAULT_WORKING_DIR) {
151
+ return {
152
+ async executeCommand(command, cwd) {
153
+ if (isDangerousCommand(command)) {
154
+ return { success: false, error: "\u8BE5\u547D\u4EE4\u88AB\u5B89\u5168\u7B56\u7565\u963B\u6B62" };
155
+ }
156
+ try {
157
+ const { exec } = await import('child_process');
158
+ const { promisify } = await import('util');
159
+ const execAsync = promisify(exec);
160
+ const { stdout, stderr } = await execAsync(command, {
161
+ cwd: cwd || workingDir,
162
+ timeout: 3e4,
163
+ maxBuffer: 1024 * 1024 * 10,
164
+ shell: "/bin/sh"
165
+ });
166
+ return { success: true, output: (stdout || stderr || "").trim() };
167
+ } catch (error) {
168
+ return { success: false, error: String(error) };
169
+ }
170
+ },
171
+ async readFile(path) {
172
+ const fs = await import('fs/promises');
173
+ return fs.readFile(path);
174
+ },
175
+ async writeFile(path, data) {
176
+ const fs = await import('fs/promises');
177
+ await fs.writeFile(path, data);
178
+ },
179
+ async readDirectory(path) {
180
+ const fs = await import('fs/promises');
181
+ return fs.readdir(path);
182
+ }
183
+ };
184
+ }
185
+
186
+ // src/constants.ts
187
+ var DEFAULT_ARK_URL = "https://ark.cn-beijing.volces.com/api/v3";
188
+ var DEFAULT_QWEN_NATIVE_URL = "https://dashscope.aliyuncs.com/api/v1";
189
+ var DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1";
190
+ var DEFAULT_MODEL = "doubao-seed-1-6-251015";
191
+ var GEMINI_IMAGE_MODEL = "gemini-2.0-flash";
192
+ var GEMINI_IMAGE_GEN_MODEL = "gemini-3-pro-image-preview";
193
+ var MAX_ITERATIONS = 10;
194
+ var MODE_PROMPTS = {
195
+ agent: `\u4F60\u53EF\u4EE5\u4F7F\u7528\u5DE5\u5177\u5B8C\u6210\u4EFB\u52A1\u3002\u76F4\u63A5\u8C03\u7528\u5DE5\u5177\uFF0C\u5B8C\u6210\u540E\u7B80\u6D01\u603B\u7ED3\u7ED3\u679C\u3002`,
196
+ plan: `\u5206\u6790\u9700\u6C42\u5E76\u5236\u5B9A\u6267\u884C\u8BA1\u5212\uFF0C\u4EE5 Markdown \u683C\u5F0F\u8F93\u51FA\uFF0C\u7B49\u5F85\u786E\u8BA4\u540E\u6267\u884C\u3002`,
197
+ ask: `\u76F4\u63A5\u56DE\u7B54\u95EE\u9898\uFF0C\u4E0D\u77E5\u9053\u5C31\u8BF4\u4E0D\u77E5\u9053\u3002`
198
+ };
199
+
200
+ // src/providers/ark.ts
201
+ var ARK_MODELS = [
202
+ "doubao-seed-1-6-251015",
203
+ "deepseek-v3-1-terminus"
204
+ ];
205
+ var MODEL_CAPABILITIES = {
206
+ "doubao-seed-1-6-251015": {
207
+ supportsTools: true,
208
+ supportsWebSearch: true,
209
+ supportsThinking: true,
210
+ supportedThinkingModes: ["enabled", "disabled"]
211
+ },
212
+ "deepseek-v3-1-terminus": {
213
+ supportsTools: true,
214
+ supportsWebSearch: true,
215
+ supportsThinking: true,
216
+ supportedThinkingModes: ["enabled", "disabled"]
217
+ }
218
+ };
219
+ var DEFAULT_CAPABILITIES = {
220
+ supportsTools: true,
221
+ supportsWebSearch: true,
222
+ supportsThinking: true,
223
+ supportedThinkingModes: ["enabled", "disabled"]
224
+ };
225
+ function convertToResponsesApiTools(tools) {
226
+ return tools.map((tool) => {
227
+ const t = tool;
228
+ return {
229
+ type: "function",
230
+ name: t.function.name,
231
+ description: t.function.description,
232
+ parameters: t.function.parameters
233
+ };
234
+ });
235
+ }
236
+ function convertToResponsesApiInput(systemPrompt, history, message) {
237
+ const input = [];
238
+ input.push({
239
+ role: "system",
240
+ content: systemPrompt
241
+ });
242
+ for (const msg of history) {
243
+ if (msg.role === "user") {
244
+ input.push({
245
+ role: "user",
246
+ content: [{ type: "input_text", text: msg.content }]
247
+ });
248
+ } else if (msg.role === "assistant") {
249
+ input.push({
250
+ role: "developer",
251
+ content: `[\u4E0A\u4E00\u8F6EAI\u56DE\u590D]: ${msg.content}`
252
+ });
253
+ }
254
+ }
255
+ input.push({
256
+ role: "user",
257
+ content: [{ type: "input_text", text: message }]
258
+ });
259
+ return input;
260
+ }
261
+ var ArkProvider = class {
262
+ name = "ark";
263
+ supportedModels = ARK_MODELS;
264
+ apiKey;
265
+ apiUrl;
266
+ enableCaching;
267
+ constructor(config) {
268
+ this.apiKey = config.apiKey;
269
+ this.apiUrl = config.apiUrl || DEFAULT_ARK_URL;
270
+ this.enableCaching = config.enableCaching ?? false;
271
+ }
272
+ supportsModel(model) {
273
+ return ARK_MODELS.includes(model) || model.startsWith("doubao-") || model.startsWith("deepseek-");
274
+ }
275
+ getModelCapabilities(model) {
276
+ return MODEL_CAPABILITIES[model] || DEFAULT_CAPABILITIES;
277
+ }
278
+ /**
279
+ * 聊天入口 - 统一使用 Responses API
280
+ */
281
+ async *chat(message, model, options, context) {
282
+ const showThinking = options.thinkingMode === "enabled";
283
+ const enableWebSearch = options.enableWebSearch === true;
284
+ if (showThinking) {
285
+ yield { type: "thinking", data: { content: "\u6B63\u5728\u601D\u8003...", isComplete: false } };
286
+ }
287
+ let searchStarted = false;
288
+ const tools = [];
289
+ if (enableWebSearch) {
290
+ tools.push({
291
+ type: "web_search",
292
+ max_keyword: 5,
293
+ limit: 20
294
+ });
295
+ }
296
+ if (options.mode !== "ask") {
297
+ const functionTools = createToolDefinitions(context.workingDir);
298
+ tools.push(...convertToResponsesApiTools(functionTools));
299
+ }
300
+ const input = convertToResponsesApiInput(context.systemPrompt, context.history, message);
301
+ let iterations = 0;
302
+ let finalText = "";
303
+ let thinkingComplete = !showThinking;
304
+ let searchComplete = !enableWebSearch;
305
+ let searchResults = [];
306
+ let hasExecutedTools = false;
307
+ const pendingFunctionCalls = /* @__PURE__ */ new Map();
308
+ while (iterations < MAX_ITERATIONS) {
309
+ if (context.signal.aborted) {
310
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
311
+ return;
312
+ }
313
+ iterations++;
314
+ try {
315
+ const requestBody = {
316
+ model,
317
+ stream: true,
318
+ max_output_tokens: 32768,
319
+ input
320
+ };
321
+ if (tools.length > 0) {
322
+ requestBody.tools = tools;
323
+ }
324
+ const effectiveThinkingMode = hasExecutedTools ? "disabled" : options.thinkingMode;
325
+ if (effectiveThinkingMode) {
326
+ const thinkingConfig = {
327
+ type: effectiveThinkingMode
328
+ };
329
+ if (effectiveThinkingMode === "enabled" && options.thinkingBudget) {
330
+ thinkingConfig.budget_tokens = options.thinkingBudget;
331
+ }
332
+ requestBody.thinking = thinkingConfig;
333
+ }
334
+ if (this.enableCaching) {
335
+ requestBody.caching = { type: "enabled" };
336
+ }
337
+ const response = await fetch(`${this.apiUrl}/responses`, {
338
+ method: "POST",
339
+ headers: {
340
+ "Authorization": `Bearer ${this.apiKey}`,
341
+ "Content-Type": "application/json"
342
+ },
343
+ body: JSON.stringify(requestBody),
344
+ signal: context.signal
345
+ });
346
+ if (!response.ok) {
347
+ const errorText = await response.text();
348
+ throw new Error(`Responses API \u9519\u8BEF: ${response.status} - ${errorText}`);
349
+ }
350
+ const reader = response.body?.getReader();
351
+ if (!reader) {
352
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41");
353
+ }
354
+ const decoder = new TextDecoder();
355
+ let buffer = "";
356
+ let currentFunctionCallId = null;
357
+ let needsFunctionExecution = false;
358
+ while (true) {
359
+ if (context.signal.aborted) {
360
+ reader.cancel();
361
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
362
+ return;
363
+ }
364
+ const { done, value } = await reader.read();
365
+ if (done) break;
366
+ buffer += decoder.decode(value, { stream: true });
367
+ const lines = buffer.split("\n");
368
+ buffer = lines.pop() || "";
369
+ for (const line of lines) {
370
+ if (!line.startsWith("data:")) continue;
371
+ const data = line.slice(5).trim();
372
+ if (data === "[DONE]") continue;
373
+ try {
374
+ const event = JSON.parse(data);
375
+ switch (event.type) {
376
+ // 添加输出项
377
+ case "response.output_item.added": {
378
+ const item = event.item;
379
+ if (item?.type === "web_search_call") {
380
+ if (!searchStarted) {
381
+ searchStarted = true;
382
+ yield {
383
+ type: "search_start",
384
+ data: { query: item.query || message }
385
+ };
386
+ }
387
+ } else if (item?.type === "function_call") {
388
+ const callId = item.id || `call_${Date.now()}`;
389
+ currentFunctionCallId = callId;
390
+ pendingFunctionCalls.set(callId, {
391
+ name: item.name || "",
392
+ arguments: ""
393
+ });
394
+ }
395
+ break;
396
+ }
397
+ // 函数名称
398
+ case "response.function_call.name": {
399
+ if (currentFunctionCallId) {
400
+ const call = pendingFunctionCalls.get(currentFunctionCallId);
401
+ if (call) {
402
+ call.name = event.name || call.name;
403
+ }
404
+ }
405
+ break;
406
+ }
407
+ // 函数参数增量
408
+ case "response.function_call_arguments.delta": {
409
+ if (currentFunctionCallId) {
410
+ const call = pendingFunctionCalls.get(currentFunctionCallId);
411
+ if (call) {
412
+ call.arguments += event.delta || "";
413
+ }
414
+ }
415
+ break;
416
+ }
417
+ // 函数调用完成
418
+ case "response.function_call_arguments.done":
419
+ case "response.output_item.done": {
420
+ const item = event.item;
421
+ if (item?.type === "function_call" && item.name) {
422
+ const callId = item.id || currentFunctionCallId || `call_${Date.now()}`;
423
+ pendingFunctionCalls.set(callId, {
424
+ name: item.name,
425
+ arguments: item.arguments || pendingFunctionCalls.get(callId)?.arguments || "{}"
426
+ });
427
+ needsFunctionExecution = true;
428
+ }
429
+ if (item?.type === "web_search_call" && item.action?.query) {
430
+ }
431
+ break;
432
+ }
433
+ // 搜索进行中(官方事件类型)
434
+ case "response.web_search_call.in_progress": {
435
+ break;
436
+ }
437
+ // 搜索完成(官方事件类型)
438
+ case "response.web_search_call.completed": {
439
+ break;
440
+ }
441
+ // 搜索引用(流式收集,最后统一发送)
442
+ case "response.output_text.annotation.added": {
443
+ const annotation = event.annotation;
444
+ const isUrlCitation = annotation?.type === "url_citation" || annotation?.type === "citation" || annotation?.url;
445
+ if (isUrlCitation && annotation?.url) {
446
+ const exists = searchResults.some((r) => r.url === annotation.url);
447
+ if (!exists) {
448
+ searchResults.push({
449
+ title: annotation.title || annotation.text || "",
450
+ url: annotation.url,
451
+ snippet: annotation.summary || annotation.snippet || ""
452
+ });
453
+ console.log("[ARK] \u6536\u96C6\u5230\u641C\u7D22\u5F15\u7528:", annotation.url);
454
+ }
455
+ }
456
+ break;
457
+ }
458
+ // 文本输出增量
459
+ case "response.output_text.delta": {
460
+ if (!thinkingComplete) {
461
+ thinkingComplete = true;
462
+ yield { type: "thinking", data: { content: "", isComplete: true } };
463
+ }
464
+ const delta = event.delta || "";
465
+ finalText += delta;
466
+ yield { type: "text_delta", data: delta };
467
+ break;
468
+ }
469
+ // 深度思考内容增量
470
+ case "response.reasoning_summary_text.delta": {
471
+ yield {
472
+ type: "thinking",
473
+ data: { content: event.delta || "", isComplete: false }
474
+ };
475
+ break;
476
+ }
477
+ // 深度思考完成
478
+ case "response.reasoning_summary_text.done": {
479
+ thinkingComplete = true;
480
+ yield {
481
+ type: "thinking",
482
+ data: { content: "", isComplete: true }
483
+ };
484
+ break;
485
+ }
486
+ // 响应完成
487
+ case "response.done": {
488
+ if (searchStarted && !searchComplete) {
489
+ searchComplete = true;
490
+ console.log("[ARK] \u53D1\u9001\u641C\u7D22\u7ED3\u679C:", searchResults.length, "\u6761");
491
+ yield { type: "search_result", data: { results: searchResults, isComplete: true } };
492
+ }
493
+ break;
494
+ }
495
+ default: {
496
+ if (event.type && !event.type.startsWith("response.created") && !event.type.startsWith("rate_limits")) {
497
+ }
498
+ }
499
+ }
500
+ } catch {
501
+ }
502
+ }
503
+ }
504
+ if (needsFunctionExecution && pendingFunctionCalls.size > 0) {
505
+ for (const [callId, call] of pendingFunctionCalls) {
506
+ if (!call.name) continue;
507
+ let args;
508
+ try {
509
+ args = JSON.parse(call.arguments || "{}");
510
+ } catch {
511
+ yield { type: "error", data: `\u5DE5\u5177\u53C2\u6570\u89E3\u6790\u5931\u8D25: ${call.arguments}` };
512
+ input.push({
513
+ type: "function_call_output",
514
+ call_id: callId,
515
+ output: "\u53C2\u6570\u89E3\u6790\u9519\u8BEF"
516
+ });
517
+ continue;
518
+ }
519
+ yield { type: "tool_call", data: { name: call.name, args } };
520
+ const result = await context.executeTool(call.name, args);
521
+ yield { type: "tool_result", data: { name: call.name, result } };
522
+ input.push({
523
+ type: "function_call_output",
524
+ call_id: callId,
525
+ output: result
526
+ });
527
+ hasExecutedTools = true;
528
+ }
529
+ pendingFunctionCalls.clear();
530
+ continue;
531
+ }
532
+ break;
533
+ } catch (error) {
534
+ if (context.signal.aborted) {
535
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
536
+ } else {
537
+ yield { type: "error", data: String(error) };
538
+ }
539
+ break;
540
+ }
541
+ }
542
+ if (!thinkingComplete) {
543
+ yield { type: "thinking", data: { content: "", isComplete: true } };
544
+ }
545
+ if (searchStarted && !searchComplete) {
546
+ yield { type: "search_result", data: { results: searchResults, isComplete: true } };
547
+ }
548
+ context.history.push({ role: "user", content: message });
549
+ context.history.push({ role: "assistant", content: finalText || "\u5B8C\u6210" });
550
+ yield { type: "text", data: finalText };
551
+ yield { type: "done", data: finalText };
552
+ }
553
+ };
554
+
555
+ // src/providers/qwen.ts
556
+ var QWEN_MODELS = [
557
+ "qwen3-max-preview",
558
+ "qwen-plus"
559
+ ];
560
+ var MODEL_CAPABILITIES2 = {
561
+ "qwen3-max-preview": {
562
+ supportsTools: true,
563
+ supportsWebSearch: true,
564
+ supportsThinking: true,
565
+ supportedThinkingModes: ["enabled", "disabled"]
566
+ },
567
+ "qwen-plus": {
568
+ supportsTools: true,
569
+ supportsWebSearch: true,
570
+ supportsThinking: true,
571
+ supportedThinkingModes: ["enabled", "disabled"]
572
+ }
573
+ };
574
+ var DEFAULT_CAPABILITIES2 = {
575
+ supportsTools: true,
576
+ supportsWebSearch: true,
577
+ supportsThinking: true,
578
+ supportedThinkingModes: ["enabled", "disabled"]
579
+ };
580
+ var QwenProvider = class {
581
+ name = "qwen";
582
+ supportedModels = QWEN_MODELS;
583
+ apiKey;
584
+ apiUrl;
585
+ constructor(config) {
586
+ this.apiKey = config.apiKey;
587
+ this.apiUrl = config.apiUrl || DEFAULT_QWEN_NATIVE_URL;
588
+ }
589
+ supportsModel(model) {
590
+ return QWEN_MODELS.includes(model) || model.startsWith("qwen");
591
+ }
592
+ getModelCapabilities(model) {
593
+ return MODEL_CAPABILITIES2[model] || DEFAULT_CAPABILITIES2;
594
+ }
595
+ async *chat(message, model, options, context) {
596
+ const showThinking = options.thinkingMode === "enabled";
597
+ const enableSearch = options.enableWebSearch === true;
598
+ if (showThinking) {
599
+ yield { type: "thinking", data: { content: "\u6B63\u5728\u601D\u8003...", isComplete: false } };
600
+ }
601
+ if (enableSearch) {
602
+ yield { type: "search_start", data: { query: message } };
603
+ }
604
+ const tools = options.mode === "ask" ? [] : createToolDefinitions(context.workingDir).map((t) => ({
605
+ type: "function",
606
+ function: {
607
+ name: t.function.name,
608
+ description: t.function.description,
609
+ parameters: t.function.parameters
610
+ }
611
+ }));
612
+ const messages = [
613
+ { role: "system", content: context.systemPrompt },
614
+ ...context.history,
615
+ { role: "user", content: message }
616
+ ];
617
+ let iterations = 0;
618
+ let finalText = "";
619
+ let thinkingContent = "";
620
+ let thinkingComplete = !showThinking;
621
+ let searchComplete = !enableSearch;
622
+ let searchResults = [];
623
+ let hasExecutedTools = false;
624
+ while (iterations < MAX_ITERATIONS) {
625
+ if (context.signal.aborted) {
626
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
627
+ return;
628
+ }
629
+ iterations++;
630
+ try {
631
+ const toolCallsMap = /* @__PURE__ */ new Map();
632
+ const effectiveEnableThinking = hasExecutedTools ? false : showThinking;
633
+ for await (const event of this.callDashScopeStream(
634
+ model,
635
+ messages,
636
+ tools,
637
+ { enableSearch, enableThinking: effectiveEnableThinking, thinkingBudget: options.thinkingBudget },
638
+ context.signal
639
+ )) {
640
+ const choice = event.output?.choices?.[0];
641
+ if (event.output?.search_info?.search_results && !searchComplete) {
642
+ searchResults = event.output.search_info.search_results;
643
+ searchComplete = true;
644
+ yield {
645
+ type: "search_result",
646
+ data: {
647
+ results: searchResults.map((r) => ({
648
+ title: r.title,
649
+ url: r.url,
650
+ snippet: r.snippet || ""
651
+ })),
652
+ isComplete: true
653
+ }
654
+ };
655
+ }
656
+ if (!choice) continue;
657
+ const msg = choice.message;
658
+ if (msg.reasoning_content && !thinkingComplete) {
659
+ thinkingContent += msg.reasoning_content;
660
+ yield { type: "thinking", data: { content: msg.reasoning_content, isComplete: false } };
661
+ }
662
+ if (msg.content) {
663
+ if (!thinkingComplete) {
664
+ thinkingComplete = true;
665
+ yield { type: "thinking", data: { content: "", isComplete: true } };
666
+ }
667
+ finalText += msg.content;
668
+ yield { type: "text_delta", data: msg.content };
669
+ }
670
+ if (msg.tool_calls?.length) {
671
+ for (const tc of msg.tool_calls) {
672
+ const index = tc.index ?? 0;
673
+ const existing = toolCallsMap.get(index);
674
+ if (existing) {
675
+ if (tc.function?.arguments) {
676
+ existing.arguments += tc.function.arguments;
677
+ }
678
+ if (tc.function?.name) {
679
+ existing.name = tc.function.name;
680
+ }
681
+ if (tc.id) {
682
+ existing.id = tc.id;
683
+ }
684
+ } else {
685
+ toolCallsMap.set(index, {
686
+ id: tc.id || `call_${Date.now()}_${index}`,
687
+ name: tc.function?.name || "",
688
+ arguments: tc.function?.arguments || ""
689
+ });
690
+ }
691
+ }
692
+ }
693
+ if (choice.finish_reason === "stop" || choice.finish_reason === "length" || choice.finish_reason === "tool_calls") {
694
+ break;
695
+ }
696
+ }
697
+ const currentToolCalls = Array.from(toolCallsMap.values()).filter((tc) => tc.name);
698
+ if (!thinkingComplete) {
699
+ thinkingComplete = true;
700
+ yield { type: "thinking", data: { content: "", isComplete: true } };
701
+ }
702
+ if (currentToolCalls.length > 0) {
703
+ messages.push({
704
+ role: "assistant",
705
+ content: finalText || "",
706
+ tool_calls: currentToolCalls.map((tc) => ({
707
+ id: tc.id,
708
+ type: "function",
709
+ function: {
710
+ name: tc.name,
711
+ arguments: tc.arguments
712
+ }
713
+ }))
714
+ });
715
+ for (const tc of currentToolCalls) {
716
+ const name = tc.name;
717
+ let args;
718
+ try {
719
+ args = JSON.parse(tc.arguments);
720
+ } catch {
721
+ yield { type: "error", data: `\u5DE5\u5177\u53C2\u6570\u89E3\u6790\u5931\u8D25: ${tc.arguments}` };
722
+ messages.push({
723
+ role: "tool",
724
+ tool_call_id: tc.id,
725
+ content: `\u53C2\u6570\u89E3\u6790\u9519\u8BEF`
726
+ });
727
+ continue;
728
+ }
729
+ yield { type: "tool_call", data: { name, args } };
730
+ const result = await context.executeTool(name, args);
731
+ yield { type: "tool_result", data: { name, result } };
732
+ messages.push({
733
+ role: "tool",
734
+ tool_call_id: tc.id,
735
+ content: result
736
+ });
737
+ hasExecutedTools = true;
738
+ }
739
+ finalText = "";
740
+ continue;
741
+ }
742
+ break;
743
+ } catch (error) {
744
+ if (context.signal.aborted) {
745
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
746
+ } else {
747
+ yield { type: "error", data: String(error) };
748
+ }
749
+ break;
750
+ }
751
+ }
752
+ if (!searchComplete) {
753
+ yield { type: "search_result", data: { results: [], isComplete: true } };
754
+ }
755
+ context.history.push({ role: "user", content: message });
756
+ context.history.push({
757
+ role: "assistant",
758
+ content: finalText || "\u5B8C\u6210",
759
+ reasoning_content: thinkingContent || void 0
760
+ });
761
+ yield { type: "text", data: finalText };
762
+ yield { type: "done", data: finalText };
763
+ }
764
+ /** 调用 DashScope API (流式) */
765
+ async *callDashScopeStream(model, messages, tools, options, signal) {
766
+ const formattedMessages = messages.map((m) => {
767
+ const msg = {
768
+ role: m.role,
769
+ content: m.content
770
+ };
771
+ if (m.tool_call_id) {
772
+ msg.tool_call_id = m.tool_call_id;
773
+ }
774
+ if (m.tool_calls?.length) {
775
+ msg.tool_calls = m.tool_calls;
776
+ }
777
+ return msg;
778
+ });
779
+ const requestBody = {
780
+ model,
781
+ input: { messages: formattedMessages },
782
+ parameters: {
783
+ result_format: "message",
784
+ incremental_output: true,
785
+ max_tokens: 32768
786
+ }
787
+ };
788
+ const params = requestBody.parameters;
789
+ if (options.enableThinking) {
790
+ params.enable_thinking = true;
791
+ if (options.thinkingBudget) {
792
+ params.thinking_budget = options.thinkingBudget;
793
+ }
794
+ }
795
+ if (options.enableSearch) {
796
+ params.enable_search = true;
797
+ params.search_options = {
798
+ enable_source: true,
799
+ enable_citation: true,
800
+ search_strategy: "turbo"
801
+ };
802
+ }
803
+ if (tools.length > 0) {
804
+ params.tools = tools;
805
+ }
806
+ const response = await fetch(`${this.apiUrl}/services/aigc/text-generation/generation`, {
807
+ method: "POST",
808
+ headers: {
809
+ "Authorization": `Bearer ${this.apiKey}`,
810
+ "Content-Type": "application/json",
811
+ "X-DashScope-SSE": "enable"
812
+ },
813
+ body: JSON.stringify(requestBody),
814
+ signal
815
+ });
816
+ if (!response.ok) {
817
+ const errorText = await response.text();
818
+ throw new Error(`DashScope API \u9519\u8BEF: ${response.status} - ${errorText}`);
819
+ }
820
+ const reader = response.body?.getReader();
821
+ if (!reader) {
822
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u54CD\u5E94\u6D41");
823
+ }
824
+ const decoder = new TextDecoder();
825
+ let buffer = "";
826
+ while (true) {
827
+ const { done, value } = await reader.read();
828
+ if (done) break;
829
+ buffer += decoder.decode(value, { stream: true });
830
+ const lines = buffer.split("\n");
831
+ buffer = lines.pop() || "";
832
+ for (const line of lines) {
833
+ if (line.startsWith("data:")) {
834
+ const data = line.slice(5).trim();
835
+ if (data && data !== "[DONE]") {
836
+ try {
837
+ yield JSON.parse(data);
838
+ } catch {
839
+ }
840
+ }
841
+ }
842
+ }
843
+ }
844
+ }
845
+ };
846
+
847
+ // src/providers/gemini.ts
848
+ var GEMINI_MODELS = [
849
+ "gemini-3-pro-preview"
850
+ ];
851
+ var MODEL_CAPABILITIES3 = {
852
+ "gemini-3-pro-preview": {
853
+ supportsTools: true,
854
+ supportsWebSearch: true,
855
+ supportsThinking: true,
856
+ supportedThinkingModes: ["enabled", "disabled"]
857
+ }
858
+ };
859
+ var DEFAULT_CAPABILITIES3 = {
860
+ supportsTools: true,
861
+ supportsWebSearch: true,
862
+ supportsThinking: true,
863
+ supportedThinkingModes: ["enabled", "disabled"]
864
+ };
865
+ var GeminiProvider = class {
866
+ name = "gemini";
867
+ supportedModels = GEMINI_MODELS;
868
+ geminiClient = null;
869
+ apiKey;
870
+ constructor(config) {
871
+ this.apiKey = config.apiKey;
872
+ }
873
+ /** 懒加载 Gemini 客户端 */
874
+ async getClient() {
875
+ if (!this.geminiClient) {
876
+ const { GoogleGenAI: GoogleGenAI2 } = await import('@google/genai');
877
+ this.geminiClient = new GoogleGenAI2({ apiKey: this.apiKey });
878
+ }
879
+ return this.geminiClient;
880
+ }
881
+ supportsModel(model) {
882
+ return GEMINI_MODELS.includes(model) || model.startsWith("gemini-");
883
+ }
884
+ getModelCapabilities(model) {
885
+ return MODEL_CAPABILITIES3[model] || DEFAULT_CAPABILITIES3;
886
+ }
887
+ async *chat(message, model, options, context) {
888
+ const geminiClient = await this.getClient();
889
+ const showThinking = options.thinkingMode === "enabled";
890
+ const tools = options.mode === "ask" ? [] : createToolDefinitions(context.workingDir);
891
+ const enableWebSearch = options.enableWebSearch === true && tools.length === 0;
892
+ if (showThinking) {
893
+ yield { type: "thinking", data: { content: "\u6B63\u5728\u601D\u8003...", isComplete: false } };
894
+ }
895
+ if (enableWebSearch) {
896
+ yield { type: "search_start", data: { query: message } };
897
+ }
898
+ const geminiMessages = [
899
+ { role: "user", parts: [{ text: context.systemPrompt }] },
900
+ { role: "model", parts: [{ text: "\u597D\u7684\uFF0C\u6211\u4F1A\u6309\u7167\u4F60\u7684\u6307\u793A\u884C\u4E8B\u3002" }] },
901
+ ...this.convertToGeminiMessages(context.history),
902
+ { role: "user", parts: [{ text: message }] }
903
+ ];
904
+ let iterations = 0;
905
+ let finalText = "";
906
+ let thinkingComplete = !showThinking;
907
+ let searchComplete = !enableWebSearch;
908
+ while (iterations < MAX_ITERATIONS) {
909
+ if (context.signal.aborted) {
910
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
911
+ return;
912
+ }
913
+ iterations++;
914
+ try {
915
+ const generateConfig = {
916
+ thinkingConfig: {
917
+ thinkingLevel: options.thinkingMode === "enabled" ? "high" : "low",
918
+ // 启用思考摘要返回(Gemini 3 支持)
919
+ includeThoughts: showThinking
920
+ }
921
+ };
922
+ if (tools.length > 0) {
923
+ generateConfig.tools = [{
924
+ functionDeclarations: this.convertToGeminiFunctions(tools)
925
+ }];
926
+ } else if (enableWebSearch) {
927
+ generateConfig.tools = [{ googleSearch: {} }];
928
+ }
929
+ const response = await geminiClient.models.generateContent({
930
+ model,
931
+ contents: geminiMessages,
932
+ config: generateConfig
933
+ });
934
+ const candidate = response.candidates?.[0];
935
+ if (!candidate) {
936
+ yield { type: "error", data: "\u65E0\u54CD\u5E94" };
937
+ break;
938
+ }
939
+ const rawCandidate = response.candidates?.[0];
940
+ const groundingMetadata = rawCandidate?.groundingMetadata;
941
+ if (groundingMetadata && !searchComplete) {
942
+ searchComplete = true;
943
+ const searchResults = (groundingMetadata.groundingChunks || []).filter((chunk) => chunk.web).map((chunk) => ({
944
+ url: chunk.web.uri,
945
+ title: chunk.web.title,
946
+ snippet: ""
947
+ }));
948
+ yield {
949
+ type: "search_result",
950
+ data: {
951
+ results: searchResults,
952
+ queries: groundingMetadata.webSearchQueries,
953
+ isComplete: true
954
+ }
955
+ };
956
+ }
957
+ const parts = candidate.content?.parts || [];
958
+ let hasToolCall = false;
959
+ for (const part of parts) {
960
+ const extendedPart = part;
961
+ if (extendedPart.thought && part.text) {
962
+ yield { type: "thinking", data: { content: part.text, isComplete: false } };
963
+ continue;
964
+ }
965
+ if (part.text && !extendedPart.thought) {
966
+ if (!thinkingComplete) {
967
+ thinkingComplete = true;
968
+ yield { type: "thinking", data: { content: "", isComplete: true } };
969
+ }
970
+ finalText += part.text;
971
+ yield { type: "text_delta", data: part.text };
972
+ }
973
+ if (part.functionCall && part.functionCall.name) {
974
+ hasToolCall = true;
975
+ const name = part.functionCall.name;
976
+ const args = part.functionCall.args || {};
977
+ const thoughtSignature = part.thoughtSignature;
978
+ yield { type: "tool_call", data: { name, args } };
979
+ const result = await context.executeTool(name, args);
980
+ yield { type: "tool_result", data: { name, result } };
981
+ const modelPart = { functionCall: { name, args } };
982
+ if (thoughtSignature) {
983
+ modelPart.thoughtSignature = thoughtSignature;
984
+ }
985
+ geminiMessages.push({
986
+ role: "model",
987
+ parts: [modelPart]
988
+ });
989
+ geminiMessages.push({
990
+ role: "user",
991
+ parts: [{ functionResponse: { name, response: { result } } }]
992
+ });
993
+ }
994
+ }
995
+ if (!hasToolCall) {
996
+ break;
997
+ }
998
+ } catch (error) {
999
+ if (context.signal.aborted) {
1000
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
1001
+ } else {
1002
+ yield { type: "error", data: String(error) };
1003
+ }
1004
+ break;
1005
+ }
1006
+ }
1007
+ if (!thinkingComplete) {
1008
+ yield { type: "thinking", data: { content: "", isComplete: true } };
1009
+ }
1010
+ if (!searchComplete) {
1011
+ yield { type: "search_result", data: { results: [], isComplete: true } };
1012
+ }
1013
+ context.history.push({ role: "user", content: message });
1014
+ context.history.push({ role: "assistant", content: finalText || "\u5B8C\u6210" });
1015
+ yield { type: "text", data: finalText };
1016
+ yield { type: "done", data: finalText };
1017
+ }
1018
+ /** 转换消息格式为 Gemini 格式 */
1019
+ convertToGeminiMessages(messages) {
1020
+ return messages.map((msg) => ({
1021
+ role: msg.role === "assistant" ? "model" : msg.role,
1022
+ parts: [{ text: msg.content }]
1023
+ }));
1024
+ }
1025
+ /** 转换工具定义为 Gemini functionDeclarations 格式 */
1026
+ convertToGeminiFunctions(tools) {
1027
+ return tools.map((tool) => {
1028
+ const t = tool;
1029
+ return {
1030
+ name: t.function.name,
1031
+ description: t.function.description,
1032
+ parameters: t.function.parameters
1033
+ };
1034
+ });
1035
+ }
1036
+ };
1037
+
1038
+ // src/providers/openrouter.ts
1039
+ var OPENROUTER_MODELS = [
1040
+ "anthropic/claude-opus-4.5"
1041
+ ];
1042
+ var MODEL_CAPABILITIES4 = {
1043
+ "anthropic/claude-opus-4.5": {
1044
+ supportsTools: true,
1045
+ supportsWebSearch: true,
1046
+ supportsThinking: true,
1047
+ supportedThinkingModes: ["enabled", "disabled"]
1048
+ }
1049
+ };
1050
+ var DEFAULT_CAPABILITIES4 = {
1051
+ supportsTools: true,
1052
+ supportsWebSearch: true,
1053
+ supportsThinking: false,
1054
+ supportedThinkingModes: ["disabled"]
1055
+ };
1056
+ var OpenRouterProvider = class {
1057
+ name = "openrouter";
1058
+ supportedModels = OPENROUTER_MODELS;
1059
+ apiKey;
1060
+ apiUrl;
1061
+ constructor(config) {
1062
+ this.apiKey = config.apiKey;
1063
+ this.apiUrl = config.apiUrl || DEFAULT_OPENROUTER_URL;
1064
+ }
1065
+ supportsModel(model) {
1066
+ return OPENROUTER_MODELS.includes(model) || model.includes("/");
1067
+ }
1068
+ getModelCapabilities(model) {
1069
+ return MODEL_CAPABILITIES4[model] || DEFAULT_CAPABILITIES4;
1070
+ }
1071
+ /** 检查模型是否支持 Extended Thinking */
1072
+ supportsThinking(model) {
1073
+ return model.includes("claude-opus-4.5") || model === "anthropic/claude-opus-4.5";
1074
+ }
1075
+ async *chat(message, model, options, context) {
1076
+ const enableThinking = options.thinkingMode === "enabled" && this.supportsThinking(model);
1077
+ const enableWebSearch = options.enableWebSearch === true;
1078
+ if (enableThinking) {
1079
+ yield { type: "thinking", data: { content: "", isComplete: false } };
1080
+ }
1081
+ if (enableWebSearch) {
1082
+ yield { type: "search_start", data: { query: message } };
1083
+ }
1084
+ const tools = options.mode === "ask" ? [] : createToolDefinitions(context.workingDir);
1085
+ const messages = [
1086
+ { role: "system", content: context.systemPrompt },
1087
+ ...context.history,
1088
+ { role: "user", content: message }
1089
+ ];
1090
+ let iterations = 0;
1091
+ let finalText = "";
1092
+ let thinkingContent = "";
1093
+ let thinkingComplete = !enableThinking;
1094
+ let searchComplete = !enableWebSearch;
1095
+ while (iterations < MAX_ITERATIONS) {
1096
+ if (context.signal.aborted) {
1097
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
1098
+ return;
1099
+ }
1100
+ iterations++;
1101
+ try {
1102
+ const response = await this.callOpenRouter(
1103
+ model,
1104
+ messages,
1105
+ tools,
1106
+ enableWebSearch,
1107
+ enableThinking,
1108
+ context.signal
1109
+ );
1110
+ const choice = response.choices?.[0];
1111
+ if (!choice) {
1112
+ yield { type: "error", data: "\u65E0\u54CD\u5E94" };
1113
+ break;
1114
+ }
1115
+ const responseMessage = choice.message;
1116
+ const finishReason = choice.finish_reason;
1117
+ if (responseMessage.reasoning && !thinkingComplete) {
1118
+ thinkingContent = responseMessage.reasoning;
1119
+ yield { type: "thinking", data: { content: responseMessage.reasoning, isComplete: false } };
1120
+ }
1121
+ if (enableThinking && !thinkingComplete && responseMessage.content) {
1122
+ thinkingComplete = true;
1123
+ yield { type: "thinking", data: { content: "", isComplete: true } };
1124
+ }
1125
+ if (enableWebSearch && !searchComplete && responseMessage.annotations?.length) {
1126
+ searchComplete = true;
1127
+ const searchResults = responseMessage.annotations.filter((a) => a.type === "url_citation").map((a) => ({
1128
+ url: a.url_citation.url,
1129
+ title: a.url_citation.title || "",
1130
+ snippet: a.url_citation.content || ""
1131
+ }));
1132
+ yield {
1133
+ type: "search_result",
1134
+ data: {
1135
+ results: searchResults,
1136
+ isComplete: true
1137
+ }
1138
+ };
1139
+ } else if (enableWebSearch && !searchComplete && iterations === 1) {
1140
+ searchComplete = true;
1141
+ yield { type: "search_result", data: { results: [], isComplete: true } };
1142
+ }
1143
+ if (finishReason === "tool_calls" && responseMessage.tool_calls?.length) {
1144
+ messages.push(responseMessage);
1145
+ for (const toolCall of responseMessage.tool_calls) {
1146
+ const name = toolCall.function.name;
1147
+ let args;
1148
+ try {
1149
+ args = JSON.parse(toolCall.function.arguments);
1150
+ } catch {
1151
+ yield { type: "error", data: `\u5DE5\u5177\u53C2\u6570\u89E3\u6790\u5931\u8D25` };
1152
+ messages.push({
1153
+ role: "tool",
1154
+ tool_call_id: toolCall.id,
1155
+ content: `\u53C2\u6570\u89E3\u6790\u9519\u8BEF: ${toolCall.function.arguments}`
1156
+ });
1157
+ continue;
1158
+ }
1159
+ yield { type: "tool_call", data: { name, args } };
1160
+ const result = await context.executeTool(name, args);
1161
+ yield { type: "tool_result", data: { name, result } };
1162
+ messages.push({
1163
+ role: "tool",
1164
+ tool_call_id: toolCall.id,
1165
+ content: result
1166
+ });
1167
+ }
1168
+ if (responseMessage.content) {
1169
+ finalText += responseMessage.content + "\n";
1170
+ yield { type: "text_delta", data: responseMessage.content };
1171
+ }
1172
+ continue;
1173
+ }
1174
+ if (responseMessage.content || finishReason === "stop" || finishReason === "end_turn") {
1175
+ if (responseMessage.content) {
1176
+ finalText += responseMessage.content;
1177
+ yield { type: "text_delta", data: responseMessage.content };
1178
+ }
1179
+ break;
1180
+ }
1181
+ break;
1182
+ } catch (error) {
1183
+ if (context.signal.aborted) {
1184
+ yield { type: "error", data: "\u8BF7\u6C42\u5DF2\u53D6\u6D88" };
1185
+ } else {
1186
+ yield { type: "error", data: String(error) };
1187
+ }
1188
+ break;
1189
+ }
1190
+ }
1191
+ if (!thinkingComplete) {
1192
+ yield { type: "thinking", data: { content: "", isComplete: true } };
1193
+ }
1194
+ if (!searchComplete) {
1195
+ yield { type: "search_result", data: { results: [], isComplete: true } };
1196
+ }
1197
+ context.history.push({ role: "user", content: message });
1198
+ context.history.push({
1199
+ role: "assistant",
1200
+ content: finalText || "\u5B8C\u6210",
1201
+ reasoning_content: thinkingContent || void 0
1202
+ });
1203
+ yield { type: "text", data: finalText };
1204
+ yield { type: "done", data: finalText };
1205
+ }
1206
+ /** 调用 OpenRouter API */
1207
+ async callOpenRouter(model, messages, tools, enableWebSearch, enableThinking, signal) {
1208
+ const requestBody = {
1209
+ model,
1210
+ messages,
1211
+ max_tokens: 32768
1212
+ };
1213
+ if (enableThinking && this.supportsThinking(model)) {
1214
+ requestBody.reasoning = {
1215
+ effort: "high",
1216
+ exclude: false
1217
+ // 包含推理过程
1218
+ };
1219
+ }
1220
+ if (tools.length > 0) {
1221
+ requestBody.tools = tools;
1222
+ }
1223
+ if (enableWebSearch) {
1224
+ requestBody.plugins = [
1225
+ {
1226
+ id: "web",
1227
+ max_results: 5
1228
+ }
1229
+ ];
1230
+ }
1231
+ const response = await fetch(`${this.apiUrl}/chat/completions`, {
1232
+ method: "POST",
1233
+ headers: {
1234
+ "Authorization": `Bearer ${this.apiKey}`,
1235
+ "Content-Type": "application/json",
1236
+ "HTTP-Referer": "https://github.com/huyooo/ai-chat",
1237
+ "X-Title": "AI Chat Agent"
1238
+ },
1239
+ body: JSON.stringify(requestBody),
1240
+ signal
1241
+ });
1242
+ if (!response.ok) {
1243
+ const errorText = await response.text();
1244
+ throw new Error(`OpenRouter API \u9519\u8BEF: ${response.status} - ${errorText}`);
1245
+ }
1246
+ return response.json();
1247
+ }
1248
+ };
1249
+
1250
+ // src/gemini.ts
1251
+ function getMimeType(ext) {
1252
+ const types = {
1253
+ ".jpg": "image/jpeg",
1254
+ ".jpeg": "image/jpeg",
1255
+ ".png": "image/png",
1256
+ ".gif": "image/gif",
1257
+ ".webp": "image/webp"
1258
+ };
1259
+ return types[ext] || "image/jpeg";
1260
+ }
1261
+ function getVideoMimeType(ext) {
1262
+ const types = {
1263
+ ".mp4": "video/mp4",
1264
+ ".mov": "video/quicktime",
1265
+ ".avi": "video/x-msvideo",
1266
+ ".webm": "video/webm"
1267
+ };
1268
+ return types[ext] || "video/mp4";
1269
+ }
1270
+ async function analyzeImage(geminiClient, imagePath, question) {
1271
+ try {
1272
+ const fs = await import('fs/promises');
1273
+ const path = await import('path');
1274
+ const imageData = await fs.readFile(imagePath);
1275
+ const base64 = imageData.toString("base64");
1276
+ const ext = path.extname(imagePath).toLowerCase();
1277
+ const mimeType = getMimeType(ext);
1278
+ const response = await geminiClient.models.generateContent({
1279
+ model: GEMINI_IMAGE_MODEL,
1280
+ contents: [{
1281
+ role: "user",
1282
+ parts: [
1283
+ { text: question || "\u8BF7\u8BE6\u7EC6\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9" },
1284
+ { inlineData: { mimeType, data: base64 } }
1285
+ ]
1286
+ }]
1287
+ });
1288
+ return response.text || "\u65E0\u6CD5\u5206\u6790\u56FE\u7247";
1289
+ } catch (error) {
1290
+ return `\u56FE\u7247\u5206\u6790\u5931\u8D25: ${error}`;
1291
+ }
1292
+ }
1293
+ async function generateImage(geminiClient, workingDir, prompt, outputPath) {
1294
+ try {
1295
+ const response = await geminiClient.models.generateContent({
1296
+ model: GEMINI_IMAGE_GEN_MODEL,
1297
+ contents: [{ role: "user", parts: [{ text: prompt }] }]
1298
+ });
1299
+ for (const part of response.candidates?.[0]?.content?.parts || []) {
1300
+ if (part.inlineData) {
1301
+ const imageData = Buffer.from(part.inlineData.data, "base64");
1302
+ const mimeType = part.inlineData.mimeType || "image/png";
1303
+ const ext = mimeType === "image/jpeg" ? ".jpg" : mimeType === "image/webp" ? ".webp" : ".png";
1304
+ if (outputPath) {
1305
+ const fs = await import('fs/promises');
1306
+ await fs.writeFile(outputPath, imageData);
1307
+ return `\u56FE\u7247\u5DF2\u751F\u6210\u5E76\u4FDD\u5B58\u5230: ${outputPath}`;
1308
+ } else {
1309
+ const savePath = `${workingDir}/generated_${Date.now()}${ext}`;
1310
+ const fs = await import('fs/promises');
1311
+ await fs.writeFile(savePath, imageData);
1312
+ return `\u56FE\u7247\u5DF2\u751F\u6210\u5E76\u4FDD\u5B58\u5230: ${savePath}`;
1313
+ }
1314
+ }
1315
+ }
1316
+ return "\u56FE\u7247\u751F\u6210\u5931\u8D25";
1317
+ } catch (error) {
1318
+ return `\u56FE\u7247\u751F\u6210\u5931\u8D25: ${error}`;
1319
+ }
1320
+ }
1321
+ async function analyzeVideo(geminiClient, videoPath, question) {
1322
+ try {
1323
+ const fs = await import('fs/promises');
1324
+ const path = await import('path');
1325
+ const videoData = await fs.readFile(videoPath);
1326
+ const base64 = videoData.toString("base64");
1327
+ const ext = path.extname(videoPath).toLowerCase();
1328
+ const mimeType = getVideoMimeType(ext);
1329
+ const response = await geminiClient.models.generateContent({
1330
+ model: GEMINI_IMAGE_MODEL,
1331
+ contents: [{
1332
+ role: "user",
1333
+ parts: [
1334
+ { text: question || "\u8BF7\u8BE6\u7EC6\u63CF\u8FF0\u8FD9\u4E2A\u89C6\u9891\u7684\u5185\u5BB9" },
1335
+ { inlineData: { mimeType, data: base64 } }
1336
+ ]
1337
+ }]
1338
+ });
1339
+ return response.text || "\u65E0\u6CD5\u5206\u6790\u89C6\u9891";
1340
+ } catch (error) {
1341
+ return `\u89C6\u9891\u5206\u6790\u5931\u8D25: ${error}`;
1342
+ }
1343
+ }
1344
+
1345
+ // src/agent.ts
1346
+ var HybridAgent = class {
1347
+ config;
1348
+ providers = /* @__PURE__ */ new Map();
1349
+ geminiClient;
1350
+ toolExecutor;
1351
+ conversationHistory = [];
1352
+ abortController = null;
1353
+ constructor(config, toolExecutor) {
1354
+ this.config = {
1355
+ arkApiKey: config.arkApiKey,
1356
+ arkApiUrl: config.arkApiUrl || DEFAULT_ARK_URL,
1357
+ qwenApiKey: config.qwenApiKey || "",
1358
+ qwenApiUrl: config.qwenApiUrl || DEFAULT_QWEN_NATIVE_URL,
1359
+ openrouterApiKey: config.openrouterApiKey || "",
1360
+ openrouterApiUrl: config.openrouterApiUrl || DEFAULT_OPENROUTER_URL,
1361
+ geminiApiKey: config.geminiApiKey,
1362
+ workingDir: config.workingDir || process.cwd()
1363
+ };
1364
+ this.geminiClient = new genai.GoogleGenAI({ apiKey: this.config.geminiApiKey });
1365
+ this.toolExecutor = toolExecutor || createDefaultToolExecutor(this.config.workingDir);
1366
+ this.initializeProviders();
1367
+ }
1368
+ /**
1369
+ * 初始化所有 Provider
1370
+ */
1371
+ initializeProviders() {
1372
+ if (this.config.arkApiKey) {
1373
+ this.providers.set("ark", new ArkProvider({
1374
+ apiKey: this.config.arkApiKey,
1375
+ apiUrl: this.config.arkApiUrl
1376
+ }));
1377
+ }
1378
+ if (this.config.qwenApiKey) {
1379
+ this.providers.set("qwen", new QwenProvider({
1380
+ apiKey: this.config.qwenApiKey,
1381
+ apiUrl: this.config.qwenApiUrl
1382
+ }));
1383
+ }
1384
+ if (this.config.geminiApiKey) {
1385
+ this.providers.set("gemini", new GeminiProvider({
1386
+ apiKey: this.config.geminiApiKey
1387
+ }));
1388
+ }
1389
+ if (this.config.openrouterApiKey) {
1390
+ this.providers.set("openrouter", new OpenRouterProvider({
1391
+ apiKey: this.config.openrouterApiKey,
1392
+ apiUrl: this.config.openrouterApiUrl
1393
+ }));
1394
+ }
1395
+ }
1396
+ /**
1397
+ * 判断模型提供商
1398
+ */
1399
+ getModelProvider(model) {
1400
+ const lowerModel = model.toLowerCase();
1401
+ if (lowerModel.startsWith("qwen")) {
1402
+ return "qwen";
1403
+ }
1404
+ if (lowerModel.startsWith("gemini")) {
1405
+ return "gemini";
1406
+ }
1407
+ if (lowerModel.includes("/")) {
1408
+ return "openrouter";
1409
+ }
1410
+ return "ark";
1411
+ }
1412
+ /**
1413
+ * 获取 Provider
1414
+ */
1415
+ getProvider(model) {
1416
+ const providerName = this.getModelProvider(model);
1417
+ return this.providers.get(providerName);
1418
+ }
1419
+ /**
1420
+ * 构建系统提示词
1421
+ */
1422
+ buildSystemPrompt(options) {
1423
+ const modePrompt = MODE_PROMPTS[options.mode || "agent"];
1424
+ const toolsInfo = options.mode === "ask" ? "" : `
1425
+ \u53EF\u7528\u5DE5\u5177\uFF1A
1426
+ 1. execute_command - \u6267\u884C Shell \u547D\u4EE4
1427
+ 2. analyze_image - \u5206\u6790\u56FE\u7247\u5185\u5BB9
1428
+ 3. generate_image - \u751F\u6210\u56FE\u7247
1429
+ 4. analyze_video - \u5206\u6790\u89C6\u9891\u5185\u5BB9`;
1430
+ return `${modePrompt}
1431
+
1432
+ \u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF1A${this.config.workingDir}
1433
+ ${toolsInfo}`;
1434
+ }
1435
+ /**
1436
+ * 获取默认深度思考模式(统一为 disabled)
1437
+ */
1438
+ getDefaultThinkingMode() {
1439
+ return "disabled";
1440
+ }
1441
+ /**
1442
+ * 执行工具
1443
+ */
1444
+ async executeTool(name, args) {
1445
+ switch (name) {
1446
+ case "execute_command": {
1447
+ const result = await this.toolExecutor.executeCommand(
1448
+ args.command,
1449
+ args.workingDir
1450
+ );
1451
+ return result.success ? result.output || "\u6267\u884C\u6210\u529F" : `\u9519\u8BEF: ${result.error}`;
1452
+ }
1453
+ case "analyze_image":
1454
+ return await analyzeImage(
1455
+ this.geminiClient,
1456
+ args.imagePath,
1457
+ args.question
1458
+ );
1459
+ case "generate_image":
1460
+ return await generateImage(
1461
+ this.geminiClient,
1462
+ this.config.workingDir,
1463
+ args.prompt,
1464
+ args.outputPath
1465
+ );
1466
+ case "analyze_video":
1467
+ return await analyzeVideo(
1468
+ this.geminiClient,
1469
+ args.videoPath,
1470
+ args.question
1471
+ );
1472
+ default:
1473
+ return `\u672A\u77E5\u5DE5\u5177: ${name}`;
1474
+ }
1475
+ }
1476
+ /**
1477
+ * 聊天入口
1478
+ *
1479
+ * 使用 Provider 模式路由请求到对应的模型提供商
1480
+ */
1481
+ async *chat(message, options = {}, images) {
1482
+ this.abortController = new AbortController();
1483
+ const signal = this.abortController.signal;
1484
+ const model = options.model || DEFAULT_MODEL;
1485
+ const provider = this.getProvider(model);
1486
+ if (!provider) {
1487
+ const providerName = this.getModelProvider(model);
1488
+ yield { type: "error", data: `\u7F3A\u5C11 ${providerName} Provider \u7684 API Key` };
1489
+ return;
1490
+ }
1491
+ const thinkingMode = options.thinkingMode ?? this.getDefaultThinkingMode();
1492
+ const optionsWithThinking = { ...options, thinkingMode };
1493
+ const systemPrompt = this.buildSystemPrompt(optionsWithThinking);
1494
+ const context = {
1495
+ history: this.conversationHistory,
1496
+ systemPrompt,
1497
+ workingDir: this.config.workingDir,
1498
+ executeTool: this.executeTool.bind(this),
1499
+ signal
1500
+ };
1501
+ try {
1502
+ yield* provider.chat(message, model, optionsWithThinking, context);
1503
+ } finally {
1504
+ this.abortController = null;
1505
+ }
1506
+ }
1507
+ /**
1508
+ * 中断当前请求
1509
+ */
1510
+ abort() {
1511
+ if (this.abortController) {
1512
+ this.abortController.abort();
1513
+ }
1514
+ }
1515
+ /**
1516
+ * 清空对话历史
1517
+ */
1518
+ clearHistory() {
1519
+ this.conversationHistory = [];
1520
+ }
1521
+ /**
1522
+ * 获取对话历史
1523
+ */
1524
+ getHistory() {
1525
+ return [...this.conversationHistory];
1526
+ }
1527
+ /**
1528
+ * 设置工作目录
1529
+ */
1530
+ setWorkingDir(dir) {
1531
+ this.config.workingDir = dir;
1532
+ }
1533
+ /**
1534
+ * 获取当前配置
1535
+ */
1536
+ getConfig() {
1537
+ return { ...this.config };
1538
+ }
1539
+ /**
1540
+ * 获取模型能力
1541
+ */
1542
+ getModelCapabilities(model) {
1543
+ const provider = this.getProvider(model);
1544
+ return provider?.getModelCapabilities(model);
1545
+ }
1546
+ /**
1547
+ * 获取所有支持的模型
1548
+ */
1549
+ getSupportedModels() {
1550
+ return AVAILABLE_MODELS;
1551
+ }
1552
+ };
1553
+
1554
+ exports.AVAILABLE_MODELS = AVAILABLE_MODELS;
1555
+ exports.ArkProvider = ArkProvider;
1556
+ exports.GeminiProvider = GeminiProvider;
1557
+ exports.HybridAgent = HybridAgent;
1558
+ exports.OpenRouterProvider = OpenRouterProvider;
1559
+ exports.QwenProvider = QwenProvider;
1560
+ exports.createDefaultToolExecutor = createDefaultToolExecutor;
1561
+ exports.createToolDefinitions = createToolDefinitions;
1562
+ //# sourceMappingURL=index.cjs.map
1563
+ //# sourceMappingURL=index.cjs.map