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 { reloadProviderApiKey, resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
2
2
|
import { sanitizeToolPairs } from '../session/context-utils.mjs';
|
|
3
3
|
import { AnthropicFallbackTriggeredError } from './retry-classifier.mjs';
|
|
4
|
+
import {
|
|
5
|
+
announceAnthropicModelFallback,
|
|
6
|
+
createAnthropicOverloadTracker,
|
|
7
|
+
isTerminalOverloadModel,
|
|
8
|
+
} from './anthropic-overload-fallback.mjs';
|
|
4
9
|
import { readStreamOutcome } from './lib/stream-outcome.mjs';
|
|
5
10
|
import { createPassthroughSignal } from '../stall-policy.mjs';
|
|
6
11
|
import { createAbortController } from '../../../shared/abort-controller.mjs';
|
|
@@ -166,6 +171,13 @@ export class AnthropicProvider {
|
|
|
166
171
|
} catch {}
|
|
167
172
|
};
|
|
168
173
|
|
|
174
|
+
// Request-loop 529s and SSE overload events share one counter; at the
|
|
175
|
+
// limit the configured fallback model takes over the whole send.
|
|
176
|
+
const overload = createAnthropicOverloadTracker({
|
|
177
|
+
model: useModel,
|
|
178
|
+
fallbackModel: opts._fallbackTriggered ? undefined : opts.fallbackModel,
|
|
179
|
+
terminalAtLimit: !isCustomProviderName(this.name) && isTerminalOverloadModel(useModel),
|
|
180
|
+
});
|
|
169
181
|
// The SDK calls and their initial-response retry policy
|
|
170
182
|
// (anthropic-api-transport.mjs).
|
|
171
183
|
const transport = createAnthropicApiTransport({
|
|
@@ -176,6 +188,7 @@ export class AnthropicProvider {
|
|
|
176
188
|
params,
|
|
177
189
|
requestHeaders,
|
|
178
190
|
totalSignal,
|
|
191
|
+
overload,
|
|
179
192
|
});
|
|
180
193
|
// Usage accounting + caller-visible projection, shared by the streaming
|
|
181
194
|
// success path and the non-streaming fallback (anthropic-turn-result.mjs).
|
|
@@ -213,8 +226,10 @@ export class AnthropicProvider {
|
|
|
213
226
|
recovery,
|
|
214
227
|
onStageChange,
|
|
215
228
|
retry529: opts.retry529 !== false,
|
|
229
|
+
overload,
|
|
216
230
|
});
|
|
217
231
|
|
|
232
|
+
let streamingFallbackTried = false;
|
|
218
233
|
try {
|
|
219
234
|
for (let attemptIndex = 0; attemptIndex <= ANTHROPIC_MAX_MIDSTREAM_RETRIES; attemptIndex++) {
|
|
220
235
|
const streamController = createAbortController();
|
|
@@ -274,13 +289,13 @@ export class AnthropicProvider {
|
|
|
274
289
|
|
|
275
290
|
return buildTurnResult(parseResult);
|
|
276
291
|
} catch (err) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
292
|
+
// The fallback trigger is handled around the whole send below.
|
|
293
|
+
if (err instanceof AnthropicFallbackTriggeredError) throw err;
|
|
294
|
+
// Gateways that 404 the streaming endpoint but serve non-streaming:
|
|
295
|
+
// one non-streaming replay (nothing was streamed yet).
|
|
296
|
+
if (err?.initialResponseError === true && Number(err.status) === 404 && !streamingFallbackTried) {
|
|
297
|
+
streamingFallbackTried = true;
|
|
298
|
+
return await recovery.issueNonStreamingFallback(streamController, err);
|
|
284
299
|
}
|
|
285
300
|
// Ordered recovery ladder: canonical outcome stamp, acknowledged
|
|
286
301
|
// non-streaming replay for exposed output, bounded streaming
|
|
@@ -300,6 +315,18 @@ export class AnthropicProvider {
|
|
|
300
315
|
}
|
|
301
316
|
}
|
|
302
317
|
throw midstream.exhaustedError();
|
|
318
|
+
} catch (err) {
|
|
319
|
+
// The fallback trigger can surface from the request loop, a mid-stream
|
|
320
|
+
// overload, or the non-streaming recovery: handle it around the whole
|
|
321
|
+
// operation, not just the first request.
|
|
322
|
+
if (!(err instanceof AnthropicFallbackTriggeredError)) throw err;
|
|
323
|
+
process.stderr.write(`[${this.name}] ${err.message}\n`);
|
|
324
|
+
announceAnthropicModelFallback(onStageChange, useModel, err.fallbackModel);
|
|
325
|
+
return await this._doSend(messages, err.fallbackModel, tools, {
|
|
326
|
+
...opts,
|
|
327
|
+
fallbackModel: undefined,
|
|
328
|
+
_fallbackTriggered: true,
|
|
329
|
+
});
|
|
303
330
|
} finally {
|
|
304
331
|
totalTimeout.cleanup();
|
|
305
332
|
}
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* comes from `retrieveUserQuotaSummary`, not the per-model catalog counters.
|
|
12
12
|
*/
|
|
13
13
|
import { makeModelCache } from './model-cache.mjs';
|
|
14
|
+
import { isGeminiFamilyModel } from './gemini-family.mjs';
|
|
15
|
+
import { isAntigravityClaude } from './antigravity-request.mjs';
|
|
16
|
+
import { getModelMetadataSync } from '../../../shared/llm/model-catalog.mjs';
|
|
14
17
|
import {
|
|
15
18
|
ANTIGRAVITY_MODELS,
|
|
16
19
|
PROJECT_ENDPOINT,
|
|
@@ -27,8 +30,9 @@ const TIER_EFFORTS = { 'extra low': 'minimal', minimal: 'minimal', low: 'low', m
|
|
|
27
30
|
const EFFORT_ORDER = ['minimal', 'low', 'medium', 'high'];
|
|
28
31
|
// IDE-internal completion/chat models are listed without a display name.
|
|
29
32
|
const INTERNAL_ID = /^(?:chat|tab)_/;
|
|
30
|
-
//
|
|
31
|
-
|
|
33
|
+
// The thinkingLevel values the gateway accepts on a single wire id; `minimal`
|
|
34
|
+
// is an error there, so it is never offered even when a catalog lists it.
|
|
35
|
+
const GATEWAY_THINKING_LEVELS = Object.freeze(['low', 'medium', 'high']);
|
|
32
36
|
|
|
33
37
|
let _mirror = null;
|
|
34
38
|
export const antigravityModelCache = makeModelCache({
|
|
@@ -116,6 +120,23 @@ function chatEntries(rawModels) {
|
|
|
116
120
|
return out;
|
|
117
121
|
}
|
|
118
122
|
|
|
123
|
+
// A Gemini wire id without tier siblings takes the ordinary thinkingLevel field.
|
|
124
|
+
// The ladder comes from the catalogs' row for the same Gemini id (models.dev /
|
|
125
|
+
// LiteLLM) narrows what the gateway accepts; when no catalog declares a ladder
|
|
126
|
+
// the gateway-accepted levels are offered and the server judges the value.
|
|
127
|
+
// Only a model the gateway does not flag as thinking-capable has no levels.
|
|
128
|
+
function bareThinkingLevels(wire, supportsReasoning) {
|
|
129
|
+
if (!supportsReasoning) return [];
|
|
130
|
+
// Claude behind the gateway takes the same thinkingLevel enum; no catalog
|
|
131
|
+
// describes these wire ids, so the gateway-accepted levels are offered.
|
|
132
|
+
if (isAntigravityClaude(wire)) return [...GATEWAY_THINKING_LEVELS];
|
|
133
|
+
if (!isGeminiFamilyModel(wire)) return [];
|
|
134
|
+
const effort = getModelMetadataSync(wire, 'gemini')?.reasoningOptions?.find((option) => option?.type === 'effort');
|
|
135
|
+
if (!Array.isArray(effort?.values) || !effort.values.length) return [...GATEWAY_THINKING_LEVELS];
|
|
136
|
+
const declared = new Set(effort.values.map((value) => String(value).toLowerCase()));
|
|
137
|
+
return GATEWAY_THINKING_LEVELS.filter((level) => declared.has(level));
|
|
138
|
+
}
|
|
139
|
+
|
|
119
140
|
/** Collapse the gateway's per-tier wire ids into picker records. */
|
|
120
141
|
export function normalizeAntigravityCatalog(rawModels) {
|
|
121
142
|
const families = new Map();
|
|
@@ -142,13 +163,13 @@ export function normalizeAntigravityCatalog(rawModels) {
|
|
|
142
163
|
? Object.fromEntries(efforts.map((effort) => [effort, pickWire(family.wires.get(effort), family.base)]))
|
|
143
164
|
: pickWire(family.wires.get(efforts[0] ?? ''), family.base);
|
|
144
165
|
const id = tiered ? key : wire;
|
|
145
|
-
const bareGemini3 = !tiered && /^gemini-3(?:\.\d+)?-(?:flash|pro)$/.test(wire);
|
|
146
166
|
const display =
|
|
147
167
|
tiered || !efforts.length ? family.base : `${family.base} (${efforts[0][0].toUpperCase()}${efforts[0].slice(1)})`;
|
|
148
168
|
const kind = familyKind(family.base);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
169
|
+
// A lone labelled tier already names its effort in the wire id.
|
|
170
|
+
let reasoningLevels = efforts;
|
|
171
|
+
if (!efforts.length) reasoningLevels = bareThinkingLevels(wire, family.supportsReasoning);
|
|
172
|
+
else if (!tiered) reasoningLevels = [];
|
|
152
173
|
models.push({
|
|
153
174
|
id,
|
|
154
175
|
name: display,
|
|
@@ -174,9 +195,9 @@ function antigravityCatalogModels() {
|
|
|
174
195
|
|
|
175
196
|
/**
|
|
176
197
|
* Turn a picker model + effort into the wire id the gateway accepts. Tiered
|
|
177
|
-
* families consume the effort (the id encodes it); a bare
|
|
178
|
-
* for thinkingLevel; every other record drops it
|
|
179
|
-
*
|
|
198
|
+
* families consume the effort (the id encodes it); a bare id whose record
|
|
199
|
+
* lists levels keeps it for thinkingLevel; every other record drops it
|
|
200
|
+
* because the gateway does not flag the model as thinking-capable. Unknown ids pass through unchanged.
|
|
180
201
|
*/
|
|
181
202
|
export function resolveAntigravityWireModel(model, effort, models = antigravityCatalogModels()) {
|
|
182
203
|
const id = String(model || '');
|
|
@@ -20,6 +20,7 @@ import { preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
|
20
20
|
import { createAntigravityStreamCollector } from './antigravity-stream.mjs';
|
|
21
21
|
import { createAntigravityRequest } from './antigravity-transport.mjs';
|
|
22
22
|
import { finalizeAntigravityTurn } from './antigravity-response.mjs';
|
|
23
|
+
import { ANTIGRAVITY_ACCOUNT_COOLDOWN_POLICY } from './antigravity-retry-policy.mjs';
|
|
23
24
|
import {
|
|
24
25
|
CONTENT_ENDPOINT,
|
|
25
26
|
ANTIGRAVITY_MODELS,
|
|
@@ -69,6 +70,11 @@ export class AntigravityOAuthProvider {
|
|
|
69
70
|
// usageMetadata.promptTokenCount is the total, cached tokens included.
|
|
70
71
|
static inputExcludesCache = false;
|
|
71
72
|
name = 'antigravity-oauth';
|
|
73
|
+
// Roster cooldowns the shared account pool applies when the server names no delay.
|
|
74
|
+
accountCooldownPolicy = ANTIGRAVITY_ACCOUNT_COOLDOWN_POLICY;
|
|
75
|
+
// The capacity / quick-retry ladder is the whole recovery: a spent ladder
|
|
76
|
+
// surfaces instead of parking on loop recovery cycles.
|
|
77
|
+
retryPolicy = Object.freeze({ recoveryCycles: false });
|
|
72
78
|
|
|
73
79
|
constructor(config = {}) {
|
|
74
80
|
this.config = config || {};
|
|
@@ -162,6 +168,7 @@ export class AntigravityOAuthProvider {
|
|
|
162
168
|
opts,
|
|
163
169
|
signal: passthrough.signal,
|
|
164
170
|
collector,
|
|
171
|
+
sleepFn: this.config.sleepFn,
|
|
165
172
|
});
|
|
166
173
|
try {
|
|
167
174
|
try {
|
|
@@ -2,10 +2,15 @@ import { randomUUID, createHash } from 'node:crypto';
|
|
|
2
2
|
import { createProviderReplay, providerReplayItems } from './lib/provider-replay.mjs';
|
|
3
3
|
import { toGeminiContents, toGeminiRequestTools, toGeminiToolConfig } from './gemini-schema.mjs';
|
|
4
4
|
import { geminiThinkingConfig } from './gemini-thinking.mjs';
|
|
5
|
+
import { isGeminiFamilyModel } from './gemini-family.mjs';
|
|
5
6
|
|
|
6
7
|
const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator';
|
|
7
8
|
const CLAUDE_MAX_OUTPUT_TOKENS = 64000;
|
|
8
9
|
|
|
10
|
+
// Claude models behind the gateway speak a different thinking/replay dialect
|
|
11
|
+
// (token budgets, anthropic-beta header, their own signatures). The roster
|
|
12
|
+
// carries no protocol marker, and a wrong guess is rejected upstream, so the
|
|
13
|
+
// vendor prefix of the wire id decides.
|
|
9
14
|
export function isAntigravityClaude(model) {
|
|
10
15
|
return /^claude-/i.test(String(model || ''));
|
|
11
16
|
}
|
|
@@ -59,33 +64,44 @@ function sessionId(contents) {
|
|
|
59
64
|
return `-${digest.readBigUInt64BE(0) % 9223372036854775807n}`;
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
// The gateway validates Claude's level against Google's ThinkingLevel enum,
|
|
68
|
+
// whose highest value is `high`; anything it does not name is left out.
|
|
69
|
+
const CLAUDE_GATEWAY_LEVELS = new Set(['low', 'medium', 'high']);
|
|
70
|
+
function claudeGatewayLevel(value) {
|
|
71
|
+
const level = String(value ?? '').toLowerCase();
|
|
72
|
+
if (level === 'xhigh' || level === 'max' || level === 'ultra') return 'high';
|
|
73
|
+
return CLAUDE_GATEWAY_LEVELS.has(level) ? level : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// An explicit token budget is sent as-is; otherwise the chosen effort rides
|
|
77
|
+
// on thinkingLevel with the wire id unchanged.
|
|
62
78
|
function claudeThinkingConfig(opts) {
|
|
63
79
|
const budget = opts.thinkingBudget ?? opts.thinkingBudgetTokens;
|
|
64
|
-
if (opts.thinkingLevel != null || (budget == null && opts.effort != null)) {
|
|
65
|
-
throw new TypeError(
|
|
66
|
-
'Antigravity Claude uses thinkingBudget; effort levels cannot be converted to a token budget automatically.'
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
80
|
const thinkingConfig = { includeThoughts: true };
|
|
70
|
-
if (budget
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
if (budget == null) {
|
|
82
|
+
const level = claudeGatewayLevel(opts.thinkingLevel ?? opts.effort);
|
|
83
|
+
if (level) thinkingConfig.thinkingLevel = level;
|
|
84
|
+
return thinkingConfig;
|
|
85
|
+
}
|
|
86
|
+
const value = Number(budget);
|
|
87
|
+
if (!Number.isInteger(value) || value < 1024 || value >= CLAUDE_MAX_OUTPUT_TOKENS) {
|
|
88
|
+
throw new TypeError('Antigravity Claude thinkingBudget must be an integer from 1024 to 63999.');
|
|
76
89
|
}
|
|
90
|
+
thinkingConfig.thinkingBudget = value;
|
|
77
91
|
return thinkingConfig;
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
// Tiered wire ids (gemini-3.8-flash-high) already encode the thinking
|
|
81
95
|
// level; the provider resolves them from the catalog and clears the
|
|
82
96
|
// effort, so only bare ids reach this field.
|
|
83
|
-
// Thought summaries on by default for every Gemini
|
|
97
|
+
// Thought summaries on by default for every Gemini-family model, matching the
|
|
84
98
|
// API-key provider. The gateway also serves GPT-OSS, which rejects the
|
|
85
|
-
// thinking fields outright
|
|
99
|
+
// thinking fields outright and which the roster's thinking flag does not
|
|
100
|
+
// separate from Gemini, so the flag stays Gemini-only.
|
|
86
101
|
function antigravityGeminiThinkingConfig(model, opts) {
|
|
87
102
|
return geminiThinkingConfig(model, opts, {
|
|
88
|
-
|
|
103
|
+
provider: 'antigravity-oauth',
|
|
104
|
+
includeThoughts: isGeminiFamilyModel(model) ? true : undefined,
|
|
89
105
|
});
|
|
90
106
|
}
|
|
91
107
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { traceAgentUsage } from '../agent-trace.mjs';
|
|
10
10
|
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
11
11
|
import { parseToolCalls, collectGeminiGroundingSources, parseGeminiTextPartMetadata } from './gemini-schema.mjs';
|
|
12
|
+
import { geminiPolicyFinishCode } from './gemini-error-contract.mjs';
|
|
12
13
|
|
|
13
14
|
// Pure usageMetadata normalization shared by the success path and a failed
|
|
14
15
|
// stream's partialUsage.
|
|
@@ -82,7 +83,7 @@ export function finalizeAntigravityTurn({ response, collector, useModel, opts, o
|
|
|
82
83
|
if (finishReason && normalizedFinish !== 'STOP') {
|
|
83
84
|
throw Object.assign(new Error(`Antigravity response incomplete: finishReason=${finishReason}`), {
|
|
84
85
|
name: 'ProviderIncompleteError',
|
|
85
|
-
code: 'PROVIDER_INCOMPLETE',
|
|
86
|
+
code: geminiPolicyFinishCode(finishReason) || 'PROVIDER_INCOMPLETE',
|
|
86
87
|
providerIncomplete: true,
|
|
87
88
|
finishReason,
|
|
88
89
|
partialContent: content,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* antigravity-retry-policy.mjs — Antigravity retry constants and the verdict
|
|
3
|
+
* for a 429:
|
|
4
|
+
*
|
|
5
|
+
* capacity (429/503/529, MODEL_CAPACITY_EXHAUSTED, "server busy"): retried on
|
|
6
|
+
* the SAME account and host with 1s → 2s → 4s (8s cap) ±10% jitter, three
|
|
7
|
+
* retries, whatever wait the server names, then the failure surfaces.
|
|
8
|
+
* Endpoint: the reference falls back to its next endpoint after a failure;
|
|
9
|
+
* generation here deliberately stays on the single daily host and never
|
|
10
|
+
* walks an endpoint list (a spent ladder surfaces to the account layer).
|
|
11
|
+
* RPM / unknown 429: ONE quick retry after 1s (or the server's advice, up to
|
|
12
|
+
* 60s), then the failure surfaces so the account layer can lock and
|
|
13
|
+
* rotate; advice beyond 60s surfaces at once.
|
|
14
|
+
* QUOTA_EXHAUSTED (and any day/plan window): never retried here.
|
|
15
|
+
*
|
|
16
|
+
* Replay stays gated by the stream-outcome contract: an attempt that exposed
|
|
17
|
+
* text or dispatched a tool is never re-issued.
|
|
18
|
+
*/
|
|
19
|
+
import { readStreamOutcome } from './lib/stream-outcome.mjs';
|
|
20
|
+
import { DETERMINISTIC_KINDS, providerErrorKind } from './provider-error-kind.mjs';
|
|
21
|
+
|
|
22
|
+
export const ANTIGRAVITY_CAPACITY_RETRIES = 3;
|
|
23
|
+
export const ANTIGRAVITY_CAPACITY_BASE_MS = 1000;
|
|
24
|
+
export const ANTIGRAVITY_CAPACITY_MAX_MS = 8000;
|
|
25
|
+
export const ANTIGRAVITY_CAPACITY_JITTER_RATIO = 0.1;
|
|
26
|
+
export const ANTIGRAVITY_QUICK_RETRY_MS = 1000;
|
|
27
|
+
export const ANTIGRAVITY_QUICK_RETRY_MAX_ADVICE_MS = 60_000;
|
|
28
|
+
|
|
29
|
+
// withRetry options for everything that is not a 429: transient transport and
|
|
30
|
+
// 5xx failures use the capacity schedule (the reference has no other same-host
|
|
31
|
+
// retry; the leading duplicate is the sleep-before-attempt index).
|
|
32
|
+
export const ANTIGRAVITY_RETRY = Object.freeze({
|
|
33
|
+
maxAttempts: ANTIGRAVITY_CAPACITY_RETRIES + 1,
|
|
34
|
+
backoffMs: Object.freeze([1000, 1000, 2000, 4000]),
|
|
35
|
+
retryJitterRatio: ANTIGRAVITY_CAPACITY_JITTER_RATIO,
|
|
36
|
+
// 429s are handled by antigravity429WaitMs, not the generic loop.
|
|
37
|
+
retry429: false,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** Capacity wait for retry number `retryIndex` (0-based): 1s, 2s, 4s, 8s cap, ±10%. */
|
|
41
|
+
export function antigravityCapacityDelayMs(retryIndex) {
|
|
42
|
+
const base = Math.min(ANTIGRAVITY_CAPACITY_BASE_MS * 2 ** retryIndex, ANTIGRAVITY_CAPACITY_MAX_MS);
|
|
43
|
+
return Math.round(base * (1 - ANTIGRAVITY_CAPACITY_JITTER_RATIO + Math.random() * 2 * ANTIGRAVITY_CAPACITY_JITTER_RATIO));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isAntigravityCapacityError(err) {
|
|
47
|
+
return err?.code === 'model_capacity_exhausted' || providerErrorKind(err).kind === 'overloaded';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wait before re-issuing a 429, or null when it must surface. `state` is the
|
|
52
|
+
* send's `{ capacityRetries, quickRetried }` record.
|
|
53
|
+
*/
|
|
54
|
+
export function antigravity429WaitMs(err, state) {
|
|
55
|
+
const status = Number(err?.status || err?.httpStatus || 0);
|
|
56
|
+
const capacity = isAntigravityCapacityError(err);
|
|
57
|
+
if (status !== 429 && !(capacity && (status === 503 || status === 529))) return null;
|
|
58
|
+
if (readStreamOutcome(err).replaySafe !== true) return null;
|
|
59
|
+
const { kind, retryAfterMs } = providerErrorKind(err);
|
|
60
|
+
if (DETERMINISTIC_KINDS.has(kind) || err.code === 'quota_exhausted' || err.quotaScope === 'day' || err.quotaScope === 'plan') {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Capacity follows the local schedule whatever wait the server names.
|
|
64
|
+
if (capacity) {
|
|
65
|
+
if (state.capacityRetries >= ANTIGRAVITY_CAPACITY_RETRIES) return null;
|
|
66
|
+
return antigravityCapacityDelayMs(state.capacityRetries++);
|
|
67
|
+
}
|
|
68
|
+
// An RPM hit gets one quick retry; advice up to a minute is honored in
|
|
69
|
+
// full, a longer wait surfaces so the account pool can rotate.
|
|
70
|
+
if (state.quickRetried || retryAfterMs > ANTIGRAVITY_QUICK_RETRY_MAX_ADVICE_MS) return null;
|
|
71
|
+
state.quickRetried = true;
|
|
72
|
+
return retryAfterMs ?? ANTIGRAVITY_QUICK_RETRY_MS;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Account-pool roster cooldowns, read
|
|
76
|
+
// generically by the shared pool from the provider's `accountCooldownPolicy`
|
|
77
|
+
// when the server named no delay: a plain rate limit rotates the account for
|
|
78
|
+
// 30s; a spent quota escalates 60s → 5m → 30m → 2h per consecutive refusal of
|
|
79
|
+
// that account (a served answer resets it); anything else 60s; never under 2s.
|
|
80
|
+
// Capacity and server errors never block an account in the shared pool.
|
|
81
|
+
export const ANTIGRAVITY_ACCOUNT_COOLDOWN_POLICY = Object.freeze({
|
|
82
|
+
floorMs: 2_000,
|
|
83
|
+
rateLimitedMs: 30_000,
|
|
84
|
+
quotaStepsMs: Object.freeze([60_000, 5 * 60_000, 30 * 60_000, 2 * 60 * 60_000]),
|
|
85
|
+
unknownMs: 60_000,
|
|
86
|
+
rotateOnRateLimit: true,
|
|
87
|
+
// A roster that is only cooling down waits up to five minutes for its
|
|
88
|
+
// earliest cooldown instead of failing the send.
|
|
89
|
+
waitForCooldownMs: 300_000,
|
|
90
|
+
// An account that needs verification cools for ten minutes while the
|
|
91
|
+
// roster serves from the next account.
|
|
92
|
+
blockedMs: 600_000,
|
|
93
|
+
});
|
|
@@ -6,13 +6,45 @@
|
|
|
6
6
|
* account-verification URL Google hides in the error details), and the
|
|
7
7
|
* per-attempt hand-off of the live stream to the turn collector.
|
|
8
8
|
*/
|
|
9
|
-
import { emitProviderRetryStage, withRetry } from './retry-classifier.mjs';
|
|
9
|
+
import { emitProviderRetryStage, markProviderRecoveryExhausted, sleepWithAbort, withRetry } from './retry-classifier.mjs';
|
|
10
|
+
import {
|
|
11
|
+
ANTIGRAVITY_RETRY,
|
|
12
|
+
antigravity429WaitMs,
|
|
13
|
+
isAntigravityCapacityError,
|
|
14
|
+
} from './antigravity-retry-policy.mjs';
|
|
10
15
|
import { createTimeoutSignal } from '../stall-policy.mjs';
|
|
11
16
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
12
17
|
import { GEMINI_FIRST_BYTE_TIMEOUT_MS } from './gemini-stream.mjs';
|
|
13
18
|
import { _scrubTokens } from './antigravity-oauth-tokens.mjs';
|
|
19
|
+
import { applyGeminiErrorContract } from './gemini-error-contract.mjs';
|
|
20
|
+
import { DETERMINISTIC_KINDS, providerErrorKind } from './provider-error-kind.mjs';
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
// Rate-limit reason: 503/529 are capacity by status; a
|
|
23
|
+
// typed ErrorInfo reason decides next; wording is the last resort for a 429.
|
|
24
|
+
const CAPACITY_MESSAGE_RE = /capacity|overloaded|resource exhausted|server (?:is )?(?:currently )?busy/i;
|
|
25
|
+
const RATE_LIMIT_MESSAGE_RE = /per minute|rate limit|too many requests/i;
|
|
26
|
+
const TYPED_REASONS = new Set(['quota_exhausted', 'rate_limit_exceeded', 'model_capacity_exhausted']);
|
|
27
|
+
|
|
28
|
+
function applyAntigravityRateLimitReason(err, status, message) {
|
|
29
|
+
if (status === 503 || status === 529) {
|
|
30
|
+
// A typed refusal of this request (bad argument, policy, auth, quota) is
|
|
31
|
+
// not an overload whatever the status says.
|
|
32
|
+
if (DETERMINISTIC_KINDS.has(providerErrorKind(err).kind)) return;
|
|
33
|
+
err.code = 'model_capacity_exhausted';
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (status !== 429 || TYPED_REASONS.has(err.code)) return;
|
|
37
|
+
if (CAPACITY_MESSAGE_RE.test(message)) err.code = 'model_capacity_exhausted';
|
|
38
|
+
else if (RATE_LIMIT_MESSAGE_RE.test(message)) err.code = 'rate_limit_exceeded';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const CAPACITY_STATUSES = new Set([429, 503, 529]);
|
|
42
|
+
|
|
43
|
+
function isCapacityFailure(err) {
|
|
44
|
+
return CAPACITY_STATUSES.has(Number(err?.status)) && isAntigravityCapacityError(err);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function antigravityError(res, text, endpoint) {
|
|
16
48
|
let payload = null;
|
|
17
49
|
try {
|
|
18
50
|
payload = text ? JSON.parse(text) : null;
|
|
@@ -30,7 +62,11 @@ function antigravityError(res, text, endpoint) {
|
|
|
30
62
|
err.error = detail;
|
|
31
63
|
err.data = payload;
|
|
32
64
|
if (detail.status) err.geminiStatus = detail.status;
|
|
65
|
+
const details = detail.details ?? detail.errorDetails;
|
|
66
|
+
applyGeminiErrorContract(err, { details, googleStatus: detail.status, message });
|
|
33
67
|
}
|
|
68
|
+
// Capacity is overload of the shared model pool, not this account's quota.
|
|
69
|
+
applyAntigravityRateLimitReason(err, res.status, message);
|
|
34
70
|
// Account verification is terminal and actionable: surface the URL Google
|
|
35
71
|
// returns instead of a raw API body the user cannot act on.
|
|
36
72
|
// Google puts the link in the error details, not the message text.
|
|
@@ -47,6 +83,9 @@ function antigravityError(res, text, endpoint) {
|
|
|
47
83
|
err.message = `Antigravity requires account verification${validationUrl ? `: open ${validationUrl} , complete the check, then retry` : ''}`;
|
|
48
84
|
err.validationUrl = validationUrl || undefined;
|
|
49
85
|
err.unsafeToRetry = true;
|
|
86
|
+
// This account needs verification; another account may still serve.
|
|
87
|
+
err.code ||= 'validation_required';
|
|
88
|
+
err.accountBlocked = true;
|
|
50
89
|
}
|
|
51
90
|
return err;
|
|
52
91
|
}
|
|
@@ -62,8 +101,17 @@ function antigravityError(res, text, endpoint) {
|
|
|
62
101
|
* @param {{ beginAttempt: Function, consume: Function }} deps.collector
|
|
63
102
|
* @returns {() => Promise<object>} one retried request that resolves the final payload
|
|
64
103
|
*/
|
|
65
|
-
export function createAntigravityRequest({ fetchFn, endpoint, headers, body, opts, signal, collector }) {
|
|
66
|
-
|
|
104
|
+
export function createAntigravityRequest({ fetchFn, endpoint, headers, body, opts, signal, collector, sleepFn }) {
|
|
105
|
+
const log = (attempt, lastErr, delayMs) => {
|
|
106
|
+
emitProviderRetryStage(opts.onStageChange, {
|
|
107
|
+
attempt: attempt + 1,
|
|
108
|
+
maxAttempts: ANTIGRAVITY_RETRY.maxAttempts,
|
|
109
|
+
lastErr,
|
|
110
|
+
delayMs,
|
|
111
|
+
});
|
|
112
|
+
process.stderr.write(`[antigravity] retry ${attempt + 1} after ${lastErr?.message || 'transient error'}\n`);
|
|
113
|
+
};
|
|
114
|
+
const attemptOnce = () =>
|
|
67
115
|
withRetry(
|
|
68
116
|
async ({ signal: attemptSignal }) => {
|
|
69
117
|
try {
|
|
@@ -94,17 +142,42 @@ export function createAntigravityRequest({ fetchFn, endpoint, headers, body, opt
|
|
|
94
142
|
}
|
|
95
143
|
if (!res.ok) {
|
|
96
144
|
const text = await res.text().catch(() => '');
|
|
97
|
-
|
|
145
|
+
const failure = antigravityError(res, text, endpoint);
|
|
146
|
+
// Capacity is paced by the local schedule below; the generic loop
|
|
147
|
+
// would sleep whatever wait the server names.
|
|
148
|
+
if (isCapacityFailure(failure)) markProviderRecoveryExhausted(failure, { owner: 'antigravity-capacity' });
|
|
149
|
+
throw failure;
|
|
98
150
|
}
|
|
99
151
|
collector.beginAttempt();
|
|
100
152
|
return collector.consume(res, attemptSignal);
|
|
101
153
|
},
|
|
102
154
|
{
|
|
155
|
+
...ANTIGRAVITY_RETRY,
|
|
156
|
+
finalOnExhaust: true,
|
|
103
157
|
signal,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
process.stderr.write(`[antigravity] retry ${attempt + 1} after ${retryErr?.message || 'transient error'}\n`);
|
|
107
|
-
},
|
|
158
|
+
sleepFn,
|
|
159
|
+
onRetry: ({ attempt, lastErr: retryErr, delayMs }) => log(attempt, retryErr, delayMs),
|
|
108
160
|
}
|
|
109
161
|
);
|
|
162
|
+
// A 429 comes back from withRetry untouched (retry429:false) and is paced by
|
|
163
|
+
// the retry schedule: capacity backs off on this host, an RPM hit
|
|
164
|
+
// gets one quick retry, a spent quota surfaces.
|
|
165
|
+
return async () => {
|
|
166
|
+
const state = { capacityRetries: 0, quickRetried: false };
|
|
167
|
+
for (let tried = 1; ; tried++) {
|
|
168
|
+
try {
|
|
169
|
+
return await attemptOnce();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const waitMs = antigravity429WaitMs(err, state);
|
|
172
|
+
if (waitMs == null) {
|
|
173
|
+
if (isCapacityFailure(err)) {
|
|
174
|
+
markProviderRecoveryExhausted(err, { owner: 'antigravity-capacity', attempts: tried });
|
|
175
|
+
}
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
log(tried, err, waitMs);
|
|
179
|
+
await sleepWithAbort(waitMs, signal, sleepFn, 'Antigravity retry wait aborted');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
110
183
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
12
12
|
import { fileMtimeMs, normalizeExpiresAtMs, scrubOAuthSecrets } from './lib/oauth-token-utils.mjs';
|
|
13
13
|
import { OpenAICompatProvider, preloadOpenAICompatRuntime } from './openai-compat.mjs';
|
|
14
|
+
import { COMPAT_RETRY_POLICY } from './openai-compat-retry-profile.mjs';
|
|
14
15
|
import { listCompatModels } from './openai-compat-models.mjs';
|
|
15
16
|
import { AnthropicProvider } from './anthropic.mjs';
|
|
16
17
|
import { toInclusiveOpenCodeGoUsage as toInclusiveUsage } from './opencode-go.mjs';
|
|
@@ -242,6 +243,8 @@ export class CommandCodeOAuthProvider {
|
|
|
242
243
|
// OpenAI-compatible usage: prompt_tokens includes cached. See registry.mjs.
|
|
243
244
|
static inputExcludesCache = false;
|
|
244
245
|
name = PROVIDER_ID;
|
|
246
|
+
// The retry profile's limits are final: no loop-level recovery cycles on top.
|
|
247
|
+
retryPolicy = COMPAT_RETRY_POLICY;
|
|
245
248
|
config;
|
|
246
249
|
tokens = null;
|
|
247
250
|
_inner = null;
|
|
@@ -8,6 +8,7 @@ import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
|
8
8
|
import { openInBrowser } from '../../../shared/open-url.mjs';
|
|
9
9
|
import { createOAuthPkce } from './lib/oauth-pkce.mjs';
|
|
10
10
|
import { accountIdentityFields, normalizeAccountIdentity } from './lib/oauth-token-utils.mjs';
|
|
11
|
+
import { retryAfterMsFromError } from './retry-classifier.mjs';
|
|
11
12
|
|
|
12
13
|
const LOGIN_URL = 'https://cursor.com/loginDeepControl';
|
|
13
14
|
const POLL_URL = 'https://api2.cursor.sh/auth/poll';
|
|
@@ -17,6 +18,23 @@ const REFRESH_SKEW_MS = 5 * 60_000;
|
|
|
17
18
|
// abort signal, which must never govern work every other waiter depends on.
|
|
18
19
|
const REFRESH_TIMEOUT_MS = 30_000;
|
|
19
20
|
const refreshesInFlight = new Map();
|
|
21
|
+
export const REAUTH_GUIDANCE = 'Open /providers in Mixdog to sign in again.';
|
|
22
|
+
// The refresh credential itself was rejected.
|
|
23
|
+
const PERMANENT_REFRESH_STATUS = new Set([400, 401, 403]);
|
|
24
|
+
const EXCHANGE_STATUS_CODE = Object.freeze({
|
|
25
|
+
400: 'invalid_argument',
|
|
26
|
+
401: 'unauthenticated',
|
|
27
|
+
403: 'permission_denied',
|
|
28
|
+
429: 'resource_exhausted',
|
|
29
|
+
500: 'internal',
|
|
30
|
+
502: 'unavailable',
|
|
31
|
+
503: 'unavailable',
|
|
32
|
+
504: 'deadline_exceeded',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function reauthError(message) {
|
|
36
|
+
return Object.assign(new Error(message), { reauthRequired: true });
|
|
37
|
+
}
|
|
20
38
|
|
|
21
39
|
// Wait on the shared refresh while still honouring THIS caller's abort signal.
|
|
22
40
|
// Aborting only ends this caller's wait; the exchange keeps running for the
|
|
@@ -197,8 +215,25 @@ export async function exchangeCursorToken(token, { fetchFn = fetch, signal } = {
|
|
|
197
215
|
});
|
|
198
216
|
if (!response.ok) {
|
|
199
217
|
const detail = await response.text().catch(() => '');
|
|
200
|
-
|
|
218
|
+
// Only a credential verdict is permanent; throttles, 5xx and network
|
|
219
|
+
// failures leave the refresh token valid and stay retryable upstream.
|
|
220
|
+
const reauthRequired = PERMANENT_REFRESH_STATUS.has(response.status);
|
|
221
|
+
const error = new Error(
|
|
222
|
+
`Cursor token exchange failed (${response.status})${detail ? `: ${detail}` : ''}${
|
|
223
|
+
reauthRequired ? `. ${REAUTH_GUIDANCE}` : ''
|
|
224
|
+
}`
|
|
225
|
+
);
|
|
201
226
|
error.status = response.status;
|
|
227
|
+
error.httpStatus = response.status;
|
|
228
|
+
const code = EXCHANGE_STATUS_CODE[response.status];
|
|
229
|
+
if (code) error.code = code;
|
|
230
|
+
const headers = Object.fromEntries(response.headers?.entries?.() ?? []);
|
|
231
|
+
if (Object.keys(headers).length) {
|
|
232
|
+
error.headers = headers;
|
|
233
|
+
const retryAfterMs = retryAfterMsFromError(error);
|
|
234
|
+
if (Number.isFinite(retryAfterMs)) error.retryAfterMs = retryAfterMs;
|
|
235
|
+
}
|
|
236
|
+
if (reauthRequired) error.reauthRequired = true;
|
|
202
237
|
throw error;
|
|
203
238
|
}
|
|
204
239
|
const data = await response.json();
|
|
@@ -277,9 +312,7 @@ export async function resolveCursorOAuthAccessToken({ forceRefresh = false, fetc
|
|
|
277
312
|
if (!forceRefresh && !expiring) return tokens.access_token;
|
|
278
313
|
if (!tokens.refresh_token) {
|
|
279
314
|
if (!tokens.expires_at || tokens.expires_at > Date.now()) return tokens.access_token;
|
|
280
|
-
throw
|
|
281
|
-
'Cursor access token expired and has no refresh token. Open /providers in Mixdog to sign in again.'
|
|
282
|
-
);
|
|
315
|
+
throw reauthError(`Cursor access token expired and has no refresh token. ${REAUTH_GUIDANCE}`);
|
|
283
316
|
}
|
|
284
317
|
const refreshPath = credentialsPath();
|
|
285
318
|
let refreshInFlight = refreshesInFlight.get(refreshPath);
|
|
@@ -296,7 +329,7 @@ export async function resolveCursorOAuthAccessToken({ forceRefresh = false, fetc
|
|
|
296
329
|
return latest;
|
|
297
330
|
}
|
|
298
331
|
if (!latest.refresh_token) {
|
|
299
|
-
throw
|
|
332
|
+
throw reauthError(`Cursor OAuth refresh token is unavailable. ${REAUTH_GUIDANCE}`);
|
|
300
333
|
}
|
|
301
334
|
// Deliberately NOT the caller's signal: this exchange is shared by
|
|
302
335
|
// every concurrent waiter, so letting the first caller's abort
|
|
@@ -4,7 +4,7 @@ import { createRemoteVersionSource } from './npm-cli-version.mjs';
|
|
|
4
4
|
// documented version endpoint; the official installer script embeds the
|
|
5
5
|
// current build (…/lab/<build>/…), parsed best-effort. Any format change or
|
|
6
6
|
// failure falls back to the floor.
|
|
7
|
-
export const CURSOR_CLIENT_VERSION_FLOOR = 'cli-2026.10.
|
|
7
|
+
export const CURSOR_CLIENT_VERSION_FLOOR = 'cli-2026.10.14-0f9edde';
|
|
8
8
|
const BUILD_PATTERN = /downloads\.cursor\.com\/lab\/(\d{4}\.\d{2}\.\d{2}-[0-9a-f]{6,40})\//;
|
|
9
9
|
export const CURSOR_INSTALL_URL = 'https://cursor.com/install';
|
|
10
10
|
|