snow-ai 0.3.1 → 0.3.3
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/summaryAgent.d.ts +31 -0
- package/dist/agents/summaryAgent.js +256 -0
- package/dist/api/anthropic.d.ts +13 -9
- package/dist/api/anthropic.js +77 -34
- package/dist/api/chat.d.ts +14 -29
- package/dist/api/chat.js +62 -19
- package/dist/api/gemini.d.ts +1 -10
- package/dist/api/gemini.js +104 -82
- package/dist/api/models.js +6 -7
- package/dist/api/responses.d.ts +2 -17
- package/dist/api/responses.js +59 -17
- package/dist/api/types.d.ts +39 -0
- package/dist/api/types.js +4 -0
- package/dist/hooks/useSessionSave.js +13 -2
- package/dist/ui/pages/ChatScreen.js +12 -5
- package/dist/ui/pages/ConfigScreen.js +62 -49
- package/dist/ui/pages/WelcomeScreen.js +1 -1
- package/dist/utils/contextCompressor.d.ts +1 -1
- package/dist/utils/contextCompressor.js +193 -81
- package/dist/utils/sessionManager.d.ts +7 -0
- package/dist/utils/sessionManager.js +83 -3
- package/package.json +1 -4
- package/readme.md +78 -57
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare class SummaryAgent {
|
|
2
|
+
private modelName;
|
|
3
|
+
private requestMethod;
|
|
4
|
+
private initialized;
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the summary agent with current configuration
|
|
7
|
+
* @returns true if initialized successfully, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
private initialize;
|
|
10
|
+
/**
|
|
11
|
+
* Check if summary agent is available
|
|
12
|
+
*/
|
|
13
|
+
isAvailable(): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Call the basic model with the same routing as main flow
|
|
16
|
+
* Uses streaming APIs and intercepts to assemble complete response
|
|
17
|
+
* This ensures 100% consistency with main flow routing
|
|
18
|
+
* @param messages - Chat messages
|
|
19
|
+
* @param abortSignal - Optional abort signal to cancel the request
|
|
20
|
+
*/
|
|
21
|
+
private callBasicModel;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a concise summary from the first user message
|
|
24
|
+
*
|
|
25
|
+
* @param userMessage - The first user message in the conversation
|
|
26
|
+
* @param abortSignal - Optional abort signal to cancel generation
|
|
27
|
+
* @returns A concise summary (10-20 words) suitable for session title
|
|
28
|
+
*/
|
|
29
|
+
generateSummary(userMessage: string, abortSignal?: AbortSignal): Promise<string>;
|
|
30
|
+
}
|
|
31
|
+
export declare const summaryAgent: SummaryAgent;
|
|
@@ -0,0 +1,256 @@
|
|
|
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
|
+
export class SummaryAgent {
|
|
8
|
+
constructor() {
|
|
9
|
+
Object.defineProperty(this, "modelName", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: ''
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "requestMethod", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: 'chat'
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "initialized", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: false
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the summary agent with current configuration
|
|
30
|
+
* @returns true if initialized successfully, false otherwise
|
|
31
|
+
*/
|
|
32
|
+
async initialize() {
|
|
33
|
+
try {
|
|
34
|
+
const config = getOpenAiConfig();
|
|
35
|
+
// Check if basic model is configured
|
|
36
|
+
if (!config.basicModel) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
this.modelName = config.basicModel;
|
|
40
|
+
this.requestMethod = config.requestMethod; // Follow main flow's request method
|
|
41
|
+
this.initialized = true;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.warn('Failed to initialize summary agent:', error);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if summary agent is available
|
|
51
|
+
*/
|
|
52
|
+
async isAvailable() {
|
|
53
|
+
if (!this.initialized) {
|
|
54
|
+
return await this.initialize();
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Call the basic model with the same routing as main flow
|
|
60
|
+
* Uses streaming APIs and intercepts to assemble complete response
|
|
61
|
+
* This ensures 100% consistency with main flow routing
|
|
62
|
+
* @param messages - Chat messages
|
|
63
|
+
* @param abortSignal - Optional abort signal to cancel the request
|
|
64
|
+
*/
|
|
65
|
+
async callBasicModel(messages, abortSignal) {
|
|
66
|
+
const config = getOpenAiConfig();
|
|
67
|
+
if (!config.basicModel) {
|
|
68
|
+
throw new Error('Basic model not configured');
|
|
69
|
+
}
|
|
70
|
+
// Get custom system prompt if configured
|
|
71
|
+
const customSystemPrompt = getCustomSystemPrompt();
|
|
72
|
+
// If custom system prompt exists, prepend it to messages
|
|
73
|
+
// This ensures summary agent respects user's custom system configuration
|
|
74
|
+
let processedMessages = messages;
|
|
75
|
+
if (customSystemPrompt) {
|
|
76
|
+
processedMessages = [
|
|
77
|
+
{
|
|
78
|
+
role: 'system',
|
|
79
|
+
content: customSystemPrompt,
|
|
80
|
+
},
|
|
81
|
+
...messages,
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
// Temporarily override advancedModel with basicModel
|
|
85
|
+
const originalAdvancedModel = config.advancedModel;
|
|
86
|
+
try {
|
|
87
|
+
// Override config to use basicModel
|
|
88
|
+
config.advancedModel = config.basicModel;
|
|
89
|
+
let streamGenerator;
|
|
90
|
+
// Route to appropriate streaming API based on request method (follows main flow exactly)
|
|
91
|
+
switch (this.requestMethod) {
|
|
92
|
+
case 'anthropic':
|
|
93
|
+
streamGenerator = createStreamingAnthropicCompletion({
|
|
94
|
+
model: this.modelName,
|
|
95
|
+
messages: processedMessages,
|
|
96
|
+
max_tokens: 1024, // Summaries are short
|
|
97
|
+
}, abortSignal);
|
|
98
|
+
break;
|
|
99
|
+
case 'gemini':
|
|
100
|
+
streamGenerator = createStreamingGeminiCompletion({
|
|
101
|
+
model: this.modelName,
|
|
102
|
+
messages: processedMessages,
|
|
103
|
+
}, abortSignal);
|
|
104
|
+
break;
|
|
105
|
+
case 'responses':
|
|
106
|
+
streamGenerator = createStreamingResponse({
|
|
107
|
+
model: this.modelName,
|
|
108
|
+
messages: processedMessages,
|
|
109
|
+
stream: true,
|
|
110
|
+
}, abortSignal);
|
|
111
|
+
break;
|
|
112
|
+
case 'chat':
|
|
113
|
+
default:
|
|
114
|
+
streamGenerator = createStreamingChatCompletion({
|
|
115
|
+
model: this.modelName,
|
|
116
|
+
messages: processedMessages,
|
|
117
|
+
stream: true,
|
|
118
|
+
}, abortSignal);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
// Intercept streaming response and assemble complete content
|
|
122
|
+
let completeContent = '';
|
|
123
|
+
let chunkCount = 0;
|
|
124
|
+
try {
|
|
125
|
+
for await (const chunk of streamGenerator) {
|
|
126
|
+
chunkCount++;
|
|
127
|
+
// Check abort signal
|
|
128
|
+
if (abortSignal?.aborted) {
|
|
129
|
+
throw new Error('Request aborted');
|
|
130
|
+
}
|
|
131
|
+
// Handle different chunk formats based on request method
|
|
132
|
+
if (this.requestMethod === 'chat') {
|
|
133
|
+
// Chat API uses standard OpenAI format: {choices: [{delta: {content}}]}
|
|
134
|
+
if (chunk.choices && chunk.choices[0]?.delta?.content) {
|
|
135
|
+
completeContent += chunk.choices[0].delta.content;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Responses, Gemini, and Anthropic APIs all use: {type: 'content', content: string}
|
|
140
|
+
if (chunk.type === 'content' && chunk.content) {
|
|
141
|
+
completeContent += chunk.content;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (streamError) {
|
|
147
|
+
// Log streaming error with details
|
|
148
|
+
if (streamError instanceof Error) {
|
|
149
|
+
logger.error('Summary agent: Streaming error:', {
|
|
150
|
+
error: streamError.message,
|
|
151
|
+
stack: streamError.stack,
|
|
152
|
+
name: streamError.name,
|
|
153
|
+
chunkCount,
|
|
154
|
+
contentLength: completeContent.length,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
logger.error('Summary agent: Unknown streaming error:', {
|
|
159
|
+
error: streamError,
|
|
160
|
+
chunkCount,
|
|
161
|
+
contentLength: completeContent.length,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
throw streamError;
|
|
165
|
+
}
|
|
166
|
+
return completeContent;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
// Log detailed error from API call setup or streaming
|
|
170
|
+
if (error instanceof Error) {
|
|
171
|
+
logger.error('Summary agent: API call failed:', {
|
|
172
|
+
error: error.message,
|
|
173
|
+
stack: error.stack,
|
|
174
|
+
name: error.name,
|
|
175
|
+
requestMethod: this.requestMethod,
|
|
176
|
+
modelName: this.modelName,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
logger.error('Summary agent: Unknown API error:', {
|
|
181
|
+
error,
|
|
182
|
+
requestMethod: this.requestMethod,
|
|
183
|
+
modelName: this.modelName,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
// Restore original config
|
|
190
|
+
config.advancedModel = originalAdvancedModel;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Generate a concise summary from the first user message
|
|
195
|
+
*
|
|
196
|
+
* @param userMessage - The first user message in the conversation
|
|
197
|
+
* @param abortSignal - Optional abort signal to cancel generation
|
|
198
|
+
* @returns A concise summary (10-20 words) suitable for session title
|
|
199
|
+
*/
|
|
200
|
+
async generateSummary(userMessage, abortSignal) {
|
|
201
|
+
const available = await this.isAvailable();
|
|
202
|
+
if (!available) {
|
|
203
|
+
// If summary agent is not available, return a truncated version of the message
|
|
204
|
+
return userMessage.slice(0, 50) + (userMessage.length > 50 ? '...' : '');
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const summaryPrompt = `Generate a concise summary (10-20 words) for the following user message. The summary should capture the main topic or intent.
|
|
208
|
+
|
|
209
|
+
User Message: ${userMessage}
|
|
210
|
+
|
|
211
|
+
Instructions:
|
|
212
|
+
1. Keep it under 20 words
|
|
213
|
+
2. Focus on the main topic or question
|
|
214
|
+
3. Use clear, simple language
|
|
215
|
+
4. Do not include quotes or special formatting
|
|
216
|
+
5. Make it suitable as a conversation title
|
|
217
|
+
|
|
218
|
+
Summary:`;
|
|
219
|
+
const messages = [
|
|
220
|
+
{
|
|
221
|
+
role: 'user',
|
|
222
|
+
content: summaryPrompt,
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
const summary = await this.callBasicModel(messages, abortSignal);
|
|
226
|
+
if (!summary || summary.trim().length === 0) {
|
|
227
|
+
logger.warn('Summary agent returned empty response, using truncated message');
|
|
228
|
+
return (userMessage.slice(0, 50) + (userMessage.length > 50 ? '...' : ''));
|
|
229
|
+
}
|
|
230
|
+
// Clean up the summary (remove quotes, trim whitespace)
|
|
231
|
+
const cleanedSummary = summary
|
|
232
|
+
.trim()
|
|
233
|
+
.replace(/^["']|["']$/g, '') // Remove leading/trailing quotes
|
|
234
|
+
.replace(/\n/g, ' ') // Replace newlines with spaces
|
|
235
|
+
.slice(0, 100); // Limit to 100 characters max
|
|
236
|
+
return cleanedSummary;
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// Log detailed error information
|
|
240
|
+
if (error instanceof Error) {
|
|
241
|
+
logger.warn('Summary agent generation failed, using truncated message:', {
|
|
242
|
+
error: error.message,
|
|
243
|
+
stack: error.stack,
|
|
244
|
+
name: error.name,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
logger.warn('Summary agent generation failed with unknown error:', error);
|
|
249
|
+
}
|
|
250
|
+
// Fallback to truncated message
|
|
251
|
+
return userMessage.slice(0, 50) + (userMessage.length > 50 ? '...' : '');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Export singleton instance
|
|
256
|
+
export const summaryAgent = new SummaryAgent();
|
package/dist/api/anthropic.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { ChatMessage } from './
|
|
2
|
-
import type { ChatCompletionTool } from 'openai/resources/chat/completions';
|
|
1
|
+
import type { ChatMessage, ChatCompletionTool, UsageInfo } from './types.js';
|
|
3
2
|
export interface AnthropicOptions {
|
|
4
3
|
model: string;
|
|
5
4
|
messages: ChatMessage[];
|
|
@@ -8,13 +7,6 @@ export interface AnthropicOptions {
|
|
|
8
7
|
tools?: ChatCompletionTool[];
|
|
9
8
|
sessionId?: string;
|
|
10
9
|
}
|
|
11
|
-
export interface UsageInfo {
|
|
12
|
-
prompt_tokens: number;
|
|
13
|
-
completion_tokens: number;
|
|
14
|
-
total_tokens: number;
|
|
15
|
-
cache_creation_input_tokens?: number;
|
|
16
|
-
cache_read_input_tokens?: number;
|
|
17
|
-
}
|
|
18
10
|
export interface AnthropicStreamChunk {
|
|
19
11
|
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'done' | 'usage';
|
|
20
12
|
content?: string;
|
|
@@ -29,6 +21,18 @@ export interface AnthropicStreamChunk {
|
|
|
29
21
|
delta?: string;
|
|
30
22
|
usage?: UsageInfo;
|
|
31
23
|
}
|
|
24
|
+
export interface AnthropicTool {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
input_schema: any;
|
|
28
|
+
cache_control?: {
|
|
29
|
+
type: 'ephemeral';
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface AnthropicMessageParam {
|
|
33
|
+
role: 'user' | 'assistant';
|
|
34
|
+
content: string | Array<any>;
|
|
35
|
+
}
|
|
32
36
|
export declare function resetAnthropicClient(): void;
|
|
33
37
|
/**
|
|
34
38
|
* Create streaming chat completion using Anthropic API
|
package/dist/api/anthropic.js
CHANGED
|
@@ -1,45 +1,28 @@
|
|
|
1
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
2
1
|
import { createHash, randomUUID } from 'crypto';
|
|
3
2
|
import { getOpenAiConfig, getCustomSystemPrompt, getCustomHeaders } from '../utils/apiConfig.js';
|
|
4
3
|
import { SYSTEM_PROMPT } from './systemPrompt.js';
|
|
5
4
|
import { withRetryGenerator } from '../utils/retryUtils.js';
|
|
6
|
-
let
|
|
7
|
-
function
|
|
8
|
-
if (!
|
|
5
|
+
let anthropicConfig = null;
|
|
6
|
+
function getAnthropicConfig() {
|
|
7
|
+
if (!anthropicConfig) {
|
|
9
8
|
const config = getOpenAiConfig();
|
|
10
9
|
if (!config.apiKey) {
|
|
11
10
|
throw new Error('Anthropic API configuration is incomplete. Please configure API key first.');
|
|
12
11
|
}
|
|
13
|
-
const clientConfig = {
|
|
14
|
-
apiKey: config.apiKey,
|
|
15
|
-
};
|
|
16
|
-
if (config.baseUrl && config.baseUrl !== 'https://api.openai.com/v1') {
|
|
17
|
-
clientConfig.baseURL = config.baseUrl;
|
|
18
|
-
}
|
|
19
12
|
const customHeaders = getCustomHeaders();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// }
|
|
28
|
-
// Intercept fetch to add beta parameter to URL
|
|
29
|
-
const originalFetch = clientConfig.fetch || globalThis.fetch;
|
|
30
|
-
clientConfig.fetch = async (url, init) => {
|
|
31
|
-
let finalUrl = url;
|
|
32
|
-
if (config.anthropicBeta && typeof url === 'string' && !url.includes('?beta=')) {
|
|
33
|
-
finalUrl = url + (url.includes('?') ? '&beta=true' : '?beta=true');
|
|
34
|
-
}
|
|
35
|
-
return originalFetch(finalUrl, init);
|
|
13
|
+
anthropicConfig = {
|
|
14
|
+
apiKey: config.apiKey,
|
|
15
|
+
baseUrl: config.baseUrl && config.baseUrl !== 'https://api.openai.com/v1'
|
|
16
|
+
? config.baseUrl
|
|
17
|
+
: 'https://api.anthropic.com/v1',
|
|
18
|
+
customHeaders,
|
|
19
|
+
anthropicBeta: config.anthropicBeta
|
|
36
20
|
};
|
|
37
|
-
anthropicClient = new Anthropic(clientConfig);
|
|
38
21
|
}
|
|
39
|
-
return
|
|
22
|
+
return anthropicConfig;
|
|
40
23
|
}
|
|
41
24
|
export function resetAnthropicClient() {
|
|
42
|
-
|
|
25
|
+
anthropicConfig = null;
|
|
43
26
|
}
|
|
44
27
|
/**
|
|
45
28
|
* Generate a user_id in the format: user_<hash>_account__session_<uuid>
|
|
@@ -209,16 +192,51 @@ function convertToAnthropicMessages(messages) {
|
|
|
209
192
|
}] : undefined;
|
|
210
193
|
return { system, messages: anthropicMessages };
|
|
211
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Parse Server-Sent Events (SSE) stream
|
|
197
|
+
*/
|
|
198
|
+
async function* parseSSEStream(reader) {
|
|
199
|
+
const decoder = new TextDecoder();
|
|
200
|
+
let buffer = '';
|
|
201
|
+
while (true) {
|
|
202
|
+
const { done, value } = await reader.read();
|
|
203
|
+
if (done)
|
|
204
|
+
break;
|
|
205
|
+
buffer += decoder.decode(value, { stream: true });
|
|
206
|
+
const lines = buffer.split('\n');
|
|
207
|
+
buffer = lines.pop() || '';
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
const trimmed = line.trim();
|
|
210
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
211
|
+
continue;
|
|
212
|
+
if (trimmed === 'data: [DONE]') {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (trimmed.startsWith('event: ')) {
|
|
216
|
+
// Event type, will be followed by data
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (trimmed.startsWith('data: ')) {
|
|
220
|
+
const data = trimmed.slice(6);
|
|
221
|
+
try {
|
|
222
|
+
yield JSON.parse(data);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
console.error('Failed to parse SSE data:', data);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
212
231
|
/**
|
|
213
232
|
* Create streaming chat completion using Anthropic API
|
|
214
233
|
*/
|
|
215
234
|
export async function* createStreamingAnthropicCompletion(options, abortSignal, onRetry) {
|
|
216
|
-
const client = getAnthropicClient();
|
|
217
235
|
yield* withRetryGenerator(async function* () {
|
|
236
|
+
const config = getAnthropicConfig();
|
|
218
237
|
const { system, messages } = convertToAnthropicMessages(options.messages);
|
|
219
238
|
const sessionId = options.sessionId || randomUUID();
|
|
220
239
|
const userId = generateUserId(sessionId);
|
|
221
|
-
const customHeaders = getCustomHeaders();
|
|
222
240
|
const requestBody = {
|
|
223
241
|
model: options.model,
|
|
224
242
|
max_tokens: options.max_tokens || 4096,
|
|
@@ -231,15 +249,40 @@ export async function* createStreamingAnthropicCompletion(options, abortSignal,
|
|
|
231
249
|
},
|
|
232
250
|
stream: true
|
|
233
251
|
};
|
|
234
|
-
|
|
235
|
-
|
|
252
|
+
// Prepare headers
|
|
253
|
+
const headers = {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
'x-api-key': config.apiKey,
|
|
256
|
+
'authorization': `Bearer ${config.apiKey}`,
|
|
257
|
+
'anthropic-version': '2023-06-01',
|
|
258
|
+
...config.customHeaders
|
|
259
|
+
};
|
|
260
|
+
// Add beta parameter if configured
|
|
261
|
+
// if (config.anthropicBeta) {
|
|
262
|
+
// headers['anthropic-beta'] = 'prompt-caching-2024-07-31';
|
|
263
|
+
// }
|
|
264
|
+
const url = config.anthropicBeta
|
|
265
|
+
? `${config.baseUrl}/messages?beta=true`
|
|
266
|
+
: `${config.baseUrl}/messages`;
|
|
267
|
+
const response = await fetch(url, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
headers,
|
|
270
|
+
body: JSON.stringify(requestBody),
|
|
271
|
+
signal: abortSignal
|
|
236
272
|
});
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const errorText = await response.text();
|
|
275
|
+
throw new Error(`Anthropic API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
276
|
+
}
|
|
277
|
+
if (!response.body) {
|
|
278
|
+
throw new Error('No response body from Anthropic API');
|
|
279
|
+
}
|
|
237
280
|
let contentBuffer = '';
|
|
238
281
|
let toolCallsBuffer = new Map();
|
|
239
282
|
let hasToolCalls = false;
|
|
240
283
|
let usageData;
|
|
241
284
|
let blockIndexToId = new Map();
|
|
242
|
-
for await (const event of
|
|
285
|
+
for await (const event of parseSSEStream(response.body.getReader())) {
|
|
243
286
|
if (abortSignal?.aborted) {
|
|
244
287
|
return;
|
|
245
288
|
}
|
package/dist/api/chat.d.ts
CHANGED
|
@@ -1,24 +1,5 @@
|
|
|
1
|
-
import type { ChatCompletionTool } from '
|
|
2
|
-
export
|
|
3
|
-
type: 'image';
|
|
4
|
-
data: string;
|
|
5
|
-
mimeType: string;
|
|
6
|
-
}
|
|
7
|
-
export interface ChatMessage {
|
|
8
|
-
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
9
|
-
content: string;
|
|
10
|
-
tool_call_id?: string;
|
|
11
|
-
tool_calls?: ToolCall[];
|
|
12
|
-
images?: ImageContent[];
|
|
13
|
-
}
|
|
14
|
-
export interface ToolCall {
|
|
15
|
-
id: string;
|
|
16
|
-
type: 'function';
|
|
17
|
-
function: {
|
|
18
|
-
name: string;
|
|
19
|
-
arguments: string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
1
|
+
import type { ChatMessage, ChatCompletionTool, ToolCall, UsageInfo, ImageContent } from './types.js';
|
|
2
|
+
export type { ChatMessage, ChatCompletionTool, ToolCall, UsageInfo, ImageContent };
|
|
22
3
|
export interface ChatCompletionOptions {
|
|
23
4
|
model: string;
|
|
24
5
|
messages: ChatMessage[];
|
|
@@ -56,15 +37,19 @@ export interface ChatCompletionChunk {
|
|
|
56
37
|
finish_reason?: string | null;
|
|
57
38
|
}>;
|
|
58
39
|
}
|
|
59
|
-
export
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
40
|
+
export interface ChatCompletionMessageParam {
|
|
41
|
+
role: 'system' | 'user' | 'assistant' | 'tool';
|
|
42
|
+
content: string | Array<{
|
|
43
|
+
type: 'text' | 'image_url';
|
|
44
|
+
text?: string;
|
|
45
|
+
image_url?: {
|
|
46
|
+
url: string;
|
|
47
|
+
};
|
|
48
|
+
}>;
|
|
49
|
+
tool_call_id?: string;
|
|
50
|
+
tool_calls?: ToolCall[];
|
|
67
51
|
}
|
|
52
|
+
export declare function resetOpenAIClient(): void;
|
|
68
53
|
export interface StreamChunk {
|
|
69
54
|
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'reasoning_delta' | 'reasoning_started' | 'done' | 'usage';
|
|
70
55
|
content?: string;
|
package/dist/api/chat.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
1
|
import { getOpenAiConfig, getCustomSystemPrompt, getCustomHeaders } from '../utils/apiConfig.js';
|
|
3
2
|
import { SYSTEM_PROMPT } from './systemPrompt.js';
|
|
4
3
|
import { withRetryGenerator } from '../utils/retryUtils.js';
|
|
@@ -89,54 +88,98 @@ function convertToOpenAIMessages(messages, includeSystemPrompt = true) {
|
|
|
89
88
|
}
|
|
90
89
|
return result;
|
|
91
90
|
}
|
|
92
|
-
let
|
|
93
|
-
function
|
|
94
|
-
if (!
|
|
91
|
+
let openaiConfig = null;
|
|
92
|
+
function getOpenAIConfig() {
|
|
93
|
+
if (!openaiConfig) {
|
|
95
94
|
const config = getOpenAiConfig();
|
|
96
95
|
if (!config.apiKey || !config.baseUrl) {
|
|
97
96
|
throw new Error('OpenAI API configuration is incomplete. Please configure API settings first.');
|
|
98
97
|
}
|
|
99
|
-
// Get custom headers
|
|
100
98
|
const customHeaders = getCustomHeaders();
|
|
101
|
-
|
|
99
|
+
openaiConfig = {
|
|
102
100
|
apiKey: config.apiKey,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
});
|
|
101
|
+
baseUrl: config.baseUrl,
|
|
102
|
+
customHeaders
|
|
103
|
+
};
|
|
108
104
|
}
|
|
109
|
-
return
|
|
105
|
+
return openaiConfig;
|
|
110
106
|
}
|
|
111
107
|
export function resetOpenAIClient() {
|
|
112
|
-
|
|
108
|
+
openaiConfig = null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parse Server-Sent Events (SSE) stream
|
|
112
|
+
*/
|
|
113
|
+
async function* parseSSEStream(reader) {
|
|
114
|
+
const decoder = new TextDecoder();
|
|
115
|
+
let buffer = '';
|
|
116
|
+
while (true) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done)
|
|
119
|
+
break;
|
|
120
|
+
buffer += decoder.decode(value, { stream: true });
|
|
121
|
+
const lines = buffer.split('\n');
|
|
122
|
+
buffer = lines.pop() || '';
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
126
|
+
continue;
|
|
127
|
+
if (trimmed === 'data: [DONE]') {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (trimmed.startsWith('data: ')) {
|
|
131
|
+
const data = trimmed.slice(6);
|
|
132
|
+
try {
|
|
133
|
+
yield JSON.parse(data);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
console.error('Failed to parse SSE data:', data);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
113
141
|
}
|
|
114
142
|
/**
|
|
115
143
|
* Simple streaming chat completion - only handles OpenAI interaction
|
|
116
144
|
* Tool execution should be handled by the caller
|
|
117
145
|
*/
|
|
118
146
|
export async function* createStreamingChatCompletion(options, abortSignal, onRetry) {
|
|
119
|
-
const
|
|
147
|
+
const config = getOpenAIConfig();
|
|
120
148
|
// 使用重试包装生成器
|
|
121
149
|
yield* withRetryGenerator(async function* () {
|
|
122
|
-
const
|
|
150
|
+
const requestBody = {
|
|
123
151
|
model: options.model,
|
|
124
152
|
messages: convertToOpenAIMessages(options.messages),
|
|
125
153
|
stream: true,
|
|
126
|
-
stream_options: { include_usage: true },
|
|
154
|
+
stream_options: { include_usage: true },
|
|
127
155
|
temperature: options.temperature || 0.7,
|
|
128
156
|
max_tokens: options.max_tokens,
|
|
129
157
|
tools: options.tools,
|
|
130
158
|
tool_choice: options.tool_choice,
|
|
131
|
-
}
|
|
132
|
-
|
|
159
|
+
};
|
|
160
|
+
const response = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: {
|
|
163
|
+
'Content-Type': 'application/json',
|
|
164
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
165
|
+
...config.customHeaders
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify(requestBody),
|
|
168
|
+
signal: abortSignal
|
|
133
169
|
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const errorText = await response.text();
|
|
172
|
+
throw new Error(`OpenAI API error: ${response.status} ${response.statusText} - ${errorText}`);
|
|
173
|
+
}
|
|
174
|
+
if (!response.body) {
|
|
175
|
+
throw new Error('No response body from OpenAI API');
|
|
176
|
+
}
|
|
134
177
|
let contentBuffer = '';
|
|
135
178
|
let toolCallsBuffer = {};
|
|
136
179
|
let hasToolCalls = false;
|
|
137
180
|
let usageData;
|
|
138
181
|
let reasoningStarted = false; // Track if reasoning has started
|
|
139
|
-
for await (const chunk of
|
|
182
|
+
for await (const chunk of parseSSEStream(response.body.getReader())) {
|
|
140
183
|
if (abortSignal?.aborted) {
|
|
141
184
|
return;
|
|
142
185
|
}
|
package/dist/api/gemini.d.ts
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
import type { ChatMessage } from './
|
|
2
|
-
import type { ChatCompletionTool } from 'openai/resources/chat/completions';
|
|
1
|
+
import type { ChatMessage, ChatCompletionTool, UsageInfo } from './types.js';
|
|
3
2
|
export interface GeminiOptions {
|
|
4
3
|
model: string;
|
|
5
4
|
messages: ChatMessage[];
|
|
6
5
|
temperature?: number;
|
|
7
6
|
tools?: ChatCompletionTool[];
|
|
8
7
|
}
|
|
9
|
-
export interface UsageInfo {
|
|
10
|
-
prompt_tokens: number;
|
|
11
|
-
completion_tokens: number;
|
|
12
|
-
total_tokens: number;
|
|
13
|
-
cache_creation_input_tokens?: number;
|
|
14
|
-
cache_read_input_tokens?: number;
|
|
15
|
-
cached_tokens?: number;
|
|
16
|
-
}
|
|
17
8
|
export interface GeminiStreamChunk {
|
|
18
9
|
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'done' | 'usage';
|
|
19
10
|
content?: string;
|