orquesta-cli 0.2.101 → 0.2.102
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/core/llm/llm-client.js +71 -46
- package/package.json +1 -1
|
@@ -221,6 +221,7 @@ export class LLMClient {
|
|
|
221
221
|
});
|
|
222
222
|
const url = '/chat/completions';
|
|
223
223
|
let controller = null;
|
|
224
|
+
let gapTimedOut = false;
|
|
224
225
|
try {
|
|
225
226
|
logger.flow('Starting message preprocessing');
|
|
226
227
|
const modelId = options.model || this.model;
|
|
@@ -272,61 +273,80 @@ export class LLMClient {
|
|
|
272
273
|
const toolCallsMap = new Map();
|
|
273
274
|
let responseId = '';
|
|
274
275
|
let responseModel = '';
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
buffer = lines.pop() || '';
|
|
282
|
-
for (const line of lines) {
|
|
283
|
-
const trimmed = line.trim();
|
|
284
|
-
if (!trimmed || trimmed === 'data: [DONE]')
|
|
285
|
-
continue;
|
|
286
|
-
if (!trimmed.startsWith('data: '))
|
|
287
|
-
continue;
|
|
276
|
+
const GAP_MS = Number(process.env['ORQUESTA_STREAM_GAP_MS']) || 45000;
|
|
277
|
+
let lastChunkAt = Date.now();
|
|
278
|
+
const gapTimer = setInterval(() => {
|
|
279
|
+
if (Date.now() - lastChunkAt > GAP_MS) {
|
|
280
|
+
gapTimedOut = true;
|
|
281
|
+
clearInterval(gapTimer);
|
|
288
282
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
283
|
+
controller?.abort();
|
|
284
|
+
}
|
|
285
|
+
catch { }
|
|
286
|
+
}
|
|
287
|
+
}, 5000);
|
|
288
|
+
gapTimer.unref?.();
|
|
289
|
+
try {
|
|
290
|
+
for await (const chunk of stream) {
|
|
291
|
+
lastChunkAt = Date.now();
|
|
292
|
+
if (this.isInterrupted) {
|
|
293
|
+
throw new Error('INTERRUPTED');
|
|
294
|
+
}
|
|
295
|
+
buffer += chunk.toString();
|
|
296
|
+
const lines = buffer.split('\n');
|
|
297
|
+
buffer = lines.pop() || '';
|
|
298
|
+
for (const line of lines) {
|
|
299
|
+
const trimmed = line.trim();
|
|
300
|
+
if (!trimmed || trimmed === 'data: [DONE]')
|
|
296
301
|
continue;
|
|
297
|
-
if (
|
|
298
|
-
finishReason = choice.finish_reason;
|
|
299
|
-
const delta = choice.delta;
|
|
300
|
-
if (!delta)
|
|
302
|
+
if (!trimmed.startsWith('data: '))
|
|
301
303
|
continue;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
304
|
+
try {
|
|
305
|
+
const data = JSON.parse(trimmed.slice(6));
|
|
306
|
+
if (data.id)
|
|
307
|
+
responseId = data.id;
|
|
308
|
+
if (data.model)
|
|
309
|
+
responseModel = data.model;
|
|
310
|
+
const choice = data.choices?.[0];
|
|
311
|
+
if (!choice)
|
|
312
|
+
continue;
|
|
313
|
+
if (choice.finish_reason)
|
|
314
|
+
finishReason = choice.finish_reason;
|
|
315
|
+
const delta = choice.delta;
|
|
316
|
+
if (!delta)
|
|
317
|
+
continue;
|
|
318
|
+
if (delta.role)
|
|
319
|
+
role = delta.role;
|
|
320
|
+
if (delta.content) {
|
|
321
|
+
contentAccum += delta.content;
|
|
322
|
+
this.onStreamingContent(delta.content);
|
|
323
|
+
}
|
|
324
|
+
if (delta.reasoning) {
|
|
325
|
+
reasoningAccum += delta.reasoning;
|
|
326
|
+
}
|
|
327
|
+
if (delta.tool_calls) {
|
|
328
|
+
for (const tc of delta.tool_calls) {
|
|
329
|
+
const idx = tc.index ?? 0;
|
|
330
|
+
if (!toolCallsMap.has(idx)) {
|
|
331
|
+
toolCallsMap.set(idx, { id: tc.id || '', type: 'function', function: { name: '', arguments: '' } });
|
|
332
|
+
}
|
|
333
|
+
const existing = toolCallsMap.get(idx);
|
|
334
|
+
if (tc.id)
|
|
335
|
+
existing.id = tc.id;
|
|
336
|
+
if (tc.function?.name)
|
|
337
|
+
existing.function.name += tc.function.name;
|
|
338
|
+
if (tc.function?.arguments)
|
|
339
|
+
existing.function.arguments += tc.function.arguments;
|
|
316
340
|
}
|
|
317
|
-
const existing = toolCallsMap.get(idx);
|
|
318
|
-
if (tc.id)
|
|
319
|
-
existing.id = tc.id;
|
|
320
|
-
if (tc.function?.name)
|
|
321
|
-
existing.function.name += tc.function.name;
|
|
322
|
-
if (tc.function?.arguments)
|
|
323
|
-
existing.function.arguments += tc.function.arguments;
|
|
324
341
|
}
|
|
325
342
|
}
|
|
343
|
+
catch { }
|
|
326
344
|
}
|
|
327
|
-
catch { }
|
|
328
345
|
}
|
|
329
346
|
}
|
|
347
|
+
finally {
|
|
348
|
+
clearInterval(gapTimer);
|
|
349
|
+
}
|
|
330
350
|
const toolCalls = Array.from(toolCallsMap.values())
|
|
331
351
|
.filter(tc => tc.id && tc.function.name)
|
|
332
352
|
.map(tc => ({ id: tc.id, type: 'function', function: { name: tc.function.name, arguments: tc.function.arguments } }));
|
|
@@ -397,6 +417,11 @@ export class LLMClient {
|
|
|
397
417
|
return response.data;
|
|
398
418
|
}
|
|
399
419
|
catch (error) {
|
|
420
|
+
if (gapTimedOut) {
|
|
421
|
+
logger.flow('Stream stalled — gap watchdog aborted the request');
|
|
422
|
+
logger.exit('chatCompletion', { success: false, stalled: true });
|
|
423
|
+
throw new Error('Upstream stalled: no streaming data received within the gap window. Try again or switch endpoint.');
|
|
424
|
+
}
|
|
400
425
|
if (axios.isCancel(error) || (error instanceof Error && error.name === 'CanceledError')) {
|
|
401
426
|
logger.flow('API call canceled (user interrupt)');
|
|
402
427
|
logger.exit('chatCompletion', { success: false, aborted: true });
|