@xagent-ai/cli 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/README_CN.md +1 -1
  3. package/dist/agents.js +164 -164
  4. package/dist/agents.js.map +1 -1
  5. package/dist/ai-client.d.ts +4 -6
  6. package/dist/ai-client.d.ts.map +1 -1
  7. package/dist/ai-client.js +137 -115
  8. package/dist/ai-client.js.map +1 -1
  9. package/dist/auth.js +4 -4
  10. package/dist/auth.js.map +1 -1
  11. package/dist/cli.js +184 -1
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.js +3 -3
  14. package/dist/config.js.map +1 -1
  15. package/dist/context-compressor.d.ts.map +1 -1
  16. package/dist/context-compressor.js +65 -81
  17. package/dist/context-compressor.js.map +1 -1
  18. package/dist/conversation.d.ts +1 -1
  19. package/dist/conversation.d.ts.map +1 -1
  20. package/dist/conversation.js +5 -31
  21. package/dist/conversation.js.map +1 -1
  22. package/dist/memory.d.ts +5 -1
  23. package/dist/memory.d.ts.map +1 -1
  24. package/dist/memory.js +77 -37
  25. package/dist/memory.js.map +1 -1
  26. package/dist/remote-ai-client.d.ts +1 -8
  27. package/dist/remote-ai-client.d.ts.map +1 -1
  28. package/dist/remote-ai-client.js +55 -65
  29. package/dist/remote-ai-client.js.map +1 -1
  30. package/dist/retry.d.ts +35 -0
  31. package/dist/retry.d.ts.map +1 -0
  32. package/dist/retry.js +166 -0
  33. package/dist/retry.js.map +1 -0
  34. package/dist/session.d.ts +0 -5
  35. package/dist/session.d.ts.map +1 -1
  36. package/dist/session.js +243 -312
  37. package/dist/session.js.map +1 -1
  38. package/dist/slash-commands.d.ts +1 -0
  39. package/dist/slash-commands.d.ts.map +1 -1
  40. package/dist/slash-commands.js +91 -9
  41. package/dist/slash-commands.js.map +1 -1
  42. package/dist/smart-approval.d.ts.map +1 -1
  43. package/dist/smart-approval.js +18 -17
  44. package/dist/smart-approval.js.map +1 -1
  45. package/dist/system-prompt-generator.d.ts.map +1 -1
  46. package/dist/system-prompt-generator.js +149 -139
  47. package/dist/system-prompt-generator.js.map +1 -1
  48. package/dist/theme.d.ts +48 -0
  49. package/dist/theme.d.ts.map +1 -1
  50. package/dist/theme.js +254 -0
  51. package/dist/theme.js.map +1 -1
  52. package/dist/tools/edit-diff.d.ts +32 -0
  53. package/dist/tools/edit-diff.d.ts.map +1 -0
  54. package/dist/tools/edit-diff.js +185 -0
  55. package/dist/tools/edit-diff.js.map +1 -0
  56. package/dist/tools/edit.d.ts +11 -0
  57. package/dist/tools/edit.d.ts.map +1 -0
  58. package/dist/tools/edit.js +129 -0
  59. package/dist/tools/edit.js.map +1 -0
  60. package/dist/tools.d.ts +19 -5
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +979 -631
  63. package/dist/tools.js.map +1 -1
  64. package/dist/types.d.ts +6 -31
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +3 -2
  67. package/src/agents.ts +504 -504
  68. package/src/ai-client.ts +1559 -1458
  69. package/src/auth.ts +4 -4
  70. package/src/cli.ts +195 -1
  71. package/src/config.ts +3 -3
  72. package/src/memory.ts +55 -14
  73. package/src/remote-ai-client.ts +663 -683
  74. package/src/retry.ts +217 -0
  75. package/src/session.ts +1736 -1840
  76. package/src/slash-commands.ts +98 -9
  77. package/src/smart-approval.ts +626 -625
  78. package/src/system-prompt-generator.ts +853 -843
  79. package/src/theme.ts +284 -0
  80. package/src/tools.ts +390 -70
package/src/ai-client.ts CHANGED
@@ -1,1459 +1,1560 @@
1
- import axios, { AxiosInstance } from 'axios';
2
- import https from 'https';
3
- import { AuthConfig } from './types.js';
4
-
5
- // Message content block type for Anthropic format
6
- export interface AnthropicContentBlock {
7
- type: 'text' | 'tool_use' | 'tool_result' | 'thinking';
8
- text?: string;
9
- id?: string;
10
- name?: string;
11
- input?: any;
12
- tool_use_id?: string;
13
- content?: string;
14
- thinking?: string;
15
- }
16
-
17
- // Markdown rendering helper function
18
- function renderMarkdown(text: string): string {
19
- // Code block rendering
20
- text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
21
- return `\n┌─[${lang || 'code'}]\n${code.trim().split('\n').map((l: string) => '│ ' + l).join('\n')}\n└─\n`;
22
- });
23
-
24
- // Inline code rendering
25
- text = text.replace(/`([^`]+)`/g, '`$1`');
26
-
27
- // Bold rendering
28
- text = text.replace(/\*\*([^*]+)\*\*/g, '●$1○');
29
-
30
- // Italic rendering
31
- text = text.replace(/\*([^*]+)\*/g, '/$1/');
32
-
33
- // List rendering
34
- text = text.replace(/^- (.*$)/gm, '○ $1');
35
- text = text.replace(/^\d+\. (.*$)/gm, ' $1');
36
-
37
- // Heading rendering
38
- text = text.replace(/^### (.*$)/gm, '\n━━━ $1 ━━━\n');
39
- text = text.replace(/^## (.*$)/gm, '\n━━━━━ $1 ━━━━━\n');
40
- text = text.replace(/^# (.*$)/gm, '\n━━━━━━━ $1 ━━━━━━━\n');
41
-
42
- // Quote rendering
43
- text = text.replace(/^> (.*$)/gm, '│ │ $1');
44
-
45
- // Link rendering
46
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[$1]($2)');
47
-
48
- return text;
49
- }
50
-
51
- // Format message content
52
- function formatMessageContent(content: string | Array<any>): string {
53
- if (typeof content === 'string') {
54
- return renderMarkdown(content);
55
- }
56
-
57
- const parts: string[] = [];
58
- let hasToolUse = false;
59
-
60
- for (const block of content) {
61
- if (block.type === 'text') {
62
- parts.push(renderMarkdown(block.text || ''));
63
- } else if (block.type === 'tool_use') {
64
- hasToolUse = true;
65
- parts.push(`[🔧 TOOL CALL PENDING: ${block.name}]`);
66
- } else if (block.type === 'tool_result') {
67
- const result = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
68
- parts.push(`[✅ TOOL RESULT]\n${result}`);
69
- } else if (block.type === 'thinking') {
70
- parts.push(`[🧠 THINKING]\n${block.thinking || ''}`);
71
- }
72
- }
73
-
74
- if (hasToolUse) {
75
- parts.push('\n[⚠️ Note: Tool calls are executed by the framework, not displayed here]');
76
- }
77
-
78
- return parts.join('\n');
79
- }
80
-
81
- // Display messages by category
82
- function displayMessages(messages: any[], systemPrompt?: string): void {
83
- const roleColors: Record<string, string> = {
84
- system: '🟫 SYSTEM',
85
- user: '👤 USER',
86
- assistant: '🤖 ASSISTANT',
87
- tool: '🔧 TOOL'
88
- };
89
-
90
- // Display system message first (if there's a separate systemPrompt parameter)
91
- if (systemPrompt) {
92
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
93
- console.log('│ 🟫 SYSTEM │');
94
- console.log('├─────────────────────────────────────────────────────────────┤');
95
- console.log(renderMarkdown(systemPrompt).split('\n').map((l: string) => '│ ' + l).join('\n'));
96
- console.log('└─────────────────────────────────────────────────────────────┘');
97
- }
98
-
99
- // Iterate through all messages
100
- for (let i = 0; i < messages.length; i++) {
101
- const msg = messages[i];
102
- const role = msg.role as string;
103
- const roleLabel = roleColors[role] || `● ${role.toUpperCase()}`;
104
-
105
- console.log(`\n┌─────────────────────────────────────────────────────────────┐`);
106
- console.log(`│ ${roleLabel} (${i + 1}/${messages.length}) │`);
107
- console.log('├─────────────────────────────────────────────────────────────┤');
108
-
109
- // Display reasoning_content (if present) - check both camelCase and snake_case
110
- const reasoningContent = (msg as any).reasoningContent || (msg as any).reasoning_content;
111
- if (reasoningContent) {
112
- console.log('│ 🧠 REASONING:');
113
- console.log('│ ───────────────────────────────────────────────────────────');
114
- const reasoningLines = renderMarkdown(reasoningContent).split('\n');
115
- for (const line of reasoningLines.slice(0, 20)) {
116
- console.log('│ ' + line.slice(0, 62));
117
- }
118
- if (reasoningContent.length > 1000) console.log('│ ... (truncated)');
119
- console.log('│ ───────────────────────────────────────────────────────────');
120
- }
121
-
122
- // Display main content
123
- const content = formatMessageContent(msg.content);
124
- const lines = content.split('\n');
125
-
126
- for (const line of lines.slice(0, 50)) {
127
- console.log('│ ' + line.slice(0, 62));
128
- }
129
- if (lines.length > 50) {
130
- console.log('│ ... (' + (lines.length - 50) + ' more lines)');
131
- }
132
-
133
- console.log('└─────────────────────────────────────────────────────────────┘');
134
- }
135
- }
136
-
137
- // Format response content
138
- function formatResponseContent(content: string | Array<any>): string {
139
- if (typeof content === 'string') {
140
- return renderMarkdown(content);
141
- }
142
-
143
- const parts: string[] = [];
144
- let hasToolUse = false;
145
-
146
- for (const block of content) {
147
- if (block.type === 'text') {
148
- parts.push(renderMarkdown(block.text || ''));
149
- } else if (block.type === 'tool_use') {
150
- hasToolUse = true;
151
- // Tool calls are handled via tool_calls field, not shown here
152
- } else if (block.type === 'tool_result') {
153
- const result = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
154
- parts.push(`[✅ TOOL RESULT]\n${result}`);
155
- } else if (block.type === 'thinking') {
156
- parts.push(`[🧠 THINKING]\n${block.thinking || ''}`);
157
- } else if (block.type === 'image') {
158
- parts.push('[IMAGE]');
159
- }
160
- }
161
-
162
- if (hasToolUse) {
163
- parts.push('\n[⚠️ Note: Tool calls are executed via tool_calls field, not shown here]');
164
- }
165
-
166
- return parts.join('\n');
167
- }
168
-
169
- export interface Message {
170
- role: 'system' | 'user' | 'assistant' | 'tool';
171
- content: string | Array<AnthropicContentBlock | { type: string; text?: string; image_url?: { url: string } }>;
172
- reasoning_content?: string;
173
- tool_calls?: any[];
174
- tool_call_id?: string;
175
- }
176
-
177
- export interface ToolDefinition {
178
- type: 'function';
179
- function: {
180
- name: string;
181
- description: string;
182
- parameters?: any;
183
- };
184
- }
185
-
186
- export interface ChatCompletionOptions {
187
- model?: string;
188
- temperature?: number;
189
- maxTokens?: number;
190
- tools?: ToolDefinition[];
191
- toolChoice?: 'auto' | 'none' | { type: string; function: { name: string } };
192
- stream?: boolean;
193
- thinkingTokens?: number;
194
- }
195
-
196
- export interface ChatCompletionResponse {
197
- id: string;
198
- object: string;
199
- created: number;
200
- model: string;
201
- choices: Array<{
202
- index: number;
203
- message: Message;
204
- finish_reason: string;
205
- }>;
206
- usage?: {
207
- prompt_tokens: number;
208
- completion_tokens: number;
209
- total_tokens: number;
210
- };
211
- }
212
-
213
- // Detect if it's Anthropic compatible API(Use x-api-key authentication header)
214
- function isAnthropicCompatible(baseUrl: string): boolean {
215
- return baseUrl.includes('anthropic') ||
216
- baseUrl.includes('minimaxi.com') ||
217
- baseUrl.includes('minimax.chat');
218
- }
219
-
220
- // MiniMax API path detection
221
- function detectMiniMaxAPI(baseUrl: string): boolean {
222
- return baseUrl.includes('minimax.chat') ||
223
- baseUrl.includes('minimaxi.com');
224
- }
225
-
226
- // Get correct endpoint path for MiniMax
227
- function getMiniMaxEndpoint(baseUrl: string): { endpoint: string; format: 'anthropic' | 'openai' } {
228
- // MiniMax Anthropic format: https://api.minimax.chat/anthropic + /v1/messages
229
- if (baseUrl.includes('/anthropic')) {
230
- return { endpoint: '/v1/messages', format: 'anthropic' };
231
- }
232
- // MiniMax OpenAI format: https://api.minimaxi.com/v1 + /chat/completions
233
- if (baseUrl.includes('/v1') && !baseUrl.includes('/anthropic')) {
234
- return { endpoint: '/chat/completions', format: 'openai' };
235
- }
236
- // Default to Anthropic format
237
- return { endpoint: '/v1/messages', format: 'anthropic' };
238
- }
239
-
240
- export class AIClient {
241
- private client: AxiosInstance;
242
- private authConfig: AuthConfig;
243
-
244
- constructor(authConfig: AuthConfig) {
245
- this.authConfig = authConfig;
246
- const isMiniMax = detectMiniMaxAPI(authConfig.baseUrl || '');
247
- const isAnthropicOfficial = !isMiniMax && isAnthropicCompatible(authConfig.baseUrl || '');
248
-
249
- const headers: Record<string, string> = {
250
- 'Content-Type': 'application/json'
251
- };
252
-
253
- if (isMiniMax) {
254
- // MiniMax: Use x-api-key authentication header
255
- headers['x-api-key'] = authConfig.apiKey || '';
256
- headers['anthropic-version'] = '2023-06-01';
257
- } else if (isAnthropicOfficial) {
258
- // Anthropic official: Use x-api-key authentication header
259
- headers['x-api-key'] = authConfig.apiKey || '';
260
- headers['anthropic-version'] = '2023-06-01';
261
- headers['anthropic-dangerous-direct-browser-access'] = 'true';
262
- } else {
263
- // Other OpenAI compatible: 使用 Bearer token
264
- headers['Authorization'] = `Bearer ${authConfig.apiKey}`;
265
- }
266
-
267
- this.client = axios.create({
268
- baseURL: authConfig.baseUrl,
269
- headers,
270
- timeout: 240000,
271
- httpsAgent: new https.Agent({ rejectUnauthorized: false })
272
- });
273
- }
274
-
275
- // Convert OpenAI format messages to Anthropic format
276
- private convertToAnthropicFormat(
277
- messages: Message[],
278
- systemPrompt?: string
279
- ): { system: string; messages: Array<{ role: string; content: AnthropicContentBlock[] }> } {
280
- const systemMessages = messages.filter(m => m.role === 'system');
281
- const otherMessages = messages.filter(m => m.role !== 'system');
282
-
283
- const systemContent = systemMessages[0]?.content;
284
- const system = systemPrompt || (typeof systemContent === 'string' ? systemContent : '');
285
-
286
- const anthropicMessages: Array<{ role: string; content: AnthropicContentBlock[] }> = [];
287
-
288
- for (const msg of otherMessages) {
289
- const blocks: AnthropicContentBlock[] = [];
290
-
291
- if (typeof msg.content === 'string') {
292
- blocks.push({ type: 'text', text: msg.content });
293
- } else if (Array.isArray(msg.content)) {
294
- for (const block of msg.content) {
295
- if (block.type === 'text' && 'text' in block) {
296
- blocks.push({ type: 'text', text: (block as any).text });
297
- } else if (block.type === 'tool_use') {
298
- blocks.push({
299
- type: 'tool_use',
300
- id: (block as any).id,
301
- name: (block as any).function?.name || (block as any).name,
302
- input: (block as any).function?.arguments || (block as any).input
303
- });
304
- } else if (block.type === 'tool_result') {
305
- blocks.push({
306
- type: 'tool_result',
307
- tool_use_id: (block as any).tool_call_id || (block as any).tool_use_id,
308
- content: typeof (block as any).content === 'string'
309
- ? (block as any).content
310
- : JSON.stringify((block as any).content)
311
- });
312
- } else if (block.type === 'thinking') {
313
- blocks.push({ type: 'thinking', thinking: (block as any).thinking });
314
- }
315
- }
316
- }
317
-
318
- // Handle tool_calls (OpenAI 格式)
319
- if (msg.tool_calls) {
320
- for (const tc of msg.tool_calls) {
321
- blocks.push({
322
- type: 'tool_use',
323
- id: tc.id,
324
- name: tc.function?.name,
325
- input: tc.function?.arguments ? (typeof tc.function.arguments === 'string' ? JSON.parse(tc.function.arguments) : tc.function.arguments) : {}
326
- });
327
- }
328
- }
329
-
330
- if (blocks.length > 0) {
331
- anthropicMessages.push({
332
- role: msg.role === 'tool' ? 'user' : msg.role,
333
- content: blocks as AnthropicContentBlock[]
334
- });
335
- }
336
- }
337
-
338
- return { system, messages: anthropicMessages };
339
- }
340
-
341
- async chatCompletion(
342
- messages: Message[],
343
- options: ChatCompletionOptions = {}
344
- ): Promise<ChatCompletionResponse> {
345
- const model = options.model || this.authConfig.modelName || 'gpt-4';
346
- const isMiniMax = detectMiniMaxAPI(this.authConfig.baseUrl || '');
347
-
348
- if (isMiniMax) {
349
- return this.minimaxChatCompletion(messages, options);
350
- }
351
-
352
- const isAnthropic = isAnthropicCompatible(this.authConfig.baseUrl || '');
353
- if (isAnthropic) {
354
- return this.anthropicNativeChatCompletion(messages, options);
355
- }
356
-
357
- // OpenAI format request
358
- const requestBody: any = {
359
- model,
360
- messages,
361
- temperature: options.temperature ?? 0.7,
362
- stream: options.stream ?? false
363
- };
364
-
365
- if (options.maxTokens && options.maxTokens > 0) {
366
- requestBody.max_tokens = options.maxTokens;
367
- }
368
-
369
- if (options.tools && options.tools.length > 0) {
370
- requestBody.tools = options.tools;
371
- requestBody.tool_choice = options.toolChoice || 'auto';
372
- }
373
-
374
- if (options.thinkingTokens && options.thinkingTokens > 0) {
375
- requestBody.max_completion_tokens = options.thinkingTokens;
376
- }
377
-
378
- // Debug output(受showAIDebugInfo配置控制)
379
- const showDebug = this.authConfig.showAIDebugInfo ?? false;
380
-
381
- if (showDebug) {
382
- console.log('\n╔══════════════════════════════════════════════════════════╗');
383
- console.log('║ AI REQUEST DEBUG ║');
384
- console.log('╚══════════════════════════════════════════════════════════╝');
385
- console.log(`📦 Model: ${model}`);
386
- console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
387
- console.log(`💬 Total Messages: ${messages.length} items`);
388
- if (options.temperature !== undefined) console.log(`🌡️ Temperature: ${options.temperature}`);
389
- if (options.maxTokens) console.log(`📏 Max Tokens: ${options.maxTokens}`);
390
- if (options.tools?.length) console.log(`🔧 Tools: ${options.tools.length} items`);
391
- if (options.thinkingTokens) console.log(`🧠 Thinking Tokens: ${options.thinkingTokens}`);
392
- console.log('─'.repeat(60));
393
-
394
- // Separate system messages
395
- const systemMsgs = messages.filter(m => m.role === 'system');
396
- const otherMsgs = messages.filter(m => m.role !== 'system');
397
-
398
- if (systemMsgs.length > 0) {
399
- const systemContent = typeof systemMsgs[0].content === 'string'
400
- ? systemMsgs[0].content
401
- : formatMessageContent(systemMsgs[0].content);
402
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
403
- console.log('│ 🟫 SYSTEM │');
404
- console.log('├─────────────────────────────────────────────────────────────┤');
405
- console.log(renderMarkdown(systemContent).split('\n').map(l => '│ ' + l).join('\n'));
406
- console.log('└─────────────────────────────────────────────────────────────┘');
407
- }
408
-
409
- displayMessages(otherMsgs);
410
-
411
- console.log('\n📤 Sending request to API...\n');
412
- }
413
-
414
- try {
415
- const response = await this.client.post('/chat/completions', requestBody);
416
-
417
- if (showDebug) {
418
- console.log('\n╔══════════════════════════════════════════════════════════╗');
419
- console.log('║ AI RESPONSE DEBUG ║');
420
- console.log('╚══════════════════════════════════════════════════════════╝');
421
- console.log(`🆔 ID: ${response.data.id}`);
422
- console.log(`🤖 Model: ${response.data.model}`);
423
- const usage = response.data.usage;
424
- if (usage) {
425
- console.log(`📊 Tokens: ${usage.prompt_tokens} (prompt) + ${usage.completion_tokens} (completion) = ${usage.total_tokens} (total)`);
426
- }
427
- const choice = response.data.choices?.[0];
428
- if (choice) {
429
- console.log(`🏁 Finish Reason: ${choice.finish_reason}`);
430
-
431
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
432
- console.log('│ 🤖 ASSISTANT │');
433
- console.log('├─────────────────────────────────────────────────────────────┤');
434
-
435
- // Display reasoning_content(如果有)
436
- if (choice.message.reasoning_content) {
437
- console.log('│ 🧠 REASONING:');
438
- console.log('│ ───────────────────────────────────────────────────────────');
439
- const reasoningLines = renderMarkdown(choice.message.reasoning_content).split('\n');
440
- for (const line of reasoningLines.slice(0, 15)) {
441
- console.log('│ ' + line.slice(0, 62));
442
- }
443
- if (choice.message.reasoning_content.length > 800) console.log('│ ... (truncated)');
444
- console.log('│ ───────────────────────────────────────────────────────────');
445
- }
446
-
447
- // Display main content
448
- const content = formatResponseContent(choice.message.content);
449
- const lines = content.split('\n');
450
- console.log('│ 💬 CONTENT:');
451
- console.log('│ ───────────────────────────────────────────────────────────');
452
- for (const line of lines.slice(0, 40)) {
453
- console.log('│ ' + line.slice(0, 62));
454
- }
455
- if (lines.length > 40) {
456
- console.log(`│ ... (${lines.length - 40} more lines)`);
457
- }
458
- console.log('└─────────────────────────────────────────────────────────────┘');
459
- }
460
- console.log('╔══════════════════════════════════════════════════════════╗');
461
- console.log('║ RESPONSE ENDED ║');
462
- console.log('╚══════════════════════════════════════════════════════════╝\n');
463
- }
464
-
465
- return response.data;
466
- } catch (error: any) {
467
- if (error.response) {
468
- throw new Error(
469
- `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
470
- );
471
- } else if (error.request) {
472
- throw new Error('Network error: No response received from server');
473
- } else {
474
- throw new Error(`Request error: ${error.message}`);
475
- }
476
- }
477
- }
478
-
479
- // Anthropic official原生 API(使用 /v1/messages 端点)
480
- private async anthropicNativeChatCompletion(
481
- messages: Message[],
482
- options: ChatCompletionOptions = {}
483
- ): Promise<ChatCompletionResponse> {
484
- const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
485
-
486
- const requestBody: any = {
487
- model: options.model || this.authConfig.modelName || 'claude-sonnet-4-20250514',
488
- messages: anthropicMessages,
489
- temperature: options.temperature ?? 1.0,
490
- stream: false,
491
- max_tokens: options.maxTokens || 4096
492
- };
493
-
494
- if (system) {
495
- requestBody.system = system;
496
- }
497
-
498
- // Anthropic native tool format
499
- if (options.tools && options.tools.length > 0) {
500
- requestBody.tools = options.tools.map(tool => ({
501
- name: tool.function.name,
502
- description: tool.function.description,
503
- input_schema: tool.function.parameters || { type: 'object', properties: {} }
504
- }));
505
-
506
- // Convert tool_choice 从 OpenAI 格式到 Anthropic 格式
507
- const toolChoice = options.toolChoice;
508
- if (toolChoice === 'none') {
509
- requestBody.tool_choice = { type: 'auto' };
510
- } else if (toolChoice && typeof toolChoice === 'object') {
511
- if (toolChoice.type === 'function' && toolChoice.function) {
512
- requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
513
- } else {
514
- requestBody.tool_choice = { type: 'auto' };
515
- }
516
- } else {
517
- requestBody.tool_choice = { type: 'auto' };
518
- }
519
- }
520
-
521
- // Anthropic thinking mode
522
- if (options.thinkingTokens && options.thinkingTokens > 0) {
523
- requestBody.thinking = { type: 'enabled', budget_tokens: options.thinkingTokens };
524
- }
525
-
526
- // Debug output(受showAIDebugInfo配置控制)
527
- const showDebug = this.authConfig.showAIDebugInfo ?? false;
528
-
529
- if (showDebug) {
530
- console.log('\n╔══════════════════════════════════════════════════════════╗');
531
- console.log('║ AI REQUEST DEBUG (ANTHROPIC) ║');
532
- console.log('╚══════════════════════════════════════════════════════════╝');
533
- console.log(`📦 Model: ${requestBody.model}`);
534
- console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
535
- console.log(`💬 Total Messages: ${anthropicMessages.length} items`);
536
- if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
537
- if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
538
- if (requestBody.tools) console.log(`🔧 Tools: ${requestBody.tools.length} items`);
539
- if (requestBody.thinking) console.log(`🧠 Thinking Budget: ${requestBody.thinking.budget_tokens}`);
540
- console.log('─'.repeat(60));
541
-
542
- // Display system messages
543
- if (system) {
544
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
545
- console.log('│ 🟫 SYSTEM │');
546
- console.log('├─────────────────────────────────────────────────────────────┤');
547
- console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
548
- console.log('└─────────────────────────────────────────────────────────────┘');
549
- }
550
-
551
- // Display user and assistant messages
552
- displayMessages(anthropicMessages);
553
-
554
- console.log('\n📤 Sending to Anthropic API (v1/messages)...\n');
555
- }
556
-
557
- try {
558
- // Use Anthropic native endpoint /v1/messages
559
- const response = await this.client.post('/v1/messages', requestBody);
560
-
561
- if (showDebug) {
562
- console.log('\n╔══════════════════════════════════════════════════════════╗');
563
- console.log('║ AI RESPONSE DEBUG (ANTHROPIC) ║');
564
- console.log('╚══════════════════════════════════════════════════════════╝');
565
- console.log(`🆔 ID: ${response.data.id}`);
566
- console.log(`🤖 Model: ${response.data.model}`);
567
- const usage = response.data.usage;
568
- if (usage) {
569
- console.log(`📊 Tokens: ${usage.input_tokens} (input) + ${usage.output_tokens} (output) = ${usage.input_tokens + usage.output_tokens} (total)`);
570
- }
571
- console.log(`🏁 Stop Reason: ${response.data.stop_reason}`);
572
-
573
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
574
- console.log(' 🤖 ASSISTANT │');
575
- console.log('├─────────────────────────────────────────────────────────────┤');
576
-
577
- const content = response.data.content || [];
578
- const reasoning = content.filter((c: any) => c.type === 'thinking').map((c: any) => c.thinking).join('');
579
- const textContent = content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('');
580
-
581
- // Display thinking
582
- if (reasoning) {
583
- console.log('│ 🧠 REASONING:');
584
- console.log('│ ───────────────────────────────────────────────────────────');
585
- const reasoningLines = renderMarkdown(reasoning).split('\n');
586
- for (const line of reasoningLines.slice(0, 15)) {
587
- console.log('│ ' + line.slice(0, 62));
588
- }
589
- if (reasoning.length > 800) console.log('│ ... (truncated)');
590
- console.log('│ ───────────────────────────────────────────────────────────');
591
- }
592
-
593
- // Display content
594
- console.log('│ 💬 CONTENT:');
595
- console.log('│ ───────────────────────────────────────────────────────────');
596
- const lines = renderMarkdown(textContent).split('\n');
597
- for (const line of lines.slice(0, 40)) {
598
- console.log('│ ' + line.slice(0, 62));
599
- }
600
- if (lines.length > 40) {
601
- console.log(`│ ... (${lines.length - 40} more lines)`);
602
- }
603
- console.log('└─────────────────────────────────────────────────────────────┘');
604
-
605
- console.log('\n╔══════════════════════════════════════════════════════════╗');
606
- console.log('║ RESPONSE ENDED ║');
607
- console.log('╚══════════════════════════════════════════════════════════╝\n');
608
- }
609
-
610
- return this.convertFromAnthropicNativeResponse(response.data);
611
- } catch (error: any) {
612
- if (error.response) {
613
- throw new Error(
614
- `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
615
- );
616
- } else if (error.request) {
617
- throw new Error('Network error: No response received from server');
618
- } else {
619
- throw new Error(`Request error: ${error.message}`);
620
- }
621
- }
622
- }
623
-
624
- // MiniMax API(Automatically select based on baseUrl Anthropic 或 OpenAI 格式)
625
- private async minimaxChatCompletion(
626
- messages: Message[],
627
- options: ChatCompletionOptions = {}
628
- ): Promise<ChatCompletionResponse> {
629
- const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
630
- const { endpoint, format } = getMiniMaxEndpoint(this.authConfig.baseUrl || '');
631
-
632
- const requestBody: any = {
633
- model: options.model || this.authConfig.modelName || 'MiniMax-M2',
634
- messages: format === 'anthropic' ? anthropicMessages : messages,
635
- temperature: options.temperature ?? 1.0,
636
- stream: false,
637
- max_tokens: options.maxTokens || 4096
638
- };
639
-
640
- if (system && format === 'anthropic') {
641
- requestBody.system = system;
642
- }
643
-
644
- if (format === 'anthropic') {
645
- // Anthropic format tools
646
- if (options.tools && options.tools.length > 0) {
647
- requestBody.tools = options.tools.map(tool => ({
648
- name: tool.function.name,
649
- description: tool.function.description,
650
- input_schema: tool.function.parameters || { type: 'object', properties: {} }
651
- }));
652
-
653
- const toolChoice = options.toolChoice;
654
- if (toolChoice === 'none') {
655
- requestBody.tool_choice = { type: 'auto' };
656
- } else if (toolChoice && typeof toolChoice === 'object') {
657
- if (toolChoice.type === 'function' && toolChoice.function) {
658
- requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
659
- } else {
660
- requestBody.tool_choice = { type: 'auto' };
661
- }
662
- } else {
663
- requestBody.tool_choice = { type: 'auto' };
664
- }
665
- }
666
- } else {
667
- // OpenAI format tools
668
- if (options.tools && options.tools.length > 0) {
669
- requestBody.tools = options.tools;
670
- requestBody.tool_choice = options.toolChoice || 'auto';
671
- }
672
- }
673
-
674
- // Debug output(受showAIDebugInfo配置控制)
675
- const showDebug = this.authConfig.showAIDebugInfo ?? false;
676
-
677
- if (showDebug) {
678
- console.log('\n╔══════════════════════════════════════════════════════════╗');
679
- console.log('║ AI REQUEST DEBUG (MINIMAX) ║');
680
- console.log('╚══════════════════════════════════════════════════════════╝');
681
- console.log(`📦 Model: ${requestBody.model}`);
682
- console.log(`🔗 Format: ${format.toUpperCase()} | Endpoint: ${endpoint}`);
683
- console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
684
- console.log(`💬 Total Messages: ${requestBody.messages.length} items`);
685
- if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
686
- if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
687
- if (requestBody.tools) console.log(`🔧 Tools: ${requestBody.tools.length} items`);
688
- console.log('─'.repeat(60));
689
-
690
- // Display system messages
691
- if (system && format === 'anthropic') {
692
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
693
- console.log('│ 🟫 SYSTEM │');
694
- console.log('├─────────────────────────────────────────────────────────────┤');
695
- console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
696
- console.log('└─────────────────────────────────────────────────────────────┘');
697
- }
698
-
699
- // Display other messages
700
- displayMessages(requestBody.messages);
701
-
702
- console.log('\n📤 Sending to MiniMax API...\n');
703
- }
704
-
705
- try {
706
- // MiniMax uses correct endpoint
707
- const response = await this.client.post(endpoint, requestBody);
708
-
709
- if (showDebug) {
710
- console.log('\n╔══════════════════════════════════════════════════════════╗');
711
- console.log('║ AI RESPONSE DEBUG (MINIMAX) ║');
712
- console.log('╚══════════════════════════════════════════════════════════╝');
713
- console.log(`🆔 ID: ${response.data.id}`);
714
- console.log(`🤖 Model: ${response.data.model}`);
715
- const usage = response.data.usage;
716
- if (usage) {
717
- console.log(`📊 Tokens: ${usage.prompt_tokens} (prompt) + ${usage.completion_tokens} (completion) = ${usage.total_tokens} (total)`);
718
- }
719
- console.log(`🏁 Stop Reason: ${response.data.stop_reason}`);
720
-
721
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
722
- console.log('│ 🤖 ASSISTANT │');
723
- console.log('├─────────────────────────────────────────────────────────────┤');
724
-
725
- const message = response.data.choices?.[0]?.message;
726
- const content = typeof message?.content === 'string' ? message.content : JSON.stringify(message?.content);
727
-
728
- console.log('│ 💬 CONTENT:');
729
- console.log(' ───────────────────────────────────────────────────────────');
730
- const lines = renderMarkdown(content).split('\n');
731
- for (const line of lines.slice(0, 40)) {
732
- console.log('│ ' + line.slice(0, 62));
733
- }
734
- if (lines.length > 40) {
735
- console.log(`│ ... (${lines.length - 40} more lines)`);
736
- }
737
- console.log('└─────────────────────────────────────────────────────────────┘');
738
-
739
- console.log('\n╔══════════════════════════════════════════════════════════╗');
740
- console.log('║ RESPONSE ENDED ║');
741
- console.log('╚══════════════════════════════════════════════════════════╝\n');
742
- }
743
-
744
- if (format === 'anthropic') {
745
- return this.convertFromAnthropicNativeResponse(response.data);
746
- } else {
747
- return this.convertFromMiniMaxResponse(response.data);
748
- }
749
- } catch (error: any) {
750
- if (error.response) {
751
- throw new Error(
752
- `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
753
- );
754
- } else if (error.request) {
755
- throw new Error('Network error: No response received from server');
756
- } else {
757
- throw new Error(`Request error: ${error.message}`);
758
- }
759
- }
760
- }
761
-
762
- // Convert Anthropic native response to unified format
763
- private convertFromAnthropicNativeResponse(anthropicResponse: any): ChatCompletionResponse {
764
- const content = anthropicResponse.content || [];
765
- let textContent = '';
766
- let reasoningContent = '';
767
- const toolCalls: any[] = [];
768
-
769
- for (const block of content) {
770
- if (block.type === 'text') {
771
- textContent += block.text || '';
772
- } else if (block.type === 'thinking') {
773
- reasoningContent += block.thinking || '';
774
- } else if (block.type === 'tool_use') {
775
- toolCalls.push({
776
- id: block.id,
777
- type: 'function',
778
- function: {
779
- name: block.name,
780
- arguments: JSON.stringify(block.input || {})
781
- }
782
- });
783
- }
784
- }
785
-
786
- return {
787
- id: anthropicResponse.id || `anthropic-${Date.now()}`,
788
- object: 'chat.completion',
789
- created: Math.floor(Date.now() / 1000),
790
- model: anthropicResponse.model || this.authConfig.modelName || 'claude-sonnet-4-20250514',
791
- choices: [{
792
- index: 0,
793
- message: {
794
- role: 'assistant',
795
- content: textContent,
796
- reasoning_content: reasoningContent || undefined,
797
- tool_calls: toolCalls.length > 0 ? toolCalls : undefined
798
- },
799
- finish_reason: anthropicResponse.stop_reason === 'end_turn' ? 'stop' :
800
- anthropicResponse.stop_reason === 'max_tokens' ? 'length' : 'stop'
801
- }],
802
- usage: anthropicResponse.usage ? {
803
- prompt_tokens: anthropicResponse.usage.input_tokens || 0,
804
- completion_tokens: anthropicResponse.usage.output_tokens || 0,
805
- total_tokens: (anthropicResponse.usage.input_tokens || 0) + (anthropicResponse.usage.output_tokens || 0)
806
- } : undefined
807
- };
808
- }
809
-
810
- // Convert MiniMax response to unified format
811
- private convertFromMiniMaxResponse(minimaxResponse: any): ChatCompletionResponse {
812
- const message = minimaxResponse.choices?.[0]?.message;
813
- const content = message?.content;
814
- let textContent = '';
815
- let reasoningContent = '';
816
- const toolCalls: any[] = [];
817
-
818
- if (typeof content === 'string') {
819
- textContent = content.trim();
820
- } else if (Array.isArray(content)) {
821
- for (const block of content) {
822
- if (block.type === 'text') {
823
- textContent += block.text || '';
824
- } else if (block.type === 'thinking') {
825
- reasoningContent += block.thinking || '';
826
- } else if (block.type === 'tool_use') {
827
- toolCalls.push({
828
- id: block.id,
829
- type: 'function',
830
- function: {
831
- name: block.name,
832
- arguments: JSON.stringify(block.input || {})
833
- }
834
- });
835
- }
836
- }
837
- }
838
-
839
- return {
840
- id: minimaxResponse.id || `minimax-${Date.now()}`,
841
- object: 'chat.completion',
842
- created: Math.floor(Date.now() / 1000),
843
- model: minimaxResponse.model || this.authConfig.modelName || 'MiniMax-M2',
844
- choices: [{
845
- index: 0,
846
- message: {
847
- role: 'assistant',
848
- content: textContent,
849
- reasoning_content: reasoningContent || undefined,
850
- tool_calls: toolCalls.length > 0 ? toolCalls : undefined
851
- },
852
- finish_reason: minimaxResponse.stop_reason === 'end_turn' ? 'stop' :
853
- minimaxResponse.stop_reason === 'max_tokens' ? 'length' : 'stop'
854
- }],
855
- usage: minimaxResponse.usage
856
- };
857
- }
858
-
859
- async *streamChatCompletion(
860
- messages: Message[],
861
- options: ChatCompletionOptions = {}
862
- ): AsyncGenerator<string, void, unknown> {
863
- const isMiniMax = detectMiniMaxAPI(this.authConfig.baseUrl || '');
864
-
865
- if (isMiniMax) {
866
- yield* this.minimaxStreamChatCompletion(messages, options);
867
- return;
868
- }
869
-
870
- const isAnthropic = isAnthropicCompatible(this.authConfig.baseUrl || '');
871
- if (isAnthropic) {
872
- yield* this.anthropicNativeStreamChatCompletion(messages, options);
873
- return;
874
- }
875
-
876
- // OpenAI streaming response
877
- const model = options.model || this.authConfig.modelName || 'gpt-4';
878
-
879
- const requestBody: any = {
880
- model,
881
- messages,
882
- temperature: options.temperature ?? 0.7,
883
- stream: true
884
- };
885
-
886
- if (options.maxTokens && options.maxTokens > 0) {
887
- requestBody.max_tokens = options.maxTokens;
888
- }
889
-
890
- if (options.tools && options.tools.length > 0) {
891
- requestBody.tools = options.tools;
892
- requestBody.tool_choice = options.toolChoice || 'auto';
893
- }
894
-
895
- if (options.thinkingTokens && options.thinkingTokens > 0) {
896
- requestBody.max_completion_tokens = options.thinkingTokens;
897
- }
898
-
899
- // Debug output(受showAIDebugInfo配置控制)
900
- const showDebug = this.authConfig.showAIDebugInfo ?? false;
901
-
902
- if (showDebug) {
903
- console.log('\n╔══════════════════════════════════════════════════════════╗');
904
- console.log('║ AI REQUEST DEBUG (STREAM) ║');
905
- console.log('╚══════════════════════════════════════════════════════════╝');
906
- console.log(`📦 Model: ${model}`);
907
- console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
908
- console.log(`💬 Total Messages: ${messages.length} items`);
909
- if (options.temperature) console.log(`🌡️ Temperature: ${options.temperature}`);
910
- if (options.maxTokens) console.log(`📏 Max Tokens: ${options.maxTokens}`);
911
- if (options.tools?.length) console.log(`🔧 Tools: ${options.tools.length} items`);
912
- console.log('─'.repeat(60));
913
-
914
- // Separate and display messages
915
- const systemMsgs = messages.filter(m => m.role === 'system');
916
- const otherMsgs = messages.filter(m => m.role !== 'system');
917
-
918
- if (systemMsgs.length > 0) {
919
- const systemContent = typeof systemMsgs[0].content === 'string'
920
- ? systemMsgs[0].content
921
- : formatMessageContent(systemMsgs[0].content);
922
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
923
- console.log('│ 🟫 SYSTEM │');
924
- console.log('├─────────────────────────────────────────────────────────────┤');
925
- console.log(renderMarkdown(systemContent).split('\n').map(l => '│ ' + l).join('\n'));
926
- console.log('└─────────────────────────────────────────────────────────────┘');
927
- }
928
-
929
- displayMessages(otherMsgs);
930
-
931
- console.log('\n📤 Starting stream...\n');
932
- }
933
-
934
- try {
935
- const response = await this.client.post('/chat/completions', requestBody, {
936
- responseType: 'stream'
937
- });
938
-
939
- console.log('📥 Receiving stream chunks...\n');
940
-
941
- let buffer = '';
942
- let chunkCount = 0;
943
- let outputBuffer = '';
944
-
945
- for await (const chunk of response.data) {
946
- buffer += chunk.toString();
947
- const lines = buffer.split('\n');
948
-
949
- buffer = lines.pop() || '';
950
-
951
- for (const line of lines) {
952
- const trimmedLine = line.trim();
953
- if (!trimmedLine) continue;
954
-
955
- if (trimmedLine.startsWith('data: ')) {
956
- const data = trimmedLine.slice(6);
957
- if (data === '[DONE]') {
958
- if (showDebug) {
959
- console.log('\n╔══════════════════════════════════════════════════════════╗');
960
- console.log('║ STREAM COMPLETED ║');
961
- console.log('╚══════════════════════════════════════════════════════════╝');
962
- console.log(`📦 Total chunks: ${chunkCount}`);
963
- console.log(`📏 Total output: ${outputBuffer.length} chars`);
964
-
965
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
966
- console.log('│ 🤖 ASSISTANT OUTPUT │');
967
- console.log('├─────────────────────────────────────────────────────────────┤');
968
- console.log('│ 💬 CONTENT:');
969
- console.log('│ ───────────────────────────────────────────────────────────');
970
- const lines = renderMarkdown(outputBuffer).split('\n');
971
- for (const line of lines.slice(0, 30)) {
972
- console.log('│ ' + line.slice(0, 62));
973
- }
974
- if (lines.length > 30) {
975
- console.log(`│ ... (${lines.length - 30} more lines)`);
976
- }
977
- console.log('└─────────────────────────────────────────────────────────────┘');
978
- console.log('');
979
- }
980
- return;
981
- }
982
-
983
- try {
984
- const parsed = JSON.parse(data);
985
- const delta = parsed.choices?.[0]?.delta;
986
- if (delta?.content) {
987
- chunkCount++;
988
- outputBuffer += delta.content;
989
- yield delta.content;
990
- } else if (delta?.reasoning_content) {
991
- chunkCount++;
992
- outputBuffer += delta.reasoning_content;
993
- yield delta.reasoning_content;
994
- }
995
- } catch (e) {
996
- // Silently ignore parsing errors
997
- }
998
- }
999
- }
1000
- }
1001
-
1002
- if (buffer.trim()) {
1003
- const trimmedLine = buffer.trim();
1004
- if (trimmedLine.startsWith('data: ')) {
1005
- const data = trimmedLine.slice(6);
1006
- if (data !== '[DONE]') {
1007
- try {
1008
- const parsed = JSON.parse(data);
1009
- const delta = parsed.choices?.[0]?.delta;
1010
- if (delta?.content) {
1011
- yield delta.content;
1012
- } else if (delta?.reasoning_content) {
1013
- yield delta.reasoning_content;
1014
- }
1015
- } catch (e) {
1016
- // Ignore final parsing errors
1017
- }
1018
- }
1019
- }
1020
- }
1021
-
1022
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1023
- console.log('║ STREAM COMPLETED ║');
1024
- console.log('╚══════════════════════════════════════════════════════════╝\n');
1025
- } catch (error: any) {
1026
- if (error.response) {
1027
- throw new Error(
1028
- `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
1029
- );
1030
- } else if (error.request) {
1031
- throw new Error('Network error: No response received from server');
1032
- } else {
1033
- throw new Error(`Request error: ${error.message}`);
1034
- }
1035
- }
1036
- }
1037
-
1038
- // Anthropic native streaming response(/v1/messages 端点)
1039
- private async *anthropicNativeStreamChatCompletion(
1040
- messages: Message[],
1041
- options: ChatCompletionOptions = {}
1042
- ): AsyncGenerator<string, void, unknown> {
1043
- const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
1044
-
1045
- const requestBody: any = {
1046
- model: options.model || this.authConfig.modelName || 'claude-sonnet-4-20250514',
1047
- messages: anthropicMessages,
1048
- temperature: options.temperature ?? 1.0,
1049
- stream: true,
1050
- max_tokens: options.maxTokens || 4096
1051
- };
1052
-
1053
- if (system) {
1054
- requestBody.system = system;
1055
- }
1056
-
1057
- // Anthropic native tool format
1058
- if (options.tools && options.tools.length > 0) {
1059
- requestBody.tools = options.tools.map(tool => ({
1060
- name: tool.function.name,
1061
- description: tool.function.description,
1062
- input_schema: tool.function.parameters || { type: 'object', properties: {} }
1063
- }));
1064
-
1065
- const toolChoice = options.toolChoice;
1066
- if (toolChoice === 'none') {
1067
- requestBody.tool_choice = { type: 'auto' };
1068
- } else if (toolChoice && typeof toolChoice === 'object') {
1069
- if (toolChoice.type === 'function' && toolChoice.function) {
1070
- requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
1071
- } else {
1072
- requestBody.tool_choice = { type: 'auto' };
1073
- }
1074
- } else {
1075
- requestBody.tool_choice = { type: 'auto' };
1076
- }
1077
- }
1078
-
1079
- if (options.thinkingTokens && options.thinkingTokens > 0) {
1080
- requestBody.thinking = { type: 'enabled', budget_tokens: options.thinkingTokens };
1081
- }
1082
-
1083
- // Debug output(受showAIDebugInfo配置控制)
1084
- const showDebug = this.authConfig.showAIDebugInfo ?? false;
1085
-
1086
- if (showDebug) {
1087
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1088
- console.log('║ AI REQUEST DEBUG (ANTHROPIC STREAM) ║');
1089
- console.log('╚══════════════════════════════════════════════════════════╝');
1090
- console.log(`📦 Model: ${requestBody.model}`);
1091
- console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
1092
- console.log(`💬 Total Messages: ${anthropicMessages.length} items`);
1093
- if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
1094
- if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
1095
- if (requestBody.thinking) console.log(`🧠 Thinking Budget: ${requestBody.thinking.budget_tokens}`);
1096
- console.log('─'.repeat(60));
1097
-
1098
- // Display system messages
1099
- if (system) {
1100
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
1101
- console.log('│ 🟫 SYSTEM │');
1102
- console.log('├─────────────────────────────────────────────────────────────┤');
1103
- console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
1104
- console.log('└─────────────────────────────────────────────────────────────┘');
1105
- }
1106
-
1107
- displayMessages(anthropicMessages);
1108
-
1109
- console.log('\n📤 Starting Anthropic stream...\n');
1110
- }
1111
-
1112
- try {
1113
- // Anthropic native streaming endpoint /v1/messages
1114
- const response = await this.client.post('/v1/messages', requestBody, {
1115
- responseType: 'stream'
1116
- });
1117
-
1118
- console.log('📥 Receiving Anthropic stream chunks...\n');
1119
-
1120
- let buffer = '';
1121
- let outputBuffer = '';
1122
-
1123
- for await (const chunk of response.data) {
1124
- buffer += chunk.toString();
1125
- const lines = buffer.split('\n');
1126
- buffer = lines.pop() || '';
1127
-
1128
- for (const line of lines) {
1129
- const trimmedLine = line.trim();
1130
- if (!trimmedLine) continue;
1131
-
1132
- // Anthropic streaming format: data: {"type":"content_block_delta",...}
1133
- if (trimmedLine.startsWith('data: ')) {
1134
- const data = trimmedLine.slice(6);
1135
-
1136
- try {
1137
- const parsed = JSON.parse(data);
1138
-
1139
- // Anthropic event types
1140
- if (parsed.type === 'content_block_delta') {
1141
- if (parsed.delta?.type === 'text_delta' && parsed.delta.text) {
1142
- outputBuffer += parsed.delta.text;
1143
- yield parsed.delta.text;
1144
- } else if (parsed.delta?.type === 'thinking_delta' && parsed.delta.thinking) {
1145
- outputBuffer += parsed.delta.thinking;
1146
- yield parsed.delta.thinking;
1147
- }
1148
- } else if (parsed.type === 'message_delta') {
1149
- if (parsed.delta?.stop_reason) {
1150
- // Message end
1151
- if (showDebug) {
1152
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1153
- console.log('║ STREAM COMPLETED ║');
1154
- console.log('╚══════════════════════════════════════════════════════════╝');
1155
- console.log(`📏 Total output: ${outputBuffer.length} chars`);
1156
-
1157
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
1158
- console.log('│ 🤖 ASSISTANT OUTPUT │');
1159
- console.log('├─────────────────────────────────────────────────────────────┤');
1160
- console.log('│ 💬 CONTENT:');
1161
- console.log('│ ───────────────────────────────────────────────────────────');
1162
- const lines = renderMarkdown(outputBuffer).split('\n');
1163
- for (const line of lines.slice(0, 30)) {
1164
- console.log('│ ' + line.slice(0, 62));
1165
- }
1166
- if (lines.length > 30) {
1167
- console.log(`│ ... (${lines.length - 30} more lines)`);
1168
- }
1169
- console.log('└─────────────────────────────────────────────────────────────┘');
1170
- console.log('');
1171
- }
1172
- return;
1173
- }
1174
- }
1175
- } catch (e) {
1176
- // Ignore parsing errors
1177
- }
1178
- }
1179
- }
1180
- }
1181
-
1182
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1183
- console.log('║ STREAM COMPLETED ║');
1184
- console.log('╚══════════════════════════════════════════════════════════╝\n');
1185
- } catch (error: any) {
1186
- if (error.response) {
1187
- throw new Error(
1188
- `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
1189
- );
1190
- } else if (error.request) {
1191
- throw new Error('Network error: No response received from server');
1192
- } else {
1193
- throw new Error(`Request error: ${error.message}`);
1194
- }
1195
- }
1196
- }
1197
-
1198
- // MiniMax streaming response(Automatically select based on baseUrl格式)
1199
- private async *minimaxStreamChatCompletion(
1200
- messages: Message[],
1201
- options: ChatCompletionOptions = {}
1202
- ): AsyncGenerator<string, void, unknown> {
1203
- const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
1204
- const { endpoint, format } = getMiniMaxEndpoint(this.authConfig.baseUrl || '');
1205
-
1206
- const requestBody: any = {
1207
- model: options.model || this.authConfig.modelName || 'MiniMax-M2',
1208
- messages: format === 'anthropic' ? anthropicMessages : messages,
1209
- temperature: options.temperature ?? 1.0,
1210
- stream: true,
1211
- max_tokens: options.maxTokens || 4096
1212
- };
1213
-
1214
- if (system && format === 'anthropic') {
1215
- requestBody.system = system;
1216
- }
1217
-
1218
- if (format === 'anthropic') {
1219
- // Anthropic 格式的工具
1220
- if (options.tools && options.tools.length > 0) {
1221
- requestBody.tools = options.tools.map(tool => ({
1222
- name: tool.function.name,
1223
- description: tool.function.description,
1224
- input_schema: tool.function.parameters || { type: 'object', properties: {} }
1225
- }));
1226
-
1227
- const toolChoice = options.toolChoice;
1228
- if (toolChoice === 'none') {
1229
- requestBody.tool_choice = { type: 'auto' };
1230
- } else if (toolChoice && typeof toolChoice === 'object') {
1231
- if (toolChoice.type === 'function' && toolChoice.function) {
1232
- requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
1233
- } else {
1234
- requestBody.tool_choice = { type: 'auto' };
1235
- }
1236
- } else {
1237
- requestBody.tool_choice = { type: 'auto' };
1238
- }
1239
- }
1240
- } else {
1241
- // OpenAI format tools
1242
- if (options.tools && options.tools.length > 0) {
1243
- requestBody.tools = options.tools;
1244
- requestBody.tool_choice = options.toolChoice || 'auto';
1245
- }
1246
- }
1247
-
1248
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1249
- console.log('║ AI REQUEST DEBUG (MINIMAX STREAM) ║');
1250
- console.log('╚══════════════════════════════════════════════════════════╝');
1251
- console.log(`📦 Model: ${requestBody.model}`);
1252
- console.log(`🔗 Format: ${format.toUpperCase()} | Endpoint: ${endpoint}`);
1253
- console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
1254
- console.log(`💬 Total Messages: ${requestBody.messages.length} items`);
1255
- if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
1256
- if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
1257
- console.log('─'.repeat(60));
1258
-
1259
- // Display system messages
1260
- if (system && format === 'anthropic') {
1261
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
1262
- console.log('│ 🟫 SYSTEM │');
1263
- console.log('├─────────────────────────────────────────────────────────────┤');
1264
- console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
1265
- console.log('└─────────────────────────────────────────────────────────────┘');
1266
- }
1267
-
1268
- displayMessages(requestBody.messages);
1269
-
1270
- console.log('\n📤 Starting MiniMax stream...\n');
1271
-
1272
- try {
1273
- // MiniMax uses correct endpoint
1274
- const response = await this.client.post(endpoint, requestBody, {
1275
- responseType: 'stream'
1276
- });
1277
-
1278
- console.log('📥 Receiving MiniMax stream chunks...\n');
1279
-
1280
- let buffer = '';
1281
- let outputBuffer = '';
1282
-
1283
- for await (const chunk of response.data) {
1284
- buffer += chunk.toString();
1285
- const lines = buffer.split('\n');
1286
- buffer = lines.pop() || '';
1287
-
1288
- for (const line of lines) {
1289
- const trimmedLine = line.trim();
1290
- if (!trimmedLine) continue;
1291
-
1292
- // Parse different streaming responses based on format
1293
- if (format === 'anthropic') {
1294
- // Anthropic SSE format: data: {"type":"content_block_delta",...}
1295
- if (trimmedLine.startsWith('data: ')) {
1296
- const data = trimmedLine.slice(6);
1297
-
1298
- try {
1299
- const parsed = JSON.parse(data);
1300
-
1301
- if (parsed.type === 'content_block_delta') {
1302
- if (parsed.delta?.type === 'text_delta' && parsed.delta.text) {
1303
- yield parsed.delta.text;
1304
- } else if (parsed.delta?.type === 'thinking_delta' && parsed.delta.thinking) {
1305
- yield parsed.delta.thinking;
1306
- }
1307
- } else if (parsed.type === 'message_delta') {
1308
- if (parsed.delta?.stop_reason) {
1309
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1310
- console.log('║ STREAM COMPLETED ║');
1311
- console.log('╚══════════════════════════════════════════════════════════╝');
1312
- console.log(`📏 Total output: ${outputBuffer.length} chars`);
1313
-
1314
- console.log('\n┌─────────────────────────────────────────────────────────────┐');
1315
- console.log('│ 🤖 ASSISTANT OUTPUT │');
1316
- console.log('├─────────────────────────────────────────────────────────────┤');
1317
- console.log('│ 💬 CONTENT:');
1318
- console.log('│ ───────────────────────────────────────────────────────────');
1319
- const lines = renderMarkdown(outputBuffer).split('\n');
1320
- for (const line of lines.slice(0, 30)) {
1321
- console.log('│ ' + line.slice(0, 62));
1322
- }
1323
- if (lines.length > 30) {
1324
- console.log(`│ ... (${lines.length - 30} more lines)`);
1325
- }
1326
- console.log('└─────────────────────────────────────────────────────────────┘');
1327
- console.log('');
1328
- return;
1329
- }
1330
- }
1331
- } catch (e) {
1332
- // Ignore parsing errors
1333
- }
1334
- }
1335
- } else {
1336
- // OpenAI SSE format: data: {...}
1337
- if (trimmedLine.startsWith('data: ')) {
1338
- const data = trimmedLine.slice(6);
1339
- if (data === '[DONE]') continue;
1340
-
1341
- try {
1342
- const parsed = JSON.parse(data);
1343
- const delta = parsed.choices?.[0]?.delta;
1344
- if (delta?.content) {
1345
- outputBuffer += delta.content;
1346
- yield delta.content;
1347
- } else if (delta?.reasoning_content) {
1348
- outputBuffer += delta.reasoning_content;
1349
- yield delta.reasoning_content;
1350
- }
1351
- } catch (e) {
1352
- // Ignore parsing errors
1353
- }
1354
- }
1355
- }
1356
- }
1357
- }
1358
-
1359
- console.log('\n╔══════════════════════════════════════════════════════════╗');
1360
- console.log('║ STREAM COMPLETED ║');
1361
- console.log('╚══════════════════════════════════════════════════════════╝\n');
1362
- } catch (error: any) {
1363
- if (error.response) {
1364
- throw new Error(
1365
- `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
1366
- );
1367
- } else if (error.request) {
1368
- throw new Error('Network error: No response received from server');
1369
- } else {
1370
- throw new Error(`Request error: ${error.message}`);
1371
- }
1372
- }
1373
- }
1374
-
1375
- async listModels(): Promise<any[]> {
1376
- try {
1377
- const response = await this.client.get('/models');
1378
- return response.data.data || [];
1379
- } catch (error: any) {
1380
- console.error('Failed to list models:', error);
1381
- return [];
1382
- }
1383
- }
1384
-
1385
- updateAuthConfig(authConfig: AuthConfig): void {
1386
- this.authConfig = authConfig;
1387
- this.client.defaults.baseURL = authConfig.baseUrl;
1388
-
1389
- const isMiniMax = detectMiniMaxAPI(authConfig.baseUrl || '');
1390
- const isAnthropic = !isMiniMax && isAnthropicCompatible(authConfig.baseUrl || '');
1391
-
1392
- if (isMiniMax || isAnthropic) {
1393
- // MiniMax/Anthropic: Use x-api-key auth header
1394
- this.client.defaults.headers['x-api-key'] = authConfig.apiKey || '';
1395
- this.client.defaults.headers['anthropic-version'] = '2023-06-01';
1396
- // Clear Bearer header
1397
- delete this.client.defaults.headers['Authorization'];
1398
- } else {
1399
- // OpenAI compatible: Use Bearer token
1400
- this.client.defaults.headers['Authorization'] = `Bearer ${authConfig.apiKey}`;
1401
- // Clear x-api-key header
1402
- delete this.client.defaults.headers['x-api-key'];
1403
- delete this.client.defaults.headers['anthropic-version'];
1404
- }
1405
- }
1406
-
1407
- getAuthConfig(): AuthConfig {
1408
- return { ...this.authConfig };
1409
- }
1410
-
1411
- // Check if messages contain tool calls
1412
- hasToolCalls(messages: Message[]): boolean {
1413
- return messages.some(msg => {
1414
- if (msg.tool_calls && msg.tool_calls.length > 0) return true;
1415
- if (Array.isArray(msg.content)) {
1416
- return msg.content.some(block =>
1417
- block.type === 'tool_use' ||
1418
- (block as any).type === 'tool_result'
1419
- );
1420
- }
1421
- return false;
1422
- });
1423
- }
1424
- }
1425
-
1426
- export function detectThinkingKeywords(text: string): 'none' | 'normal' | 'hard' | 'mega' | 'ultra' {
1427
- const ultraKeywords = ['super think', 'extreme think', 'deep think', 'full think', 'ultra think', 'careful think',
1428
- 'ultrathink', 'think really super hard', 'think intensely'];
1429
- const megaKeywords = ['strong think', 'powerful think', 'think hard', 'try hard to think', 'think well', 'think carefully',
1430
- 'megathink', 'think really hard', 'think a lot'];
1431
- const hardKeywords = ['think again', 'think more', 'think clearly', 'think thoroughly', 'consider carefully',
1432
- 'think about it', 'think more', 'think harder'];
1433
- const normalKeywords = ['think', 'think', 'consider', 'think'];
1434
-
1435
- const lowerText = text.toLowerCase();
1436
-
1437
- if (ultraKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1438
- return 'ultra';
1439
- } else if (megaKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1440
- return 'mega';
1441
- } else if (hardKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1442
- return 'hard';
1443
- } else if (normalKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1444
- return 'normal';
1445
- }
1446
-
1447
- return 'none';
1448
- }
1449
-
1450
- export function getThinkingTokens(mode: 'none' | 'normal' | 'hard' | 'mega' | 'ultra'): number {
1451
- const tokensMap = {
1452
- none: 0,
1453
- normal: 2000,
1454
- hard: 4000,
1455
- mega: 10000,
1456
- ultra: 32000
1457
- };
1458
- return tokensMap[mode];
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import https from 'https';
3
+ import { AuthConfig } from './types.js';
4
+ import { withRetry, RetryConfig } from './retry.js';
5
+
6
+ // Message content block type for Anthropic format
7
+ export interface AnthropicContentBlock {
8
+ type: 'text' | 'tool_use' | 'tool_result' | 'thinking';
9
+ text?: string;
10
+ id?: string;
11
+ name?: string;
12
+ input?: any;
13
+ tool_use_id?: string;
14
+ content?: string;
15
+ thinking?: string;
16
+ }
17
+
18
+ // Markdown rendering helper function
19
+ export function renderMarkdown(text: string): string {
20
+ // Code block rendering
21
+ text = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
22
+ return `\n┌─[${lang || 'code'}]\n${code.trim().split('\n').map((l: string) => '│ ' + l).join('\n')}\n└─\n`;
23
+ });
24
+
25
+ // Inline code rendering
26
+ text = text.replace(/`([^`]+)`/g, '`$1`');
27
+
28
+ // Bold rendering
29
+ text = text.replace(/\*\*([^*]+)\*\*/g, '●$1○');
30
+
31
+ // Italic rendering
32
+ text = text.replace(/\*([^*]+)\*/g, '/$1/');
33
+
34
+ // List rendering
35
+ text = text.replace(/^- (.*$)/gm, ' $1');
36
+ text = text.replace(/^\d+\. (.*$)/gm, '• $1');
37
+
38
+ // Heading rendering
39
+ text = text.replace(/^### (.*$)/gm, '\n━━━ $1 ━━━\n');
40
+ text = text.replace(/^## (.*$)/gm, '\n━━━━━ $1 ━━━━━\n');
41
+ text = text.replace(/^# (.*$)/gm, '\n━━━━━━━ $1 ━━━━━━━\n');
42
+
43
+ // Quote rendering
44
+ text = text.replace(/^> (.*$)/gm, '│ │ $1');
45
+
46
+ // Link rendering
47
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '[$1]($2)');
48
+
49
+ return text;
50
+ }
51
+
52
+ // Format message content
53
+ function formatMessageContent(content: string | Array<any>): string {
54
+ if (typeof content === 'string') {
55
+ return renderMarkdown(content);
56
+ }
57
+
58
+ const parts: string[] = [];
59
+ let hasToolUse = false;
60
+
61
+ for (const block of content) {
62
+ if (block.type === 'text') {
63
+ parts.push(renderMarkdown(block.text || ''));
64
+ } else if (block.type === 'tool_use') {
65
+ hasToolUse = true;
66
+ parts.push(`[🔧 TOOL CALL PENDING: ${block.name}]`);
67
+ } else if (block.type === 'tool_result') {
68
+ const result = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
69
+ parts.push(`[✅ TOOL RESULT]\n${result}`);
70
+ } else if (block.type === 'thinking') {
71
+ parts.push(`[🧠 THINKING]\n${block.thinking || ''}`);
72
+ }
73
+ }
74
+
75
+ if (hasToolUse) {
76
+ parts.push('\n[⚠️ Note: Tool calls are executed by the framework, not displayed here]');
77
+ }
78
+
79
+ return parts.join('\n');
80
+ }
81
+
82
+ // Display messages by category
83
+ export function displayMessages(messages: any[], systemPrompt?: string): void {
84
+ const roleColors: Record<string, string> = {
85
+ system: '🟫 SYSTEM',
86
+ user: '👤 USER',
87
+ assistant: '🤖 ASSISTANT',
88
+ tool: '🔧 TOOL'
89
+ };
90
+
91
+ // Display system message first (if there's a separate systemPrompt parameter)
92
+ if (systemPrompt) {
93
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
94
+ console.log('│ 🟫 SYSTEM │');
95
+ console.log('├─────────────────────────────────────────────────────────────┤');
96
+ console.log(renderMarkdown(systemPrompt).split('\n').map((l: string) => '│ ' + l).join('\n'));
97
+ console.log('└─────────────────────────────────────────────────────────────┘');
98
+ }
99
+
100
+ // Iterate through all messages
101
+ for (let i = 0; i < messages.length; i++) {
102
+ const msg = messages[i];
103
+ const role = msg.role as string;
104
+ const roleLabel = roleColors[role] || `● ${role.toUpperCase()}`;
105
+
106
+ console.log(`\n┌─────────────────────────────────────────────────────────────┐`);
107
+ console.log(`│ ${roleLabel} (${i + 1}/${messages.length}) │`);
108
+ console.log('├─────────────────────────────────────────────────────────────┤');
109
+
110
+ // Display reasoning_content (if present) - check both camelCase and snake_case
111
+ const reasoningContent = (msg as any).reasoningContent || (msg as any).reasoning_content;
112
+ if (reasoningContent) {
113
+ console.log('│ 🧠 REASONING:');
114
+ console.log('│ ───────────────────────────────────────────────────────────');
115
+ const reasoningLines = renderMarkdown(reasoningContent).split('\n');
116
+ for (const line of reasoningLines.slice(0, 20)) {
117
+ console.log('│ ' + line.slice(0, 62));
118
+ }
119
+ if (reasoningContent.length > 1000) console.log('│ ... (truncated)');
120
+ console.log('│ ───────────────────────────────────────────────────────────');
121
+ }
122
+
123
+ // Display main content
124
+ const content = formatMessageContent(msg.content);
125
+ const lines = content.split('\n');
126
+
127
+ for (const line of lines.slice(0, 50)) {
128
+ console.log('│ ' + line.slice(0, 62));
129
+ }
130
+ if (lines.length > 50) {
131
+ console.log('│ ... (' + (lines.length - 50) + ' more lines)');
132
+ }
133
+
134
+ console.log('└─────────────────────────────────────────────────────────────┘');
135
+ }
136
+ }
137
+
138
+ // Format response content
139
+ function formatResponseContent(content: string | Array<any>): string {
140
+ if (typeof content === 'string') {
141
+ return renderMarkdown(content);
142
+ }
143
+
144
+ const parts: string[] = [];
145
+ let hasToolUse = false;
146
+
147
+ for (const block of content) {
148
+ if (block.type === 'text') {
149
+ parts.push(renderMarkdown(block.text || ''));
150
+ } else if (block.type === 'tool_use') {
151
+ hasToolUse = true;
152
+ // Tool calls are handled via tool_calls field, not shown here
153
+ } else if (block.type === 'tool_result') {
154
+ const result = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
155
+ parts.push(`[✅ TOOL RESULT]\n${result}`);
156
+ } else if (block.type === 'thinking') {
157
+ parts.push(`[🧠 THINKING]\n${block.thinking || ''}`);
158
+ } else if (block.type === 'image') {
159
+ parts.push('[IMAGE]');
160
+ }
161
+ }
162
+
163
+ if (hasToolUse) {
164
+ parts.push('\n[⚠️ Note: Tool calls are executed via tool_calls field, not shown here]');
165
+ }
166
+
167
+ return parts.join('\n');
168
+ }
169
+
170
+ export interface Message {
171
+ role: 'system' | 'user' | 'assistant' | 'tool';
172
+ content: string | Array<AnthropicContentBlock | { type: string; text?: string; image_url?: { url: string } }>;
173
+ reasoning_content?: string;
174
+ tool_calls?: any[];
175
+ tool_call_id?: string;
176
+ }
177
+
178
+ export interface ToolDefinition {
179
+ type: 'function';
180
+ function: {
181
+ name: string;
182
+ description: string;
183
+ parameters?: any;
184
+ };
185
+ }
186
+
187
+ export interface ChatCompletionOptions {
188
+ model?: string;
189
+ temperature?: number;
190
+ maxTokens?: number;
191
+ tools?: ToolDefinition[];
192
+ toolChoice?: 'auto' | 'none' | { type: string; function: { name: string } };
193
+ stream?: boolean;
194
+ thinkingTokens?: number;
195
+ }
196
+
197
+ export interface ChatCompletionResponse {
198
+ id: string;
199
+ object: string;
200
+ created: number;
201
+ model: string;
202
+ choices: Array<{
203
+ index: number;
204
+ message: Message;
205
+ finish_reason: string;
206
+ }>;
207
+ usage?: {
208
+ prompt_tokens: number;
209
+ completion_tokens: number;
210
+ total_tokens: number;
211
+ };
212
+ }
213
+
214
+ // Detect if it's Anthropic compatible API(Use x-api-key authentication header)
215
+ function isAnthropicCompatible(baseUrl: string): boolean {
216
+ return baseUrl.includes('anthropic') ||
217
+ baseUrl.includes('minimaxi.com') ||
218
+ baseUrl.includes('minimax.chat');
219
+ }
220
+
221
+ // MiniMax API path detection
222
+ function detectMiniMaxAPI(baseUrl: string): boolean {
223
+ return baseUrl.includes('minimax.chat') ||
224
+ baseUrl.includes('minimaxi.com');
225
+ }
226
+
227
+ // Get correct endpoint path for MiniMax
228
+ function getMiniMaxEndpoint(baseUrl: string): { endpoint: string; format: 'anthropic' | 'openai' } {
229
+ // MiniMax Anthropic format: https://api.minimax.chat/anthropic + /v1/messages
230
+ if (baseUrl.includes('/anthropic')) {
231
+ return { endpoint: '/v1/messages', format: 'anthropic' };
232
+ }
233
+ // MiniMax OpenAI format: https://api.minimaxi.com/v1 + /chat/completions
234
+ if (baseUrl.includes('/v1') && !baseUrl.includes('/anthropic')) {
235
+ return { endpoint: '/chat/completions', format: 'openai' };
236
+ }
237
+ // Default to Anthropic format
238
+ return { endpoint: '/v1/messages', format: 'anthropic' };
239
+ }
240
+
241
+ export class AIClient {
242
+ private client: AxiosInstance;
243
+ private authConfig: AuthConfig;
244
+
245
+ constructor(authConfig: AuthConfig) {
246
+ this.authConfig = authConfig;
247
+ const isMiniMax = detectMiniMaxAPI(authConfig.baseUrl || '');
248
+ const isAnthropicOfficial = !isMiniMax && isAnthropicCompatible(authConfig.baseUrl || '');
249
+
250
+ const headers: Record<string, string> = {
251
+ 'Content-Type': 'application/json'
252
+ };
253
+
254
+ if (isMiniMax) {
255
+ // MiniMax: Use x-api-key authentication header
256
+ headers['x-api-key'] = authConfig.apiKey || '';
257
+ headers['anthropic-version'] = '2023-06-01';
258
+ } else if (isAnthropicOfficial) {
259
+ // Anthropic official: Use x-api-key authentication header
260
+ headers['x-api-key'] = authConfig.apiKey || '';
261
+ headers['anthropic-version'] = '2023-06-01';
262
+ headers['anthropic-dangerous-direct-browser-access'] = 'true';
263
+ } else {
264
+ // Other OpenAI compatible: 使用 Bearer token
265
+ headers['Authorization'] = `Bearer ${authConfig.apiKey}`;
266
+ }
267
+
268
+ this.client = axios.create({
269
+ baseURL: authConfig.baseUrl,
270
+ headers,
271
+ timeout: 300000,
272
+ httpsAgent: new https.Agent({ rejectUnauthorized: false })
273
+ });
274
+ }
275
+
276
+ // Convert OpenAI format messages to Anthropic format
277
+ private convertToAnthropicFormat(
278
+ messages: Message[],
279
+ systemPrompt?: string
280
+ ): { system: string; messages: Array<{ role: string; content: AnthropicContentBlock[] }> } {
281
+ const systemMessages = messages.filter(m => m.role === 'system');
282
+ const otherMessages = messages.filter(m => m.role !== 'system');
283
+
284
+ const systemContent = systemMessages[0]?.content;
285
+ const system = systemPrompt || (typeof systemContent === 'string' ? systemContent : '');
286
+
287
+ const anthropicMessages: Array<{ role: string; content: AnthropicContentBlock[] }> = [];
288
+
289
+ for (const msg of otherMessages) {
290
+ const blocks: AnthropicContentBlock[] = [];
291
+
292
+ if (typeof msg.content === 'string') {
293
+ blocks.push({ type: 'text', text: msg.content });
294
+ } else if (Array.isArray(msg.content)) {
295
+ for (const block of msg.content) {
296
+ if (block.type === 'text' && 'text' in block) {
297
+ blocks.push({ type: 'text', text: (block as any).text });
298
+ } else if (block.type === 'tool_use') {
299
+ blocks.push({
300
+ type: 'tool_use',
301
+ id: (block as any).id,
302
+ name: (block as any).function?.name || (block as any).name,
303
+ input: (block as any).function?.arguments || (block as any).input
304
+ });
305
+ } else if (block.type === 'tool_result') {
306
+ blocks.push({
307
+ type: 'tool_result',
308
+ tool_use_id: (block as any).tool_call_id || (block as any).tool_use_id,
309
+ content: typeof (block as any).content === 'string'
310
+ ? (block as any).content
311
+ : JSON.stringify((block as any).content)
312
+ });
313
+ } else if (block.type === 'thinking') {
314
+ blocks.push({ type: 'thinking', thinking: (block as any).thinking });
315
+ }
316
+ }
317
+ }
318
+
319
+ // Handle tool_calls (OpenAI 格式)
320
+ if (msg.tool_calls) {
321
+ for (const tc of msg.tool_calls) {
322
+ blocks.push({
323
+ type: 'tool_use',
324
+ id: tc.id,
325
+ name: tc.function?.name,
326
+ input: tc.function?.arguments ? (typeof tc.function.arguments === 'string' ? JSON.parse(tc.function.arguments) : tc.function.arguments) : {}
327
+ });
328
+ }
329
+ }
330
+
331
+ if (blocks.length > 0) {
332
+ anthropicMessages.push({
333
+ role: msg.role === 'tool' ? 'user' : msg.role,
334
+ content: blocks as AnthropicContentBlock[]
335
+ });
336
+ }
337
+ }
338
+
339
+ return { system, messages: anthropicMessages };
340
+ }
341
+
342
+ async chatCompletion(
343
+ messages: Message[],
344
+ options: ChatCompletionOptions = {}
345
+ ): Promise<ChatCompletionResponse> {
346
+ const model = options.model || this.authConfig.modelName || 'gpt-4';
347
+ const isMiniMax = detectMiniMaxAPI(this.authConfig.baseUrl || '');
348
+
349
+ if (isMiniMax) {
350
+ return this.minimaxChatCompletion(messages, options);
351
+ }
352
+
353
+ const isAnthropic = isAnthropicCompatible(this.authConfig.baseUrl || '');
354
+ if (isAnthropic) {
355
+ return this.anthropicNativeChatCompletion(messages, options);
356
+ }
357
+
358
+ // OpenAI format request
359
+ const requestBody: any = {
360
+ model,
361
+ messages,
362
+ temperature: options.temperature ?? 0.7,
363
+ stream: options.stream ?? false
364
+ };
365
+
366
+ if (options.maxTokens && options.maxTokens > 0) {
367
+ requestBody.max_tokens = options.maxTokens;
368
+ }
369
+
370
+ if (options.tools && options.tools.length > 0) {
371
+ requestBody.tools = options.tools;
372
+ requestBody.tool_choice = options.toolChoice || 'auto';
373
+ }
374
+
375
+ if (options.thinkingTokens && options.thinkingTokens > 0) {
376
+ requestBody.max_completion_tokens = options.thinkingTokens;
377
+ }
378
+
379
+ // Debug output(受showAIDebugInfo配置控制)
380
+ const showDebug = this.authConfig.showAIDebugInfo ?? false;
381
+
382
+ if (showDebug) {
383
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
384
+ console.log('║ AI REQUEST DEBUG ║');
385
+ console.log('╚══════════════════════════════════════════════════════════╝');
386
+ console.log(`📦 Model: ${model}`);
387
+ console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
388
+ console.log(`💬 Total Messages: ${messages.length} items`);
389
+ if (options.temperature !== undefined) console.log(`🌡️ Temperature: ${options.temperature}`);
390
+ if (options.maxTokens) console.log(`📏 Max Tokens: ${options.maxTokens}`);
391
+ if (options.tools?.length) console.log(`🔧 Tools: ${options.tools.length} items`);
392
+ if (options.thinkingTokens) console.log(`🧠 Thinking Tokens: ${options.thinkingTokens}`);
393
+ console.log('─'.repeat(60));
394
+
395
+ // Separate system messages
396
+ const systemMsgs = messages.filter(m => m.role === 'system');
397
+ const otherMsgs = messages.filter(m => m.role !== 'system');
398
+
399
+ if (systemMsgs.length > 0) {
400
+ const systemContent = typeof systemMsgs[0].content === 'string'
401
+ ? systemMsgs[0].content
402
+ : formatMessageContent(systemMsgs[0].content);
403
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
404
+ console.log('│ 🟫 SYSTEM │');
405
+ console.log('├─────────────────────────────────────────────────────────────┤');
406
+ console.log(renderMarkdown(systemContent).split('\n').map(l => '│ ' + l).join('\n'));
407
+ console.log('└─────────────────────────────────────────────────────────────┘');
408
+ }
409
+
410
+ displayMessages(otherMsgs);
411
+
412
+ console.log('\n📤 Sending request to API...\n');
413
+ }
414
+
415
+ try {
416
+ const response = await this.client.post('/chat/completions', requestBody);
417
+
418
+ if (showDebug) {
419
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
420
+ console.log('║ AI RESPONSE DEBUG ║');
421
+ console.log('╚══════════════════════════════════════════════════════════╝');
422
+ console.log(`🆔 ID: ${response.data.id}`);
423
+ console.log(`🤖 Model: ${response.data.model}`);
424
+ const usage = response.data.usage;
425
+ if (usage) {
426
+ console.log(`📊 Tokens: ${usage.prompt_tokens} (prompt) + ${usage.completion_tokens} (completion) = ${usage.total_tokens} (total)`);
427
+ }
428
+ const choice = response.data.choices?.[0];
429
+ if (choice) {
430
+ console.log(`🏁 Finish Reason: ${choice.finish_reason}`);
431
+
432
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
433
+ console.log('│ 🤖 ASSISTANT │');
434
+ console.log('├─────────────────────────────────────────────────────────────┤');
435
+
436
+ // Display reasoning_content(如果有)
437
+ if (choice.message.reasoning_content) {
438
+ console.log('│ 🧠 REASONING:');
439
+ console.log('│ ───────────────────────────────────────────────────────────');
440
+ const reasoningLines = renderMarkdown(choice.message.reasoning_content).split('\n');
441
+ for (const line of reasoningLines.slice(0, 15)) {
442
+ console.log('│ ' + line.slice(0, 62));
443
+ }
444
+ if (choice.message.reasoning_content.length > 800) console.log('│ ... (truncated)');
445
+ console.log('│ ───────────────────────────────────────────────────────────');
446
+ }
447
+
448
+ // Display main content
449
+ const content = formatResponseContent(choice.message.content);
450
+ const lines = content.split('\n');
451
+ console.log('│ 💬 CONTENT:');
452
+ console.log('│ ───────────────────────────────────────────────────────────');
453
+ for (const line of lines.slice(0, 40)) {
454
+ console.log('│ ' + line.slice(0, 62));
455
+ }
456
+ if (lines.length > 40) {
457
+ console.log(`│ ... (${lines.length - 40} more lines)`);
458
+ }
459
+ console.log('└─────────────────────────────────────────────────────────────┘');
460
+ }
461
+ console.log('╔══════════════════════════════════════════════════════════╗');
462
+ console.log('║ RESPONSE ENDED ║');
463
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
464
+ }
465
+
466
+ return response.data;
467
+ } catch (error: any) {
468
+ // Check if error is retryable (timeout, network error, or 5xx)
469
+ const isRetryable = this.isRetryableError(error);
470
+ if (!isRetryable) {
471
+ if (error.response) {
472
+ throw new Error(
473
+ `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
474
+ );
475
+ } else if (error.request) {
476
+ throw new Error('Network error: No response received from server');
477
+ } else {
478
+ throw new Error(`Request error: ${error.message}`);
479
+ }
480
+ }
481
+
482
+ // Retry with exponential backoff
483
+ const retryResult = await withRetry(async () => {
484
+ const response = await this.client.post('/chat/completions', requestBody);
485
+ if (showDebug) {
486
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
487
+ console.log('║ AI RESPONSE DEBUG (RETRY) ║');
488
+ console.log('╚══════════════════════════════════════════════════════════╝');
489
+ console.log(`🆔 ID: ${response.data.id}`);
490
+ console.log(`🤖 Model: ${response.data.model}`);
491
+ const usage = response.data.usage;
492
+ if (usage) {
493
+ console.log(`📊 Tokens: ${usage.prompt_tokens} (prompt) + ${usage.completion_tokens} (completion) = ${usage.total_tokens} (total)`);
494
+ }
495
+ console.log('╔══════════════════════════════════════════════════════════╗');
496
+ console.log('║ RESPONSE ENDED ║');
497
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
498
+ }
499
+ return response.data;
500
+ }, { maxRetries: 3, baseDelay: 1000, maxDelay: 10000, jitter: true });
501
+
502
+ if (!retryResult.success) {
503
+ throw retryResult.error || new Error('Retry failed');
504
+ }
505
+
506
+ if (!retryResult.data) {
507
+ throw new Error('Retry returned empty response');
508
+ }
509
+
510
+ return retryResult.data;
511
+ }
512
+ }
513
+
514
+ private isRetryableError(error: any): boolean {
515
+ // Timeout or network error (no response received)
516
+ if (error.code === 'ECONNABORTED' || !error.response) {
517
+ return true;
518
+ }
519
+ // 5xx server errors
520
+ if (error.response?.status && error.response.status >= 500) {
521
+ return true;
522
+ }
523
+ // 429 rate limit
524
+ if (error.response?.status === 429) {
525
+ return true;
526
+ }
527
+ return false;
528
+ }
529
+
530
+ // Anthropic official原生 API(使用 /v1/messages 端点)
531
+ private async anthropicNativeChatCompletion(
532
+ messages: Message[],
533
+ options: ChatCompletionOptions = {}
534
+ ): Promise<ChatCompletionResponse> {
535
+ const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
536
+
537
+ const requestBody: any = {
538
+ model: options.model || this.authConfig.modelName || 'claude-sonnet-4-20250514',
539
+ messages: anthropicMessages,
540
+ temperature: options.temperature ?? 1.0,
541
+ stream: false,
542
+ max_tokens: options.maxTokens || 4096
543
+ };
544
+
545
+ if (system) {
546
+ requestBody.system = system;
547
+ }
548
+
549
+ // Anthropic native tool format
550
+ if (options.tools && options.tools.length > 0) {
551
+ requestBody.tools = options.tools.map(tool => ({
552
+ name: tool.function.name,
553
+ description: tool.function.description,
554
+ input_schema: tool.function.parameters || { type: 'object', properties: {} }
555
+ }));
556
+
557
+ // Convert tool_choice 从 OpenAI 格式到 Anthropic 格式
558
+ const toolChoice = options.toolChoice;
559
+ if (toolChoice === 'none') {
560
+ requestBody.tool_choice = { type: 'auto' };
561
+ } else if (toolChoice && typeof toolChoice === 'object') {
562
+ if (toolChoice.type === 'function' && toolChoice.function) {
563
+ requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
564
+ } else {
565
+ requestBody.tool_choice = { type: 'auto' };
566
+ }
567
+ } else {
568
+ requestBody.tool_choice = { type: 'auto' };
569
+ }
570
+ }
571
+
572
+ // Anthropic thinking mode
573
+ if (options.thinkingTokens && options.thinkingTokens > 0) {
574
+ requestBody.thinking = { type: 'enabled', budget_tokens: options.thinkingTokens };
575
+ }
576
+
577
+ // Debug output(受showAIDebugInfo配置控制)
578
+ const showDebug = this.authConfig.showAIDebugInfo ?? false;
579
+
580
+ if (showDebug) {
581
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
582
+ console.log('║ AI REQUEST DEBUG (ANTHROPIC) ║');
583
+ console.log('╚══════════════════════════════════════════════════════════╝');
584
+ console.log(`📦 Model: ${requestBody.model}`);
585
+ console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
586
+ console.log(`💬 Total Messages: ${anthropicMessages.length} items`);
587
+ if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
588
+ if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
589
+ if (requestBody.tools) console.log(`🔧 Tools: ${requestBody.tools.length} items`);
590
+ if (requestBody.thinking) console.log(`🧠 Thinking Budget: ${requestBody.thinking.budget_tokens}`);
591
+ console.log('─'.repeat(60));
592
+
593
+ // Display system messages
594
+ if (system) {
595
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
596
+ console.log('│ 🟫 SYSTEM │');
597
+ console.log('├─────────────────────────────────────────────────────────────┤');
598
+ console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
599
+ console.log('└─────────────────────────────────────────────────────────────┘');
600
+ }
601
+
602
+ // Display user and assistant messages
603
+ displayMessages(anthropicMessages);
604
+
605
+ console.log('\n📤 Sending to Anthropic API (v1/messages)...\n');
606
+ }
607
+
608
+ try {
609
+ // Use Anthropic native endpoint /v1/messages
610
+ const response = await this.client.post('/v1/messages', requestBody);
611
+
612
+ if (showDebug) {
613
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
614
+ console.log('║ AI RESPONSE DEBUG (ANTHROPIC) ║');
615
+ console.log('╚══════════════════════════════════════════════════════════╝');
616
+ console.log(`🆔 ID: ${response.data.id}`);
617
+ console.log(`🤖 Model: ${response.data.model}`);
618
+ const usage = response.data.usage;
619
+ if (usage) {
620
+ console.log(`📊 Tokens: ${usage.input_tokens} (input) + ${usage.output_tokens} (output) = ${usage.input_tokens + usage.output_tokens} (total)`);
621
+ }
622
+ console.log(`🏁 Stop Reason: ${response.data.stop_reason}`);
623
+
624
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
625
+ console.log('│ 🤖 ASSISTANT │');
626
+ console.log('├─────────────────────────────────────────────────────────────┤');
627
+
628
+ const content = response.data.content || [];
629
+ const reasoning = content.filter((c: any) => c.type === 'thinking').map((c: any) => c.thinking).join('');
630
+ const textContent = content.filter((c: any) => c.type === 'text').map((c: any) => c.text).join('');
631
+
632
+ // Display thinking
633
+ if (reasoning) {
634
+ console.log(' 🧠 REASONING:');
635
+ console.log('│ ───────────────────────────────────────────────────────────');
636
+ const reasoningLines = renderMarkdown(reasoning).split('\n');
637
+ for (const line of reasoningLines.slice(0, 15)) {
638
+ console.log('│ ' + line.slice(0, 62));
639
+ }
640
+ if (reasoning.length > 800) console.log('│ ... (truncated)');
641
+ console.log('│ ───────────────────────────────────────────────────────────');
642
+ }
643
+
644
+ // Display content
645
+ console.log('│ 💬 CONTENT:');
646
+ console.log('│ ───────────────────────────────────────────────────────────');
647
+ const lines = renderMarkdown(textContent).split('\n');
648
+ for (const line of lines.slice(0, 40)) {
649
+ console.log('│ ' + line.slice(0, 62));
650
+ }
651
+ if (lines.length > 40) {
652
+ console.log(`│ ... (${lines.length - 40} more lines)`);
653
+ }
654
+ console.log('└─────────────────────────────────────────────────────────────┘');
655
+
656
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
657
+ console.log('║ RESPONSE ENDED ║');
658
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
659
+ }
660
+
661
+ return this.convertFromAnthropicNativeResponse(response.data);
662
+ } catch (error: any) {
663
+ const isRetryable = this.isRetryableError(error);
664
+ if (!isRetryable) {
665
+ if (error.response) {
666
+ throw new Error(
667
+ `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
668
+ );
669
+ } else if (error.request) {
670
+ throw new Error('Network error: No response received from server');
671
+ } else {
672
+ throw new Error(`Request error: ${error.message}`);
673
+ }
674
+ }
675
+
676
+ const retryResult = await withRetry(async () => {
677
+ const response = await this.client.post('/v1/messages', requestBody);
678
+ if (showDebug) {
679
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
680
+ console.log('║ AI RESPONSE DEBUG (ANTHROPIC RETRY) ║');
681
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
682
+ }
683
+ return this.convertFromAnthropicNativeResponse(response.data);
684
+ }, { maxRetries: 3, baseDelay: 1000, maxDelay: 10000, jitter: true });
685
+
686
+ if (!retryResult.success) {
687
+ throw retryResult.error || new Error('Retry failed');
688
+ }
689
+
690
+ if (!retryResult.data) {
691
+ throw new Error('Retry returned empty response');
692
+ }
693
+
694
+ return retryResult.data;
695
+ }
696
+ }
697
+
698
+ // MiniMax API(Automatically select based on baseUrl Anthropic 或 OpenAI 格式)
699
+ private async minimaxChatCompletion(
700
+ messages: Message[],
701
+ options: ChatCompletionOptions = {}
702
+ ): Promise<ChatCompletionResponse> {
703
+ const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
704
+ const { endpoint, format } = getMiniMaxEndpoint(this.authConfig.baseUrl || '');
705
+
706
+ const requestBody: any = {
707
+ model: options.model || this.authConfig.modelName || 'MiniMax-M2',
708
+ messages: format === 'anthropic' ? anthropicMessages : messages,
709
+ temperature: options.temperature ?? 1.0,
710
+ stream: false,
711
+ max_tokens: options.maxTokens || 4096
712
+ };
713
+
714
+ if (system && format === 'anthropic') {
715
+ requestBody.system = system;
716
+ }
717
+
718
+ if (format === 'anthropic') {
719
+ // Anthropic format tools
720
+ if (options.tools && options.tools.length > 0) {
721
+ requestBody.tools = options.tools.map(tool => ({
722
+ name: tool.function.name,
723
+ description: tool.function.description,
724
+ input_schema: tool.function.parameters || { type: 'object', properties: {} }
725
+ }));
726
+
727
+ const toolChoice = options.toolChoice;
728
+ if (toolChoice === 'none') {
729
+ requestBody.tool_choice = { type: 'auto' };
730
+ } else if (toolChoice && typeof toolChoice === 'object') {
731
+ if (toolChoice.type === 'function' && toolChoice.function) {
732
+ requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
733
+ } else {
734
+ requestBody.tool_choice = { type: 'auto' };
735
+ }
736
+ } else {
737
+ requestBody.tool_choice = { type: 'auto' };
738
+ }
739
+ }
740
+ } else {
741
+ // OpenAI format tools
742
+ if (options.tools && options.tools.length > 0) {
743
+ requestBody.tools = options.tools;
744
+ requestBody.tool_choice = options.toolChoice || 'auto';
745
+ }
746
+ }
747
+
748
+ // Debug output(受showAIDebugInfo配置控制)
749
+ const showDebug = this.authConfig.showAIDebugInfo ?? false;
750
+
751
+ if (showDebug) {
752
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
753
+ console.log('║ AI REQUEST DEBUG (MINIMAX) ║');
754
+ console.log('╚══════════════════════════════════════════════════════════╝');
755
+ console.log(`📦 Model: ${requestBody.model}`);
756
+ console.log(`🔗 Format: ${format.toUpperCase()} | Endpoint: ${endpoint}`);
757
+ console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
758
+ console.log(`💬 Total Messages: ${requestBody.messages.length} items`);
759
+ if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
760
+ if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
761
+ if (requestBody.tools) console.log(`🔧 Tools: ${requestBody.tools.length} items`);
762
+ console.log('─'.repeat(60));
763
+
764
+ // Display system messages
765
+ if (system && format === 'anthropic') {
766
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
767
+ console.log('│ 🟫 SYSTEM │');
768
+ console.log('├─────────────────────────────────────────────────────────────┤');
769
+ console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
770
+ console.log('└─────────────────────────────────────────────────────────────┘');
771
+ }
772
+
773
+ // Display other messages
774
+ displayMessages(requestBody.messages);
775
+
776
+ console.log('\n📤 Sending to MiniMax API...\n');
777
+ }
778
+
779
+ try {
780
+ // MiniMax uses correct endpoint
781
+ const response = await this.client.post(endpoint, requestBody);
782
+
783
+ if (showDebug) {
784
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
785
+ console.log('║ AI RESPONSE DEBUG (MINIMAX) ║');
786
+ console.log('╚══════════════════════════════════════════════════════════╝');
787
+ console.log(`🆔 ID: ${response.data.id}`);
788
+ console.log(`🤖 Model: ${response.data.model}`);
789
+ const usage = response.data.usage;
790
+ if (usage) {
791
+ console.log(`📊 Tokens: ${usage.prompt_tokens} (prompt) + ${usage.completion_tokens} (completion) = ${usage.total_tokens} (total)`);
792
+ }
793
+ console.log(`🏁 Stop Reason: ${response.data.stop_reason}`);
794
+
795
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
796
+ console.log('│ 🤖 ASSISTANT │');
797
+ console.log('├─────────────────────────────────────────────────────────────┤');
798
+
799
+ const message = response.data.choices?.[0]?.message;
800
+ const content = typeof message?.content === 'string' ? message.content : JSON.stringify(message?.content);
801
+
802
+ console.log('│ 💬 CONTENT:');
803
+ console.log('│ ───────────────────────────────────────────────────────────');
804
+ const lines = renderMarkdown(content).split('\n');
805
+ for (const line of lines.slice(0, 40)) {
806
+ console.log('│ ' + line.slice(0, 62));
807
+ }
808
+ if (lines.length > 40) {
809
+ console.log(`│ ... (${lines.length - 40} more lines)`);
810
+ }
811
+ console.log('└─────────────────────────────────────────────────────────────┘');
812
+
813
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
814
+ console.log('║ RESPONSE ENDED ║');
815
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
816
+ }
817
+
818
+ if (format === 'anthropic') {
819
+ return this.convertFromAnthropicNativeResponse(response.data);
820
+ } else {
821
+ return this.convertFromMiniMaxResponse(response.data);
822
+ }
823
+ } catch (error: any) {
824
+ const isRetryable = this.isRetryableError(error);
825
+ if (!isRetryable) {
826
+ if (error.response) {
827
+ throw new Error(
828
+ `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
829
+ );
830
+ } else if (error.request) {
831
+ throw new Error('Network error: No response received from server');
832
+ } else {
833
+ throw new Error(`Request error: ${error.message}`);
834
+ }
835
+ }
836
+
837
+ const retryResult = await withRetry(async () => {
838
+ const response = await this.client.post(endpoint, requestBody);
839
+ if (showDebug) {
840
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
841
+ console.log('║ AI RESPONSE DEBUG (MINIMAX RETRY) ║');
842
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
843
+ }
844
+ if (format === 'anthropic') {
845
+ return this.convertFromAnthropicNativeResponse(response.data);
846
+ } else {
847
+ return this.convertFromMiniMaxResponse(response.data);
848
+ }
849
+ }, { maxRetries: 3, baseDelay: 1000, maxDelay: 10000, jitter: true });
850
+
851
+ if (!retryResult.success) {
852
+ throw retryResult.error || new Error('Retry failed');
853
+ }
854
+
855
+ if (!retryResult.data) {
856
+ throw new Error('Retry returned empty response');
857
+ }
858
+
859
+ return retryResult.data;
860
+ }
861
+ }
862
+
863
+ // Convert Anthropic native response to unified format
864
+ private convertFromAnthropicNativeResponse(anthropicResponse: any): ChatCompletionResponse {
865
+ const content = anthropicResponse.content || [];
866
+ let textContent = '';
867
+ let reasoningContent = '';
868
+ const toolCalls: any[] = [];
869
+
870
+ for (const block of content) {
871
+ if (block.type === 'text') {
872
+ textContent += block.text || '';
873
+ } else if (block.type === 'thinking') {
874
+ reasoningContent += block.thinking || '';
875
+ } else if (block.type === 'tool_use') {
876
+ toolCalls.push({
877
+ id: block.id,
878
+ type: 'function',
879
+ function: {
880
+ name: block.name,
881
+ arguments: JSON.stringify(block.input || {})
882
+ }
883
+ });
884
+ }
885
+ }
886
+
887
+ return {
888
+ id: anthropicResponse.id || `anthropic-${Date.now()}`,
889
+ object: 'chat.completion',
890
+ created: Math.floor(Date.now() / 1000),
891
+ model: anthropicResponse.model || this.authConfig.modelName || 'claude-sonnet-4-20250514',
892
+ choices: [{
893
+ index: 0,
894
+ message: {
895
+ role: 'assistant',
896
+ content: textContent,
897
+ reasoning_content: reasoningContent || undefined,
898
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined
899
+ },
900
+ finish_reason: anthropicResponse.stop_reason === 'end_turn' ? 'stop' :
901
+ anthropicResponse.stop_reason === 'max_tokens' ? 'length' : 'stop'
902
+ }],
903
+ usage: anthropicResponse.usage ? {
904
+ prompt_tokens: anthropicResponse.usage.input_tokens || 0,
905
+ completion_tokens: anthropicResponse.usage.output_tokens || 0,
906
+ total_tokens: (anthropicResponse.usage.input_tokens || 0) + (anthropicResponse.usage.output_tokens || 0)
907
+ } : undefined
908
+ };
909
+ }
910
+
911
+ // Convert MiniMax response to unified format
912
+ private convertFromMiniMaxResponse(minimaxResponse: any): ChatCompletionResponse {
913
+ const message = minimaxResponse.choices?.[0]?.message;
914
+ const content = message?.content;
915
+ let textContent = '';
916
+ let reasoningContent = '';
917
+ const toolCalls: any[] = [];
918
+
919
+ if (typeof content === 'string') {
920
+ textContent = content.trim();
921
+ } else if (Array.isArray(content)) {
922
+ for (const block of content) {
923
+ if (block.type === 'text') {
924
+ textContent += block.text || '';
925
+ } else if (block.type === 'thinking') {
926
+ reasoningContent += block.thinking || '';
927
+ } else if (block.type === 'tool_use') {
928
+ toolCalls.push({
929
+ id: block.id,
930
+ type: 'function',
931
+ function: {
932
+ name: block.name,
933
+ arguments: JSON.stringify(block.input || {})
934
+ }
935
+ });
936
+ }
937
+ }
938
+ }
939
+
940
+ return {
941
+ id: minimaxResponse.id || `minimax-${Date.now()}`,
942
+ object: 'chat.completion',
943
+ created: Math.floor(Date.now() / 1000),
944
+ model: minimaxResponse.model || this.authConfig.modelName || 'MiniMax-M2',
945
+ choices: [{
946
+ index: 0,
947
+ message: {
948
+ role: 'assistant',
949
+ content: textContent,
950
+ reasoning_content: reasoningContent || undefined,
951
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined
952
+ },
953
+ finish_reason: minimaxResponse.stop_reason === 'end_turn' ? 'stop' :
954
+ minimaxResponse.stop_reason === 'max_tokens' ? 'length' : 'stop'
955
+ }],
956
+ usage: minimaxResponse.usage
957
+ };
958
+ }
959
+
960
+ async *streamChatCompletion(
961
+ messages: Message[],
962
+ options: ChatCompletionOptions = {}
963
+ ): AsyncGenerator<string, void, unknown> {
964
+ const isMiniMax = detectMiniMaxAPI(this.authConfig.baseUrl || '');
965
+
966
+ if (isMiniMax) {
967
+ yield* this.minimaxStreamChatCompletion(messages, options);
968
+ return;
969
+ }
970
+
971
+ const isAnthropic = isAnthropicCompatible(this.authConfig.baseUrl || '');
972
+ if (isAnthropic) {
973
+ yield* this.anthropicNativeStreamChatCompletion(messages, options);
974
+ return;
975
+ }
976
+
977
+ // OpenAI streaming response
978
+ const model = options.model || this.authConfig.modelName || 'gpt-4';
979
+
980
+ const requestBody: any = {
981
+ model,
982
+ messages,
983
+ temperature: options.temperature ?? 0.7,
984
+ stream: true
985
+ };
986
+
987
+ if (options.maxTokens && options.maxTokens > 0) {
988
+ requestBody.max_tokens = options.maxTokens;
989
+ }
990
+
991
+ if (options.tools && options.tools.length > 0) {
992
+ requestBody.tools = options.tools;
993
+ requestBody.tool_choice = options.toolChoice || 'auto';
994
+ }
995
+
996
+ if (options.thinkingTokens && options.thinkingTokens > 0) {
997
+ requestBody.max_completion_tokens = options.thinkingTokens;
998
+ }
999
+
1000
+ // Debug output(受showAIDebugInfo配置控制)
1001
+ const showDebug = this.authConfig.showAIDebugInfo ?? false;
1002
+
1003
+ if (showDebug) {
1004
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1005
+ console.log('║ AI REQUEST DEBUG (STREAM) ║');
1006
+ console.log('╚══════════════════════════════════════════════════════════╝');
1007
+ console.log(`📦 Model: ${model}`);
1008
+ console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
1009
+ console.log(`💬 Total Messages: ${messages.length} items`);
1010
+ if (options.temperature) console.log(`🌡️ Temperature: ${options.temperature}`);
1011
+ if (options.maxTokens) console.log(`📏 Max Tokens: ${options.maxTokens}`);
1012
+ if (options.tools?.length) console.log(`🔧 Tools: ${options.tools.length} items`);
1013
+ console.log('─'.repeat(60));
1014
+
1015
+ // Separate and display messages
1016
+ const systemMsgs = messages.filter(m => m.role === 'system');
1017
+ const otherMsgs = messages.filter(m => m.role !== 'system');
1018
+
1019
+ if (systemMsgs.length > 0) {
1020
+ const systemContent = typeof systemMsgs[0].content === 'string'
1021
+ ? systemMsgs[0].content
1022
+ : formatMessageContent(systemMsgs[0].content);
1023
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
1024
+ console.log('│ 🟫 SYSTEM │');
1025
+ console.log('├─────────────────────────────────────────────────────────────┤');
1026
+ console.log(renderMarkdown(systemContent).split('\n').map(l => '│ ' + l).join('\n'));
1027
+ console.log('└─────────────────────────────────────────────────────────────┘');
1028
+ }
1029
+
1030
+ displayMessages(otherMsgs);
1031
+
1032
+ console.log('\n📤 Starting stream...\n');
1033
+ }
1034
+
1035
+ try {
1036
+ const response = await this.client.post('/chat/completions', requestBody, {
1037
+ responseType: 'stream'
1038
+ });
1039
+
1040
+ console.log('📥 Receiving stream chunks...\n');
1041
+
1042
+ let buffer = '';
1043
+ let chunkCount = 0;
1044
+ let outputBuffer = '';
1045
+
1046
+ for await (const chunk of response.data) {
1047
+ buffer += chunk.toString();
1048
+ const lines = buffer.split('\n');
1049
+
1050
+ buffer = lines.pop() || '';
1051
+
1052
+ for (const line of lines) {
1053
+ const trimmedLine = line.trim();
1054
+ if (!trimmedLine) continue;
1055
+
1056
+ if (trimmedLine.startsWith('data: ')) {
1057
+ const data = trimmedLine.slice(6);
1058
+ if (data === '[DONE]') {
1059
+ if (showDebug) {
1060
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1061
+ console.log('║ STREAM COMPLETED ║');
1062
+ console.log('╚══════════════════════════════════════════════════════════╝');
1063
+ console.log(`📦 Total chunks: ${chunkCount}`);
1064
+ console.log(`📏 Total output: ${outputBuffer.length} chars`);
1065
+
1066
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
1067
+ console.log('│ 🤖 ASSISTANT OUTPUT │');
1068
+ console.log('├─────────────────────────────────────────────────────────────┤');
1069
+ console.log('│ 💬 CONTENT:');
1070
+ console.log('│ ───────────────────────────────────────────────────────────');
1071
+ const lines = renderMarkdown(outputBuffer).split('\n');
1072
+ for (const line of lines.slice(0, 30)) {
1073
+ console.log('│ ' + line.slice(0, 62));
1074
+ }
1075
+ if (lines.length > 30) {
1076
+ console.log(`│ ... (${lines.length - 30} more lines)`);
1077
+ }
1078
+ console.log('└─────────────────────────────────────────────────────────────┘');
1079
+ console.log('');
1080
+ }
1081
+ return;
1082
+ }
1083
+
1084
+ try {
1085
+ const parsed = JSON.parse(data);
1086
+ const delta = parsed.choices?.[0]?.delta;
1087
+ if (delta?.content) {
1088
+ chunkCount++;
1089
+ outputBuffer += delta.content;
1090
+ yield delta.content;
1091
+ } else if (delta?.reasoning_content) {
1092
+ chunkCount++;
1093
+ outputBuffer += delta.reasoning_content;
1094
+ yield delta.reasoning_content;
1095
+ }
1096
+ } catch (e) {
1097
+ // Silently ignore parsing errors
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ if (buffer.trim()) {
1104
+ const trimmedLine = buffer.trim();
1105
+ if (trimmedLine.startsWith('data: ')) {
1106
+ const data = trimmedLine.slice(6);
1107
+ if (data !== '[DONE]') {
1108
+ try {
1109
+ const parsed = JSON.parse(data);
1110
+ const delta = parsed.choices?.[0]?.delta;
1111
+ if (delta?.content) {
1112
+ yield delta.content;
1113
+ } else if (delta?.reasoning_content) {
1114
+ yield delta.reasoning_content;
1115
+ }
1116
+ } catch (e) {
1117
+ // Ignore final parsing errors
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1124
+ console.log('║ STREAM COMPLETED ║');
1125
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
1126
+ } catch (error: any) {
1127
+ if (error.response) {
1128
+ throw new Error(
1129
+ `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
1130
+ );
1131
+ } else if (error.request) {
1132
+ throw new Error('Network error: No response received from server');
1133
+ } else {
1134
+ throw new Error(`Request error: ${error.message}`);
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ // Anthropic native streaming response(/v1/messages 端点)
1140
+ private async *anthropicNativeStreamChatCompletion(
1141
+ messages: Message[],
1142
+ options: ChatCompletionOptions = {}
1143
+ ): AsyncGenerator<string, void, unknown> {
1144
+ const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
1145
+
1146
+ const requestBody: any = {
1147
+ model: options.model || this.authConfig.modelName || 'claude-sonnet-4-20250514',
1148
+ messages: anthropicMessages,
1149
+ temperature: options.temperature ?? 1.0,
1150
+ stream: true,
1151
+ max_tokens: options.maxTokens || 4096
1152
+ };
1153
+
1154
+ if (system) {
1155
+ requestBody.system = system;
1156
+ }
1157
+
1158
+ // Anthropic native tool format
1159
+ if (options.tools && options.tools.length > 0) {
1160
+ requestBody.tools = options.tools.map(tool => ({
1161
+ name: tool.function.name,
1162
+ description: tool.function.description,
1163
+ input_schema: tool.function.parameters || { type: 'object', properties: {} }
1164
+ }));
1165
+
1166
+ const toolChoice = options.toolChoice;
1167
+ if (toolChoice === 'none') {
1168
+ requestBody.tool_choice = { type: 'auto' };
1169
+ } else if (toolChoice && typeof toolChoice === 'object') {
1170
+ if (toolChoice.type === 'function' && toolChoice.function) {
1171
+ requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
1172
+ } else {
1173
+ requestBody.tool_choice = { type: 'auto' };
1174
+ }
1175
+ } else {
1176
+ requestBody.tool_choice = { type: 'auto' };
1177
+ }
1178
+ }
1179
+
1180
+ if (options.thinkingTokens && options.thinkingTokens > 0) {
1181
+ requestBody.thinking = { type: 'enabled', budget_tokens: options.thinkingTokens };
1182
+ }
1183
+
1184
+ // Debug output(受showAIDebugInfo配置控制)
1185
+ const showDebug = this.authConfig.showAIDebugInfo ?? false;
1186
+
1187
+ if (showDebug) {
1188
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1189
+ console.log('║ AI REQUEST DEBUG (ANTHROPIC STREAM) ║');
1190
+ console.log('╚══════════════════════════════════════════════════════════╝');
1191
+ console.log(`📦 Model: ${requestBody.model}`);
1192
+ console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
1193
+ console.log(`💬 Total Messages: ${anthropicMessages.length} items`);
1194
+ if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
1195
+ if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
1196
+ if (requestBody.thinking) console.log(`🧠 Thinking Budget: ${requestBody.thinking.budget_tokens}`);
1197
+ console.log('─'.repeat(60));
1198
+
1199
+ // Display system messages
1200
+ if (system) {
1201
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
1202
+ console.log('│ 🟫 SYSTEM │');
1203
+ console.log('├─────────────────────────────────────────────────────────────┤');
1204
+ console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
1205
+ console.log('└─────────────────────────────────────────────────────────────┘');
1206
+ }
1207
+
1208
+ displayMessages(anthropicMessages);
1209
+
1210
+ console.log('\n📤 Starting Anthropic stream...\n');
1211
+ }
1212
+
1213
+ try {
1214
+ // Anthropic native streaming endpoint /v1/messages
1215
+ const response = await this.client.post('/v1/messages', requestBody, {
1216
+ responseType: 'stream'
1217
+ });
1218
+
1219
+ console.log('📥 Receiving Anthropic stream chunks...\n');
1220
+
1221
+ let buffer = '';
1222
+ let outputBuffer = '';
1223
+
1224
+ for await (const chunk of response.data) {
1225
+ buffer += chunk.toString();
1226
+ const lines = buffer.split('\n');
1227
+ buffer = lines.pop() || '';
1228
+
1229
+ for (const line of lines) {
1230
+ const trimmedLine = line.trim();
1231
+ if (!trimmedLine) continue;
1232
+
1233
+ // Anthropic streaming format: data: {"type":"content_block_delta",...}
1234
+ if (trimmedLine.startsWith('data: ')) {
1235
+ const data = trimmedLine.slice(6);
1236
+
1237
+ try {
1238
+ const parsed = JSON.parse(data);
1239
+
1240
+ // Anthropic event types
1241
+ if (parsed.type === 'content_block_delta') {
1242
+ if (parsed.delta?.type === 'text_delta' && parsed.delta.text) {
1243
+ outputBuffer += parsed.delta.text;
1244
+ yield parsed.delta.text;
1245
+ } else if (parsed.delta?.type === 'thinking_delta' && parsed.delta.thinking) {
1246
+ outputBuffer += parsed.delta.thinking;
1247
+ yield parsed.delta.thinking;
1248
+ }
1249
+ } else if (parsed.type === 'message_delta') {
1250
+ if (parsed.delta?.stop_reason) {
1251
+ // Message end
1252
+ if (showDebug) {
1253
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1254
+ console.log('║ STREAM COMPLETED ║');
1255
+ console.log('╚══════════════════════════════════════════════════════════╝');
1256
+ console.log(`📏 Total output: ${outputBuffer.length} chars`);
1257
+
1258
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
1259
+ console.log('│ 🤖 ASSISTANT OUTPUT │');
1260
+ console.log('├─────────────────────────────────────────────────────────────┤');
1261
+ console.log('│ 💬 CONTENT:');
1262
+ console.log('│ ───────────────────────────────────────────────────────────');
1263
+ const lines = renderMarkdown(outputBuffer).split('\n');
1264
+ for (const line of lines.slice(0, 30)) {
1265
+ console.log('' + line.slice(0, 62));
1266
+ }
1267
+ if (lines.length > 30) {
1268
+ console.log(`│ ... (${lines.length - 30} more lines)`);
1269
+ }
1270
+ console.log('└─────────────────────────────────────────────────────────────┘');
1271
+ console.log('');
1272
+ }
1273
+ return;
1274
+ }
1275
+ }
1276
+ } catch (e) {
1277
+ // Ignore parsing errors
1278
+ }
1279
+ }
1280
+ }
1281
+ }
1282
+
1283
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1284
+ console.log('║ STREAM COMPLETED ║');
1285
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
1286
+ } catch (error: any) {
1287
+ if (error.response) {
1288
+ throw new Error(
1289
+ `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
1290
+ );
1291
+ } else if (error.request) {
1292
+ throw new Error('Network error: No response received from server');
1293
+ } else {
1294
+ throw new Error(`Request error: ${error.message}`);
1295
+ }
1296
+ }
1297
+ }
1298
+
1299
+ // MiniMax streaming response(Automatically select based on baseUrl格式)
1300
+ private async *minimaxStreamChatCompletion(
1301
+ messages: Message[],
1302
+ options: ChatCompletionOptions = {}
1303
+ ): AsyncGenerator<string, void, unknown> {
1304
+ const { system, messages: anthropicMessages } = this.convertToAnthropicFormat(messages);
1305
+ const { endpoint, format } = getMiniMaxEndpoint(this.authConfig.baseUrl || '');
1306
+
1307
+ const requestBody: any = {
1308
+ model: options.model || this.authConfig.modelName || 'MiniMax-M2',
1309
+ messages: format === 'anthropic' ? anthropicMessages : messages,
1310
+ temperature: options.temperature ?? 1.0,
1311
+ stream: true,
1312
+ max_tokens: options.maxTokens || 4096
1313
+ };
1314
+
1315
+ if (system && format === 'anthropic') {
1316
+ requestBody.system = system;
1317
+ }
1318
+
1319
+ if (format === 'anthropic') {
1320
+ // Anthropic 格式的工具
1321
+ if (options.tools && options.tools.length > 0) {
1322
+ requestBody.tools = options.tools.map(tool => ({
1323
+ name: tool.function.name,
1324
+ description: tool.function.description,
1325
+ input_schema: tool.function.parameters || { type: 'object', properties: {} }
1326
+ }));
1327
+
1328
+ const toolChoice = options.toolChoice;
1329
+ if (toolChoice === 'none') {
1330
+ requestBody.tool_choice = { type: 'auto' };
1331
+ } else if (toolChoice && typeof toolChoice === 'object') {
1332
+ if (toolChoice.type === 'function' && toolChoice.function) {
1333
+ requestBody.tool_choice = { type: 'tool', tool: { name: toolChoice.function.name } };
1334
+ } else {
1335
+ requestBody.tool_choice = { type: 'auto' };
1336
+ }
1337
+ } else {
1338
+ requestBody.tool_choice = { type: 'auto' };
1339
+ }
1340
+ }
1341
+ } else {
1342
+ // OpenAI format tools
1343
+ if (options.tools && options.tools.length > 0) {
1344
+ requestBody.tools = options.tools;
1345
+ requestBody.tool_choice = options.toolChoice || 'auto';
1346
+ }
1347
+ }
1348
+
1349
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1350
+ console.log('║ AI REQUEST DEBUG (MINIMAX STREAM) ║');
1351
+ console.log('╚══════════════════════════════════════════════════════════╝');
1352
+ console.log(`📦 Model: ${requestBody.model}`);
1353
+ console.log(`🔗 Format: ${format.toUpperCase()} | Endpoint: ${endpoint}`);
1354
+ console.log(`🌐 Base URL: ${this.authConfig.baseUrl}`);
1355
+ console.log(`💬 Total Messages: ${requestBody.messages.length} items`);
1356
+ if (requestBody.temperature) console.log(`🌡️ Temperature: ${requestBody.temperature}`);
1357
+ if (requestBody.max_tokens) console.log(`📏 Max Tokens: ${requestBody.max_tokens}`);
1358
+ console.log('─'.repeat(60));
1359
+
1360
+ // Display system messages
1361
+ if (system && format === 'anthropic') {
1362
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
1363
+ console.log('│ 🟫 SYSTEM │');
1364
+ console.log('├─────────────────────────────────────────────────────────────┤');
1365
+ console.log(renderMarkdown(system).split('\n').map(l => '│ ' + l).join('\n'));
1366
+ console.log('└─────────────────────────────────────────────────────────────┘');
1367
+ }
1368
+
1369
+ displayMessages(requestBody.messages);
1370
+
1371
+ console.log('\n📤 Starting MiniMax stream...\n');
1372
+
1373
+ try {
1374
+ // MiniMax uses correct endpoint
1375
+ const response = await this.client.post(endpoint, requestBody, {
1376
+ responseType: 'stream'
1377
+ });
1378
+
1379
+ console.log('📥 Receiving MiniMax stream chunks...\n');
1380
+
1381
+ let buffer = '';
1382
+ let outputBuffer = '';
1383
+
1384
+ for await (const chunk of response.data) {
1385
+ buffer += chunk.toString();
1386
+ const lines = buffer.split('\n');
1387
+ buffer = lines.pop() || '';
1388
+
1389
+ for (const line of lines) {
1390
+ const trimmedLine = line.trim();
1391
+ if (!trimmedLine) continue;
1392
+
1393
+ // Parse different streaming responses based on format
1394
+ if (format === 'anthropic') {
1395
+ // Anthropic SSE format: data: {"type":"content_block_delta",...}
1396
+ if (trimmedLine.startsWith('data: ')) {
1397
+ const data = trimmedLine.slice(6);
1398
+
1399
+ try {
1400
+ const parsed = JSON.parse(data);
1401
+
1402
+ if (parsed.type === 'content_block_delta') {
1403
+ if (parsed.delta?.type === 'text_delta' && parsed.delta.text) {
1404
+ yield parsed.delta.text;
1405
+ } else if (parsed.delta?.type === 'thinking_delta' && parsed.delta.thinking) {
1406
+ yield parsed.delta.thinking;
1407
+ }
1408
+ } else if (parsed.type === 'message_delta') {
1409
+ if (parsed.delta?.stop_reason) {
1410
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1411
+ console.log('║ STREAM COMPLETED ║');
1412
+ console.log('╚══════════════════════════════════════════════════════════╝');
1413
+ console.log(`📏 Total output: ${outputBuffer.length} chars`);
1414
+
1415
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
1416
+ console.log('│ 🤖 ASSISTANT OUTPUT │');
1417
+ console.log('├─────────────────────────────────────────────────────────────┤');
1418
+ console.log('│ 💬 CONTENT:');
1419
+ console.log('│ ───────────────────────────────────────────────────────────');
1420
+ const lines = renderMarkdown(outputBuffer).split('\n');
1421
+ for (const line of lines.slice(0, 30)) {
1422
+ console.log('│ ' + line.slice(0, 62));
1423
+ }
1424
+ if (lines.length > 30) {
1425
+ console.log(`│ ... (${lines.length - 30} more lines)`);
1426
+ }
1427
+ console.log('└─────────────────────────────────────────────────────────────┘');
1428
+ console.log('');
1429
+ return;
1430
+ }
1431
+ }
1432
+ } catch (e) {
1433
+ // Ignore parsing errors
1434
+ }
1435
+ }
1436
+ } else {
1437
+ // OpenAI SSE format: data: {...}
1438
+ if (trimmedLine.startsWith('data: ')) {
1439
+ const data = trimmedLine.slice(6);
1440
+ if (data === '[DONE]') continue;
1441
+
1442
+ try {
1443
+ const parsed = JSON.parse(data);
1444
+ const delta = parsed.choices?.[0]?.delta;
1445
+ if (delta?.content) {
1446
+ outputBuffer += delta.content;
1447
+ yield delta.content;
1448
+ } else if (delta?.reasoning_content) {
1449
+ outputBuffer += delta.reasoning_content;
1450
+ yield delta.reasoning_content;
1451
+ }
1452
+ } catch (e) {
1453
+ // Ignore parsing errors
1454
+ }
1455
+ }
1456
+ }
1457
+ }
1458
+ }
1459
+
1460
+ console.log('\n╔══════════════════════════════════════════════════════════╗');
1461
+ console.log('║ STREAM COMPLETED ║');
1462
+ console.log('╚══════════════════════════════════════════════════════════╝\n');
1463
+ } catch (error: any) {
1464
+ if (error.response) {
1465
+ throw new Error(
1466
+ `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`
1467
+ );
1468
+ } else if (error.request) {
1469
+ throw new Error('Network error: No response received from server');
1470
+ } else {
1471
+ throw new Error(`Request error: ${error.message}`);
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ async listModels(): Promise<any[]> {
1477
+ try {
1478
+ const response = await this.client.get('/models');
1479
+ return response.data.data || [];
1480
+ } catch (error: any) {
1481
+ console.error('Failed to list models:', error);
1482
+ return [];
1483
+ }
1484
+ }
1485
+
1486
+ updateAuthConfig(authConfig: AuthConfig): void {
1487
+ this.authConfig = authConfig;
1488
+ this.client.defaults.baseURL = authConfig.baseUrl;
1489
+
1490
+ const isMiniMax = detectMiniMaxAPI(authConfig.baseUrl || '');
1491
+ const isAnthropic = !isMiniMax && isAnthropicCompatible(authConfig.baseUrl || '');
1492
+
1493
+ if (isMiniMax || isAnthropic) {
1494
+ // MiniMax/Anthropic: Use x-api-key auth header
1495
+ this.client.defaults.headers['x-api-key'] = authConfig.apiKey || '';
1496
+ this.client.defaults.headers['anthropic-version'] = '2023-06-01';
1497
+ // Clear Bearer header
1498
+ delete this.client.defaults.headers['Authorization'];
1499
+ } else {
1500
+ // OpenAI compatible: Use Bearer token
1501
+ this.client.defaults.headers['Authorization'] = `Bearer ${authConfig.apiKey}`;
1502
+ // Clear x-api-key header
1503
+ delete this.client.defaults.headers['x-api-key'];
1504
+ delete this.client.defaults.headers['anthropic-version'];
1505
+ }
1506
+ }
1507
+
1508
+ getAuthConfig(): AuthConfig {
1509
+ return { ...this.authConfig };
1510
+ }
1511
+
1512
+ // Check if messages contain tool calls
1513
+ hasToolCalls(messages: Message[]): boolean {
1514
+ return messages.some(msg => {
1515
+ if (msg.tool_calls && msg.tool_calls.length > 0) return true;
1516
+ if (Array.isArray(msg.content)) {
1517
+ return msg.content.some(block =>
1518
+ block.type === 'tool_use' ||
1519
+ (block as any).type === 'tool_result'
1520
+ );
1521
+ }
1522
+ return false;
1523
+ });
1524
+ }
1525
+ }
1526
+
1527
+ export function detectThinkingKeywords(text: string): 'none' | 'normal' | 'hard' | 'mega' | 'ultra' {
1528
+ const ultraKeywords = ['super think', 'extreme think', 'deep think', 'full think', 'ultra think', 'careful think',
1529
+ 'ultrathink', 'think really super hard', 'think intensely'];
1530
+ const megaKeywords = ['strong think', 'powerful think', 'think hard', 'try hard to think', 'think well', 'think carefully',
1531
+ 'megathink', 'think really hard', 'think a lot'];
1532
+ const hardKeywords = ['think again', 'think more', 'think clearly', 'think thoroughly', 'consider carefully',
1533
+ 'think about it', 'think more', 'think harder'];
1534
+ const normalKeywords = ['think', 'think', 'consider', 'think'];
1535
+
1536
+ const lowerText = text.toLowerCase();
1537
+
1538
+ if (ultraKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1539
+ return 'ultra';
1540
+ } else if (megaKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1541
+ return 'mega';
1542
+ } else if (hardKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1543
+ return 'hard';
1544
+ } else if (normalKeywords.some(keyword => lowerText.includes(keyword.toLowerCase()))) {
1545
+ return 'normal';
1546
+ }
1547
+
1548
+ return 'none';
1549
+ }
1550
+
1551
+ export function getThinkingTokens(mode: 'none' | 'normal' | 'hard' | 'mega' | 'ultra'): number {
1552
+ const tokensMap = {
1553
+ none: 0,
1554
+ normal: 2000,
1555
+ hard: 4000,
1556
+ mega: 10000,
1557
+ ultra: 32000
1558
+ };
1559
+ return tokensMap[mode];
1459
1560
  }