snow-ai 0.3.6 → 0.3.7

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 (101) hide show
  1. package/dist/agents/reviewAgent.d.ts +50 -0
  2. package/dist/agents/reviewAgent.js +264 -0
  3. package/dist/api/anthropic.js +104 -71
  4. package/dist/api/chat.d.ts +1 -1
  5. package/dist/api/chat.js +60 -41
  6. package/dist/api/gemini.js +97 -57
  7. package/dist/api/responses.d.ts +9 -1
  8. package/dist/api/responses.js +110 -70
  9. package/dist/api/systemPrompt.d.ts +1 -1
  10. package/dist/api/systemPrompt.js +36 -7
  11. package/dist/api/types.d.ts +8 -0
  12. package/dist/hooks/useCommandHandler.d.ts +1 -0
  13. package/dist/hooks/useCommandHandler.js +44 -1
  14. package/dist/hooks/useCommandPanel.js +13 -0
  15. package/dist/hooks/useConversation.d.ts +4 -1
  16. package/dist/hooks/useConversation.js +48 -6
  17. package/dist/hooks/useKeyboardInput.js +19 -0
  18. package/dist/hooks/useTerminalFocus.js +13 -3
  19. package/dist/mcp/aceCodeSearch.d.ts +2 -76
  20. package/dist/mcp/aceCodeSearch.js +31 -467
  21. package/dist/mcp/bash.d.ts +1 -8
  22. package/dist/mcp/bash.js +20 -40
  23. package/dist/mcp/filesystem.d.ts +3 -68
  24. package/dist/mcp/filesystem.js +32 -348
  25. package/dist/mcp/ideDiagnostics.js +2 -4
  26. package/dist/mcp/todo.d.ts +1 -17
  27. package/dist/mcp/todo.js +11 -15
  28. package/dist/mcp/types/aceCodeSearch.types.d.ts +92 -0
  29. package/dist/mcp/types/aceCodeSearch.types.js +4 -0
  30. package/dist/mcp/types/bash.types.d.ts +13 -0
  31. package/dist/mcp/types/bash.types.js +4 -0
  32. package/dist/mcp/types/filesystem.types.d.ts +44 -0
  33. package/dist/mcp/types/filesystem.types.js +4 -0
  34. package/dist/mcp/types/todo.types.d.ts +27 -0
  35. package/dist/mcp/types/todo.types.js +4 -0
  36. package/dist/mcp/types/websearch.types.d.ts +30 -0
  37. package/dist/mcp/types/websearch.types.js +4 -0
  38. package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +34 -0
  39. package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +146 -0
  40. package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +14 -0
  41. package/dist/mcp/utils/aceCodeSearch/language.utils.js +99 -0
  42. package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +31 -0
  43. package/dist/mcp/utils/aceCodeSearch/search.utils.js +136 -0
  44. package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +20 -0
  45. package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +141 -0
  46. package/dist/mcp/utils/bash/security.utils.d.ts +20 -0
  47. package/dist/mcp/utils/bash/security.utils.js +34 -0
  48. package/dist/mcp/utils/filesystem/code-analysis.utils.d.ts +18 -0
  49. package/dist/mcp/utils/filesystem/code-analysis.utils.js +165 -0
  50. package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +16 -0
  51. package/dist/mcp/utils/filesystem/match-finder.utils.js +85 -0
  52. package/dist/mcp/utils/filesystem/similarity.utils.d.ts +22 -0
  53. package/dist/mcp/utils/filesystem/similarity.utils.js +75 -0
  54. package/dist/mcp/utils/todo/date.utils.d.ts +9 -0
  55. package/dist/mcp/utils/todo/date.utils.js +14 -0
  56. package/dist/mcp/utils/websearch/browser.utils.d.ts +8 -0
  57. package/dist/mcp/utils/websearch/browser.utils.js +58 -0
  58. package/dist/mcp/utils/websearch/text.utils.d.ts +16 -0
  59. package/dist/mcp/utils/websearch/text.utils.js +39 -0
  60. package/dist/mcp/websearch.d.ts +1 -31
  61. package/dist/mcp/websearch.js +21 -97
  62. package/dist/ui/components/ChatInput.d.ts +2 -1
  63. package/dist/ui/components/ChatInput.js +10 -3
  64. package/dist/ui/components/MarkdownRenderer.d.ts +1 -2
  65. package/dist/ui/components/MarkdownRenderer.js +16 -153
  66. package/dist/ui/components/MessageList.js +4 -4
  67. package/dist/ui/components/SessionListScreen.js +37 -17
  68. package/dist/ui/components/ToolResultPreview.js +6 -6
  69. package/dist/ui/components/UsagePanel.d.ts +2 -0
  70. package/dist/ui/components/UsagePanel.js +360 -0
  71. package/dist/ui/pages/ChatScreen.d.ts +4 -0
  72. package/dist/ui/pages/ChatScreen.js +70 -30
  73. package/dist/ui/pages/ConfigScreen.js +23 -19
  74. package/dist/ui/pages/HeadlessModeScreen.js +2 -4
  75. package/dist/ui/pages/SubAgentConfigScreen.js +17 -17
  76. package/dist/ui/pages/SystemPromptConfigScreen.js +7 -6
  77. package/dist/utils/commandExecutor.d.ts +3 -3
  78. package/dist/utils/commandExecutor.js +4 -4
  79. package/dist/utils/commands/home.d.ts +2 -0
  80. package/dist/utils/commands/home.js +12 -0
  81. package/dist/utils/commands/review.d.ts +2 -0
  82. package/dist/utils/commands/review.js +81 -0
  83. package/dist/utils/commands/role.d.ts +2 -0
  84. package/dist/utils/commands/role.js +37 -0
  85. package/dist/utils/commands/usage.d.ts +2 -0
  86. package/dist/utils/commands/usage.js +12 -0
  87. package/dist/utils/contextCompressor.js +99 -367
  88. package/dist/utils/fileUtils.js +3 -3
  89. package/dist/utils/mcpToolsManager.js +12 -12
  90. package/dist/utils/proxyUtils.d.ts +15 -0
  91. package/dist/utils/proxyUtils.js +50 -0
  92. package/dist/utils/retryUtils.d.ts +27 -0
  93. package/dist/utils/retryUtils.js +114 -2
  94. package/dist/utils/sessionManager.d.ts +2 -5
  95. package/dist/utils/sessionManager.js +16 -83
  96. package/dist/utils/terminal.js +4 -3
  97. package/dist/utils/usageLogger.d.ts +11 -0
  98. package/dist/utils/usageLogger.js +99 -0
  99. package/package.json +3 -7
  100. package/dist/agents/summaryAgent.d.ts +0 -31
  101. package/dist/agents/summaryAgent.js +0 -256
@@ -0,0 +1,50 @@
1
+ export declare class ReviewAgent {
2
+ private modelName;
3
+ private requestMethod;
4
+ private initialized;
5
+ /**
6
+ * Initialize the review agent with current configuration
7
+ * Uses advanced model (same as main flow)
8
+ */
9
+ private initialize;
10
+ /**
11
+ * Check if review agent is available
12
+ */
13
+ isAvailable(): Promise<boolean>;
14
+ /**
15
+ * Check if current directory or any parent directory is a git repository
16
+ * @param startDir - Starting directory to check
17
+ * @returns Path to git root directory, or null if not found
18
+ */
19
+ private findGitRoot;
20
+ /**
21
+ * Check if git is available and current directory is in a git repository
22
+ * @returns Object with isGitRepo flag and optional error message
23
+ */
24
+ checkGitRepository(): {
25
+ isGitRepo: boolean;
26
+ gitRoot?: string;
27
+ error?: string;
28
+ };
29
+ /**
30
+ * Get git diff for uncommitted changes
31
+ * @param gitRoot - Git repository root directory
32
+ * @returns Git diff output
33
+ */
34
+ getGitDiff(gitRoot: string): string;
35
+ /**
36
+ * Generate code review prompt
37
+ */
38
+ private generateReviewPrompt;
39
+ /**
40
+ * Call the advanced model with streaming (same routing as main flow)
41
+ */
42
+ private callAdvancedModel;
43
+ /**
44
+ * Review git changes and return streaming generator
45
+ * @param abortSignal - Optional abort signal
46
+ * @returns Async generator for streaming response
47
+ */
48
+ reviewChanges(abortSignal?: AbortSignal): AsyncGenerator<any, void, unknown>;
49
+ }
50
+ export declare const reviewAgent: ReviewAgent;
@@ -0,0 +1,264 @@
1
+ import { getOpenAiConfig, getCustomSystemPrompt } from '../utils/apiConfig.js';
2
+ import { logger } from '../utils/logger.js';
3
+ import { createStreamingChatCompletion } from '../api/chat.js';
4
+ import { createStreamingResponse } from '../api/responses.js';
5
+ import { createStreamingGeminiCompletion } from '../api/gemini.js';
6
+ import { createStreamingAnthropicCompletion } from '../api/anthropic.js';
7
+ import { execSync } from 'child_process';
8
+ import * as path from 'path';
9
+ import * as fs from 'fs';
10
+ export class ReviewAgent {
11
+ constructor() {
12
+ Object.defineProperty(this, "modelName", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: ''
17
+ });
18
+ Object.defineProperty(this, "requestMethod", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: 'chat'
23
+ });
24
+ Object.defineProperty(this, "initialized", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: false
29
+ });
30
+ }
31
+ /**
32
+ * Initialize the review agent with current configuration
33
+ * Uses advanced model (same as main flow)
34
+ */
35
+ async initialize() {
36
+ try {
37
+ const config = getOpenAiConfig();
38
+ if (!config.advancedModel) {
39
+ return false;
40
+ }
41
+ this.modelName = config.advancedModel;
42
+ this.requestMethod = config.requestMethod;
43
+ this.initialized = true;
44
+ return true;
45
+ }
46
+ catch (error) {
47
+ logger.warn('Failed to initialize review agent:', error);
48
+ return false;
49
+ }
50
+ }
51
+ /**
52
+ * Check if review agent is available
53
+ */
54
+ async isAvailable() {
55
+ if (!this.initialized) {
56
+ return await this.initialize();
57
+ }
58
+ return true;
59
+ }
60
+ /**
61
+ * Check if current directory or any parent directory is a git repository
62
+ * @param startDir - Starting directory to check
63
+ * @returns Path to git root directory, or null if not found
64
+ */
65
+ findGitRoot(startDir) {
66
+ let currentDir = path.resolve(startDir);
67
+ const root = path.parse(currentDir).root;
68
+ while (currentDir !== root) {
69
+ const gitDir = path.join(currentDir, '.git');
70
+ if (fs.existsSync(gitDir)) {
71
+ return currentDir;
72
+ }
73
+ currentDir = path.dirname(currentDir);
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Check if git is available and current directory is in a git repository
79
+ * @returns Object with isGitRepo flag and optional error message
80
+ */
81
+ checkGitRepository() {
82
+ try {
83
+ // Check if git command is available
84
+ try {
85
+ execSync('git --version', { stdio: 'ignore' });
86
+ }
87
+ catch {
88
+ return {
89
+ isGitRepo: false,
90
+ error: 'Git is not installed or not available in PATH',
91
+ };
92
+ }
93
+ // Find git root directory (check current and parent directories)
94
+ const gitRoot = this.findGitRoot(process.cwd());
95
+ if (!gitRoot) {
96
+ return {
97
+ isGitRepo: false,
98
+ error: 'Current directory is not in a git repository. Please run this command from within a git repository.',
99
+ };
100
+ }
101
+ return { isGitRepo: true, gitRoot };
102
+ }
103
+ catch (error) {
104
+ return {
105
+ isGitRepo: false,
106
+ error: error instanceof Error
107
+ ? error.message
108
+ : 'Failed to check git repository',
109
+ };
110
+ }
111
+ }
112
+ /**
113
+ * Get git diff for uncommitted changes
114
+ * @param gitRoot - Git repository root directory
115
+ * @returns Git diff output
116
+ */
117
+ getGitDiff(gitRoot) {
118
+ try {
119
+ // Get staged changes
120
+ const stagedDiff = execSync('git diff --cached', {
121
+ cwd: gitRoot,
122
+ encoding: 'utf-8',
123
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
124
+ });
125
+ // Get unstaged changes
126
+ const unstagedDiff = execSync('git diff', {
127
+ cwd: gitRoot,
128
+ encoding: 'utf-8',
129
+ maxBuffer: 10 * 1024 * 1024,
130
+ });
131
+ // Combine both diffs
132
+ let combinedDiff = '';
133
+ if (stagedDiff) {
134
+ combinedDiff += '# Staged Changes\n\n' + stagedDiff + '\n\n';
135
+ }
136
+ if (unstagedDiff) {
137
+ combinedDiff += '# Unstaged Changes\n\n' + unstagedDiff;
138
+ }
139
+ if (!combinedDiff) {
140
+ return 'No changes detected in the repository.';
141
+ }
142
+ return combinedDiff;
143
+ }
144
+ catch (error) {
145
+ logger.error('Failed to get git diff:', error);
146
+ throw new Error('Failed to get git changes: ' +
147
+ (error instanceof Error ? error.message : 'Unknown error'));
148
+ }
149
+ }
150
+ /**
151
+ * Generate code review prompt
152
+ */
153
+ generateReviewPrompt(gitDiff) {
154
+ return `You are a senior code reviewer. Please review the following git changes and provide feedback.
155
+
156
+ **Your task:**
157
+ 1. Identify potential bugs, security issues, or logic errors
158
+ 2. Suggest performance optimizations
159
+ 3. Point out code quality issues (readability, maintainability)
160
+ 4. Check for best practices violations
161
+ 5. Highlight any breaking changes or compatibility issues
162
+
163
+ **Important:**
164
+ - DO NOT modify the code yourself
165
+ - Focus on finding issues and suggesting improvements
166
+ - Ask the user if they want to fix any issues you find
167
+ - Be constructive and specific in your feedback
168
+ - Prioritize critical issues over minor style preferences
169
+
170
+ **Git Changes:**
171
+
172
+ \`\`\`diff
173
+ ${gitDiff}
174
+ \`\`\`
175
+
176
+ Please provide your review in a clear, structured format.`;
177
+ }
178
+ /**
179
+ * Call the advanced model with streaming (same routing as main flow)
180
+ */
181
+ async *callAdvancedModel(messages, abortSignal) {
182
+ const config = getOpenAiConfig();
183
+ if (!config.advancedModel) {
184
+ throw new Error('Advanced model not configured');
185
+ }
186
+ // Get custom system prompt if configured
187
+ const customSystemPrompt = getCustomSystemPrompt();
188
+ // If custom system prompt exists, prepend it to messages
189
+ let processedMessages = messages;
190
+ if (customSystemPrompt) {
191
+ processedMessages = [
192
+ {
193
+ role: 'system',
194
+ content: customSystemPrompt,
195
+ },
196
+ ...messages,
197
+ ];
198
+ }
199
+ // Route to appropriate streaming API based on request method
200
+ switch (this.requestMethod) {
201
+ case 'anthropic':
202
+ yield* createStreamingAnthropicCompletion({
203
+ model: this.modelName,
204
+ messages: processedMessages,
205
+ max_tokens: 4096,
206
+ }, abortSignal);
207
+ break;
208
+ case 'gemini':
209
+ yield* createStreamingGeminiCompletion({
210
+ model: this.modelName,
211
+ messages: processedMessages,
212
+ }, abortSignal);
213
+ break;
214
+ case 'responses':
215
+ yield* createStreamingResponse({
216
+ model: this.modelName,
217
+ messages: processedMessages,
218
+ stream: true,
219
+ }, abortSignal);
220
+ break;
221
+ case 'chat':
222
+ default:
223
+ yield* createStreamingChatCompletion({
224
+ model: this.modelName,
225
+ messages: processedMessages,
226
+ stream: true,
227
+ }, abortSignal);
228
+ break;
229
+ }
230
+ }
231
+ /**
232
+ * Review git changes and return streaming generator
233
+ * @param abortSignal - Optional abort signal
234
+ * @returns Async generator for streaming response
235
+ */
236
+ async *reviewChanges(abortSignal) {
237
+ const available = await this.isAvailable();
238
+ if (!available) {
239
+ throw new Error('Review agent is not available');
240
+ }
241
+ // Check git repository
242
+ const gitCheck = this.checkGitRepository();
243
+ if (!gitCheck.isGitRepo) {
244
+ throw new Error(gitCheck.error || 'Not a git repository');
245
+ }
246
+ // Get git diff
247
+ const gitDiff = this.getGitDiff(gitCheck.gitRoot);
248
+ if (gitDiff === 'No changes detected in the repository.') {
249
+ throw new Error('No changes detected. Please make some changes before running code review.');
250
+ }
251
+ // Generate review prompt
252
+ const reviewPrompt = this.generateReviewPrompt(gitDiff);
253
+ const messages = [
254
+ {
255
+ role: 'user',
256
+ content: reviewPrompt,
257
+ },
258
+ ];
259
+ // Stream the response
260
+ yield* this.callAdvancedModel(messages, abortSignal);
261
+ }
262
+ }
263
+ // Export singleton instance
264
+ export const reviewAgent = new ReviewAgent();
@@ -1,7 +1,10 @@
1
1
  import { createHash, randomUUID } from 'crypto';
2
- import { getOpenAiConfig, getCustomSystemPrompt, getCustomHeaders } from '../utils/apiConfig.js';
3
- import { SYSTEM_PROMPT } from './systemPrompt.js';
4
- import { withRetryGenerator } from '../utils/retryUtils.js';
2
+ import { getOpenAiConfig, getCustomSystemPrompt, getCustomHeaders, } from '../utils/apiConfig.js';
3
+ import { getSystemPrompt } from './systemPrompt.js';
4
+ import { withRetryGenerator, parseJsonWithFix } from '../utils/retryUtils.js';
5
+ import { logger } from '../utils/logger.js';
6
+ import { addProxyToFetchOptions } from '../utils/proxyUtils.js';
7
+ import { saveUsageToFile } from '../utils/usageLogger.js';
5
8
  let anthropicConfig = null;
6
9
  function getAnthropicConfig() {
7
10
  if (!anthropicConfig) {
@@ -16,7 +19,7 @@ function getAnthropicConfig() {
16
19
  ? config.baseUrl
17
20
  : 'https://api.anthropic.com/v1',
18
21
  customHeaders,
19
- anthropicBeta: config.anthropicBeta
22
+ anthropicBeta: config.anthropicBeta,
20
23
  };
21
24
  }
22
25
  return anthropicConfig;
@@ -51,7 +54,7 @@ function convertToolsToAnthropic(tools) {
51
54
  return {
52
55
  name: tool.function.name,
53
56
  description: tool.function.description || '',
54
- input_schema: tool.function.parameters
57
+ input_schema: tool.function.parameters,
55
58
  };
56
59
  }
57
60
  throw new Error('Invalid tool format');
@@ -78,11 +81,13 @@ function convertToAnthropicMessages(messages) {
78
81
  if (msg.role === 'tool' && msg.tool_call_id) {
79
82
  anthropicMessages.push({
80
83
  role: 'user',
81
- content: [{
84
+ content: [
85
+ {
82
86
  type: 'tool_result',
83
87
  tool_use_id: msg.tool_call_id,
84
- content: msg.content
85
- }]
88
+ content: msg.content,
89
+ },
90
+ ],
86
91
  });
87
92
  continue;
88
93
  }
@@ -91,7 +96,7 @@ function convertToAnthropicMessages(messages) {
91
96
  if (msg.content) {
92
97
  content.push({
93
98
  type: 'text',
94
- text: msg.content
99
+ text: msg.content,
95
100
  });
96
101
  }
97
102
  for (const image of msg.images) {
@@ -102,23 +107,25 @@ function convertToAnthropicMessages(messages) {
102
107
  source: {
103
108
  type: 'base64',
104
109
  media_type: base64Match[1] || image.mimeType,
105
- data: base64Match[2] || ''
106
- }
110
+ data: base64Match[2] || '',
111
+ },
107
112
  });
108
113
  }
109
114
  }
110
115
  anthropicMessages.push({
111
116
  role: 'user',
112
- content
117
+ content,
113
118
  });
114
119
  continue;
115
120
  }
116
- if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
121
+ if (msg.role === 'assistant' &&
122
+ msg.tool_calls &&
123
+ msg.tool_calls.length > 0) {
117
124
  const content = [];
118
125
  if (msg.content) {
119
126
  content.push({
120
127
  type: 'text',
121
- text: msg.content
128
+ text: msg.content,
122
129
  });
123
130
  }
124
131
  for (const toolCall of msg.tool_calls) {
@@ -126,19 +133,19 @@ function convertToAnthropicMessages(messages) {
126
133
  type: 'tool_use',
127
134
  id: toolCall.id,
128
135
  name: toolCall.function.name,
129
- input: JSON.parse(toolCall.function.arguments)
136
+ input: JSON.parse(toolCall.function.arguments),
130
137
  });
131
138
  }
132
139
  anthropicMessages.push({
133
140
  role: 'assistant',
134
- content
141
+ content,
135
142
  });
136
143
  continue;
137
144
  }
138
145
  if (msg.role === 'user' || msg.role === 'assistant') {
139
146
  anthropicMessages.push({
140
147
  role: msg.role,
141
- content: msg.content
148
+ content: msg.content,
142
149
  });
143
150
  }
144
151
  }
@@ -146,15 +153,17 @@ function convertToAnthropicMessages(messages) {
146
153
  systemContent = customSystemPrompt;
147
154
  anthropicMessages.unshift({
148
155
  role: 'user',
149
- content: [{
156
+ content: [
157
+ {
150
158
  type: 'text',
151
- text: SYSTEM_PROMPT,
152
- cache_control: { type: 'ephemeral' }
153
- }]
159
+ text: getSystemPrompt(),
160
+ cache_control: { type: 'ephemeral' },
161
+ },
162
+ ],
154
163
  });
155
164
  }
156
165
  else if (!systemContent) {
157
- systemContent = SYSTEM_PROMPT;
166
+ systemContent = getSystemPrompt();
158
167
  }
159
168
  let lastUserMessageIndex = -1;
160
169
  for (let i = anthropicMessages.length - 1; i >= 0; i--) {
@@ -170,11 +179,13 @@ function convertToAnthropicMessages(messages) {
170
179
  const lastMessage = anthropicMessages[lastUserMessageIndex];
171
180
  if (lastMessage && lastMessage.role === 'user') {
172
181
  if (typeof lastMessage.content === 'string') {
173
- lastMessage.content = [{
182
+ lastMessage.content = [
183
+ {
174
184
  type: 'text',
175
185
  text: lastMessage.content,
176
- cache_control: { type: 'ephemeral' }
177
- }];
186
+ cache_control: { type: 'ephemeral' },
187
+ },
188
+ ];
178
189
  }
179
190
  else if (Array.isArray(lastMessage.content)) {
180
191
  const lastContentIndex = lastMessage.content.length - 1;
@@ -185,11 +196,15 @@ function convertToAnthropicMessages(messages) {
185
196
  }
186
197
  }
187
198
  }
188
- const system = systemContent ? [{
189
- type: 'text',
190
- text: systemContent,
191
- cache_control: { type: 'ephemeral' }
192
- }] : undefined;
199
+ const system = systemContent
200
+ ? [
201
+ {
202
+ type: 'text',
203
+ text: systemContent,
204
+ cache_control: { type: 'ephemeral' },
205
+ },
206
+ ]
207
+ : undefined;
193
208
  return { system, messages: anthropicMessages };
194
209
  }
195
210
  /**
@@ -209,20 +224,24 @@ async function* parseSSEStream(reader) {
209
224
  const trimmed = line.trim();
210
225
  if (!trimmed || trimmed.startsWith(':'))
211
226
  continue;
212
- if (trimmed === 'data: [DONE]') {
227
+ if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
213
228
  return;
214
229
  }
215
- if (trimmed.startsWith('event: ')) {
230
+ // Handle both "event: " and "event:" formats
231
+ if (trimmed.startsWith('event:')) {
216
232
  // Event type, will be followed by data
217
233
  continue;
218
234
  }
219
- if (trimmed.startsWith('data: ')) {
220
- const data = trimmed.slice(6);
235
+ // Handle both "data: " and "data:" formats
236
+ if (trimmed.startsWith('data:')) {
237
+ const data = trimmed.startsWith('data: ')
238
+ ? trimmed.slice(6)
239
+ : trimmed.slice(5);
221
240
  try {
222
241
  yield JSON.parse(data);
223
242
  }
224
243
  catch (e) {
225
- console.error('Failed to parse SSE data:', data);
244
+ logger.error('Failed to parse SSE data:', data);
226
245
  }
227
246
  }
228
247
  }
@@ -245,17 +264,17 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
245
264
  messages,
246
265
  tools: convertToolsToAnthropic(options.tools),
247
266
  metadata: {
248
- user_id: userId
267
+ user_id: userId,
249
268
  },
250
- stream: true
269
+ stream: true,
251
270
  };
252
271
  // Prepare headers
253
272
  const headers = {
254
273
  'Content-Type': 'application/json',
255
274
  'x-api-key': config.apiKey,
256
- 'authorization': `Bearer ${config.apiKey}`,
275
+ 'Authorization': `Bearer ${config.apiKey}`,
257
276
  'anthropic-version': '2023-06-01',
258
- ...config.customHeaders
277
+ ...config.customHeaders,
259
278
  };
260
279
  // Add beta parameter if configured
261
280
  // if (config.anthropicBeta) {
@@ -264,12 +283,13 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
264
283
  const url = config.anthropicBeta
265
284
  ? `${config.baseUrl}/messages?beta=true`
266
285
  : `${config.baseUrl}/messages`;
267
- const response = await fetch(url, {
286
+ const fetchOptions = addProxyToFetchOptions(url, {
268
287
  method: 'POST',
269
288
  headers,
270
289
  body: JSON.stringify(requestBody),
271
- signal: abortSignal
290
+ signal: abortSignal,
272
291
  });
292
+ const response = await fetch(url, fetchOptions);
273
293
  if (!response.ok) {
274
294
  const errorText = await response.text();
275
295
  throw new Error(`Anthropic API error: ${response.status} ${response.statusText} - ${errorText}`);
@@ -298,12 +318,12 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
298
318
  type: 'function',
299
319
  function: {
300
320
  name: block.name,
301
- arguments: ''
302
- }
321
+ arguments: '',
322
+ },
303
323
  });
304
324
  yield {
305
325
  type: 'tool_call_delta',
306
- delta: block.name
326
+ delta: block.name,
307
327
  };
308
328
  }
309
329
  }
@@ -314,7 +334,7 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
314
334
  contentBuffer += text;
315
335
  yield {
316
336
  type: 'content',
317
- content: text
337
+ content: text,
318
338
  };
319
339
  }
320
340
  if (delta.type === 'input_json_delta') {
@@ -331,7 +351,7 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
331
351
  toolCall.function.arguments += cleanedDelta;
332
352
  yield {
333
353
  type: 'tool_call_delta',
334
- delta: cleanedDelta
354
+ delta: cleanedDelta,
335
355
  };
336
356
  }
337
357
  }
@@ -351,9 +371,12 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
351
371
  usageData = {
352
372
  prompt_tokens: event.message.usage.input_tokens || 0,
353
373
  completion_tokens: event.message.usage.output_tokens || 0,
354
- total_tokens: (event.message.usage.input_tokens || 0) + (event.message.usage.output_tokens || 0),
355
- cache_creation_input_tokens: event.message.usage.cache_creation_input_tokens,
356
- cache_read_input_tokens: event.message.usage.cache_read_input_tokens
374
+ total_tokens: (event.message.usage.input_tokens || 0) +
375
+ (event.message.usage.output_tokens || 0),
376
+ cache_creation_input_tokens: event.message.usage
377
+ .cache_creation_input_tokens,
378
+ cache_read_input_tokens: event.message.usage
379
+ .cache_read_input_tokens,
357
380
  };
358
381
  }
359
382
  }
@@ -363,11 +386,12 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
363
386
  usageData = {
364
387
  prompt_tokens: 0,
365
388
  completion_tokens: 0,
366
- total_tokens: 0
389
+ total_tokens: 0,
367
390
  };
368
391
  }
369
392
  usageData.completion_tokens = event.usage.output_tokens || 0;
370
- usageData.total_tokens = usageData.prompt_tokens + usageData.completion_tokens;
393
+ usageData.total_tokens =
394
+ usageData.prompt_tokens + usageData.completion_tokens;
371
395
  if (event.usage.cache_creation_input_tokens !== undefined) {
372
396
  usageData.cache_creation_input_tokens = event.usage.cache_creation_input_tokens;
373
397
  }
@@ -386,42 +410,51 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
386
410
  if (!args) {
387
411
  args = '{}';
388
412
  }
389
- // Try to parse the JSON
390
- try {
391
- JSON.parse(args);
392
- toolCall.function.arguments = args;
413
+ // Try to parse the JSON using the unified parseJsonWithFix utility
414
+ if (completedToolBlocks.has(toolCall.id)) {
415
+ // Tool block was completed, parse with fix and logging
416
+ const parseResult = parseJsonWithFix(args, {
417
+ toolName: toolCall.function.name,
418
+ fallbackValue: {},
419
+ logWarning: true,
420
+ logError: true,
421
+ });
422
+ // Use the parsed data or fallback value
423
+ toolCall.function.arguments = JSON.stringify(parseResult.data);
393
424
  }
394
- catch (e) {
395
- // Only throw error if this tool block was marked as completed
396
- // This prevents errors from incomplete streaming
397
- if (completedToolBlocks.has(toolCall.id)) {
398
- const errorMsg = e instanceof Error ? e.message : 'Unknown error';
399
- throw new Error(`Invalid tool call JSON for ${toolCall.function.name}: ${args} (${errorMsg})`);
400
- }
401
- else {
402
- // Tool block wasn't completed, likely interrupted stream
403
- // Use partial data or empty object
404
- console.warn(`Warning: Tool call ${toolCall.function.name} (${toolCall.id}) was incomplete. Using partial data.`);
405
- toolCall.function.arguments = '{}';
425
+ else {
426
+ // Tool block wasn't completed, likely interrupted stream
427
+ // Try to parse without logging errors (incomplete data is expected)
428
+ const parseResult = parseJsonWithFix(args, {
429
+ toolName: toolCall.function.name,
430
+ fallbackValue: {},
431
+ logWarning: false,
432
+ logError: false,
433
+ });
434
+ if (!parseResult.success) {
435
+ logger.warn(`Warning: Tool call ${toolCall.function.name} (${toolCall.id}) was incomplete. Using fallback data.`);
406
436
  }
437
+ toolCall.function.arguments = JSON.stringify(parseResult.data);
407
438
  }
408
439
  }
409
440
  yield {
410
441
  type: 'tool_calls',
411
- tool_calls: toolCalls
442
+ tool_calls: toolCalls,
412
443
  };
413
444
  }
414
445
  if (usageData) {
446
+ // Save usage to file system at API layer
447
+ saveUsageToFile(options.model, usageData);
415
448
  yield {
416
449
  type: 'usage',
417
- usage: usageData
450
+ usage: usageData,
418
451
  };
419
452
  }
420
453
  yield {
421
- type: 'done'
454
+ type: 'done',
422
455
  };
423
456
  }, {
424
457
  abortSignal,
425
- onRetry
458
+ onRetry,
426
459
  });
427
460
  }