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
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
2
|
import { reloadProviderApiKey, resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
canFallbackNonStreaming,
|
|
5
|
+
emitProviderRetryStage,
|
|
6
|
+
markProviderRecoveryExhausted,
|
|
7
|
+
withRetry,
|
|
8
|
+
} from './retry-classifier.mjs';
|
|
4
9
|
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
5
10
|
import { traceNonStreamingFallback } from './lib/transport-fallback-trace.mjs';
|
|
6
11
|
import {
|
|
@@ -12,6 +17,13 @@ import {
|
|
|
12
17
|
} from '../stall-policy.mjs';
|
|
13
18
|
import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
14
19
|
import { GEMINI_FIRST_BYTE_TIMEOUT_MS, consumeGeminiRestStreamResponse } from './gemini-stream.mjs';
|
|
20
|
+
import { applyGeminiErrorContract } from './gemini-error-contract.mjs';
|
|
21
|
+
import {
|
|
22
|
+
GEMINI_MIDSTREAM_MAX_ATTEMPTS,
|
|
23
|
+
geminiEndsWithFunctionResponse,
|
|
24
|
+
geminiInvalidStreamReason,
|
|
25
|
+
runGeminiStreamAttempts,
|
|
26
|
+
} from './gemini-retry-policy.mjs';
|
|
15
27
|
import { geminiTextLeakGuardFor, streamGeminiSdkAttempt } from './gemini-sdk-request.mjs';
|
|
16
28
|
import { emitGeminiToolCalls } from './gemini-schema.mjs';
|
|
17
29
|
import { buildGeminiRequest, geminiCachedRestBody } from './gemini-request-body.mjs';
|
|
@@ -32,6 +44,8 @@ import {
|
|
|
32
44
|
GEMINI_GLOBAL_CACHE_DELETE_GRACE_MS,
|
|
33
45
|
_geminiCredentialFingerprint,
|
|
34
46
|
_invalidateGeminiCachesForCredentialFingerprint,
|
|
47
|
+
geminiExplicitCacheRefused,
|
|
48
|
+
markGeminiExplicitCacheRefused,
|
|
35
49
|
} from './gemini-cache.mjs';
|
|
36
50
|
import {
|
|
37
51
|
awaitSharedCreate,
|
|
@@ -59,7 +73,7 @@ import {
|
|
|
59
73
|
let _modelRefreshInFlight = null;
|
|
60
74
|
const GEMINI_AVAILABILITY_TIMEOUT_MS = 1_000;
|
|
61
75
|
|
|
62
|
-
function geminiRestError(res, text, label) {
|
|
76
|
+
export function geminiRestError(res, text, label) {
|
|
63
77
|
let payload = null;
|
|
64
78
|
try {
|
|
65
79
|
payload = text ? JSON.parse(text) : null;
|
|
@@ -78,16 +92,11 @@ function geminiRestError(res, text, label) {
|
|
|
78
92
|
err.error = detail;
|
|
79
93
|
err.data = payload;
|
|
80
94
|
if (detail.status) err.geminiStatus = detail.status;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
// A 429 RESOURCE_EXHAUSTED is a request-local rate limit even without
|
|
87
|
-
// Retry-After (Google/LiteLLM retry). Do not stamp it as a quota code.
|
|
88
|
-
if (detail.status && (retryAfter == null || retryAfter === '') && res.status !== 429) {
|
|
89
|
-
err.code = detail.status;
|
|
90
|
-
}
|
|
95
|
+
const details = detail.details ?? detail.errorDetails;
|
|
96
|
+
if (Array.isArray(details)) err.details = details;
|
|
97
|
+
// Typed contract (code/retryAfterMs/resetAt/quotaScope); the core decides
|
|
98
|
+
// whether a 429 RESOURCE_EXHAUSTED is request-local or a quota refusal.
|
|
99
|
+
applyGeminiErrorContract(err, { details, googleStatus: detail.status, message: detail.message });
|
|
91
100
|
}
|
|
92
101
|
return err;
|
|
93
102
|
}
|
|
@@ -139,6 +148,9 @@ export class GeminiProvider {
|
|
|
139
148
|
// input already includes cache. See registry.mjs.
|
|
140
149
|
static inputExcludesCache = false;
|
|
141
150
|
name = 'gemini';
|
|
151
|
+
// The two-phase ladder (gemini-retry-policy.mjs) is the whole recovery:
|
|
152
|
+
// a spent ladder surfaces instead of parking on loop recovery cycles.
|
|
153
|
+
retryPolicy = Object.freeze({ recoveryCycles: false });
|
|
142
154
|
genAI;
|
|
143
155
|
config;
|
|
144
156
|
_fetch;
|
|
@@ -263,7 +275,7 @@ export class GeminiProvider {
|
|
|
263
275
|
if (decision.canReuseState) {
|
|
264
276
|
return state.cacheName;
|
|
265
277
|
}
|
|
266
|
-
if (!apiKey) return null;
|
|
278
|
+
if (!apiKey || geminiExplicitCacheRefused(model)) return null;
|
|
267
279
|
const { canAttachState } = decision;
|
|
268
280
|
if (geminiPrefixBelowMinimum({ model, systemInstruction, geminiTools, contents, opts, currentIter })) {
|
|
269
281
|
return canAttachState ? state.cacheName : null;
|
|
@@ -443,6 +455,7 @@ export class GeminiProvider {
|
|
|
443
455
|
_setGeminiGlobalCache(globalCacheKey, entry);
|
|
444
456
|
return entry;
|
|
445
457
|
} catch (err) {
|
|
458
|
+
markGeminiExplicitCacheRefused(model, err);
|
|
446
459
|
process.stderr.write(`[gemini] cachedContents.create error: ${err?.message || err}\n`);
|
|
447
460
|
return null;
|
|
448
461
|
}
|
|
@@ -574,7 +587,18 @@ export class GeminiProvider {
|
|
|
574
587
|
emitGeminiToolCalls(parsed.nativeToolCalls, callbacks.onToolCall);
|
|
575
588
|
const usage = resolveGeminiUsage(response, opts, cachedContent, useModel);
|
|
576
589
|
const incomplete = geminiIncompleteError(response, parsed, useModel, usage);
|
|
577
|
-
if (incomplete)
|
|
590
|
+
if (incomplete) {
|
|
591
|
+
// A malformed / unexpected tool call that survived the stream ladder
|
|
592
|
+
// (4 attempts) is spent; the loop must not multiply that budget.
|
|
593
|
+
if (
|
|
594
|
+
geminiInvalidStreamReason(response, textLeakGuard, {
|
|
595
|
+
afterToolResult: geminiEndsWithFunctionResponse(request.contents),
|
|
596
|
+
})
|
|
597
|
+
) {
|
|
598
|
+
markProviderRecoveryExhausted(incomplete, { owner: 'gemini-stream', attempts: GEMINI_MIDSTREAM_MAX_ATTEMPTS });
|
|
599
|
+
}
|
|
600
|
+
throw incomplete;
|
|
601
|
+
}
|
|
578
602
|
return geminiSendResult(parsed, useModel, opts, usage);
|
|
579
603
|
}
|
|
580
604
|
|
|
@@ -643,12 +667,19 @@ export class GeminiProvider {
|
|
|
643
667
|
const restPassthrough = createPassthroughSignal(signal);
|
|
644
668
|
const totalSignal = restPassthrough.signal;
|
|
645
669
|
try {
|
|
646
|
-
|
|
647
|
-
|
|
670
|
+
const afterToolResult = geminiEndsWithFunctionResponse(contents);
|
|
671
|
+
return await runGeminiStreamAttempts({
|
|
672
|
+
signal: totalSignal,
|
|
673
|
+
sleepFn: this.config?.sleepFn,
|
|
674
|
+
onRetry: geminiRetryLogger(opts, '[gemini-rest]'),
|
|
675
|
+
afterToolResult,
|
|
676
|
+
run: async ({ signal: attemptSignal, connected }) => {
|
|
648
677
|
signalRequesting(opts);
|
|
649
678
|
const res = await this._openRestStream(genUrl, body, attemptSignal);
|
|
679
|
+
connected();
|
|
650
680
|
const textLeakGuard = geminiTextLeakGuardFor(stream);
|
|
651
681
|
const response = await consumeGeminiRestStreamResponse(res, {
|
|
682
|
+
acceptEmptyNoFinish: afterToolResult,
|
|
652
683
|
signal: attemptSignal,
|
|
653
684
|
onStreamDelta: callbacks.onStreamDelta,
|
|
654
685
|
onTextDelta: callbacks.onTextDelta,
|
|
@@ -658,8 +689,7 @@ export class GeminiProvider {
|
|
|
658
689
|
});
|
|
659
690
|
return { response, textLeakGuard };
|
|
660
691
|
},
|
|
661
|
-
|
|
662
|
-
);
|
|
692
|
+
});
|
|
663
693
|
} catch (err) {
|
|
664
694
|
if (
|
|
665
695
|
!internal.skipExplicitCache &&
|
|
@@ -698,13 +728,16 @@ export class GeminiProvider {
|
|
|
698
728
|
const sdkPassthrough = createPassthroughSignal(signal);
|
|
699
729
|
const totalSignal = sdkPassthrough.signal;
|
|
700
730
|
try {
|
|
701
|
-
return await
|
|
702
|
-
|
|
731
|
+
return await runGeminiStreamAttempts({
|
|
732
|
+
signal: totalSignal,
|
|
733
|
+
sleepFn: this.config?.sleepFn,
|
|
734
|
+
onRetry: geminiRetryLogger(opts, '[gemini]'),
|
|
735
|
+
afterToolResult: geminiEndsWithFunctionResponse(contents),
|
|
736
|
+
run: async ({ signal: attemptSignal, connected }) => {
|
|
703
737
|
signalRequesting(opts);
|
|
704
|
-
return await streamGeminiSdkAttempt(genModel, stream, attemptSignal);
|
|
738
|
+
return await streamGeminiSdkAttempt(genModel, stream, attemptSignal, connected);
|
|
705
739
|
},
|
|
706
|
-
|
|
707
|
-
);
|
|
740
|
+
});
|
|
708
741
|
} catch (err) {
|
|
709
742
|
const recovered = await this._recoverGeminiNonStreaming({
|
|
710
743
|
streamErr: err,
|
|
@@ -333,6 +333,9 @@ async function _postRefresh(tokens) {
|
|
|
333
333
|
isTerminalRefresh,
|
|
334
334
|
reauthRequired: isTerminalRefresh,
|
|
335
335
|
oauthError: oauthError || null,
|
|
336
|
+
status: res.status,
|
|
337
|
+
headers: res.headers,
|
|
338
|
+
...(oauthError ? { code: oauthError } : {}),
|
|
336
339
|
});
|
|
337
340
|
}
|
|
338
341
|
const accessToken = json?.access_token;
|
|
@@ -18,6 +18,8 @@ import { enrichModels, getModelMetadataSync } from './model-catalog.mjs';
|
|
|
18
18
|
import { sanitizeModelList } from './model-list-sanitize.mjs';
|
|
19
19
|
import { makeModelCache } from './model-cache.mjs';
|
|
20
20
|
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
21
|
+
import { xaiModelSupportsReasoningEffort } from './openai-compat-xai.mjs';
|
|
22
|
+
import { XAI_COMPAT_RETRY_POLICY } from './openai-compat-retry-profile.mjs';
|
|
21
23
|
import { createTimeoutSignal } from '../stall-policy.mjs';
|
|
22
24
|
import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
23
25
|
import { normalizeGrokToolSchemas } from './lib/grok-tool-schema.mjs';
|
|
@@ -48,19 +50,44 @@ export {
|
|
|
48
50
|
} from './grok-oauth-tokens.mjs';
|
|
49
51
|
export { beginOAuthLogin, loginOAuth } from './grok-oauth-login.mjs';
|
|
50
52
|
|
|
53
|
+
// Typed wire codes that say the bearer itself expired or was revoked. An
|
|
54
|
+
// ordinary permission 403 carries none of them and is never a refresh signal.
|
|
55
|
+
const EXPIRED_TOKEN_CODES = new Set([
|
|
56
|
+
'token_expired',
|
|
57
|
+
'expired_token',
|
|
58
|
+
'access_token_expired',
|
|
59
|
+
'token_revoked',
|
|
60
|
+
'revoked_token',
|
|
61
|
+
'invalid_token',
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
function hasExpiredTokenSignature(err) {
|
|
65
|
+
return [err?.code, err?.type, err?.providerErrorCode, err?.error?.code, err?.error?.type].some(
|
|
66
|
+
(field) => typeof field === 'string' && EXPIRED_TOKEN_CODES.has(field.trim().toLowerCase())
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
const _modelCache = makeModelCache({
|
|
52
71
|
fileName: 'grok-oauth-models.json',
|
|
53
72
|
ttlMs: MODEL_CACHE_TTL_MS,
|
|
54
73
|
version: GROK_MODEL_CACHE_SCHEMA_VERSION,
|
|
55
74
|
});
|
|
56
75
|
|
|
57
|
-
|
|
76
|
+
// A model takes a reasoning effort when its own roster row or the catalog
|
|
77
|
+
// publishes effort values; otherwise the shared xAI rule decides (catalog row
|
|
78
|
+
// first, then pass-through). Two id tokens are kept: xAI's own
|
|
79
|
+
// `non-reasoning` variant marker, and the Build endpoint family. Neither
|
|
80
|
+
// publishes capabilities, and both 400 on any effort.
|
|
81
|
+
function _grokModelSupportsEffort(id, model = null) {
|
|
58
82
|
const text = String(id || '').toLowerCase();
|
|
59
83
|
if (!text) return false;
|
|
60
84
|
if (NON_CHAT_MODEL_RE.test(text)) return false;
|
|
61
|
-
if (
|
|
62
|
-
if (
|
|
63
|
-
|
|
85
|
+
if (_grokCatalogEffortValues(model).length || _grokApiEffortValues(model).length) return true;
|
|
86
|
+
if (/non[-_]?reasoning/.test(text)) return false;
|
|
87
|
+
// Build endpoint models reject reasoning_effort outright; a catalog or
|
|
88
|
+
// roster that publishes effort values has already answered above.
|
|
89
|
+
if (/^grok[-_]build/.test(text)) return false;
|
|
90
|
+
return xaiModelSupportsReasoningEffort(text);
|
|
64
91
|
}
|
|
65
92
|
|
|
66
93
|
// Pre-catalog fallback matches Grok 4.5 (low/medium/high). Grok 4.6 adds
|
|
@@ -86,7 +113,8 @@ function _grokApiEffortValues(model) {
|
|
|
86
113
|
model?.reasoningEfforts ??
|
|
87
114
|
model?.reasoning_efforts ??
|
|
88
115
|
model?._meta?.reasoningEfforts ??
|
|
89
|
-
model?._meta?.reasoning_efforts
|
|
116
|
+
model?._meta?.reasoning_efforts ??
|
|
117
|
+
model?.capabilities?.reasoning_effort;
|
|
90
118
|
return Array.isArray(raw) ? _uniqueKnownGrokEfforts(raw) : [];
|
|
91
119
|
}
|
|
92
120
|
|
|
@@ -103,7 +131,7 @@ function _grokCatalogEffortValues(model) {
|
|
|
103
131
|
}
|
|
104
132
|
|
|
105
133
|
function _grokReasoningLevels(id, model) {
|
|
106
|
-
if (!_grokModelSupportsEffort(id)) return [];
|
|
134
|
+
if (!_grokModelSupportsEffort(id, model)) return [];
|
|
107
135
|
const catalog = _grokCatalogEffortValues(model);
|
|
108
136
|
if (catalog.length) return catalog;
|
|
109
137
|
const api = _grokApiEffortValues(model);
|
|
@@ -144,13 +172,14 @@ function _normalizeGrokModel(m) {
|
|
|
144
172
|
const contextWindow = _grokApiContextWindow(m);
|
|
145
173
|
const apiEfforts = _grokApiEffortValues(m);
|
|
146
174
|
const reasoningOptions = apiEfforts.length ? [{ type: 'effort', values: apiEfforts }] : undefined;
|
|
175
|
+
const row = { ...m, reasoningOptions };
|
|
147
176
|
return {
|
|
148
177
|
id,
|
|
149
178
|
name: id,
|
|
150
179
|
display: _displayGrokModel(m),
|
|
151
180
|
provider: 'grok-oauth',
|
|
152
|
-
family: _normalizeGrokFamily(id),
|
|
153
|
-
reasoningLevels: _grokReasoningLevels(id,
|
|
181
|
+
family: _normalizeGrokFamily(id, row),
|
|
182
|
+
reasoningLevels: _grokReasoningLevels(id, row),
|
|
154
183
|
...(reasoningOptions ? { reasoningOptions } : {}),
|
|
155
184
|
tier: 'version',
|
|
156
185
|
latest: false,
|
|
@@ -165,7 +194,7 @@ function _sanitizeGrokModels(models) {
|
|
|
165
194
|
const next = models.map((m) => {
|
|
166
195
|
let out = m;
|
|
167
196
|
const display = _displayGrokModel(m);
|
|
168
|
-
const family = _normalizeGrokFamily(m?.id);
|
|
197
|
+
const family = _normalizeGrokFamily(m?.id, out);
|
|
169
198
|
const reasoningLevels = _grokReasoningLevels(m?.id, out);
|
|
170
199
|
// api.x.ai /models does not expose a max-output field. Historical
|
|
171
200
|
// enrichment could persist contextWindow as outputTokens (for example
|
|
@@ -248,6 +277,15 @@ function resolveLatestGrokModel() {
|
|
|
248
277
|
return _newestDatedGrokChatModel(cached)?.id || null;
|
|
249
278
|
}
|
|
250
279
|
|
|
280
|
+
// Whether a request for this model may carry an effort: the levels its cached
|
|
281
|
+
// roster row already resolved from the roster and catalog answer, else the id
|
|
282
|
+
// alone follows the catalog rule.
|
|
283
|
+
function _grokSendSupportsEffort(id) {
|
|
284
|
+
const cached = _modelCache.loadSync();
|
|
285
|
+
const row = Array.isArray(cached) ? cached.find((m) => m?.id === id) : null;
|
|
286
|
+
return Array.isArray(row?.reasoningLevels) ? row.reasoningLevels.length > 0 : _grokModelSupportsEffort(id);
|
|
287
|
+
}
|
|
288
|
+
|
|
251
289
|
async function ensureLatestGrokModel(provider) {
|
|
252
290
|
let m = resolveLatestGrokModel();
|
|
253
291
|
if (m) return m;
|
|
@@ -265,6 +303,8 @@ export class GrokOAuthProvider {
|
|
|
265
303
|
// OpenAI-compatible usage: prompt_tokens includes cached. See registry.mjs.
|
|
266
304
|
static inputExcludesCache = false;
|
|
267
305
|
name = 'grok-oauth';
|
|
306
|
+
// The inner xai profile's limits are final: no loop-level recovery cycles on top.
|
|
307
|
+
retryPolicy = XAI_COMPAT_RETRY_POLICY;
|
|
268
308
|
config;
|
|
269
309
|
tokens = null;
|
|
270
310
|
_inner = null;
|
|
@@ -395,11 +435,12 @@ export class GrokOAuthProvider {
|
|
|
395
435
|
});
|
|
396
436
|
const inner = this._ensureInner(tokens.access_token, useModel, requestHeaders);
|
|
397
437
|
const grokTools = normalizeGrokToolSchemas(tools);
|
|
398
|
-
// Effort-less models
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
|
|
438
|
+
// Effort-less models: the service 400s on ANY reasoningEffort parameter,
|
|
439
|
+
// and pinned model settings can carry one (e.g. the bench harness pins
|
|
440
|
+
// effort per route). Strip it here so a saved effort never breaks a model
|
|
441
|
+
// that cannot accept it. The model's own roster row decides; a model not
|
|
442
|
+
// on the roster yet follows the catalog rule.
|
|
443
|
+
if (sendOpts && !_grokSendSupportsEffort(useModel)) {
|
|
403
444
|
// 'none' (not null) so the ?? fallback chain in the xai builder
|
|
404
445
|
// cannot fall through to a config/env effort; the normalizer maps
|
|
405
446
|
// it to "omit the parameter".
|
|
@@ -414,9 +455,10 @@ export class GrokOAuthProvider {
|
|
|
414
455
|
return await inner._doSend(messages, useModel, grokTools, sendOpts);
|
|
415
456
|
} catch (err) {
|
|
416
457
|
noteErrorUsage(err);
|
|
417
|
-
// Refresh-and-retry on a server-rejected OAuth session
|
|
418
|
-
//
|
|
419
|
-
// text is never
|
|
458
|
+
// Refresh-and-retry on a server-rejected OAuth session: 401, or a 403
|
|
459
|
+
// carrying an explicit expired/revoked-token code. TYPED evidence only:
|
|
460
|
+
// message text is never an auth verdict, and a plain permission 403 is
|
|
461
|
+
// surfaced as-is.
|
|
420
462
|
const rejectedStatus = Number(err?.httpStatus || err?.status);
|
|
421
463
|
// 426 = client version gate. Retry once only when the body stated a
|
|
422
464
|
// minimum we did not already satisfy; nothing was emitted before it.
|
|
@@ -434,7 +476,7 @@ export class GrokOAuthProvider {
|
|
|
434
476
|
sendOpts
|
|
435
477
|
);
|
|
436
478
|
}
|
|
437
|
-
if (rejectedStatus === 401 || rejectedStatus === 403) {
|
|
479
|
+
if (rejectedStatus === 401 || (rejectedStatus === 403 && hasExpiredTokenSignature(err))) {
|
|
438
480
|
// A stream-level rejection after text/tool dispatch cannot be safely
|
|
439
481
|
// replayed: the client has already observed output or may have
|
|
440
482
|
// executed a side-effecting tool.
|
|
@@ -444,7 +486,14 @@ export class GrokOAuthProvider {
|
|
|
444
486
|
process.stderr.write(`[grok-oauth] ${rejectedStatus}, force-refreshing token...\n`);
|
|
445
487
|
const fresh = await this.ensureAuth({ forceRefresh: true });
|
|
446
488
|
const retryInner = this._ensureInner(fresh.access_token, useModel, requestHeaders);
|
|
447
|
-
|
|
489
|
+
try {
|
|
490
|
+
return await retryInner._doSend(messages, useModel, grokTools, sendOpts);
|
|
491
|
+
} catch (retryErr) {
|
|
492
|
+
// Rejected again on a token that was just refreshed: re-login is the
|
|
493
|
+
// only remaining path.
|
|
494
|
+
if (Number(retryErr?.httpStatus || retryErr?.status) === 401) retryErr.reauthRequired = true;
|
|
495
|
+
throw retryErr;
|
|
496
|
+
}
|
|
448
497
|
}
|
|
449
498
|
throw err;
|
|
450
499
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
10
10
|
import { fileMtimeMs, scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
|
|
11
11
|
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
12
|
+
import { COMPAT_RETRY_POLICY } from './openai-compat-retry-profile.mjs';
|
|
12
13
|
import { fetchCompatModelItems, listCompatModels } from './openai-compat-models.mjs';
|
|
13
14
|
import { makeModelCache } from './model-cache.mjs';
|
|
14
15
|
import {
|
|
@@ -114,6 +115,8 @@ export class MimoOAuthProvider {
|
|
|
114
115
|
// OpenAI-compatible usage: prompt_tokens includes cached. See registry.mjs.
|
|
115
116
|
static inputExcludesCache = false;
|
|
116
117
|
name = 'mimo-oauth';
|
|
118
|
+
// The retry profile's limits are final: no loop-level recovery cycles on top.
|
|
119
|
+
retryPolicy = COMPAT_RETRY_POLICY;
|
|
117
120
|
config;
|
|
118
121
|
tokens = null;
|
|
119
122
|
_inner = null;
|
|
@@ -208,7 +208,8 @@ function _stalenessFamily(row, id) {
|
|
|
208
208
|
const modelId = String(id || row?.id || row?.name || '')
|
|
209
209
|
.trim()
|
|
210
210
|
.toLowerCase();
|
|
211
|
-
|
|
211
|
+
// Claude ids name their family right after the brand (`claude-<family>-<version>`).
|
|
212
|
+
const claude = modelId.match(/(?:^|\/)claude-([a-z]+)(?:-|$)/);
|
|
212
213
|
if (claude) return `claude-${claude[1]}`;
|
|
213
214
|
const family = row && typeof row.family === 'string' ? row.family.trim().toLowerCase() : '';
|
|
214
215
|
return family || null;
|
|
@@ -10,8 +10,10 @@ import { createHash } from 'node:crypto';
|
|
|
10
10
|
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
11
11
|
import { fileMtimeMs } from './lib/oauth-token-utils.mjs';
|
|
12
12
|
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
13
|
+
import { COMPAT_RETRY_POLICY } from './openai-compat-retry-profile.mjs';
|
|
13
14
|
import { getModelMetadataSync } from './model-catalog.mjs';
|
|
14
15
|
import { makeModelCache } from './model-cache.mjs';
|
|
16
|
+
import { normalizeCacheKey } from '../agent-runtime/cache-strategy.mjs';
|
|
15
17
|
import { PRICING_RATE_KEYS } from '../../../shared/llm/model-pricing-rates.mjs';
|
|
16
18
|
import {
|
|
17
19
|
MUSE_DEFAULT_EFFORT_LEVELS as FIVE_LEVELS,
|
|
@@ -100,15 +102,6 @@ function museRow(id, discovered = null) {
|
|
|
100
102
|
const EFFORT_DOWNGRADE = { max: 'xhigh' };
|
|
101
103
|
const SUMMARY_MODES = new Set(['auto', 'detailed', 'concise']);
|
|
102
104
|
|
|
103
|
-
// Cache identities beyond the wire limit become a bounded hash identity.
|
|
104
|
-
function normalizeCacheKey(value) {
|
|
105
|
-
const text = String(value ?? '').trim();
|
|
106
|
-
if (!text) return '';
|
|
107
|
-
const wellFormed = text.toWellFormed();
|
|
108
|
-
if (wellFormed.length <= 64) return wellFormed;
|
|
109
|
-
return `pc_${createHash('sha256').update(wellFormed).digest('hex').slice(0, 32)}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
105
|
// Final request shaping on the Responses wire (installed through the shared
|
|
113
106
|
// transport's opt-in params hook).
|
|
114
107
|
function shapeResponsesParams(params, { opts, modelInfo }, config = {}) {
|
|
@@ -321,6 +314,8 @@ export class MuseCodeOAuthProvider {
|
|
|
321
314
|
nativePdf = false;
|
|
322
315
|
static inputExcludesCache = false;
|
|
323
316
|
name = 'muse-code-oauth';
|
|
317
|
+
// The retry profile's limits are final: no loop-level recovery cycles on top.
|
|
318
|
+
retryPolicy = COMPAT_RETRY_POLICY;
|
|
324
319
|
config;
|
|
325
320
|
tokens = null;
|
|
326
321
|
_inner = null;
|
|
@@ -21,6 +21,7 @@ import { normalizeCompatChatResponse } from './openai-compat-response-normalizat
|
|
|
21
21
|
import { applyCompatToolChoice, compatStreamRetryReporter } from './compat-request-policy.mjs';
|
|
22
22
|
import { ensureChatToolPairs } from './lib/wire-pairing.mjs';
|
|
23
23
|
import { xaiCacheRouting } from './openai-compat-xai.mjs';
|
|
24
|
+
import { compatRetryOptions, withCompatRetryProfile } from './openai-compat-retry-profile.mjs';
|
|
24
25
|
import { preparePdfTextForProvider } from './media-normalization.mjs';
|
|
25
26
|
import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
|
|
26
27
|
|
|
@@ -85,7 +86,7 @@ export async function sendCompatChat(provider, messages, useModel, tools, opts)
|
|
|
85
86
|
try {
|
|
86
87
|
try {
|
|
87
88
|
assembled = await withRetry(
|
|
88
|
-
refineAttemptErrors(profile, async ({ signal: attemptSignal }) => {
|
|
89
|
+
withCompatRetryProfile(provider.name, refineAttemptErrors(profile, async ({ signal: attemptSignal }) => {
|
|
89
90
|
try {
|
|
90
91
|
opts.onStageChange?.('requesting');
|
|
91
92
|
} catch {
|
|
@@ -139,11 +140,12 @@ export async function sendCompatChat(provider, messages, useModel, tools, opts)
|
|
|
139
140
|
}
|
|
140
141
|
throw error;
|
|
141
142
|
}
|
|
142
|
-
}),
|
|
143
|
+
})),
|
|
143
144
|
{
|
|
144
145
|
signal: totalSignal.signal,
|
|
145
146
|
onRetry: compatStreamRetryReporter(provider.name, opts),
|
|
146
147
|
retry529: opts.retry529,
|
|
148
|
+
...compatRetryOptions(provider.name),
|
|
147
149
|
}
|
|
148
150
|
);
|
|
149
151
|
} catch (streamErr) {
|
package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { truncatedCompatStreamError } from '../lib/openai-tool-args.mjs';
|
|
|
8
8
|
import {
|
|
9
9
|
emitCompatToolCallOnce,
|
|
10
10
|
firstByteCompatStreamError,
|
|
11
|
+
failingFinishReasonError,
|
|
11
12
|
markErrorLiveTextEmitted,
|
|
12
13
|
markUnsafeRetryIfToolEmitted,
|
|
13
14
|
} from '../openai-compat-stream-common.mjs';
|
|
@@ -61,7 +62,28 @@ function parseAccumulatedToolCalls(state, { label, parseToolCalls }) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
// A gateway-reported failing finish_reason (`network_error`, `error`) is an
|
|
66
|
+
// interruption: the stream carries no terminal, so exposure keeps its replay
|
|
67
|
+
// gates.
|
|
68
|
+
function finishReasonInterruption(state, err) {
|
|
69
|
+
attachPartial(state, err);
|
|
70
|
+
if (state.emittedText) {
|
|
71
|
+
markErrorLiveTextEmitted(err);
|
|
72
|
+
try {
|
|
73
|
+
err.streamStalled = true;
|
|
74
|
+
} catch {
|
|
75
|
+
/* best-effort */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return stampCompatOutcome(state, markUnsafeRetryIfToolEmitted(err, state.streamEmitState), {
|
|
79
|
+
terminalObserved: false,
|
|
80
|
+
continuation: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
export function settleCompatStream(state, { label, parseToolCalls }) {
|
|
85
|
+
const failing = failingFinishReasonError(state.stopReason, label);
|
|
86
|
+
if (failing) throw finishReasonInterruption(state, failing);
|
|
65
87
|
const response = buildResponse(state);
|
|
66
88
|
let toolCalls = parseAccumulatedToolCalls(state, { label, parseToolCalls });
|
|
67
89
|
if (Array.isArray(toolCalls) && toolCalls.length) {
|
|
@@ -71,6 +71,7 @@ export async function consumeCompatChatCompletionStream(
|
|
|
71
71
|
idleDeadlineAt: state.semanticIdleDeadlineAt,
|
|
72
72
|
idleEnabled: state.sawFirstEvent && idleEnabled && state.semanticIdleDeadlineAt > 0,
|
|
73
73
|
idleLabel: `${label} SSE idle`,
|
|
74
|
+
streamLabel: label,
|
|
74
75
|
emittedToolCall: () => toolWorkStarted(state),
|
|
75
76
|
});
|
|
76
77
|
if (done) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* openai-compat-error-contract.mjs — the error contract of the OpenAI-compatible
|
|
3
|
+
* provider boundary: `status`, lowercased `code` (from the wire error's `code`
|
|
4
|
+
* or `type`), `headers`, `retryAfterMs` and `resetAt` when the provider sent
|
|
5
|
+
* them. Evidence is structural only; message text is never parsed.
|
|
6
|
+
*/
|
|
7
|
+
import { retryAfterMsFromError, typedStatusFrom } from './retry-classifier.mjs';
|
|
8
|
+
|
|
9
|
+
const lowered = (value) => (typeof value === 'string' && value.trim() ? value.trim().toLowerCase() : '');
|
|
10
|
+
|
|
11
|
+
// Epoch seconds, epoch ms or an ISO date → epoch ms; `resets_in_seconds` is relative.
|
|
12
|
+
function resetAtFrom(wire, now) {
|
|
13
|
+
const inSeconds = Number(wire?.resets_in_seconds);
|
|
14
|
+
if (wire?.resets_in_seconds != null && Number.isFinite(inSeconds) && inSeconds >= 0) {
|
|
15
|
+
return now + Math.ceil(inSeconds * 1000);
|
|
16
|
+
}
|
|
17
|
+
const raw = wire?.resets_at ?? wire?.reset_at;
|
|
18
|
+
if (raw == null || raw === '') return null;
|
|
19
|
+
const numeric = Number(raw);
|
|
20
|
+
if (Number.isFinite(numeric) && numeric > 0) return numeric < 1e12 ? Math.round(numeric * 1000) : Math.round(numeric);
|
|
21
|
+
const parsed = Date.parse(String(raw));
|
|
22
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Stamp the boundary contract onto a provider error in place and return it. */
|
|
26
|
+
export function applyCompatErrorContract(err, detail = err?.error ?? err?.providerError, now = Date.now()) {
|
|
27
|
+
if (!err || typeof err !== 'object') return err;
|
|
28
|
+
try {
|
|
29
|
+
const wire = detail && typeof detail === 'object' ? detail : null;
|
|
30
|
+
const status = typedStatusFrom(err, err.response, wire);
|
|
31
|
+
if (status && !(Number(err.status) >= 100)) err.status = status;
|
|
32
|
+
const wireCode = lowered(wire?.code) || lowered(wire?.type) || lowered(err.type);
|
|
33
|
+
// Keep an errno / SDK-local code (ECONNRESET, ...) that is not the wire code.
|
|
34
|
+
if (wireCode && (typeof err.code !== 'string' || !err.code.trim() || lowered(err.code) === wireCode)) {
|
|
35
|
+
err.code = wireCode;
|
|
36
|
+
}
|
|
37
|
+
if (!err.headers && err.response?.headers) err.headers = err.response.headers;
|
|
38
|
+
if (!Number.isFinite(err.retryAfterMs)) {
|
|
39
|
+
const retryAfterMs = retryAfterMsFromError(err);
|
|
40
|
+
if (retryAfterMs != null) err.retryAfterMs = retryAfterMs;
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isFinite(err.resetAt)) {
|
|
43
|
+
const resetAt = resetAtFrom(wire, now);
|
|
44
|
+
if (resetAt) err.resetAt = resetAt;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
/* the contract is best-effort; the original error stays authoritative */
|
|
48
|
+
}
|
|
49
|
+
return err;
|
|
50
|
+
}
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
writeXaiResponsesCacheTrace,
|
|
8
8
|
} from './openai-compat-xai.mjs';
|
|
9
9
|
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
10
|
+
import { stampStreamOutcome, STREAM_TRANSPORTS } from './lib/stream-outcome.mjs';
|
|
11
|
+
import { failingFinishReasonError } from './openai-compat-stream-common.mjs';
|
|
10
12
|
|
|
11
13
|
const reportedNumber = (value) =>
|
|
12
14
|
value == null || (typeof value === 'string' && value.trim() === '') ? Number.NaN : Number(value);
|
|
@@ -141,6 +143,27 @@ export function normalizeCompatChatResponse({
|
|
|
141
143
|
const choice = response.choices[0];
|
|
142
144
|
const toolCalls = assembled.toolCalls;
|
|
143
145
|
const stopReason = choice?.finish_reason || null;
|
|
146
|
+
const failingFinish = failingFinishReasonError(stopReason, providerName);
|
|
147
|
+
if (failingFinish) {
|
|
148
|
+
const err = failingFinish;
|
|
149
|
+
const partialContent = choice?.message?.content || '';
|
|
150
|
+
Object.assign(err, {
|
|
151
|
+
partialContent,
|
|
152
|
+
partialToolCalls: Array.isArray(toolCalls) && toolCalls.length ? toolCalls : undefined,
|
|
153
|
+
pendingToolUse: false,
|
|
154
|
+
partialModel: response.model || useModel,
|
|
155
|
+
...(response.usage ? { partialUsage: chatCompletionUsage(providerName, response.usage) } : {}),
|
|
156
|
+
});
|
|
157
|
+
stampStreamOutcome(err, {
|
|
158
|
+
transport: STREAM_TRANSPORTS.NON_STREAMING,
|
|
159
|
+
provider: 'openai-compat',
|
|
160
|
+
terminalObserved: false,
|
|
161
|
+
continuation: true,
|
|
162
|
+
textObservedChars: partialContent.length,
|
|
163
|
+
toolCallsComplete: err.partialToolCalls?.length || 0,
|
|
164
|
+
});
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
144
167
|
if (
|
|
145
168
|
(stopReason === 'length' && Array.isArray(toolCalls) && toolCalls.length > 0) ||
|
|
146
169
|
stopReason === 'content_filter'
|