mixdog 1.2.1 → 1.2.2
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/package.json +1 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +1 -0
- package/src/repl/slash-commands.mjs +16 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +77 -17
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +56 -23
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +31 -20
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +41 -21
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/err-text.mjs +25 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +123 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/turn-changes.mjs +447 -219
- package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
- package/src/runtime/shared/turn-snapshot.mjs +170 -48
- package/src/runtime/shared/turn-worktree-snapshot.mjs +135 -46
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/boot/apis.mjs +6 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/connect.mjs +29 -9
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +83 -15
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +3 -7
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/create-app-pickers.mjs +4 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/dist/index.mjs +816 -43
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +33 -24
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +7 -0
- package/src/tui/session/session-api/lifecycle.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +5 -14
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/turn.mjs +38 -15
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// wire event type maps to one handler over the shared stream `state`; `ctx`
|
|
3
3
|
// carries the label, the caller callbacks and the leaked-text relay.
|
|
4
4
|
import { typedStatusFrom } from './retry-classifier.mjs';
|
|
5
|
+
import { applyCompatErrorContract } from './openai-compat-error-contract.mjs';
|
|
5
6
|
import { customToolCallFromResponseItem, nativeToolSearchCallFromArguments } from './custom-tool-wire.mjs';
|
|
6
7
|
import { emitCompatToolCallOnce } from './openai-compat-stream-common.mjs';
|
|
7
8
|
import { truncatedCompatStreamError, parseCompletedToolCallArgumentsJson } from './lib/openai-tool-args.mjs';
|
|
@@ -41,9 +42,11 @@ function typedResponsesFailure(message, event) {
|
|
|
41
42
|
if (code != null && code !== '') err.providerErrorCode = String(code);
|
|
42
43
|
if (detail) err.providerError = detail;
|
|
43
44
|
err.providerWireError = true;
|
|
44
|
-
return err;
|
|
45
|
+
return applyCompatErrorContract(err, detail || (event?.code != null ? { code: event.code } : null));
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const streamLabel = (ctx) => ctx?.label || 'compat Responses';
|
|
49
|
+
|
|
47
50
|
// Reconcile a COMPLETED function_call item into state.toolCalls: fill in the
|
|
48
51
|
// id/name a streamed call may still be missing, or adopt the item as a new
|
|
49
52
|
// call. `response.output_item.done` and the `response.completed` sweep see the
|
|
@@ -289,6 +292,7 @@ function onCompleted(event, state, ctx) {
|
|
|
289
292
|
}
|
|
290
293
|
|
|
291
294
|
function onDone(event, state, ctx) {
|
|
295
|
+
const label = streamLabel(ctx);
|
|
292
296
|
if (!event.response || event.response.status === 'completed') {
|
|
293
297
|
state.completed = true;
|
|
294
298
|
// A stream that never emits response.completed: this frame carries usage.
|
|
@@ -298,7 +302,7 @@ function onDone(event, state, ctx) {
|
|
|
298
302
|
if (event.response.status === 'failed') {
|
|
299
303
|
const msg = event.response?.error?.message || 'response.done failed';
|
|
300
304
|
throw withIncompleteUsage(
|
|
301
|
-
typedResponsesFailure(
|
|
305
|
+
typedResponsesFailure(`${label} stream response.done failed: ${msg}`, event),
|
|
302
306
|
event,
|
|
303
307
|
state
|
|
304
308
|
);
|
|
@@ -306,27 +310,31 @@ function onDone(event, state, ctx) {
|
|
|
306
310
|
if (event.response.status === 'incomplete') {
|
|
307
311
|
const reason = incompleteReasonFromEvent(event);
|
|
308
312
|
if (isMaxOutputIncompleteReason(reason)) return settleMaxOutputIncomplete(event, state, ctx, reason);
|
|
309
|
-
throw withIncompleteUsage(new Error(
|
|
313
|
+
throw withIncompleteUsage(new Error(`${label} stream response.done incomplete: ${reason}`), event, state);
|
|
310
314
|
}
|
|
311
315
|
}
|
|
312
316
|
|
|
313
|
-
function onFailed(event, state) {
|
|
317
|
+
function onFailed(event, state, ctx) {
|
|
314
318
|
const msg = event.response?.error?.message || event.error?.message || event.message || 'response.failed';
|
|
315
319
|
// The wire event's OWN typed status/code is preserved verbatim. A
|
|
316
320
|
// forbidden/unknown failure is never coerced into a synthetic 500:
|
|
317
321
|
// without typed evidence it stays unclassified and is surfaced.
|
|
318
|
-
throw withIncompleteUsage(
|
|
322
|
+
throw withIncompleteUsage(
|
|
323
|
+
typedResponsesFailure(`${streamLabel(ctx)} stream response.failed: ${msg}`, event),
|
|
324
|
+
event,
|
|
325
|
+
state
|
|
326
|
+
);
|
|
319
327
|
}
|
|
320
328
|
|
|
321
329
|
function onIncomplete(event, state, ctx) {
|
|
322
330
|
const reason = incompleteReasonFromEvent(event);
|
|
323
331
|
if (isMaxOutputIncompleteReason(reason)) return settleMaxOutputIncomplete(event, state, ctx, reason);
|
|
324
|
-
throw withIncompleteUsage(new Error(
|
|
332
|
+
throw withIncompleteUsage(new Error(`${streamLabel(ctx)} stream response.incomplete: ${reason}`), event, state);
|
|
325
333
|
}
|
|
326
334
|
|
|
327
|
-
function onError(event) {
|
|
335
|
+
function onError(event, _state, ctx) {
|
|
328
336
|
const msg = event.message || event.error?.message || 'unknown';
|
|
329
|
-
throw typedResponsesFailure(
|
|
337
|
+
throw typedResponsesFailure(`${streamLabel(ctx)} stream error: ${msg}`, event);
|
|
330
338
|
}
|
|
331
339
|
|
|
332
340
|
const RESPONSES_EVENT_HANDLERS = new Map([
|
|
@@ -139,11 +139,11 @@ export function createResponsesOutcomeStamper(state, leakedCalls) {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/** A streamed function_call whose id/name never arrived cannot be dispatched. */
|
|
142
|
-
export function unresolvedSalvageError(state) {
|
|
142
|
+
export function unresolvedSalvageError(state, label) {
|
|
143
143
|
const unresolved = state.toolCalls.find((t) => t._pendingItemId);
|
|
144
144
|
if (!unresolved) return null;
|
|
145
145
|
return new Error(
|
|
146
|
-
|
|
146
|
+
`${label || 'compat Responses'} stream function_call salvage failed: missing call_id/name for item_id=${unresolved._pendingItemId || '?'}`
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -169,5 +169,6 @@ export function finalizeResponsesStream(state, { leakedCalls, parseResponsesTool
|
|
|
169
169
|
model: state.model || response.model || null,
|
|
170
170
|
responseId: state.responseId || response.id || null,
|
|
171
171
|
stopReason: state.stopReason || null,
|
|
172
|
+
reasoningEmitted: state.emittedReasoning === true,
|
|
172
173
|
};
|
|
173
174
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// turn tool loops keep the model's reasoning chain without server storage.
|
|
8
8
|
|
|
9
9
|
import { markProviderRecoveryExhausted, withRetry } from './retry-classifier.mjs';
|
|
10
|
+
import { compatRetryOptions, withCompatRetryProfile } from './openai-compat-retry-profile.mjs';
|
|
10
11
|
import { consumeCompatResponsesStream } from './openai-compat-stream.mjs';
|
|
11
12
|
import { getModelMetadataSync } from './model-catalog.mjs';
|
|
12
13
|
import { PROVIDER_FIRST_BYTE_TIMEOUT_MS, createPassthroughSignal } from '../stall-policy.mjs';
|
|
@@ -24,6 +25,8 @@ import {
|
|
|
24
25
|
import { preparePdfTextForProvider } from './media-normalization.mjs';
|
|
25
26
|
import { normalizeOpencodeGoReasoningEffort } from './openai-compat-xai.mjs';
|
|
26
27
|
import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
|
|
28
|
+
import { projectEffortConfiguration, withEffortConfigurationFallback } from './effort-configuration.mjs';
|
|
29
|
+
import { normalizeCacheKey } from '../agent-runtime/cache-strategy.mjs';
|
|
27
30
|
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
28
31
|
import {
|
|
29
32
|
applyCompatToolChoice,
|
|
@@ -53,9 +56,65 @@ function resolveReasoning(provider, useModel, opts) {
|
|
|
53
56
|
return reasoningEffort ? { effort: reasoningEffort } : null;
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/**
|
|
60
|
+
* A custom route's mid-conversation effort projection (GPT-6+ on this wire)
|
|
61
|
+
* with every effort in the row's wire value, or null for the per-request
|
|
62
|
+
* effort. Consecutive efforts sharing one wire value collapse into none; an
|
|
63
|
+
* effort the row cannot serve falls back to the per-request effort.
|
|
64
|
+
*/
|
|
65
|
+
function customEffortProjection(provider, useModel, messages, opts) {
|
|
66
|
+
if (!isCustomProviderName(provider.name)) return null;
|
|
67
|
+
const projection = projectEffortConfiguration(messages, provider.name, useModel, {
|
|
68
|
+
...opts,
|
|
69
|
+
protocol: 'openai-responses',
|
|
70
|
+
});
|
|
71
|
+
if (!projection) return null;
|
|
72
|
+
const model = provider.getCachedModelInfo?.(useModel) || null;
|
|
73
|
+
const wire = (effort) =>
|
|
74
|
+
resolveCustomReasoning({ protocol: 'openai-responses', model, effort }).fields.reasoning?.effort || null;
|
|
75
|
+
const initialEffort = wire(projection.initialEffort);
|
|
76
|
+
if (!initialEffort) return null;
|
|
77
|
+
const updates = new Map();
|
|
78
|
+
let current = initialEffort;
|
|
79
|
+
for (const [message, effort] of projection.updates) {
|
|
80
|
+
const value = wire(effort);
|
|
81
|
+
if (!value) return null;
|
|
82
|
+
if (value !== current) {
|
|
83
|
+
current = value;
|
|
84
|
+
updates.set(message, value);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { ...projection, initialEffort, effort: current, updates };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A custom route's session identity: its prompt_cache_key and session
|
|
92
|
+
* headers, '' without a session. The session wins over the provider-scoped
|
|
93
|
+
* cache key, so parallel sessions never evict each other; a one-shot role
|
|
94
|
+
* ('shared' scope, a fresh session per call) keys on the provider key instead.
|
|
95
|
+
* Never the effort, so an effort change keeps the cache routing.
|
|
96
|
+
*/
|
|
97
|
+
function customSessionCacheKey(provider, opts) {
|
|
98
|
+
if (!isCustomProviderName(provider.name)) return '';
|
|
99
|
+
const explicit = opts.promptCacheKey || opts.providerCacheKey || opts.session?.promptCacheKey;
|
|
100
|
+
return normalizeCacheKey(
|
|
101
|
+
opts.promptCacheScope === 'shared' ? explicit || opts.sessionId : opts.sessionId || explicit
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
56
105
|
/** The request body: lowered transcript plus the per-turn knobs (output cap,
|
|
57
|
-
* tools and tool choice, reasoning effort). */
|
|
58
|
-
function buildCompatResponsesParams({
|
|
106
|
+
* tools and tool choice, reasoning effort, session cache key). */
|
|
107
|
+
function buildCompatResponsesParams({
|
|
108
|
+
provider,
|
|
109
|
+
useModel,
|
|
110
|
+
input,
|
|
111
|
+
previousResponseId,
|
|
112
|
+
tools,
|
|
113
|
+
opts,
|
|
114
|
+
instructions,
|
|
115
|
+
effortProjection,
|
|
116
|
+
sessionCacheKey,
|
|
117
|
+
}) {
|
|
59
118
|
const params = {
|
|
60
119
|
model: useModel,
|
|
61
120
|
input,
|
|
@@ -71,14 +130,19 @@ function buildCompatResponsesParams({ provider, useModel, input, previousRespons
|
|
|
71
130
|
params.parallel_tool_calls = true;
|
|
72
131
|
}
|
|
73
132
|
applyCompatToolChoice(params, opts);
|
|
74
|
-
|
|
133
|
+
// A projected conversation keeps its initial effort on top; changes ride as input items.
|
|
134
|
+
const reasoning = effortProjection
|
|
135
|
+
? { effort: effortProjection.initialEffort }
|
|
136
|
+
: resolveReasoning(provider, useModel, opts);
|
|
75
137
|
if (reasoning) params.reasoning = reasoning;
|
|
76
138
|
// Opt-in system-as-instructions: set before the hook so the hook can override it.
|
|
77
139
|
if (instructions) params.instructions = instructions;
|
|
140
|
+
if (sessionCacheKey) params.prompt_cache_key = sessionCacheKey;
|
|
78
141
|
// Opt-in per-provider final shaping (a provider passes the hook in its config).
|
|
79
142
|
const hook = provider.config?.responsesParamsHook;
|
|
80
143
|
if (typeof hook === 'function') {
|
|
81
|
-
const modelInfo =
|
|
144
|
+
const modelInfo =
|
|
145
|
+
(typeof provider.getCachedModelInfo === 'function' && provider.getCachedModelInfo(useModel)) || null;
|
|
82
146
|
hook(params, { useModel, opts, modelInfo });
|
|
83
147
|
}
|
|
84
148
|
return params;
|
|
@@ -90,16 +154,16 @@ function buildCompatResponsesParams({ provider, useModel, input, previousRespons
|
|
|
90
154
|
* that re-opens the request. A content-idle stall is terminal for this
|
|
91
155
|
* transport, so it is marked recovery-exhausted rather than retried.
|
|
92
156
|
*/
|
|
93
|
-
async function streamCompatResponses({ provider, params, label, signal, opts }) {
|
|
157
|
+
async function streamCompatResponses({ provider, params, label, signal, opts, headers }) {
|
|
94
158
|
const totalSignal = createPassthroughSignal(signal);
|
|
95
159
|
try {
|
|
96
160
|
return await withRetry(
|
|
97
|
-
async ({ signal: attemptSignal }) => {
|
|
161
|
+
withCompatRetryProfile(provider.name, async ({ signal: attemptSignal }) => {
|
|
98
162
|
const stream = await withRetry(
|
|
99
163
|
({ signal: openSignal }) =>
|
|
100
164
|
provider.client.responses.create(params, {
|
|
101
165
|
signal: openSignal,
|
|
102
|
-
...(
|
|
166
|
+
...(headers ? { headers } : {}),
|
|
103
167
|
}),
|
|
104
168
|
{
|
|
105
169
|
signal: attemptSignal,
|
|
@@ -130,11 +194,12 @@ async function streamCompatResponses({ provider, params, label, signal, opts })
|
|
|
130
194
|
}
|
|
131
195
|
throw error;
|
|
132
196
|
}
|
|
133
|
-
},
|
|
197
|
+
}),
|
|
134
198
|
{
|
|
135
199
|
signal: totalSignal.signal,
|
|
136
200
|
onRetry: compatStreamRetryReporter(label, opts),
|
|
137
201
|
retry529: opts.retry529,
|
|
202
|
+
...compatRetryOptions(provider.name),
|
|
138
203
|
}
|
|
139
204
|
);
|
|
140
205
|
} finally {
|
|
@@ -145,9 +210,17 @@ async function streamCompatResponses({ provider, params, label, signal, opts })
|
|
|
145
210
|
/**
|
|
146
211
|
* Send one turn over `POST {baseURL}/responses` (streaming) using the
|
|
147
212
|
* provider's existing OpenAI SDK client. Returns the same result shape as
|
|
148
|
-
* the chat/completions path so the agent loop stays wire-agnostic.
|
|
213
|
+
* the chat/completions path so the agent loop stays wire-agnostic. A custom
|
|
214
|
+
* route whose model rejects the mid-conversation effort update is replayed
|
|
215
|
+
* once with the per-request effort.
|
|
149
216
|
*/
|
|
150
|
-
export
|
|
217
|
+
export function sendCompatResponses(provider, messages, useModel, tools, opts = {}) {
|
|
218
|
+
const send = (sendOpts) => sendCompatResponsesOnce(provider, messages, useModel, tools, sendOpts);
|
|
219
|
+
if (!isCustomProviderName(provider.name)) return send(opts);
|
|
220
|
+
return withEffortConfigurationFallback(provider.name, useModel, opts, send);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function sendCompatResponsesOnce(provider, messages, useModel, tools, opts) {
|
|
151
224
|
const replayProvider = compatResponsesReplayProvider(provider.name);
|
|
152
225
|
const signal = opts.signal || null;
|
|
153
226
|
if (signal?.aborted) {
|
|
@@ -156,7 +229,10 @@ export async function sendCompatResponses(provider, messages, useModel, tools, o
|
|
|
156
229
|
}
|
|
157
230
|
const label = `${provider.name}:responses`;
|
|
158
231
|
messages = await preparePdfTextForProvider(messages, provider);
|
|
232
|
+
const effortProjection = customEffortProjection(provider, useModel, messages, opts);
|
|
233
|
+
const sessionCacheKey = customSessionCacheKey(provider, opts);
|
|
159
234
|
const { input, previousResponseId, continuationResetReason } = toXaiResponsesInput(messages, opts.providerState, {
|
|
235
|
+
effortProjection,
|
|
160
236
|
model: useModel,
|
|
161
237
|
stateKey: COMPAT_RESPONSES_STATE_KEY,
|
|
162
238
|
replayProvider,
|
|
@@ -165,8 +241,7 @@ export async function sendCompatResponses(provider, messages, useModel, tools, o
|
|
|
165
241
|
// Opt-in: system prompts travel as top-level `instructions`, not input items.
|
|
166
242
|
includeSystem: provider.config?.responsesSystemAsInstructions !== true,
|
|
167
243
|
});
|
|
168
|
-
const instructions =
|
|
169
|
-
provider.config?.responsesSystemAsInstructions === true ? xaiSystemInstructions(messages) : '';
|
|
244
|
+
const instructions = provider.config?.responsesSystemAsInstructions === true ? xaiSystemInstructions(messages) : '';
|
|
170
245
|
const params = buildCompatResponsesParams({
|
|
171
246
|
provider,
|
|
172
247
|
useModel,
|
|
@@ -175,14 +250,20 @@ export async function sendCompatResponses(provider, messages, useModel, tools, o
|
|
|
175
250
|
tools,
|
|
176
251
|
opts,
|
|
177
252
|
instructions,
|
|
253
|
+
effortProjection,
|
|
254
|
+
sessionCacheKey,
|
|
178
255
|
});
|
|
256
|
+
// The session identity also travels as headers; caller headers win.
|
|
257
|
+
const headers = sessionCacheKey
|
|
258
|
+
? { 'session-id': sessionCacheKey, 'x-client-request-id': sessionCacheKey, ...opts.requestHeaders }
|
|
259
|
+
: opts.requestHeaders;
|
|
179
260
|
|
|
180
261
|
try {
|
|
181
262
|
opts.onStageChange?.('requesting');
|
|
182
263
|
} catch {
|
|
183
264
|
/* heartbeat best-effort */
|
|
184
265
|
}
|
|
185
|
-
const streamed = await streamCompatResponses({ provider, params, label, signal, opts });
|
|
266
|
+
const streamed = await streamCompatResponses({ provider, params, label, signal, opts, headers });
|
|
186
267
|
const response = streamed.response;
|
|
187
268
|
const usage = response?.usage || null;
|
|
188
269
|
const inputTokens = Number(usage?.input_tokens ?? usage?.prompt_tokens ?? 0);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Per-provider request retry profile for the OpenAI-compatible family. Each
|
|
2
|
+
// provider carries its own retry constants; the
|
|
3
|
+
// shared withRetry still owns classification, the exposure/replay veto,
|
|
4
|
+
// quota refusals, x-should-retry and Retry-After. Providers with no profile
|
|
5
|
+
// keep the shared defaults.
|
|
6
|
+
//
|
|
7
|
+
// `backoffMs[n]` is the base wait before retry n (index 0 is never read, so
|
|
8
|
+
// the first value is the duplicate the withRetry schedule expects).
|
|
9
|
+
// statusRetryCaps — retries allowed for one status (for example a
|
|
10
|
+
// 429 stops after a single retry, whatever the overall budget).
|
|
11
|
+
// terminalStatuses — statuses that are terminal although the
|
|
12
|
+
// shared classifier would retry them.
|
|
13
|
+
// retryOnlyStatuses — the complete retryable error set: any other 4xx/5xx
|
|
14
|
+
// status surfaces.
|
|
15
|
+
// maxRetryAfterMs — give up instead of waiting longer than this.
|
|
16
|
+
// maxFailedAttemptMs — a 5xx that took longer than this is not replayed.
|
|
17
|
+
// retryEmptyResponse — a completed turn with no text, tool call or
|
|
18
|
+
// reasoning is re-asked inside the ladder (nothing was exposed); a number
|
|
19
|
+
// bounds how many times per send, true leaves it to the ladder.
|
|
20
|
+
// adviceWindow - outside exceptStatuses, Retry-After is clamped to maxMs
|
|
21
|
+
// and jittered by jitterRatio (the advice of the excepted statuses stays raw).
|
|
22
|
+
// recycleDispatcherOn5xx — a retried 5xx rebuilds the shared HTTP pool.
|
|
23
|
+
// adviceAtLeastBackoff — the advice is a floor under the jittered backoff step.
|
|
24
|
+
// Every stop is provider-terminal: the profile's limits are final, so the
|
|
25
|
+
// agent loop never re-runs a stopped or exhausted request (see
|
|
26
|
+
// COMPAT_RETRY_POLICY).
|
|
27
|
+
import { markProviderRecoveryExhausted } from './retry-classifier.mjs';
|
|
28
|
+
import { retryAfterMsFromError } from './provider-error-kind.mjs';
|
|
29
|
+
import { STREAM_TRANSPORTS, stampStreamOutcome } from './lib/stream-outcome.mjs';
|
|
30
|
+
import { jitterDelayMs } from './retry-backoff.mjs';
|
|
31
|
+
|
|
32
|
+
const statusOf = (err) => Number(err?.httpStatus || err?.status || err?.response?.status || 0);
|
|
33
|
+
|
|
34
|
+
/** Loop retry policy of every profiled provider: no recovery cycles on top of the profile. */
|
|
35
|
+
export const COMPAT_RETRY_POLICY = Object.freeze({ recoveryCycles: false });
|
|
36
|
+
|
|
37
|
+
/** xAI additionally treats an in-band invalid-request error as retryable. */
|
|
38
|
+
export const XAI_COMPAT_RETRY_POLICY = Object.freeze({ ...COMPAT_RETRY_POLICY, inBandInvalidRequestRetryable: true });
|
|
39
|
+
|
|
40
|
+
export const COMPAT_RETRY_PROFILES = Object.freeze({
|
|
41
|
+
// A budget of 15 sends (the retry limit is reached when the next retry number
|
|
42
|
+
// equals it, so 14 waits), 2s doubling to 30s, ±20% jitter, one retry for a
|
|
43
|
+
// 429. Outside a 429, Retry-After is capped at 30s and jittered.
|
|
44
|
+
xai: Object.freeze({
|
|
45
|
+
maxAttempts: 15,
|
|
46
|
+
backoffMs: [2000, 2000, 4000, 8000, 16000, 30000],
|
|
47
|
+
retryJitterRatio: 0.2,
|
|
48
|
+
statusRetryCaps: { 429: 1 },
|
|
49
|
+
terminalStatuses: [408, 409, 425],
|
|
50
|
+
retryEmptyResponse: true,
|
|
51
|
+
adviceWindow: Object.freeze({ maxMs: 30_000, jitterRatio: 0.2, exceptStatuses: [429] }),
|
|
52
|
+
recycleDispatcherOn5xx: true,
|
|
53
|
+
}),
|
|
54
|
+
// 5 retries, 500ms doubling to 10s, ±10% jitter;
|
|
55
|
+
// a Retry-After beyond the 10s ceiling is not waited out.
|
|
56
|
+
deepseek: Object.freeze({
|
|
57
|
+
maxAttempts: 6,
|
|
58
|
+
backoffMs: [500, 500, 1000, 2000, 4000, 8000],
|
|
59
|
+
retryJitterRatio: 0.1,
|
|
60
|
+
maxRetryAfterMs: 10_000,
|
|
61
|
+
terminalStatuses: [408, 409, 425],
|
|
62
|
+
retryEmptyResponse: true,
|
|
63
|
+
}),
|
|
64
|
+
// The HTTP transport layer governs one request: 6 sends, 500ms doubling, no
|
|
65
|
+
// jitter, a Retry-After beyond 60s is not waited out; only 408, 429 and 5xx
|
|
66
|
+
// are transient, every other 4xx is terminal. A benign empty completion is
|
|
67
|
+
// re-asked at most twice.
|
|
68
|
+
'muse-code-oauth': Object.freeze({
|
|
69
|
+
maxAttempts: 6,
|
|
70
|
+
backoffMs: [500, 500, 1000, 2000, 4000, 8000],
|
|
71
|
+
retryJitterRatio: 0,
|
|
72
|
+
terminalStatuses: [409, 425],
|
|
73
|
+
maxRetryAfterMs: 60_000,
|
|
74
|
+
retryEmptyResponse: 2,
|
|
75
|
+
}),
|
|
76
|
+
// server budget 8 retries (2s doubling to 30s,
|
|
77
|
+
// ±10%), rate-limit budget 5 retries; 408, 409, 501 and 505 are terminal
|
|
78
|
+
// (425 stays retryable); a Retry-After beyond 300s is not waited out.
|
|
79
|
+
'mimo-oauth': Object.freeze({
|
|
80
|
+
maxAttempts: 9,
|
|
81
|
+
backoffMs: [2000, 2000, 4000, 8000, 16000, 30000],
|
|
82
|
+
retryJitterRatio: 0.1,
|
|
83
|
+
statusRetryCaps: { 429: 5 },
|
|
84
|
+
terminalStatuses: [408, 409, 501, 505],
|
|
85
|
+
maxRetryAfterMs: 300_000,
|
|
86
|
+
}),
|
|
87
|
+
// 3 sends (400ms doubling to 5s) for exactly
|
|
88
|
+
// 500/502/503/504/520/521/522 — every 4xx (429 included) and every other
|
|
89
|
+
// 5xx is terminal; a failed attempt slower than 15s is returned.
|
|
90
|
+
'command-code-oauth': Object.freeze({
|
|
91
|
+
maxAttempts: 3,
|
|
92
|
+
backoffMs: [400, 400, 800],
|
|
93
|
+
retryOnlyStatuses: [500, 502, 503, 504, 520, 521, 522],
|
|
94
|
+
maxFailedAttemptMs: 15_000,
|
|
95
|
+
maxRetryAfterMs: 60_000,
|
|
96
|
+
adviceAtLeastBackoff: true,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/** withRetry options (attempt budget, schedule, jitter) for a provider; {} for the shared defaults. */
|
|
101
|
+
export function compatRetryOptions(providerName) {
|
|
102
|
+
const profile = COMPAT_RETRY_PROFILES[providerName];
|
|
103
|
+
if (!profile) return {};
|
|
104
|
+
return {
|
|
105
|
+
maxAttempts: profile.maxAttempts,
|
|
106
|
+
backoffMs: profile.backoffMs,
|
|
107
|
+
retryJitterRatio: profile.retryJitterRatio,
|
|
108
|
+
finalOnExhaust: true,
|
|
109
|
+
...(profile.adviceAtLeastBackoff ? { adviceAtLeastBackoff: true } : {}),
|
|
110
|
+
...(profile.recycleDispatcherOn5xx ? { recycleDispatcherOn5xx: true } : {}),
|
|
111
|
+
...(profile.retryJitterMode ? { retryJitterMode: profile.retryJitterMode } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function profileStops(profile, err, { attempt, elapsedMs }) {
|
|
116
|
+
const status = statusOf(err);
|
|
117
|
+
if (profile.terminalStatuses?.includes(status)) return true;
|
|
118
|
+
const cap = profile.statusRetryCaps?.[status];
|
|
119
|
+
if (cap !== undefined && attempt >= cap) return true;
|
|
120
|
+
if (profile.retryOnlyStatuses && status >= 400 && status < 600 && !profile.retryOnlyStatuses.includes(status)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (profile.maxFailedAttemptMs && status >= 500 && status < 600 && elapsedMs > profile.maxFailedAttemptMs) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const advice = retryAfterMsFromError(err);
|
|
127
|
+
return profile.maxRetryAfterMs !== undefined && advice != null && advice > profile.maxRetryAfterMs;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Restamps the advice as a deadline (the highest-precedence source) so the
|
|
131
|
+
// shared loop waits the clamped, jittered value instead of the raw one.
|
|
132
|
+
function shapeAdvice(window, err) {
|
|
133
|
+
if (!window || !err || typeof err !== 'object' || window.exceptStatuses?.includes(statusOf(err))) return;
|
|
134
|
+
const advice = retryAfterMsFromError(err);
|
|
135
|
+
if (advice == null) return;
|
|
136
|
+
err.retryAfterAt = Date.now() + jitterDelayMs(Math.min(advice, window.maxMs), window.jitterRatio);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// A completed turn that produced nothing: no text, tool call or reasoning, and
|
|
140
|
+
// a normal finish (a Responses turn carries no stop reason when it completes).
|
|
141
|
+
function isEmptyNormalFinish(result) {
|
|
142
|
+
if (!result || typeof result !== 'object') return false;
|
|
143
|
+
if (result.stopReason != null && result.stopReason !== 'stop') return false;
|
|
144
|
+
return (
|
|
145
|
+
!String(result.content ?? '').trim() &&
|
|
146
|
+
!result.toolCalls?.length &&
|
|
147
|
+
!result.reasoningContent &&
|
|
148
|
+
!result.reasoningDetails?.length &&
|
|
149
|
+
result.reasoningEmitted !== true
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function emptyResponseError(providerName) {
|
|
154
|
+
const err = Object.assign(new Error(`${providerName} returned an empty response`), {
|
|
155
|
+
code: 'server_error',
|
|
156
|
+
emptyResponse: true,
|
|
157
|
+
});
|
|
158
|
+
// The turn finished: a failed ladder must not reopen it as a non-streaming request.
|
|
159
|
+
stampStreamOutcome(err, { transport: STREAM_TRANSPORTS.SSE, terminalObserved: true });
|
|
160
|
+
return err;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Wrap one withRetry attempt with the provider's profile: a failure the
|
|
165
|
+
* profile treats as final is marked recovery-exhausted and provider-terminal
|
|
166
|
+
* (withRetry then surfaces it unchanged and no outer layer replays it), and a
|
|
167
|
+
* profiled empty response is raised as a retryable failure.
|
|
168
|
+
*/
|
|
169
|
+
export function withCompatRetryProfile(providerName, attemptFn) {
|
|
170
|
+
const profile = COMPAT_RETRY_PROFILES[providerName];
|
|
171
|
+
if (!profile) return attemptFn;
|
|
172
|
+
let emptyRetries = 0;
|
|
173
|
+
return async (ctx) => {
|
|
174
|
+
const startedAt = Date.now();
|
|
175
|
+
try {
|
|
176
|
+
const result = await attemptFn(ctx);
|
|
177
|
+
if (profile.retryEmptyResponse && isEmptyNormalFinish(result)) {
|
|
178
|
+
const bound = profile.retryEmptyResponse === true ? Infinity : profile.retryEmptyResponse;
|
|
179
|
+
if (emptyRetries < bound) {
|
|
180
|
+
emptyRetries += 1;
|
|
181
|
+
throw emptyResponseError(providerName);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (profileStops(profile, err, { attempt: ctx?.attempt | 0, elapsedMs: Date.now() - startedAt })) {
|
|
187
|
+
throw markProviderRecoveryExhausted(err, { owner: `${providerName}-retry-profile`, terminal: true });
|
|
188
|
+
}
|
|
189
|
+
shapeAdvice(profile.adviceWindow, err);
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { PROVIDER_FIRST_BYTE_TIMEOUT_MS, providerTimeoutError, streamStalledError } from '../stall-policy.mjs';
|
|
3
3
|
import { typedStatusFrom } from './retry-classifier.mjs';
|
|
4
|
+
import { applyCompatErrorContract } from './openai-compat-error-contract.mjs';
|
|
5
|
+
import { truncatedCompatStreamError } from './lib/openai-tool-args.mjs';
|
|
4
6
|
|
|
5
7
|
// Shared lifecycle and replay boundaries for both OpenAI-compatible protocols.
|
|
6
8
|
|
|
@@ -22,10 +24,68 @@ function markInBandWireError(err) {
|
|
|
22
24
|
if (typed && !err.httpStatus) err.httpStatus = typed;
|
|
23
25
|
const code = detail.code ?? detail.type ?? null;
|
|
24
26
|
if (code != null && code !== '' && !err.providerErrorCode) err.providerErrorCode = String(code);
|
|
27
|
+
applyCompatErrorContract(err, detail);
|
|
25
28
|
} catch {}
|
|
26
29
|
return err;
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
/**
|
|
33
|
+
* A frame the SDK failed to JSON-decode. Raised only at the wire-decoding
|
|
34
|
+
* boundary (the iterator's own next()); consumers stamp the exposure evidence
|
|
35
|
+
* (partial output, replay safety) on it in their failure path.
|
|
36
|
+
*/
|
|
37
|
+
export function malformedCompatStreamError(label, cause) {
|
|
38
|
+
const err = new Error(`${label || 'compat SSE'} malformed stream frame: ${cause?.message || 'invalid JSON'}`, {
|
|
39
|
+
cause,
|
|
40
|
+
});
|
|
41
|
+
err.name = 'MalformedStreamError';
|
|
42
|
+
err.code = 'malformed_stream';
|
|
43
|
+
err.malformedStream = true;
|
|
44
|
+
return err;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A chat `finish_reason: network_error` is an interruption the gateway reports
|
|
49
|
+
* in-band, never a completion. Typed as a truncated stream so the shared
|
|
50
|
+
* classifier treats it as transient; exposure gates decide replay.
|
|
51
|
+
*/
|
|
52
|
+
export function networkInterruptedCompatStreamError(label) {
|
|
53
|
+
const err = truncatedCompatStreamError(label, 'finish_reason=network_error');
|
|
54
|
+
err.name = 'StreamInterruptedError';
|
|
55
|
+
err.streamInterrupted = true;
|
|
56
|
+
err.finishReason = 'network_error';
|
|
57
|
+
return err;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isNetworkErrorFinishReason(finishReason) {
|
|
61
|
+
return typeof finishReason === 'string' && finishReason.trim().toLowerCase() === 'network_error';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A chat `finish_reason: error` means the upstream failed the generation
|
|
66
|
+
* (gateways surface it as a 502 upstream_error). Typed like the network interruption, with the gateway status.
|
|
67
|
+
*/
|
|
68
|
+
export function upstreamErrorCompatStreamError(label) {
|
|
69
|
+
const err = truncatedCompatStreamError(label, 'finish_reason=error');
|
|
70
|
+
err.name = 'StreamInterruptedError';
|
|
71
|
+
err.streamInterrupted = true;
|
|
72
|
+
err.finishReason = 'error';
|
|
73
|
+
err.status = 502;
|
|
74
|
+
err.code = 'upstream_error';
|
|
75
|
+
return err;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isErrorFinishReason(finishReason) {
|
|
79
|
+
return typeof finishReason === 'string' && finishReason.trim().toLowerCase() === 'error';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The typed interruption for a failing chat finish_reason, or null for any other value. */
|
|
83
|
+
export function failingFinishReasonError(finishReason, label) {
|
|
84
|
+
if (isNetworkErrorFinishReason(finishReason)) return networkInterruptedCompatStreamError(label);
|
|
85
|
+
if (isErrorFinishReason(finishReason)) return upstreamErrorCompatStreamError(label);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
29
89
|
export function synthLeakedOpenAICall(recovered) {
|
|
30
90
|
let args = recovered?.arguments;
|
|
31
91
|
if (args === null || typeof args !== 'object' || Array.isArray(args)) args = {};
|
|
@@ -59,7 +119,7 @@ export function closeCompatStream(stream, iterator) {
|
|
|
59
119
|
|
|
60
120
|
export async function nextAsyncWithWatchdog(
|
|
61
121
|
iterator,
|
|
62
|
-
{ signal, idleMs, idleDeadlineAt, idleEnabled, idleLabel, emittedToolCall } = {}
|
|
122
|
+
{ signal, idleMs, idleDeadlineAt, idleEnabled, idleLabel, streamLabel, emittedToolCall } = {}
|
|
63
123
|
) {
|
|
64
124
|
let idleTimer = null;
|
|
65
125
|
let idleReject = null;
|
|
@@ -106,7 +166,11 @@ export async function nextAsyncWithWatchdog(
|
|
|
106
166
|
};
|
|
107
167
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
108
168
|
}
|
|
109
|
-
|
|
169
|
+
// The SDK JSON-decodes each frame inside next(): a SyntaxError here is a
|
|
170
|
+
// malformed wire frame, typed at this boundary and nowhere else.
|
|
171
|
+
iterator.next().then(resolve, (error) =>
|
|
172
|
+
reject(error instanceof SyntaxError ? malformedCompatStreamError(streamLabel || idleLabel, error) : error)
|
|
173
|
+
);
|
|
110
174
|
});
|
|
111
175
|
} catch (err) {
|
|
112
176
|
if (idleTimedOut)
|
|
@@ -99,6 +99,7 @@ export async function consumeCompatResponsesStream(
|
|
|
99
99
|
idleDeadlineAt: state.semanticIdleDeadlineAt,
|
|
100
100
|
idleEnabled: sawFirstEvent && idleEnabled && state.semanticIdleDeadlineAt > 0,
|
|
101
101
|
idleLabel: `${label} SSE idle`,
|
|
102
|
+
streamLabel: label,
|
|
102
103
|
// Unsafe-to-retry once any tool call (native or recovered-leaked)
|
|
103
104
|
// has been emitted this stream — avoid a double side-effect.
|
|
104
105
|
emittedToolCall: () => state.emittedToolCall || leakedCalls.length > 0,
|
|
@@ -144,7 +145,7 @@ export async function consumeCompatResponsesStream(
|
|
|
144
145
|
}
|
|
145
146
|
throw stampOutcome(err);
|
|
146
147
|
}
|
|
147
|
-
const unresolved = unresolvedSalvageError(state);
|
|
148
|
+
const unresolved = unresolvedSalvageError(state, label);
|
|
148
149
|
if (unresolved) {
|
|
149
150
|
attachPartialState(unresolved, state, leakedCalls);
|
|
150
151
|
throw stampOutcome(unresolved);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { customToolCallFromResponseItem, nativeToolSearchCallFromArguments } from './custom-tool-wire.mjs';
|
|
19
19
|
import { providerReplayItems } from './lib/provider-replay.mjs';
|
|
20
20
|
import { ensureResponsesCallOutputs } from './lib/wire-pairing.mjs';
|
|
21
|
+
import { responsesEffortUpdates } from './effort-configuration.mjs';
|
|
21
22
|
|
|
22
23
|
function positiveTokenInt(value) {
|
|
23
24
|
const n = Number(value);
|
|
@@ -423,10 +424,14 @@ export function toXaiResponsesInput(messages, providerState, options = {}) {
|
|
|
423
424
|
if (!pendingToolMedia.length) return;
|
|
424
425
|
input.push({ role: 'user', content: pendingToolMedia.splice(0) });
|
|
425
426
|
};
|
|
427
|
+
// `options.effortProjection` (custom GPT-6+ routes) carries wire-ready effort updates.
|
|
428
|
+
const effortUpdates = responsesEffortUpdates(options.effortProjection);
|
|
426
429
|
for (let messageIndex = startIndex; messageIndex < messages.length; messageIndex += 1) {
|
|
427
430
|
const m = messages[messageIndex];
|
|
428
431
|
if (!includeSystem && m.role === 'system') continue;
|
|
432
|
+
const updates = effortUpdates.before(m);
|
|
429
433
|
if (m.role !== 'tool') flushToolMedia();
|
|
434
|
+
input.push(...updates);
|
|
430
435
|
const orderedReplay = m.role === 'assistant' ? providerReplayItems(m, replayProvider) : undefined;
|
|
431
436
|
if (orderedReplay?.length) {
|
|
432
437
|
input.push(...orderedReplay);
|
|
@@ -444,6 +449,7 @@ export function toXaiResponsesInput(messages, providerState, options = {}) {
|
|
|
444
449
|
flushToolMedia();
|
|
445
450
|
const trailingReasoning = reasoningByMessageIndex.get(messages.length);
|
|
446
451
|
if (trailingReasoning) input.push(...trailingReasoning);
|
|
452
|
+
input.push(...effortUpdates.end());
|
|
447
453
|
// Wire-level pairing guard: replay envelopes can carry a call whose
|
|
448
454
|
// result never committed (cancel/abort). The provider hard-rejects the
|
|
449
455
|
// unpaired call, so synthesize the missing outputs here.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
2
2
|
import { markProviderRecoveryExhausted, withRetry } from './retry-classifier.mjs';
|
|
3
|
+
import { compatRetryOptions, withCompatRetryProfile } from './openai-compat-retry-profile.mjs';
|
|
3
4
|
import { consumeCompatResponsesStream } from './openai-compat-stream.mjs';
|
|
4
5
|
import { PROVIDER_FIRST_BYTE_TIMEOUT_MS, createPassthroughSignal } from '../stall-policy.mjs';
|
|
5
6
|
import {
|
|
@@ -91,7 +92,7 @@ export async function sendXaiResponses(provider, messages, useModel, tools, opts
|
|
|
91
92
|
let streamed;
|
|
92
93
|
try {
|
|
93
94
|
streamed = await withRetry(
|
|
94
|
-
async ({ signal: attemptSignal }) => {
|
|
95
|
+
withCompatRetryProfile(provider.name, async ({ signal: attemptSignal }) => {
|
|
95
96
|
const stream = await withRetry(
|
|
96
97
|
({ signal: openSignal }) => provider.client.responses.create(params, { signal: openSignal }),
|
|
97
98
|
{
|
|
@@ -127,11 +128,12 @@ export async function sendXaiResponses(provider, messages, useModel, tools, opts
|
|
|
127
128
|
}
|
|
128
129
|
throw error;
|
|
129
130
|
}
|
|
130
|
-
},
|
|
131
|
+
}),
|
|
131
132
|
{
|
|
132
133
|
signal: totalSignal.signal,
|
|
133
134
|
onRetry: compatStreamRetryReporter('xai:responses', opts),
|
|
134
135
|
retry529: opts.retry529,
|
|
136
|
+
...compatRetryOptions(provider.name),
|
|
135
137
|
}
|
|
136
138
|
);
|
|
137
139
|
} finally {
|