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