@blockrun/franklin 3.15.50 → 3.15.51
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/agent/llm.js +58 -5
- package/package.json +1 -1
package/dist/agent/llm.js
CHANGED
|
@@ -431,11 +431,64 @@ export class ModelClient {
|
|
|
431
431
|
if (!response.ok) {
|
|
432
432
|
const errorBody = await response.text().catch(() => 'unknown error');
|
|
433
433
|
const message = extractApiErrorMessage(errorBody);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
434
|
+
// Runtime tool_choice retry. The static allowlist at line ~35
|
|
435
|
+
// catches the case where the request goes directly to a model
|
|
436
|
+
// whose name contains `deepseek-reasoner` / `openai/o1` /
|
|
437
|
+
// `openai/o3`. But the gateway sometimes ALIASES a different
|
|
438
|
+
// model name to a reasoner backend — verified 2026-05-04 in a
|
|
439
|
+
// live session: a request for `deepseek/deepseek-v4-pro`
|
|
440
|
+
// returned `400 Invalid request: 400 deepseek-reasoner does not
|
|
441
|
+
// support this tool_choice`, because the gateway routed v4-pro
|
|
442
|
+
// to a deepseek-reasoner upstream. The static allowlist can't
|
|
443
|
+
// know that. Catch the error, drop tool_choice, re-fire once.
|
|
444
|
+
// No payment re-sign needed — original 402 already settled, and
|
|
445
|
+
// the gateway treats this as the same logical request.
|
|
446
|
+
const lc = message.toLowerCase();
|
|
447
|
+
const looksLikeToolChoiceReject = response.status === 400 &&
|
|
448
|
+
lc.includes('tool_choice') &&
|
|
449
|
+
(lc.includes('not support') || lc.includes('unsupported') || lc.includes('does not support'));
|
|
450
|
+
if (looksLikeToolChoiceReject && requestPayload['tool_choice'] !== undefined) {
|
|
451
|
+
delete requestPayload['tool_choice'];
|
|
452
|
+
const retryBody = JSON.stringify(requestPayload);
|
|
453
|
+
if (this.debug) {
|
|
454
|
+
console.error(`[franklin] tool_choice rejected by upstream; retrying without it (model=${request.model})`);
|
|
455
|
+
}
|
|
456
|
+
response = await withAbortableTimeout(() => fetch(endpoint, {
|
|
457
|
+
method: 'POST',
|
|
458
|
+
headers,
|
|
459
|
+
body: retryBody,
|
|
460
|
+
signal: requestController.signal,
|
|
461
|
+
}), requestController, createModelTimeoutError('request', request.model, requestTimeoutMs), requestTimeoutMs);
|
|
462
|
+
if (response.status === 402) {
|
|
463
|
+
const paymentHeader = await this.signPayment(response);
|
|
464
|
+
if (!paymentHeader) {
|
|
465
|
+
yield { kind: 'error', payload: { message: 'Payment signing failed' } };
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
response = await withAbortableTimeout(() => fetch(endpoint, {
|
|
469
|
+
method: 'POST',
|
|
470
|
+
headers: { ...headers, ...paymentHeader },
|
|
471
|
+
body: retryBody,
|
|
472
|
+
signal: requestController.signal,
|
|
473
|
+
}), requestController, createModelTimeoutError('request', request.model, requestTimeoutMs), requestTimeoutMs);
|
|
474
|
+
}
|
|
475
|
+
if (!response.ok) {
|
|
476
|
+
const retryBodyText = await response.text().catch(() => 'unknown error');
|
|
477
|
+
yield {
|
|
478
|
+
kind: 'error',
|
|
479
|
+
payload: { status: response.status, message: extractApiErrorMessage(retryBodyText) },
|
|
480
|
+
};
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
// Successful retry — fall through to SSE parsing below.
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
yield {
|
|
487
|
+
kind: 'error',
|
|
488
|
+
payload: { status: response.status, message },
|
|
489
|
+
};
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
439
492
|
}
|
|
440
493
|
// Parse SSE stream
|
|
441
494
|
yield* this.parseSSEStream(response, requestController, streamTimeoutMs, request.model);
|
package/package.json
CHANGED