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
|
@@ -14,13 +14,16 @@ import {
|
|
|
14
14
|
ANTHROPIC_RETRY_JITTER_RATIO,
|
|
15
15
|
anthropicMaxAttempts,
|
|
16
16
|
anthropicRequestTimeoutMs,
|
|
17
|
+
AnthropicFallbackTriggeredError,
|
|
17
18
|
emitProviderRetryStage,
|
|
18
19
|
withRetry,
|
|
19
|
-
retryAfterMsFromError,
|
|
20
20
|
retryDelayLabel,
|
|
21
21
|
} from './retry-classifier.mjs';
|
|
22
|
+
import { ANTHROPIC_MAX_CONSECUTIVE_529 } from './retry-backoff.mjs';
|
|
22
23
|
import { PROVIDER_FIRST_BYTE_TIMEOUT_MS, createTimeoutSignal } from '../stall-policy.mjs';
|
|
23
|
-
import { noteFastModeCapacityError } from './anthropic-fast-mode.mjs';
|
|
24
|
+
import { fastModeDowngraded, noteFastModeCapacityError } from './anthropic-fast-mode.mjs';
|
|
25
|
+
import { adjustMaxTokensForContextOverflow } from './anthropic-max-tokens-recovery.mjs';
|
|
26
|
+
import { applyAnthropicErrorContract, retryAfterMsFromHeaders } from './anthropic-error-contract.mjs';
|
|
24
27
|
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
25
28
|
import { notifyCurrentAnthropicRateLimit } from './admission-scheduler.mjs';
|
|
26
29
|
|
|
@@ -78,12 +81,61 @@ export function armFirstByteWatchdog(streamController, midState) {
|
|
|
78
81
|
* @param {object} deps.params the streaming request body (mutated on fast-pool de-latch)
|
|
79
82
|
* @param {object|null} deps.requestHeaders per-call beta/extra headers
|
|
80
83
|
* @param {AbortSignal|null} deps.totalSignal session-lifetime pass-through
|
|
84
|
+
* @param {{ state: { consecutive529Errors: number } }|null} [deps.overload] the send's shared overload counter
|
|
81
85
|
*/
|
|
82
|
-
export function createAnthropicApiTransport({
|
|
86
|
+
export function createAnthropicApiTransport({
|
|
87
|
+
client,
|
|
88
|
+
label,
|
|
89
|
+
opts,
|
|
90
|
+
useModel,
|
|
91
|
+
params,
|
|
92
|
+
requestHeaders,
|
|
93
|
+
totalSignal,
|
|
94
|
+
overload = null,
|
|
95
|
+
}) {
|
|
83
96
|
const requestOptions = (attemptSignal) => ({
|
|
84
97
|
signal: attemptSignal,
|
|
85
98
|
...(requestHeaders ? { headers: requestHeaders } : {}),
|
|
86
99
|
});
|
|
100
|
+
// The SDK throws a non-2xx before `.asResponse()` resolves, so an HTTP
|
|
101
|
+
// failure arrives as an APIError rather than a Response. Stamp the
|
|
102
|
+
// provider-boundary error contract on it (status, lowercased error type as
|
|
103
|
+
// `code`, preserved headers, uncapped retryAfterMs / resetAt).
|
|
104
|
+
const annotateHttpFailure = (err) => {
|
|
105
|
+
if (err && typeof err === 'object' && Number(err.status || err.httpStatus || 0) > 0) {
|
|
106
|
+
applyAnthropicErrorContract(err, { status: err.status || err.httpStatus, headers: err.headers });
|
|
107
|
+
}
|
|
108
|
+
return err;
|
|
109
|
+
};
|
|
110
|
+
// A fast-pool refusal (or a "Fast mode is not enabled" 400) says nothing
|
|
111
|
+
// about standard-speed capacity: drop `speed` and let the caller replay at
|
|
112
|
+
// once instead of waiting out a window that only the fast pool owns.
|
|
113
|
+
const dropFastSpeedAfter = (body, err) => {
|
|
114
|
+
if (body.speed !== 'fast') return false;
|
|
115
|
+
if (!fastModeDowngraded(noteFastModeCapacityError(err, { fast: true }))) return false;
|
|
116
|
+
delete body.speed;
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
// In-attempt body repairs, each replayed once: drop speed after a fast-pool
|
|
120
|
+
// refusal, and shrink max_tokens after the "input length and max_tokens
|
|
121
|
+
// exceed context limit" 400.
|
|
122
|
+
const withBodyRecovery = (body, attempt) => async (ctx) => {
|
|
123
|
+
let speedDropped = false;
|
|
124
|
+
let tokensAdjusted = false;
|
|
125
|
+
for (;;) {
|
|
126
|
+
try {
|
|
127
|
+
return await attempt(ctx);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
if (!speedDropped && dropFastSpeedAfter(body, err)) {
|
|
130
|
+
speedDropped = true;
|
|
131
|
+
} else if (!tokensAdjusted && adjustMaxTokensForContextOverflow(body, err?.status, err?.message)) {
|
|
132
|
+
tokensAdjusted = true;
|
|
133
|
+
} else {
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
87
139
|
// Initial-response retry policy shared by the streaming request and the
|
|
88
140
|
// non-streaming fallback.
|
|
89
141
|
const retryPolicy = (signal, perAttemptLabel, recoveryOwner) => ({
|
|
@@ -95,23 +147,27 @@ export function createAnthropicApiTransport({ client, label, opts, useModel, par
|
|
|
95
147
|
perAttemptTimeoutMs: anthropicRequestTimeoutMs(),
|
|
96
148
|
perAttemptLabel,
|
|
97
149
|
provider: 'anthropic',
|
|
150
|
+
// This transport honors the positive x-should-retry override.
|
|
151
|
+
honorShouldRetryTrue: true,
|
|
98
152
|
recoveryOwner,
|
|
99
153
|
retry529: opts.retry529,
|
|
100
154
|
model: useModel,
|
|
101
155
|
fallbackModel: opts._fallbackTriggered ? undefined : opts.fallbackModel,
|
|
156
|
+
// One overload counter for the send: SSE overloads count toward the same
|
|
157
|
+
// limit as request-loop 529s.
|
|
158
|
+
overloadState: overload?.state,
|
|
159
|
+
overloadFallbackAfter: ANTHROPIC_MAX_CONSECUTIVE_529,
|
|
160
|
+
stopAfterRepeated529: overload?.stopAfterRepeated529 === true,
|
|
102
161
|
});
|
|
103
162
|
|
|
104
|
-
const requestStreamingResponse = () =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
163
|
+
const requestStreamingResponse = async () => {
|
|
164
|
+
const attempt = async ({ signal: attemptSignal }) => {
|
|
165
|
+
noteRequestServiceTier(params.speed);
|
|
166
|
+
try {
|
|
108
167
|
const res = await client.messages.create(params, requestOptions(attemptSignal)).asResponse();
|
|
109
168
|
if (!res.ok) {
|
|
110
169
|
const text = await res.text().catch(() => '');
|
|
111
170
|
const err = new Error(`Anthropic API ${res.status}: ${text.slice(0, 200)}`);
|
|
112
|
-
err.status = res.status;
|
|
113
|
-
err.httpStatus = res.status;
|
|
114
|
-
err.initialResponseError = true;
|
|
115
171
|
// Carry response headers so withRetry can honor a
|
|
116
172
|
// short Retry-After and upstream can read quota hints.
|
|
117
173
|
err.headers = res.headers;
|
|
@@ -120,48 +176,55 @@ export function createAnthropicApiTransport({ client, label, opts, useModel, par
|
|
|
120
176
|
// output/tool exposure, so the request-local
|
|
121
177
|
// withRetry loop may retry it with jitter.
|
|
122
178
|
if (res.status === 429) {
|
|
123
|
-
const retryAfterMs = retryAfterMsFromError({
|
|
124
|
-
headers: res.headers,
|
|
125
|
-
response: { headers: res.headers },
|
|
126
|
-
});
|
|
127
179
|
err.name = 'ProviderQuotaError';
|
|
128
|
-
err.
|
|
129
|
-
err.retryAfterMs = retryAfterMs;
|
|
180
|
+
err.retryAfterMs = retryAfterMsFromHeaders(res.headers);
|
|
130
181
|
err.providerQuota = true;
|
|
131
182
|
err.quotaExceeded = true;
|
|
132
183
|
}
|
|
133
|
-
throw err;
|
|
184
|
+
throw applyAnthropicErrorContract(err, { status: res.status, headers: res.headers, bodyText: text });
|
|
134
185
|
}
|
|
135
186
|
if (!res.body) {
|
|
136
187
|
throw new Error('Anthropic streaming response has no body');
|
|
137
188
|
}
|
|
138
189
|
return res;
|
|
139
|
-
}
|
|
140
|
-
|
|
190
|
+
} catch (err) {
|
|
191
|
+
throw annotateHttpFailure(err);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
try {
|
|
195
|
+
return await withRetry(withBodyRecovery(params, attempt), {
|
|
141
196
|
...retryPolicy(totalSignal, `${label} Anthropic streaming response`, `${label}-initial-response`),
|
|
142
|
-
onRetry: ({ attempt, maxAttempts, lastErr, delayMs, delayReason }) => {
|
|
143
|
-
emitProviderRetryStage(opts.onStageChange, { attempt:
|
|
144
|
-
// Long/unknown fast-pool window: replay at
|
|
145
|
-
// standard speed rather than waiting it out.
|
|
146
|
-
if (params?.speed === 'fast' && noteFastModeCapacityError(lastErr, { fast: true }) !== 'retry-fast') {
|
|
147
|
-
delete params.speed;
|
|
148
|
-
}
|
|
197
|
+
onRetry: ({ attempt: retryNo, maxAttempts, lastErr, delayMs, delayReason }) => {
|
|
198
|
+
emitProviderRetryStage(opts.onStageChange, { attempt: retryNo + 1, maxAttempts, lastErr, delayMs });
|
|
149
199
|
const status = Number(lastErr?.httpStatus || lastErr?.status || lastErr?.response?.status || 0);
|
|
150
200
|
if (status === 429) notifyCurrentAnthropicRateLimit(lastErr);
|
|
151
201
|
const delayLabel = retryDelayLabel(delayMs, delayReason);
|
|
152
202
|
process.stderr.write(
|
|
153
|
-
`[${label}] retry attempt ${
|
|
203
|
+
`[${label}] retry attempt ${retryNo + 1} after ${lastErr?.message || lastErr?.code || 'transient error'}${delayLabel}\n`
|
|
154
204
|
);
|
|
155
205
|
},
|
|
206
|
+
});
|
|
207
|
+
} catch (err) {
|
|
208
|
+
// Whatever escapes withRetry failed before any stream existed and has
|
|
209
|
+
// already spent the request-level budget: mark it so mid-stream
|
|
210
|
+
// recovery never grants it a second, SSE-sized retry budget.
|
|
211
|
+
if (err && typeof err === 'object' && !(err instanceof AnthropicFallbackTriggeredError)) {
|
|
212
|
+
err.initialResponseError = true;
|
|
156
213
|
}
|
|
157
|
-
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
158
217
|
|
|
159
218
|
const requestNonStreamingMessage = (nonStreamingParams, signal) =>
|
|
160
219
|
withRetry(
|
|
161
|
-
async ({ signal: attemptSignal }) => {
|
|
220
|
+
withBodyRecovery(nonStreamingParams, async ({ signal: attemptSignal }) => {
|
|
162
221
|
noteRequestServiceTier(nonStreamingParams.speed);
|
|
163
|
-
|
|
164
|
-
|
|
222
|
+
try {
|
|
223
|
+
return await client.messages.create(nonStreamingParams, requestOptions(attemptSignal));
|
|
224
|
+
} catch (err) {
|
|
225
|
+
throw annotateHttpFailure(err);
|
|
226
|
+
}
|
|
227
|
+
}),
|
|
165
228
|
{
|
|
166
229
|
...retryPolicy(signal, `${label} Anthropic non-streaming fallback`, `${label}-nonstreaming-request`),
|
|
167
230
|
onRetry: ({ attempt, maxAttempts, lastErr, delayMs }) =>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anthropic-error-contract.mjs — the fields every error thrown at an Anthropic
|
|
3
|
+
* provider boundary carries, so the shared core never has to re-derive them
|
|
4
|
+
* from message text:
|
|
5
|
+
*
|
|
6
|
+
* status / httpStatus the HTTP status
|
|
7
|
+
* code the lowercased Anthropic error type (`rate_limit_error`,
|
|
8
|
+
* `overloaded_error`, ...); a 413 is always
|
|
9
|
+
* `request_too_large` (a byte/media size refusal), which
|
|
10
|
+
* stays distinct from a 400 `invalid_request_error`
|
|
11
|
+
* "prompt is too long" (a token-count refusal)
|
|
12
|
+
* headers the response headers, preserved as received
|
|
13
|
+
* retryAfterMs the server's Retry-After / retry-after-ms window, uncapped
|
|
14
|
+
* resetAt epoch ms of anthropic-ratelimit-unified-reset, uncapped
|
|
15
|
+
*/
|
|
16
|
+
import { headerValue, retryAfterMsFromError } from './retry-classification.mjs';
|
|
17
|
+
|
|
18
|
+
const RESET_HEADER = 'anthropic-ratelimit-unified-reset';
|
|
19
|
+
const REQUEST_TOO_LARGE = 'request_too_large';
|
|
20
|
+
|
|
21
|
+
// Anthropic's documented error-type enumeration by status, used only when the
|
|
22
|
+
// body names no type of its own.
|
|
23
|
+
const STATUS_ERROR_TYPE = new Map([
|
|
24
|
+
[400, 'invalid_request_error'],
|
|
25
|
+
[401, 'authentication_error'],
|
|
26
|
+
[403, 'permission_error'],
|
|
27
|
+
[404, 'not_found_error'],
|
|
28
|
+
[413, REQUEST_TOO_LARGE],
|
|
29
|
+
[429, 'rate_limit_error'],
|
|
30
|
+
[500, 'api_error'],
|
|
31
|
+
[529, 'overloaded_error'],
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const BODY_TYPE_RE = /"error"\s*:\s*\{\s*"type"\s*:\s*"([A-Za-z_]+)"/;
|
|
35
|
+
|
|
36
|
+
function present(value) {
|
|
37
|
+
return value != null && value !== '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function lowered(value) {
|
|
41
|
+
return typeof value === 'string' && value.trim() && value.trim() !== 'error' ? value.trim().toLowerCase() : '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The lowercased error type named by an Anthropic error body (JSON text or parsed object). */
|
|
45
|
+
export function anthropicErrorTypeFromBody(body) {
|
|
46
|
+
if (body && typeof body === 'object') return lowered((body.error && body.error.type) || body.type);
|
|
47
|
+
const match = BODY_TYPE_RE.exec(String(body || ''));
|
|
48
|
+
return match ? lowered(match[1]) : '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Retry-After / retry-after-ms in ms, uncapped; null when the server sent neither. */
|
|
52
|
+
export function retryAfterMsFromHeaders(headers) {
|
|
53
|
+
if (!present(headerValue(headers, 'retry-after-ms')) && !present(headerValue(headers, 'retry-after'))) return null;
|
|
54
|
+
return retryAfterMsFromError({ headers });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Epoch ms of the unified rate-limit reset header, uncapped; null when absent or past. */
|
|
58
|
+
export function resetAtFromHeaders(headers) {
|
|
59
|
+
const raw = headerValue(headers, RESET_HEADER);
|
|
60
|
+
if (!present(raw)) return null;
|
|
61
|
+
const seconds = Number(raw);
|
|
62
|
+
return Number.isFinite(seconds) && seconds > 0 ? Math.ceil(seconds * 1000) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Stamp the contract onto `err`. Existing `headers` and a finite
|
|
67
|
+
* `retryAfterMs` are preserved. `type` is the wire error type when the caller
|
|
68
|
+
* already parsed one; otherwise it is read from `bodyText` (or err.error, the
|
|
69
|
+
* SDK's parsed body, or err.message) and finally from the status.
|
|
70
|
+
*/
|
|
71
|
+
export function applyAnthropicErrorContract(err, { status, headers, type = '', bodyText = '' } = {}) {
|
|
72
|
+
if (!err || typeof err !== 'object') return err;
|
|
73
|
+
const httpStatus = Number(status || err.httpStatus || err.status || 0) || 0;
|
|
74
|
+
if (httpStatus) {
|
|
75
|
+
err.status = httpStatus;
|
|
76
|
+
err.httpStatus = httpStatus;
|
|
77
|
+
const wireType =
|
|
78
|
+
lowered(type) ||
|
|
79
|
+
anthropicErrorTypeFromBody(bodyText) ||
|
|
80
|
+
anthropicErrorTypeFromBody(err.error) ||
|
|
81
|
+
anthropicErrorTypeFromBody(err.message) ||
|
|
82
|
+
STATUS_ERROR_TYPE.get(httpStatus) ||
|
|
83
|
+
'';
|
|
84
|
+
const code = httpStatus === 413 ? REQUEST_TOO_LARGE : wireType;
|
|
85
|
+
if (code) err.code = code;
|
|
86
|
+
}
|
|
87
|
+
if (!err.headers && headers) err.headers = headers;
|
|
88
|
+
const effectiveHeaders = err.headers || headers || null;
|
|
89
|
+
if (!(err.retryAfterMs != null && Number.isFinite(Number(err.retryAfterMs)))) {
|
|
90
|
+
const retryAfterMs = retryAfterMsFromHeaders(effectiveHeaders);
|
|
91
|
+
if (retryAfterMs != null) err.retryAfterMs = retryAfterMs;
|
|
92
|
+
}
|
|
93
|
+
const resetAt = resetAtFromHeaders(effectiveHeaders);
|
|
94
|
+
if (resetAt != null) err.resetAt = resetAt;
|
|
95
|
+
return err;
|
|
96
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// cache, because the retry after the wait carries a different speed anyway.
|
|
7
7
|
// The reference client's rule: a SHORT server window is worth waiting for at
|
|
8
8
|
// fast speed (the cache prefix survives), anything longer drops to standard
|
|
9
|
-
// speed for a cooldown. An explicit "overage billing unavailable" header is
|
|
9
|
+
// speed for a cooldown (reference thresholds: <20s keeps fast; otherwise\n// max(Retry-After ?? 30min, 10min)). An explicit "overage billing unavailable" header is
|
|
10
10
|
// terminal for the process — no cooldown can make paid overage appear.
|
|
11
11
|
//
|
|
12
12
|
// State is process-wide because the capacity pool is per-account, not per
|
|
@@ -14,23 +14,28 @@
|
|
|
14
14
|
// sessions hammering it.
|
|
15
15
|
|
|
16
16
|
import { headerValue } from './retry-classification.mjs';
|
|
17
|
+
import { retryAfterMsFromHeaders } from './anthropic-error-contract.mjs';
|
|
17
18
|
|
|
18
19
|
// Server-declared reason header for a fast-mode rejection caused by missing
|
|
19
20
|
// overage billing (matches the reference client's header name).
|
|
20
21
|
const OVERAGE_DISABLED_HEADER = 'anthropic-ratelimit-unified-overage-disabled-reason';
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const
|
|
22
|
+
// Reasons that mean the wallet is empty / capped for now rather than that
|
|
23
|
+
// overage can never exist: the request drops to standard speed, but fast mode
|
|
24
|
+
// is not switched off (the reference client keeps its setting for these).
|
|
25
|
+
const TRANSIENT_OVERAGE_REASONS = new Set(['org_level_disabled_until', 'out_of_credits']);
|
|
26
|
+
// A 400 from an account/endpoint that cannot serve fast mode at all. The same
|
|
27
|
+
// request succeeds at standard speed, and no cooldown makes it succeed fast.
|
|
28
|
+
const NOT_ENABLED_PATTERN = /fast mode is not enabled/i;
|
|
29
|
+
const NOT_ENABLED_REASON = 'fast_mode_not_enabled';
|
|
25
30
|
|
|
26
31
|
// A Retry-After under this bound is short enough that waiting keeps fast mode
|
|
27
32
|
// (and its cache prefix) worthwhile.
|
|
28
|
-
const SHORT_RETRY_THRESHOLD_MS =
|
|
33
|
+
const SHORT_RETRY_THRESHOLD_MS = 20_000;
|
|
29
34
|
// Cooldown floor: without it a 1s window would flip fast mode on and off every
|
|
30
35
|
// turn, which costs more in cache misses than the downgrade saves.
|
|
31
|
-
const MIN_COOLDOWN_MS =
|
|
36
|
+
const MIN_COOLDOWN_MS = 600_000;
|
|
32
37
|
// Applied when the server gives no window at all.
|
|
33
|
-
const DEFAULT_COOLDOWN_MS =
|
|
38
|
+
const DEFAULT_COOLDOWN_MS = 1_800_000;
|
|
34
39
|
|
|
35
40
|
let _cooldownUntilMs = 0;
|
|
36
41
|
let _disabledReason = null;
|
|
@@ -62,23 +67,23 @@ export function fastModeDisabledReason() {
|
|
|
62
67
|
export function noteFastModeCapacityError(err, { fast = false, now = Date.now() } = {}) {
|
|
63
68
|
if (!fast) return 'ignored';
|
|
64
69
|
const status = Number(err?.httpStatus || err?.status || err?.response?.status || 0);
|
|
70
|
+
if (status === 400 && NOT_ENABLED_PATTERN.test(String(err?.message || ''))) {
|
|
71
|
+
_disabledReason = NOT_ENABLED_REASON;
|
|
72
|
+
return 'disabled';
|
|
73
|
+
}
|
|
65
74
|
if (status !== 429 && status !== 529) return 'ignored';
|
|
66
75
|
|
|
67
76
|
const headers = err?.headers || err?.response?.headers || err?.data?.responseHeaders || null;
|
|
68
77
|
const overageReason = headerValue(headers, OVERAGE_DISABLED_HEADER);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return '
|
|
72
|
-
|
|
73
|
-
// Same terminal condition reported only in the error body: the account has
|
|
74
|
-
// no usage credits for fast mode, so no cooldown can make it succeed.
|
|
75
|
-
if (CREDITS_REQUIRED_PATTERN.test(String(err?.message || ''))) {
|
|
76
|
-
_disabledReason = CREDITS_REQUIRED_REASON;
|
|
78
|
+
// The header's presence (even empty) is the signal, as in the reference client.
|
|
79
|
+
if (overageReason != null) {
|
|
80
|
+
if (TRANSIENT_OVERAGE_REASONS.has(String(overageReason))) return 'downgrade';
|
|
81
|
+
_disabledReason = String(overageReason) || 'overage_unavailable';
|
|
77
82
|
return 'disabled';
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
const retryAfterMs =
|
|
81
|
-
const hasWindow =
|
|
85
|
+
const retryAfterMs = serverWindowMs(err);
|
|
86
|
+
const hasWindow = retryAfterMs != null;
|
|
82
87
|
if (hasWindow && retryAfterMs < SHORT_RETRY_THRESHOLD_MS) return 'retry-fast';
|
|
83
88
|
|
|
84
89
|
const cooldownMs = Math.max(hasWindow ? retryAfterMs : DEFAULT_COOLDOWN_MS, MIN_COOLDOWN_MS);
|
|
@@ -86,6 +91,22 @@ export function noteFastModeCapacityError(err, { fast = false, now = Date.now()
|
|
|
86
91
|
return 'downgrade';
|
|
87
92
|
}
|
|
88
93
|
|
|
94
|
+
/** True when the decision means the caller must drop `speed` before replaying. */
|
|
95
|
+
export function fastModeDowngraded(decision) {
|
|
96
|
+
return decision === 'downgrade' || decision === 'disabled';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The server's Retry-After window: an explicit err.retryAfterMs, else the
|
|
100
|
+
// Retry-After headers. The unified-reset header is deliberately not a window
|
|
101
|
+
// here (the reference client reads Retry-After only). null when none.
|
|
102
|
+
function serverWindowMs(err) {
|
|
103
|
+
const explicit = err?.retryAfterMs;
|
|
104
|
+
if (explicit != null && explicit !== '' && Number.isFinite(Number(explicit)) && Number(explicit) >= 0) {
|
|
105
|
+
return Number(explicit);
|
|
106
|
+
}
|
|
107
|
+
return retryAfterMsFromHeaders(err?.headers || err?.response?.headers || err?.data?.responseHeaders || null);
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
/** Manual re-enable (user action) and test seam. */
|
|
90
111
|
export function clearFastModeCooldown() {
|
|
91
112
|
_cooldownUntilMs = 0;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* anthropic-max-tokens-recovery.mjs — the two max_tokens adjustments the
|
|
3
|
+
* reference client makes (withRetry.ts / claude.ts):
|
|
4
|
+
*
|
|
5
|
+
* - a 400 "input length and `max_tokens` exceed context limit: A + B > C" is
|
|
6
|
+
* answered by shrinking max_tokens to what the context still has room for
|
|
7
|
+
* (1000-token safety buffer, 3000-token floor, thinking budget + 1 minimum)
|
|
8
|
+
* and replaying; with less than the floor available the error surfaces.
|
|
9
|
+
* - the non-streaming replay caps max_tokens at 64000 (the API's long-request
|
|
10
|
+
* boundary) and keeps thinking.budget_tokens below it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const FLOOR_OUTPUT_TOKENS = 3000;
|
|
14
|
+
export const CONTEXT_SAFETY_BUFFER_TOKENS = 1000;
|
|
15
|
+
export const MAX_NON_STREAMING_TOKENS = 64_000;
|
|
16
|
+
|
|
17
|
+
const OVERFLOW_RE = /input length and `max_tokens` exceed context limit: (\d+) \+ (\d+) > (\d+)/;
|
|
18
|
+
|
|
19
|
+
/** `{ inputTokens, maxTokens, contextLimit }` for the exact 400, else null. */
|
|
20
|
+
export function parseMaxTokensContextOverflow(status, message) {
|
|
21
|
+
if (Number(status) !== 400) return null;
|
|
22
|
+
const match = OVERFLOW_RE.exec(String(message || ''));
|
|
23
|
+
if (!match) return null;
|
|
24
|
+
const [inputTokens, maxTokens, contextLimit] = match.slice(1).map((value) => Number.parseInt(value, 10));
|
|
25
|
+
return [inputTokens, maxTokens, contextLimit].every(Number.isFinite) ? { inputTokens, maxTokens, contextLimit } : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const thinkingBudget = (body) =>
|
|
29
|
+
body?.thinking?.type === 'enabled' && Number(body.thinking.budget_tokens) > 0 ? Number(body.thinking.budget_tokens) : 0;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Shrink `body.max_tokens` to fit the reported context. Returns true when the
|
|
33
|
+
* body was adjusted and is worth replaying.
|
|
34
|
+
*/
|
|
35
|
+
export function adjustMaxTokensForContextOverflow(body, status, message) {
|
|
36
|
+
const overflow = parseMaxTokensContextOverflow(status, message);
|
|
37
|
+
if (!overflow || !body) return false;
|
|
38
|
+
const available = Math.max(0, overflow.contextLimit - overflow.inputTokens - CONTEXT_SAFETY_BUFFER_TOKENS);
|
|
39
|
+
if (available < FLOOR_OUTPUT_TOKENS) return false;
|
|
40
|
+
body.max_tokens = Math.max(FLOOR_OUTPUT_TOKENS, available, thinkingBudget(body) + 1);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Cap a non-streaming body in place (max_tokens and the thinking budget). */
|
|
45
|
+
export function capForNonStreaming(body) {
|
|
46
|
+
if (!body || !(Number(body.max_tokens) > MAX_NON_STREAMING_TOKENS)) return body;
|
|
47
|
+
body.max_tokens = MAX_NON_STREAMING_TOKENS;
|
|
48
|
+
if (thinkingBudget(body)) {
|
|
49
|
+
body.thinking = {
|
|
50
|
+
...body.thinking,
|
|
51
|
+
budget_tokens: Math.min(body.thinking.budget_tokens, MAX_NON_STREAMING_TOKENS - 1),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return body;
|
|
55
|
+
}
|
|
@@ -8,25 +8,17 @@
|
|
|
8
8
|
// 2. Catalog outputTokens for the model id (trusted over hardcoded
|
|
9
9
|
// heuristics when present — the API reports real per-model limits),
|
|
10
10
|
// clamped to [MAX_TOKENS_FLOOR, safetyCap].
|
|
11
|
-
// 3.
|
|
12
|
-
//
|
|
11
|
+
// 3. The shared model catalog's outputTokens for the model (cached roster /
|
|
12
|
+
// LiteLLM / models.dev), same clamp.
|
|
13
|
+
// 4. The pre-catalog family limits for a model no catalog describes, else
|
|
14
|
+
// the floor.
|
|
15
|
+
|
|
16
|
+
import { getModelMetadataSync } from './model-catalog.mjs';
|
|
13
17
|
|
|
14
18
|
const MAX_TOKENS_FLOOR = 8192;
|
|
15
19
|
const DEFAULT_SAFETY_CAP = 65536;
|
|
16
20
|
const ENV_VAR = 'MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS';
|
|
17
21
|
|
|
18
|
-
// Per-model max_tokens when the model id is explicitly listed. New models
|
|
19
|
-
// (e.g., Sonnet 4.7) won't match a specific entry and fall through to the
|
|
20
|
-
// family-based heuristic below. Conservative defaults — model may support
|
|
21
|
-
// more but we'd rather stay within safe bounds.
|
|
22
|
-
const MAX_TOKENS = {
|
|
23
|
-
'claude-opus-4-8': 65536,
|
|
24
|
-
'claude-opus-4-7': 65536,
|
|
25
|
-
'claude-opus-4-6': 65536,
|
|
26
|
-
'claude-sonnet-4-6': 16384,
|
|
27
|
-
'claude-haiku-4-5-20251001': 8192,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
22
|
// Strict-positive env override parsing. Invalid values ("0", negatives,
|
|
31
23
|
// garbage, whitespace) are treated as UNSET — not as "use the default cap
|
|
32
24
|
// outright" — so catalog/fallback still decide for low-cap models. Raw env
|
|
@@ -39,11 +31,18 @@ function envAnthropicMaxOutputOverride() {
|
|
|
39
31
|
return Math.floor(n);
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
34
|
+
// Last resort when no catalog describes the model: the limits known before
|
|
35
|
+
// the catalogs existed. Sonnet 5+ ships a much larger output budget than the
|
|
36
|
+
// legacy 4.x line (16384 starved visible output once extended thinking ate
|
|
37
|
+
// into the same hard cap). Unknown families stay at the floor.
|
|
38
|
+
const MAX_TOKENS = {
|
|
39
|
+
'claude-opus-4-8': 65536,
|
|
40
|
+
'claude-opus-4-7': 65536,
|
|
41
|
+
'claude-opus-4-6': 65536,
|
|
42
|
+
'claude-sonnet-4-6': 16384,
|
|
43
|
+
'claude-haiku-4-5-20251001': 8192,
|
|
44
|
+
};
|
|
45
|
+
|
|
47
46
|
function fallbackAnthropicMaxTokens(model) {
|
|
48
47
|
if (MAX_TOKENS[model]) return MAX_TOKENS[model];
|
|
49
48
|
const id = String(model || '').toLowerCase();
|
|
@@ -52,8 +51,12 @@ function fallbackAnthropicMaxTokens(model) {
|
|
|
52
51
|
const sonnetVersion = id.match(/^claude-sonnet-(\d+)/);
|
|
53
52
|
if (sonnetVersion) return Number(sonnetVersion[1]) >= 5 ? 65536 : 16384;
|
|
54
53
|
if (id.includes('sonnet')) return 16384;
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
return MAX_TOKENS_FLOOR;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sharedCatalogOutputTokens(model) {
|
|
58
|
+
const declared = Number(getModelMetadataSync(String(model || ''), 'anthropic')?.outputTokens);
|
|
59
|
+
return Number.isFinite(declared) && declared > 0 ? declared : null;
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
// catalogLookup(model) -> number|null. Providers supply their own strategy
|
|
@@ -71,6 +74,7 @@ export function resolveAnthropicMaxTokens(model, { catalogLookup } = {}) {
|
|
|
71
74
|
catalogValue = null;
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
if (!(Number.isFinite(catalogValue) && catalogValue > 0)) catalogValue = sharedCatalogOutputTokens(model);
|
|
74
78
|
if (Number.isFinite(catalogValue) && catalogValue > 0) {
|
|
75
79
|
return Math.max(MAX_TOKENS_FLOOR, Math.min(catalogValue, safetyCap));
|
|
76
80
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Anthropic model catalog + message conversion helpers.
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { effortValuesForModel } from './anthropic-effort.mjs';
|
|
3
|
+
import { effortValuesForModel, thinkingTypesFromCapabilities } from './anthropic-effort.mjs';
|
|
4
4
|
import { makeModelCache } from './model-cache.mjs';
|
|
5
5
|
import { resolveAnthropicMaxTokens } from './anthropic-max-tokens.mjs';
|
|
6
6
|
import {
|
|
@@ -73,6 +73,7 @@ export function _normalizeAnthropicModel(raw, provider = 'anthropic') {
|
|
|
73
73
|
const dated = /-\d{8}$/.test(String(id));
|
|
74
74
|
const versioned = !dated && /^claude-[a-z]+-\d+(?:-\d+)?$/i.test(String(id));
|
|
75
75
|
const effortValues = effortValuesForModel(raw?.capabilities, id);
|
|
76
|
+
const thinkingTypes = thinkingTypesFromCapabilities(raw?.capabilities);
|
|
76
77
|
return {
|
|
77
78
|
id,
|
|
78
79
|
display: raw?.display_name || raw?.displayName || raw?.display || _prettyName(id, family),
|
|
@@ -90,6 +91,7 @@ export function _normalizeAnthropicModel(raw, provider = 'anthropic') {
|
|
|
90
91
|
latest: false,
|
|
91
92
|
supportsReasoning: effortValues.length > 0 || _capabilitySupported(raw?.capabilities?.thinking),
|
|
92
93
|
reasoningOptions: effortValues.length ? [{ type: 'effort', values: effortValues }] : [],
|
|
94
|
+
...(thinkingTypes ? { thinkingTypes } : {}),
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
97
|
// The API-key provider has no catalog cache of its own — it reads the same
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
} from './retry-classifier.mjs';
|
|
25
25
|
import { _classifyMidstreamError, _midstreamSleepWithAbort, stampAnthropicStreamOutcome } from './anthropic-sse.mjs';
|
|
26
26
|
import { notifyCurrentAnthropicRateLimit } from './admission-scheduler.mjs';
|
|
27
|
+
import { applyAnthropicErrorContract } from './anthropic-error-contract.mjs';
|
|
28
|
+
import { isAnthropicOverloadError } from './anthropic-overload-fallback.mjs';
|
|
27
29
|
import { noteAbandonedUsage } from '../../../shared/llm/usage-context.mjs';
|
|
28
30
|
|
|
29
31
|
export function createAnthropicMidState(attemptIndex) {
|
|
@@ -88,6 +90,8 @@ export function assertAnthropicStreamNotEmpty(midState, result, label) {
|
|
|
88
90
|
* @param {{ recoverNonStreaming: Function, issueNonStreamingFallback: Function, requireTransportRecoveryBudget: Function }} deps.recovery
|
|
89
91
|
* @param {Function|null} [deps.onStageChange] receives the display-only 'reconnecting' stage per retry
|
|
90
92
|
* @param {boolean} [deps.retry529] false for a background call: an overload mid-stream is not retried
|
|
93
|
+
* @param {ReturnType<typeof import('./anthropic-overload-fallback.mjs').createAnthropicOverloadTracker>|null} [deps.overload]
|
|
94
|
+
* the send's shared overload counter (SSE overloads feed it)
|
|
91
95
|
*/
|
|
92
96
|
export function createAnthropicMidstreamRecovery({
|
|
93
97
|
label,
|
|
@@ -101,6 +105,7 @@ export function createAnthropicMidstreamRecovery({
|
|
|
101
105
|
recovery,
|
|
102
106
|
onStageChange = null,
|
|
103
107
|
retry529 = true,
|
|
108
|
+
overload = null,
|
|
104
109
|
}) {
|
|
105
110
|
const log = (line) => {
|
|
106
111
|
try {
|
|
@@ -128,7 +133,7 @@ export function createAnthropicMidstreamRecovery({
|
|
|
128
133
|
firstAttemptClassifier = classifier;
|
|
129
134
|
abortStream(controller, err);
|
|
130
135
|
log(message);
|
|
131
|
-
const waitMs = delayMs ?? midstreamBackoffFor(attemptIndex + 1);
|
|
136
|
+
const waitMs = delayMs ?? midstreamBackoffFor(attemptIndex + 1, undefined, err);
|
|
132
137
|
emitProviderRetryStage(onStageChange, {
|
|
133
138
|
attempt: attemptIndex + 1,
|
|
134
139
|
maxAttempts: maxRetries,
|
|
@@ -192,6 +197,16 @@ export function createAnthropicMidstreamRecovery({
|
|
|
192
197
|
abortStream(controller, err);
|
|
193
198
|
throw err;
|
|
194
199
|
}
|
|
200
|
+
// An SSE overload counts toward the same limit as a request-loop 529; at
|
|
201
|
+
// the limit the configured fallback model takes over (the provider's
|
|
202
|
+
// send loop catches the trigger around this whole recovery).
|
|
203
|
+
if (overload && isAnthropicOverloadError(err)) {
|
|
204
|
+
const trigger = overload.noteStreamOverload(err);
|
|
205
|
+
if (trigger) {
|
|
206
|
+
abortStream(controller, err);
|
|
207
|
+
throw trigger;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
195
210
|
const canRetry = attemptIndex < maxRetries;
|
|
196
211
|
const attemptLabel = `${attemptIndex + 1}/${maxRetries}`;
|
|
197
212
|
// Empty/dropped stream (no message_start): safe to retry once —
|
|
@@ -270,10 +285,9 @@ export function createAnthropicMidstreamRecovery({
|
|
|
270
285
|
const status = Number(err?.httpStatus || err?.status || 0);
|
|
271
286
|
let retryDelayMs = null;
|
|
272
287
|
if (status === 429) {
|
|
273
|
-
if (!err.headers && response?.headers) err.headers = response.headers;
|
|
274
288
|
if (!err.response && response) err.response = { status, headers: response.headers };
|
|
289
|
+
applyAnthropicErrorContract(err, { status, headers: response?.headers });
|
|
275
290
|
retryDelayMs = retryAfterMsFromError(err);
|
|
276
|
-
if (retryDelayMs != null) err.retryAfterMs = retryDelayMs;
|
|
277
291
|
notifyCurrentAnthropicRateLimit(err);
|
|
278
292
|
}
|
|
279
293
|
try {
|
|
@@ -283,7 +297,7 @@ export function createAnthropicMidstreamRecovery({
|
|
|
283
297
|
log(`abort on stream error failed: ${abortErr?.message ?? String(abortErr)}`);
|
|
284
298
|
}
|
|
285
299
|
log(`mid-stream recovered: retry ${attemptLabel} (cause: ${classifier})`);
|
|
286
|
-
const waitMs = retryDelayMs ?? midstreamBackoffFor(attemptIndex + 1);
|
|
300
|
+
const waitMs = retryDelayMs ?? midstreamBackoffFor(attemptIndex + 1, undefined, err);
|
|
287
301
|
emitProviderRetryStage(onStageChange, {
|
|
288
302
|
attempt: attemptIndex + 1,
|
|
289
303
|
maxAttempts: maxRetries,
|