snow-ai 0.4.3 → 0.4.4
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/api/anthropic.d.ts +0 -3
- package/dist/api/anthropic.js +45 -30
- package/dist/api/chat.js +46 -30
- package/dist/api/gemini.js +97 -81
- package/dist/api/responses.js +46 -30
- package/dist/hooks/useConversation.js +4 -1
- package/package.json +1 -1
package/dist/api/anthropic.d.ts
CHANGED
|
@@ -41,7 +41,4 @@ export interface AnthropicMessageParam {
|
|
|
41
41
|
content: string | Array<any>;
|
|
42
42
|
}
|
|
43
43
|
export declare function resetAnthropicClient(): void;
|
|
44
|
-
/**
|
|
45
|
-
* Create streaming chat completion using Anthropic API
|
|
46
|
-
*/
|
|
47
44
|
export declare function createStreamingAnthropicCompletion(options: AnthropicOptions, abortSignal?: AbortSignal, onRetry?: (error: Error, attempt: number, nextDelay: number) => void): AsyncGenerator<AnthropicStreamChunk, void, unknown>;
|
package/dist/api/anthropic.js
CHANGED
|
@@ -251,43 +251,58 @@ function convertToAnthropicMessages(messages, includeBuiltinSystemPrompt = true)
|
|
|
251
251
|
async function* parseSSEStream(reader) {
|
|
252
252
|
const decoder = new TextDecoder();
|
|
253
253
|
let buffer = '';
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
continue;
|
|
265
|
-
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
// Handle both "event: " and "event:" formats
|
|
269
|
-
if (trimmed.startsWith('event:')) {
|
|
270
|
-
// Event type, will be followed by data
|
|
271
|
-
continue;
|
|
254
|
+
try {
|
|
255
|
+
while (true) {
|
|
256
|
+
const { done, value } = await reader.read();
|
|
257
|
+
if (done) {
|
|
258
|
+
// ✅ 关键修复:检查buffer是否有残留数据
|
|
259
|
+
if (buffer.trim()) {
|
|
260
|
+
// 连接异常中断,抛出明确错误
|
|
261
|
+
throw new Error(`Stream terminated unexpectedly with incomplete data: ${buffer.substring(0, 100)}...`);
|
|
262
|
+
}
|
|
263
|
+
break; // 正常结束
|
|
272
264
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
265
|
+
buffer += decoder.decode(value, { stream: true });
|
|
266
|
+
const lines = buffer.split('\n');
|
|
267
|
+
buffer = lines.pop() || '';
|
|
268
|
+
for (const line of lines) {
|
|
269
|
+
const trimmed = line.trim();
|
|
270
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
271
|
+
continue;
|
|
272
|
+
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
273
|
+
return;
|
|
280
274
|
}
|
|
281
|
-
|
|
282
|
-
|
|
275
|
+
// Handle both "event: " and "event:" formats
|
|
276
|
+
if (trimmed.startsWith('event:')) {
|
|
277
|
+
// Event type, will be followed by data
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
// Handle both "data: " and "data:" formats
|
|
281
|
+
if (trimmed.startsWith('data:')) {
|
|
282
|
+
const data = trimmed.startsWith('data: ')
|
|
283
|
+
? trimmed.slice(6)
|
|
284
|
+
: trimmed.slice(5);
|
|
285
|
+
const parseResult = parseJsonWithFix(data, {
|
|
286
|
+
toolName: 'SSE stream',
|
|
287
|
+
logWarning: false,
|
|
288
|
+
logError: true,
|
|
289
|
+
});
|
|
290
|
+
if (parseResult.success) {
|
|
291
|
+
yield parseResult.data;
|
|
292
|
+
}
|
|
283
293
|
}
|
|
284
294
|
}
|
|
285
295
|
}
|
|
286
296
|
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
const { logger } = await import('../utils/logger.js');
|
|
299
|
+
logger.error('SSE stream parsing error:', {
|
|
300
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
301
|
+
remainingBuffer: buffer.substring(0, 200),
|
|
302
|
+
});
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
287
305
|
}
|
|
288
|
-
/**
|
|
289
|
-
* Create streaming chat completion using Anthropic API
|
|
290
|
-
*/
|
|
291
306
|
export async function* createStreamingAnthropicCompletion(options, abortSignal, onRetry) {
|
|
292
307
|
yield* withRetryGenerator(async function* () {
|
|
293
308
|
const config = getAnthropicConfig();
|
package/dist/api/chat.js
CHANGED
|
@@ -126,41 +126,57 @@ export function resetOpenAIClient() {
|
|
|
126
126
|
async function* parseSSEStream(reader) {
|
|
127
127
|
const decoder = new TextDecoder();
|
|
128
128
|
let buffer = '';
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
continue;
|
|
140
|
-
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
// Handle both "event: " and "event:" formats
|
|
144
|
-
if (trimmed.startsWith('event:')) {
|
|
145
|
-
// Event type, will be followed by data
|
|
146
|
-
continue;
|
|
129
|
+
try {
|
|
130
|
+
while (true) {
|
|
131
|
+
const { done, value } = await reader.read();
|
|
132
|
+
if (done) {
|
|
133
|
+
// ✅ 关键修复:检查buffer是否有残留数据
|
|
134
|
+
if (buffer.trim()) {
|
|
135
|
+
// 连接异常中断,抛出明确错误
|
|
136
|
+
throw new Error(`Stream terminated unexpectedly with incomplete data: ${buffer.substring(0, 100)}...`);
|
|
137
|
+
}
|
|
138
|
+
break; // 正常结束
|
|
147
139
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
140
|
+
buffer += decoder.decode(value, { stream: true });
|
|
141
|
+
const lines = buffer.split('\n');
|
|
142
|
+
buffer = lines.pop() || '';
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
const trimmed = line.trim();
|
|
145
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
146
|
+
continue;
|
|
147
|
+
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Handle both "event: " and "event:" formats
|
|
151
|
+
if (trimmed.startsWith('event:')) {
|
|
152
|
+
// Event type, will be followed by data
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Handle both "data: " and "data:" formats
|
|
156
|
+
if (trimmed.startsWith('data:')) {
|
|
157
|
+
const data = trimmed.startsWith('data: ')
|
|
158
|
+
? trimmed.slice(6)
|
|
159
|
+
: trimmed.slice(5);
|
|
160
|
+
const parseResult = parseJsonWithFix(data, {
|
|
161
|
+
toolName: 'SSE stream',
|
|
162
|
+
logWarning: false,
|
|
163
|
+
logError: true,
|
|
164
|
+
});
|
|
165
|
+
if (parseResult.success) {
|
|
166
|
+
yield parseResult.data;
|
|
167
|
+
}
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const { logger } = await import('../utils/logger.js');
|
|
174
|
+
logger.error('SSE stream parsing error:', {
|
|
175
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
176
|
+
remainingBuffer: buffer.substring(0, 200),
|
|
177
|
+
});
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
164
180
|
}
|
|
165
181
|
/**
|
|
166
182
|
* Simple streaming chat completion - only handles OpenAI interaction
|
package/dist/api/gemini.js
CHANGED
|
@@ -280,97 +280,113 @@ export async function* createStreamingGeminiCompletion(options, abortSignal, onR
|
|
|
280
280
|
const reader = response.body.getReader();
|
|
281
281
|
const decoder = new TextDecoder();
|
|
282
282
|
let buffer = '';
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
for (const line of lines) {
|
|
294
|
-
const trimmed = line.trim();
|
|
295
|
-
if (!trimmed || trimmed.startsWith(':'))
|
|
296
|
-
continue;
|
|
297
|
-
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
298
|
-
break;
|
|
283
|
+
try {
|
|
284
|
+
while (true) {
|
|
285
|
+
const { done, value } = await reader.read();
|
|
286
|
+
if (done) {
|
|
287
|
+
// ✅ 关键修复:检查buffer是否有残留数据
|
|
288
|
+
if (buffer.trim()) {
|
|
289
|
+
// 连接异常中断,抛出明确错误
|
|
290
|
+
throw new Error(`Stream terminated unexpectedly with incomplete data: ${buffer.substring(0, 100)}...`);
|
|
291
|
+
}
|
|
292
|
+
break; // 正常结束
|
|
299
293
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// Event type, will be followed by data
|
|
303
|
-
continue;
|
|
294
|
+
if (abortSignal?.aborted) {
|
|
295
|
+
return;
|
|
304
296
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
297
|
+
buffer += decoder.decode(value, { stream: true });
|
|
298
|
+
const lines = buffer.split('\n');
|
|
299
|
+
buffer = lines.pop() || '';
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
const trimmed = line.trim();
|
|
302
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
303
|
+
continue;
|
|
304
|
+
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
// Handle both "event: " and "event:" formats
|
|
308
|
+
if (trimmed.startsWith('event:')) {
|
|
309
|
+
// Event type, will be followed by data
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
// Handle both "data: " and "data:" formats
|
|
313
|
+
if (trimmed.startsWith('data:')) {
|
|
314
|
+
const data = trimmed.startsWith('data: ')
|
|
315
|
+
? trimmed.slice(6)
|
|
316
|
+
: trimmed.slice(5);
|
|
317
|
+
const parseResult = parseJsonWithFix(data, {
|
|
318
|
+
toolName: 'Gemini SSE stream',
|
|
319
|
+
logWarning: false,
|
|
320
|
+
logError: true,
|
|
321
|
+
});
|
|
322
|
+
if (parseResult.success) {
|
|
323
|
+
const chunk = parseResult.data;
|
|
324
|
+
// Process candidates
|
|
325
|
+
if (chunk.candidates && chunk.candidates.length > 0) {
|
|
326
|
+
const candidate = chunk.candidates[0];
|
|
327
|
+
if (candidate.content && candidate.content.parts) {
|
|
328
|
+
for (const part of candidate.content.parts) {
|
|
329
|
+
// Process thought content (Gemini thinking)
|
|
330
|
+
// When part.thought === true, the text field contains thinking content
|
|
331
|
+
if (part.thought === true && part.text) {
|
|
332
|
+
thinkingTextBuffer += part.text;
|
|
333
|
+
yield {
|
|
334
|
+
type: 'reasoning_delta',
|
|
335
|
+
delta: part.text,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
// Process regular text content (when thought is not true)
|
|
339
|
+
else if (part.text) {
|
|
340
|
+
contentBuffer += part.text;
|
|
341
|
+
yield {
|
|
342
|
+
type: 'content',
|
|
343
|
+
content: part.text,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
// Process function calls
|
|
347
|
+
if (part.functionCall) {
|
|
348
|
+
hasToolCalls = true;
|
|
349
|
+
const fc = part.functionCall;
|
|
350
|
+
const toolCall = {
|
|
351
|
+
id: `call_${toolCallIndex++}`,
|
|
352
|
+
type: 'function',
|
|
353
|
+
function: {
|
|
354
|
+
name: fc.name,
|
|
355
|
+
arguments: JSON.stringify(fc.args || {}),
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
toolCallsBuffer.push(toolCall);
|
|
359
|
+
// Yield delta for token counting
|
|
360
|
+
const deltaText = fc.name + JSON.stringify(fc.args || {});
|
|
361
|
+
yield {
|
|
362
|
+
type: 'tool_call_delta',
|
|
363
|
+
delta: deltaText,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
358
366
|
}
|
|
359
367
|
}
|
|
360
368
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
+
// Track usage info
|
|
370
|
+
if (chunk.usageMetadata) {
|
|
371
|
+
totalTokens = {
|
|
372
|
+
prompt: chunk.usageMetadata.promptTokenCount || 0,
|
|
373
|
+
completion: chunk.usageMetadata.candidatesTokenCount || 0,
|
|
374
|
+
total: chunk.usageMetadata.totalTokenCount || 0,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
379
|
}
|
|
372
380
|
}
|
|
373
381
|
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
const { logger } = await import('../utils/logger.js');
|
|
384
|
+
logger.error('Gemini SSE stream parsing error:', {
|
|
385
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
386
|
+
remainingBuffer: buffer.substring(0, 200),
|
|
387
|
+
});
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
374
390
|
// Yield tool calls if any
|
|
375
391
|
if (hasToolCalls && toolCallsBuffer.length > 0) {
|
|
376
392
|
yield {
|
package/dist/api/responses.js
CHANGED
|
@@ -207,41 +207,57 @@ function convertToResponseInput(messages, includeBuiltinSystemPrompt = true) {
|
|
|
207
207
|
async function* parseSSEStream(reader) {
|
|
208
208
|
const decoder = new TextDecoder();
|
|
209
209
|
let buffer = '';
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
continue;
|
|
221
|
-
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
// Handle both "event: " and "event:" formats
|
|
225
|
-
if (trimmed.startsWith('event:')) {
|
|
226
|
-
// Event type, will be followed by data
|
|
227
|
-
continue;
|
|
210
|
+
try {
|
|
211
|
+
while (true) {
|
|
212
|
+
const { done, value } = await reader.read();
|
|
213
|
+
if (done) {
|
|
214
|
+
// ✅ 关键修复:检查buffer是否有残留数据
|
|
215
|
+
if (buffer.trim()) {
|
|
216
|
+
// 连接异常中断,抛出明确错误
|
|
217
|
+
throw new Error(`Stream terminated unexpectedly with incomplete data: ${buffer.substring(0, 100)}...`);
|
|
218
|
+
}
|
|
219
|
+
break; // 正常结束
|
|
228
220
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
221
|
+
buffer += decoder.decode(value, { stream: true });
|
|
222
|
+
const lines = buffer.split('\n');
|
|
223
|
+
buffer = lines.pop() || '';
|
|
224
|
+
for (const line of lines) {
|
|
225
|
+
const trimmed = line.trim();
|
|
226
|
+
if (!trimmed || trimmed.startsWith(':'))
|
|
227
|
+
continue;
|
|
228
|
+
if (trimmed === 'data: [DONE]' || trimmed === 'data:[DONE]') {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Handle both "event: " and "event:" formats
|
|
232
|
+
if (trimmed.startsWith('event:')) {
|
|
233
|
+
// Event type, will be followed by data
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Handle both "data: " and "data:" formats
|
|
237
|
+
if (trimmed.startsWith('data:')) {
|
|
238
|
+
const data = trimmed.startsWith('data: ')
|
|
239
|
+
? trimmed.slice(6)
|
|
240
|
+
: trimmed.slice(5);
|
|
241
|
+
const parseResult = parseJsonWithFix(data, {
|
|
242
|
+
toolName: 'Responses API SSE stream',
|
|
243
|
+
logWarning: false,
|
|
244
|
+
logError: true,
|
|
245
|
+
});
|
|
246
|
+
if (parseResult.success) {
|
|
247
|
+
yield parseResult.data;
|
|
248
|
+
}
|
|
241
249
|
}
|
|
242
250
|
}
|
|
243
251
|
}
|
|
244
252
|
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
const { logger } = await import('../utils/logger.js');
|
|
255
|
+
logger.error('Responses API SSE stream parsing error:', {
|
|
256
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
257
|
+
remainingBuffer: buffer.substring(0, 200),
|
|
258
|
+
});
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
245
261
|
}
|
|
246
262
|
/**
|
|
247
263
|
* 使用 Responses API 创建流式响应(带自动工具调用)
|
|
@@ -1041,9 +1041,12 @@ export async function handleConversationWithTools(options) {
|
|
|
1041
1041
|
freeEncoder();
|
|
1042
1042
|
}
|
|
1043
1043
|
catch (error) {
|
|
1044
|
-
freeEncoder();
|
|
1045
1044
|
throw error;
|
|
1046
1045
|
}
|
|
1046
|
+
finally {
|
|
1047
|
+
// ✅ 确保总是释放encoder资源,避免资源泄漏
|
|
1048
|
+
freeEncoder();
|
|
1049
|
+
}
|
|
1047
1050
|
// Return the accumulated usage data
|
|
1048
1051
|
return { usage: accumulatedUsage };
|
|
1049
1052
|
}
|