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.
- package/dist/agents/reviewAgent.d.ts +50 -0
- package/dist/agents/reviewAgent.js +264 -0
- package/dist/api/anthropic.js +104 -71
- package/dist/api/chat.d.ts +1 -1
- package/dist/api/chat.js +60 -41
- package/dist/api/gemini.js +97 -57
- package/dist/api/responses.d.ts +9 -1
- package/dist/api/responses.js +110 -70
- package/dist/api/systemPrompt.d.ts +1 -1
- package/dist/api/systemPrompt.js +36 -7
- package/dist/api/types.d.ts +8 -0
- package/dist/hooks/useCommandHandler.d.ts +1 -0
- package/dist/hooks/useCommandHandler.js +44 -1
- package/dist/hooks/useCommandPanel.js +13 -0
- package/dist/hooks/useConversation.d.ts +4 -1
- package/dist/hooks/useConversation.js +48 -6
- package/dist/hooks/useKeyboardInput.js +19 -0
- package/dist/hooks/useTerminalFocus.js +13 -3
- package/dist/mcp/aceCodeSearch.d.ts +2 -76
- package/dist/mcp/aceCodeSearch.js +31 -467
- package/dist/mcp/bash.d.ts +1 -8
- package/dist/mcp/bash.js +20 -40
- package/dist/mcp/filesystem.d.ts +3 -68
- package/dist/mcp/filesystem.js +32 -348
- package/dist/mcp/ideDiagnostics.js +2 -4
- package/dist/mcp/todo.d.ts +1 -17
- package/dist/mcp/todo.js +11 -15
- package/dist/mcp/types/aceCodeSearch.types.d.ts +92 -0
- package/dist/mcp/types/aceCodeSearch.types.js +4 -0
- package/dist/mcp/types/bash.types.d.ts +13 -0
- package/dist/mcp/types/bash.types.js +4 -0
- package/dist/mcp/types/filesystem.types.d.ts +44 -0
- package/dist/mcp/types/filesystem.types.js +4 -0
- package/dist/mcp/types/todo.types.d.ts +27 -0
- package/dist/mcp/types/todo.types.js +4 -0
- package/dist/mcp/types/websearch.types.d.ts +30 -0
- package/dist/mcp/types/websearch.types.js +4 -0
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.d.ts +34 -0
- package/dist/mcp/utils/aceCodeSearch/filesystem.utils.js +146 -0
- package/dist/mcp/utils/aceCodeSearch/language.utils.d.ts +14 -0
- package/dist/mcp/utils/aceCodeSearch/language.utils.js +99 -0
- package/dist/mcp/utils/aceCodeSearch/search.utils.d.ts +31 -0
- package/dist/mcp/utils/aceCodeSearch/search.utils.js +136 -0
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.d.ts +20 -0
- package/dist/mcp/utils/aceCodeSearch/symbol.utils.js +141 -0
- package/dist/mcp/utils/bash/security.utils.d.ts +20 -0
- package/dist/mcp/utils/bash/security.utils.js +34 -0
- package/dist/mcp/utils/filesystem/code-analysis.utils.d.ts +18 -0
- package/dist/mcp/utils/filesystem/code-analysis.utils.js +165 -0
- package/dist/mcp/utils/filesystem/match-finder.utils.d.ts +16 -0
- package/dist/mcp/utils/filesystem/match-finder.utils.js +85 -0
- package/dist/mcp/utils/filesystem/similarity.utils.d.ts +22 -0
- package/dist/mcp/utils/filesystem/similarity.utils.js +75 -0
- package/dist/mcp/utils/todo/date.utils.d.ts +9 -0
- package/dist/mcp/utils/todo/date.utils.js +14 -0
- package/dist/mcp/utils/websearch/browser.utils.d.ts +8 -0
- package/dist/mcp/utils/websearch/browser.utils.js +58 -0
- package/dist/mcp/utils/websearch/text.utils.d.ts +16 -0
- package/dist/mcp/utils/websearch/text.utils.js +39 -0
- package/dist/mcp/websearch.d.ts +1 -31
- package/dist/mcp/websearch.js +21 -97
- package/dist/ui/components/ChatInput.d.ts +2 -1
- package/dist/ui/components/ChatInput.js +10 -3
- package/dist/ui/components/MarkdownRenderer.d.ts +1 -2
- package/dist/ui/components/MarkdownRenderer.js +16 -153
- package/dist/ui/components/MessageList.js +4 -4
- package/dist/ui/components/SessionListScreen.js +37 -17
- package/dist/ui/components/ToolResultPreview.js +6 -6
- package/dist/ui/components/UsagePanel.d.ts +2 -0
- package/dist/ui/components/UsagePanel.js +360 -0
- package/dist/ui/pages/ChatScreen.d.ts +4 -0
- package/dist/ui/pages/ChatScreen.js +70 -30
- package/dist/ui/pages/ConfigScreen.js +23 -19
- package/dist/ui/pages/HeadlessModeScreen.js +2 -4
- package/dist/ui/pages/SubAgentConfigScreen.js +17 -17
- package/dist/ui/pages/SystemPromptConfigScreen.js +7 -6
- package/dist/utils/commandExecutor.d.ts +3 -3
- package/dist/utils/commandExecutor.js +4 -4
- package/dist/utils/commands/home.d.ts +2 -0
- package/dist/utils/commands/home.js +12 -0
- package/dist/utils/commands/review.d.ts +2 -0
- package/dist/utils/commands/review.js +81 -0
- package/dist/utils/commands/role.d.ts +2 -0
- package/dist/utils/commands/role.js +37 -0
- package/dist/utils/commands/usage.d.ts +2 -0
- package/dist/utils/commands/usage.js +12 -0
- package/dist/utils/contextCompressor.js +99 -367
- package/dist/utils/fileUtils.js +3 -3
- package/dist/utils/mcpToolsManager.js +12 -12
- package/dist/utils/proxyUtils.d.ts +15 -0
- package/dist/utils/proxyUtils.js +50 -0
- package/dist/utils/retryUtils.d.ts +27 -0
- package/dist/utils/retryUtils.js +114 -2
- package/dist/utils/sessionManager.d.ts +2 -5
- package/dist/utils/sessionManager.js +16 -83
- package/dist/utils/terminal.js +4 -3
- package/dist/utils/usageLogger.d.ts +11 -0
- package/dist/utils/usageLogger.js +99 -0
- package/package.json +3 -7
- package/dist/agents/summaryAgent.d.ts +0 -31
- 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();
|
package/dist/api/anthropic.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { getOpenAiConfig, getCustomSystemPrompt, getCustomHeaders } from '../utils/apiConfig.js';
|
|
3
|
-
import {
|
|
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' &&
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
|
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) +
|
|
355
|
-
|
|
356
|
-
|
|
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 =
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
}
|