mixdog 1.3.3 → 1.3.5
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/LICENSES/oh-my-pi-MIT.txt +23 -0
- package/NOTICE.md +15 -0
- package/README.md +11 -0
- package/package.json +3 -2
- package/scripts/embed-google-oauth-client.mjs +49 -0
- package/scripts/lib/test-spawn-ledger.mjs +46 -1
- package/scripts/test.mjs +5 -0
- package/src/defaults/shipped-history.json +8 -0
- package/src/defaults/skills/browser-use/SKILL.md +21 -9
- package/src/defaults/skills/setup/SKILL.md +7 -2
- package/src/defaults/skills/setup/references/actions.md +82 -27
- package/src/defaults/skills/setup/references/surfaces.md +67 -34
- package/src/defaults/skills/setup-guide/SKILL.md +3 -3
- package/src/lib/keychain-cjs.cjs +359 -29
- package/src/rules/agent-mode/AGENT_MODE.md +10 -2
- package/src/rules/agent-mode/MIXY.md +12 -0
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
- package/src/runtime/agent/orchestrator/config.mjs +18 -0
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +2 -2
- package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +68 -23
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +22 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
- package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
- package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +50 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
- package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +125 -12
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +7 -1
- package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
- package/src/runtime/channels/lib/scheduler.mjs +109 -14
- package/src/runtime/channels/lib/schedules-db.mjs +49 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +13 -0
- package/src/runtime/channels/lib/worker-main.mjs +9 -0
- package/src/runtime/office/com/com-adapter.mjs +17 -1
- package/src/runtime/office/com/office-com-host.ps1 +51 -1
- package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
- package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
- package/src/runtime/office/pdf/document-preview.mjs +326 -8
- package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
- package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
- package/src/runtime/office/portable/portable-soffice.mjs +45 -5
- package/src/runtime/shared/agent-room-host.mjs +1 -1
- package/src/runtime/shared/bot-hops.mjs +62 -2
- package/src/runtime/shared/config.mjs +49 -22
- package/src/runtime/shared/connector-catalog.d.mts +4 -2
- package/src/runtime/shared/connector-catalog.mjs +16 -8
- package/src/runtime/shared/developer-options.mjs +32 -1
- package/src/runtime/shared/google-oauth-client.generated.json +4 -0
- package/src/runtime/shared/google-oauth-client.mjs +34 -9
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +4 -383
- package/src/runtime/shared/llm/well-formed.mjs +66 -0
- package/src/runtime/shared/provider-accounts.mjs +224 -58
- package/src/runtime/shared/provider-api-key.mjs +19 -8
- package/src/runtime/shared/routine-trigger.mjs +42 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/transcript-metadata.mjs +38 -0
- package/src/runtime/shared/user-data-guard.mjs +30 -2
- package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
- package/src/session-runtime/agent-home-delivery.mjs +57 -13
- package/src/session-runtime/agent-room-delivery.mjs +28 -1
- package/src/session-runtime/agent-task-sessions.mjs +292 -79
- package/src/session-runtime/agent-tool-routing.mjs +49 -4
- package/src/session-runtime/boot/begin.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +4 -0
- package/src/session-runtime/bot-chain.mjs +32 -0
- package/src/session-runtime/bot-status.mjs +7 -4
- package/src/session-runtime/channel-config-api.mjs +63 -17
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
- package/src/session-runtime/internal-tool-executor.mjs +4 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -1
- package/src/session-runtime/mixy-heartbeat.mjs +807 -0
- package/src/session-runtime/model-route/route-tuning.mjs +10 -6
- package/src/session-runtime/owned-automation-run.mjs +33 -12
- package/src/session-runtime/provider-auth/login.mjs +7 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
- package/src/session-runtime/routine-home-turn.mjs +33 -6
- package/src/session-runtime/schedule-session-run.mjs +7 -3
- package/src/session-runtime/services/agent-tool/close-flow.mjs +14 -2
- package/src/session-runtime/services/agent-tool/execute.mjs +6 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +11 -2
- package/src/session-runtime/services/agent-tool/render.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
- package/src/session-runtime/services/agent-tool/tool-def.mjs +1 -1
- package/src/session-runtime/services/channel-admin.mjs +66 -18
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +128 -25
- package/src/session-runtime/services/schedule-access.mjs +14 -0
- package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
- package/src/session-runtime/session-create/session-options.mjs +7 -4
- package/src/session-runtime/settings-system-api.mjs +16 -1
- package/src/session-runtime/setup-tool/executor.mjs +49 -14
- package/src/session-runtime/setup-tool/extended-actions.mjs +29 -3
- package/src/session-runtime/setup-tool/settings-contract.mjs +65 -4
- package/src/session-runtime/setup-tool/tool-defs.mjs +30 -3
- package/src/session-runtime/task-session-route.mjs +99 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +5 -2
- package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
- package/src/session-runtime/tool-surface.mjs +4 -0
- package/src/session-runtime/usage-stats-api.mjs +1 -1
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
- package/src/session-runtime/workspace/transitions.mjs +10 -2
- package/src/standalone/daemon-session-control.mjs +88 -20
- package/src/standalone/daemon.mjs +8 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
- package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
- package/src/standalone/session-service.mjs +13 -0
- package/src/standalone/session-tool-output.mjs +8 -5
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/dist/index.mjs +335 -172
- package/src/tui/session/notice-surface.mjs +17 -1
- package/src/tui/session/session-api/integrations.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +8 -0
- package/src/tui/session/session-api/settings.mjs +14 -2
- package/src/tui/session/turn-stream.mjs +17 -1
- package/src/tui/session/turn.mjs +22 -2
- package/src/vendor/statusline/src/gateway/claude-current.mjs +0 -58
- package/src/vendor/statusline/src/gateway/route-meta.mjs +1 -361
- package/src/vendor/statusline/src/gateway/session-routes.mjs +0 -55
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +0 -285
|
@@ -9,11 +9,14 @@ import {
|
|
|
9
9
|
providerAccountExhaustedForModelOnly,
|
|
10
10
|
providerAccountPolicy,
|
|
11
11
|
providerAccountsResetAt,
|
|
12
|
+
providerAccountWithBlocks,
|
|
13
|
+
providerAccountBlockedUntil,
|
|
14
|
+
staleQuotaWindows,
|
|
12
15
|
} from '../../../shared/provider-accounts.mjs';
|
|
13
16
|
import { withProviderAccount, hasExplicitProviderAuthBinding } from '../../../shared/provider-auth-binding.mjs';
|
|
14
17
|
import { isExplicitUserAbortError, markProviderRecoveryExhausted } from './retry-classifier.mjs';
|
|
15
18
|
import { sleepWithAbort } from './retry-backoff.mjs';
|
|
16
|
-
import { QUOTA_KINDS, providerErrorKind } from './provider-error-kind.mjs';
|
|
19
|
+
import { QUOTA_KINDS, boundedCauseChain, providerErrorKind } from './provider-error-kind.mjs';
|
|
17
20
|
import { fetchOAuthUsageSnapshot, readCachedOAuthUsageSnapshot } from './oauth-usage.mjs';
|
|
18
21
|
|
|
19
22
|
// Only an explicit quota / plan / billing refusal advances the roster as
|
|
@@ -124,15 +127,18 @@ function accountsExhaustedError(useCredits) {
|
|
|
124
127
|
// evidence): a rate limit, advising the earliest cooldown end as the wait.
|
|
125
128
|
// The pool already spent its cooldown wait, so this is a terminal verdict for
|
|
126
129
|
// the loop, not an unclassified failure.
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
// A row's cooldown for `model` ends when every block holding that model lifts.
|
|
131
|
+
const coolingUntil = (row, model) => providerAccountBlockedUntil(row, model) ?? 0;
|
|
132
|
+
|
|
133
|
+
function earliestCooldownMs(rows, now, model) {
|
|
134
|
+
const ends = rows.map((row) => coolingUntil(row, model)).filter((until) => until > now);
|
|
129
135
|
return ends.length ? Math.min(...ends) - now : null;
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
function accountsCoolingDownError(rows, now) {
|
|
138
|
+
function accountsCoolingDownError(rows, now, model) {
|
|
133
139
|
const error = new Error('Every connected account is cooling down after a rate limit. Try again shortly.');
|
|
134
140
|
error.code = 'rate_limited';
|
|
135
|
-
const wait = earliestCooldownMs(rows, now);
|
|
141
|
+
const wait = earliestCooldownMs(rows, now, model);
|
|
136
142
|
if (wait != null) error.retryAfterMs = wait;
|
|
137
143
|
return markProviderRecoveryExhausted(error, { owner: 'account-pool', terminal: true });
|
|
138
144
|
}
|
|
@@ -258,6 +264,36 @@ function recordServingAccount(providerName, result, id, bindingId) {
|
|
|
258
264
|
};
|
|
259
265
|
}
|
|
260
266
|
|
|
267
|
+
// A block covers the quota bucket of the model that failed, which only a usage
|
|
268
|
+
// reading names. An account without a recorded reading is measured once before
|
|
269
|
+
// it is blocked — once per account for this process, whatever the outcome,
|
|
270
|
+
// through the ordinary cache (a fresh failed reading is not repeated) — and
|
|
271
|
+
// again only after a reading has recorded windows and they were dropped.
|
|
272
|
+
// Without a reading the block stays account-wide.
|
|
273
|
+
const measureAttempted = new Set();
|
|
274
|
+
|
|
275
|
+
async function measureOnce(providerName, row, account) {
|
|
276
|
+
const key = `${providerName}:${row.id}`;
|
|
277
|
+
if (row.usage?.windows?.length) {
|
|
278
|
+
measureAttempted.delete(key);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (measureAttempted.has(key)) return;
|
|
282
|
+
measureAttempted.add(key);
|
|
283
|
+
await fetchOAuthUsageSnapshot({ provider: providerName, accountId: row.id }, account(row.id), () => {}).catch(
|
|
284
|
+
() => null
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// The server's id of the limit a refusal names (a Codex model-specific limit),
|
|
289
|
+
// stamped on the error by the provider boundary.
|
|
290
|
+
function quotaLimitOf(error) {
|
|
291
|
+
for (const item of boundedCauseChain(error)) {
|
|
292
|
+
if (typeof item?.quotaLimit === 'string' && item.quotaLimit) return item.quotaLimit;
|
|
293
|
+
}
|
|
294
|
+
return '';
|
|
295
|
+
}
|
|
296
|
+
|
|
261
297
|
// Whether the failed account's quota window is exhausted, plus the server's
|
|
262
298
|
// retry delay. Anthropic and other subscriptions may use the same 429 code
|
|
263
299
|
// for burst throttling and exhausted subscription windows, so the native
|
|
@@ -272,7 +308,7 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
|
|
|
272
308
|
const policy = cooldownPolicyOf(account(row.id));
|
|
273
309
|
const floorMs = policy ? finiteMs(policy.floorMs, 0) : 0;
|
|
274
310
|
const blockUntil = (withReset, reason = 'unknown') => {
|
|
275
|
-
if (withReset && resetAt > now) return { until: resetAt, source: 'retry-after' };
|
|
311
|
+
if (withReset && resetAt > now) return { until: resetAt, source: 'retry-after', reset: true };
|
|
276
312
|
if (delay > 0) return { until: now + Math.max(delay, floorMs), source: 'retry-after' };
|
|
277
313
|
if (policy) {
|
|
278
314
|
const ms = policyCooldownMs(policy, `${providerName}:${row.id}`, reason);
|
|
@@ -298,10 +334,14 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
|
|
|
298
334
|
};
|
|
299
335
|
}
|
|
300
336
|
const typed = isAccountQuotaError(error);
|
|
301
|
-
if (typed)
|
|
337
|
+
if (typed) {
|
|
338
|
+
await measureOnce(providerName, row, account);
|
|
339
|
+
return { exhausted: true, delay, evidence: true, ...blockUntil(true, 'quota') };
|
|
340
|
+
}
|
|
302
341
|
// A policy with rotateOnRateLimit rotates on a plain rate limit too (the
|
|
303
342
|
// advice as its cooldown, else rateLimitedMs); never quota evidence.
|
|
304
343
|
if (policy?.rotateOnRateLimit === true && kind === 'rate_limited' && status === 429) {
|
|
344
|
+
await measureOnce(providerName, row, account);
|
|
305
345
|
return { exhausted: true, delay, evidence: false, ...blockUntil(false, 'rate_limited') };
|
|
306
346
|
}
|
|
307
347
|
let exhausted = status === 429 && delay > QUOTA_WINDOW_RETRY_AFTER_MS;
|
|
@@ -315,7 +355,7 @@ async function accountQuotaExhausted(providerName, row, model, error, account) {
|
|
|
315
355
|
const pool = readProviderAccountPool(providerName);
|
|
316
356
|
const entry = pool.accounts.find((item) => item.id === row.id);
|
|
317
357
|
const options = { useCredits: pool.useCredits === true };
|
|
318
|
-
evidence = providerAccountExhausted(
|
|
358
|
+
evidence = providerAccountExhausted(providerAccountWithBlocks(entry), Date.now(), model, options);
|
|
319
359
|
exhausted = exhausted || evidence || providerAccountExhausted(entry, Date.now(), model, options);
|
|
320
360
|
}
|
|
321
361
|
return { exhausted, delay, evidence, ...(exhausted ? blockUntil(evidence, evidence ? 'quota' : 'unknown') : {}) };
|
|
@@ -350,7 +390,7 @@ function poolSend(providerName, account) {
|
|
|
350
390
|
rows.length > 0 &&
|
|
351
391
|
rows.every((entry) =>
|
|
352
392
|
providerAccountExhausted(
|
|
353
|
-
evidenced.has(entry.id)
|
|
393
|
+
evidenced.has(entry.id) ? entry : providerAccountWithBlocks(entry, (block) => block.blockedQuota === true),
|
|
354
394
|
now,
|
|
355
395
|
model,
|
|
356
396
|
opts
|
|
@@ -363,7 +403,7 @@ function poolSend(providerName, account) {
|
|
|
363
403
|
if (all) {
|
|
364
404
|
return quotaExhausted(error || accountsExhaustedError(opts.useCredits), providerName, model, rows, opts);
|
|
365
405
|
}
|
|
366
|
-
return error || accountsCoolingDownError(rows, now);
|
|
406
|
+
return error || accountsCoolingDownError(rows, now, model);
|
|
367
407
|
};
|
|
368
408
|
// Accounts this send blocked on a rate-limit cooldown, the time it spent
|
|
369
409
|
// waiting for cooldowns, and the provider's wait bound.
|
|
@@ -381,8 +421,9 @@ function poolSend(providerName, account) {
|
|
|
381
421
|
const bound = waitBoundMs(ids);
|
|
382
422
|
// Persisting a short cooldown can outlast its deadline. Release only
|
|
383
423
|
// attempts cooled by this send, and only under an enabled wait policy.
|
|
384
|
-
const expired = bound > 0 && rows.some((row) => cooled.has(row.id) && !(
|
|
385
|
-
|
|
424
|
+
const expired = bound > 0 && rows.some((row) => cooled.has(row.id) && !(coolingUntil(row, model) > now));
|
|
425
|
+
let wait = null;
|
|
426
|
+
if (!all) wait = expired ? 0 : earliestCooldownMs(rows, now, model);
|
|
386
427
|
if (wait == null || waitedMs + wait > bound) throw markExhausted(error, ids);
|
|
387
428
|
options.onStageChange?.('reconnecting', {
|
|
388
429
|
message: `Rate limited — every account is cooling down; retrying in ${Math.ceil(wait / 1000)}s`,
|
|
@@ -397,7 +438,7 @@ function poolSend(providerName, account) {
|
|
|
397
438
|
waitedMs += wait;
|
|
398
439
|
const lifted = Date.now();
|
|
399
440
|
for (const row of readProviderAccountPool(providerName).accounts) {
|
|
400
|
-
if (cooled.has(row.id) && !(
|
|
441
|
+
if (cooled.has(row.id) && !(coolingUntil(row, model) > lifted)) {
|
|
401
442
|
cooled.delete(row.id);
|
|
402
443
|
attempted.delete(row.id);
|
|
403
444
|
}
|
|
@@ -409,7 +450,8 @@ function poolSend(providerName, account) {
|
|
|
409
450
|
if (!pool.accounts.length) {
|
|
410
451
|
if (pool.useCredits !== true) {
|
|
411
452
|
const stored = readCachedOAuthUsageSnapshot({ provider: providerName, model: '' });
|
|
412
|
-
|
|
453
|
+
// A snapshot cached before per-model gating is stale evidence.
|
|
454
|
+
if (stored?.quotaWindows?.length && !staleQuotaWindows(providerName, stored.quotaWindows)) {
|
|
413
455
|
const row = { usage: { windows: stored.quotaWindows, checkedAt: stored.cachedAt } };
|
|
414
456
|
if (providerAccountExhausted(row, Date.now(), model)) {
|
|
415
457
|
throw quotaExhausted(accountsExhaustedError(false), providerName, model, [row]);
|
|
@@ -480,7 +522,7 @@ function poolSend(providerName, account) {
|
|
|
480
522
|
options.onStageChange?.('reconnecting', { message: 'Sign-in expired — switching to the next account' });
|
|
481
523
|
continue;
|
|
482
524
|
}
|
|
483
|
-
const { exhausted, evidence, until, source, accountBlocked } = await accountQuotaExhausted(
|
|
525
|
+
const { exhausted, evidence, until, source, reset, accountBlocked } = await accountQuotaExhausted(
|
|
484
526
|
providerName,
|
|
485
527
|
row,
|
|
486
528
|
model,
|
|
@@ -490,7 +532,13 @@ function poolSend(providerName, account) {
|
|
|
490
532
|
if (options.signal?.aborted || !exhausted) throw error;
|
|
491
533
|
if (evidence) evidenced.add(row.id);
|
|
492
534
|
else cooled.add(row.id);
|
|
493
|
-
|
|
535
|
+
// An account-level block holds every model, so it carries no model or
|
|
536
|
+
// limit scope.
|
|
537
|
+
await blockProviderAccount(providerName, row.id, until, accountBlocked ? '' : model, source, {
|
|
538
|
+
quota: evidence === true,
|
|
539
|
+
reset: reset === true,
|
|
540
|
+
limit: accountBlocked ? '' : quotaLimitOf(error),
|
|
541
|
+
});
|
|
494
542
|
// An account block is unsafe to re-issue on THAT account only; with no
|
|
495
543
|
// output emitted, the next account may serve the same request.
|
|
496
544
|
if (
|
|
@@ -509,13 +557,10 @@ function poolSend(providerName, account) {
|
|
|
509
557
|
lastError = error;
|
|
510
558
|
switched = true;
|
|
511
559
|
refusalScoped = accountExhaustedForModelOnly(providerName, row.id, model);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
? 'Account blocked — switching to the next account'
|
|
517
|
-
: 'Rate limited (cooldown) — switching to the next account',
|
|
518
|
-
});
|
|
560
|
+
let reason = 'Rate limited (cooldown)';
|
|
561
|
+
if (evidence) reason = 'Quota exhausted';
|
|
562
|
+
else if (accountBlocked) reason = 'Account blocked';
|
|
563
|
+
options.onStageChange?.('reconnecting', { message: `${reason} — switching to the next account` });
|
|
519
564
|
}
|
|
520
565
|
}
|
|
521
566
|
throw markExhausted(lastError || new Error('Provider account attempts exhausted.'));
|
|
@@ -2,6 +2,13 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { PRIORITY_RANK, normalizePriority, resourceAdmission } from '../../../shared/resource-admission.mjs';
|
|
4
4
|
import { accountProviderSend } from '../../../shared/llm/usage-accounting.mjs';
|
|
5
|
+
import { sanitizeSurrogates } from '../../../shared/llm/well-formed.mjs';
|
|
6
|
+
import { providerAvailable, providerUnavailableError } from './provider-availability.mjs';
|
|
7
|
+
import {
|
|
8
|
+
finalizeProviderRequestTools,
|
|
9
|
+
isFinalizedProviderRequestTools,
|
|
10
|
+
providerNativeToolPrefixCount,
|
|
11
|
+
} from '../runtime-core/provider-request-tools.mjs';
|
|
5
12
|
|
|
6
13
|
// Normal provider traffic is not concurrency-gated. Independent sessions and
|
|
7
14
|
// accounts start immediately; a finite limit exists only when an operator
|
|
@@ -518,7 +525,18 @@ export function wrapProviderAdmission(provider, providerName, scheduler = provid
|
|
|
518
525
|
if (!provider || typeof provider.send !== 'function' || provider[WRAPPED]) return provider;
|
|
519
526
|
const originalSend = provider.send;
|
|
520
527
|
Object.defineProperty(provider, WRAPPED, { value: true });
|
|
521
|
-
provider.send = function admittedProviderSend(
|
|
528
|
+
provider.send = function admittedProviderSend(rawMessages, model, rawTools, sendOpts) {
|
|
529
|
+
// Every registered provider (and its account-pool / proxy / custom variants)
|
|
530
|
+
// is wrapped here, so this is the one point all model requests pass before
|
|
531
|
+
// serialization. A lone surrogate would make the API reject the body on
|
|
532
|
+
// every send; only the outgoing request is rewritten, never the transcript.
|
|
533
|
+
const messages = sanitizeSurrogates(rawMessages);
|
|
534
|
+
let tools = sanitizeSurrogates(rawTools);
|
|
535
|
+
// A finalized tool list is recognised by identity; its sanitized copy must
|
|
536
|
+
// stay finalized with the same native prefix (the model proxy reads both).
|
|
537
|
+
if (tools !== rawTools && isFinalizedProviderRequestTools(rawTools)) {
|
|
538
|
+
tools = finalizeProviderRequestTools(tools, providerNativeToolPrefixCount(rawTools));
|
|
539
|
+
}
|
|
522
540
|
const opts = sendOpts || {};
|
|
523
541
|
const signal = opts.signal || null;
|
|
524
542
|
const key = providerAdmissionKey(providerName, this);
|
|
@@ -530,6 +548,9 @@ export function wrapProviderAdmission(provider, providerName, scheduler = provid
|
|
|
530
548
|
scheduler.run(
|
|
531
549
|
key,
|
|
532
550
|
(admissionSignal, admissionMetrics) => {
|
|
551
|
+
// The Developer option may have been switched off after lookup or
|
|
552
|
+
// while this request waited: the gate is rechecked right before send.
|
|
553
|
+
if (!providerAvailable(providerName)) throw providerUnavailableError(providerName);
|
|
533
554
|
// Admission is the common request-clock boundary for WS/SSE/HTTP.
|
|
534
555
|
// Queue wait therefore cannot consume first-byte or agent-watchdog
|
|
535
556
|
// time. Provider-local retry remains the sole retry owner.
|
|
@@ -18,13 +18,17 @@ import { applyAnthropicEffortToBody, shouldIncludeEffortBeta } from './anthropic
|
|
|
18
18
|
import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
|
|
19
19
|
import {
|
|
20
20
|
anthropicThinkingActive,
|
|
21
|
+
anthropicTurnScopedReminders,
|
|
21
22
|
appendAnthropicTailReminder,
|
|
23
|
+
appendAnthropicTurnReminder,
|
|
22
24
|
applyAnthropicCacheMarkers,
|
|
23
25
|
clampAnthropicThinkingBudget as clampThinkingBudgetTokens,
|
|
26
|
+
hasAnthropicTurnScopedMessages,
|
|
24
27
|
resolveAnthropicCacheTtls as resolveCacheTtls,
|
|
25
28
|
resolveAnthropicMessageCacheSlots,
|
|
26
29
|
sharedCachePrefixBoundary,
|
|
27
30
|
toAnthropicToolChoice,
|
|
31
|
+
withAnthropicTurnReminders,
|
|
28
32
|
} from './lib/anthropic-request-utils.mjs';
|
|
29
33
|
import { splitTailReminder } from './lib/tail-reminder-split.mjs';
|
|
30
34
|
import {
|
|
@@ -97,12 +101,17 @@ export async function buildAnthropicApiRequest({
|
|
|
97
101
|
// are applied to the FINAL sanitized array by invariant, so block
|
|
98
102
|
// drops / inserts / reorders performed by the sanitizer can never move
|
|
99
103
|
// or delete a marked block. NEVER sanitize again after this.
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
// Turn-scoped tail reminders (tail-reminder-split.mjs): stored reminders are
|
|
105
|
+
// re-sent before their assistant turns, the current one ends the request.
|
|
106
|
+
const turnScoped = anthropicTurnScopedReminders(name, useModel, config);
|
|
107
|
+
const lowerHistory = (history, projection) => {
|
|
108
|
+
const lowered = lowerAnthropicEffortHistory(
|
|
102
109
|
history.filter((m) => m.role !== 'system'),
|
|
103
110
|
(segment) => toAnthropicMessages(segment, requestTools, name),
|
|
104
111
|
projection
|
|
105
112
|
);
|
|
113
|
+
return turnScoped ? withAnthropicTurnReminders(lowered) : lowered;
|
|
114
|
+
};
|
|
106
115
|
const anthropicMessages = applyAnthropicCacheMarkers(
|
|
107
116
|
lowerHistory(chatMsgs, effortProjection),
|
|
108
117
|
messageCacheSlots,
|
|
@@ -115,9 +124,12 @@ export async function buildAnthropicApiRequest({
|
|
|
115
124
|
model: useModel,
|
|
116
125
|
max_tokens: maxTokens,
|
|
117
126
|
system: systemBlocks.length ? systemBlocks : undefined,
|
|
118
|
-
messages: await prepareAnthropicImages(
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
messages: await prepareAnthropicImages(
|
|
128
|
+
turnScoped
|
|
129
|
+
? appendAnthropicTurnReminder(anthropicMessages, reminder)
|
|
130
|
+
: appendAnthropicTailReminder(anthropicMessages, reminder),
|
|
131
|
+
{ signal: opts.signal }
|
|
132
|
+
),
|
|
121
133
|
};
|
|
122
134
|
// Server-side fallback and fast mode are decided from Claude model ids:
|
|
123
135
|
// first-party knowledge, never applied to a custom endpoint.
|
|
@@ -255,6 +267,7 @@ function requestHeadersFor(
|
|
|
255
267
|
effort: isCustom ? Boolean(params.output_config?.effort) : shouldIncludeEffortBeta(capabilityModel, opts),
|
|
256
268
|
serverFallback: params.fallbacks === 'default',
|
|
257
269
|
thinkingDisplayUpdates: params.thinking?.display === 'updates',
|
|
270
|
+
turnScopedSystem: hasAnthropicTurnScopedMessages(params.messages),
|
|
258
271
|
}),
|
|
259
272
|
...(effortProjection ? [EFFORT_CONFIGURATION_BETA] : []),
|
|
260
273
|
].filter(Boolean);
|
|
@@ -10,7 +10,7 @@ import { createNpmVersionSource, maxSemver } from './npm-cli-version.mjs';
|
|
|
10
10
|
// source release just to advance the user-agent version.
|
|
11
11
|
// Claude Opus 5.5 requires 2.1.280 or newer. The release workflow raises this
|
|
12
12
|
// to the published CLI version (scripts/sync-client-version-floors.mjs).
|
|
13
|
-
export const DEFAULT_CLI_VERSION = '2.1.
|
|
13
|
+
export const DEFAULT_CLI_VERSION = '2.1.310';
|
|
14
14
|
export const CLAUDE_CLI_NPM_PACKAGE = '@anthropic-ai/claude-code';
|
|
15
15
|
|
|
16
16
|
const CACHE_SCHEMA_VERSION = 1;
|
|
@@ -61,10 +61,13 @@ import { ANTHROPIC_EFFORT_LEVELS, withEffortRejectionFallback } from './effort-r
|
|
|
61
61
|
import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
62
62
|
import {
|
|
63
63
|
anthropicThinkingActive,
|
|
64
|
+
anthropicTurnScopedReminders,
|
|
64
65
|
appendAnthropicTailReminder,
|
|
66
|
+
appendAnthropicTurnReminder,
|
|
65
67
|
applyAnthropicCacheMarkers,
|
|
66
68
|
clampAnthropicThinkingBudget as clampThinkingBudgetTokens,
|
|
67
69
|
deferredAnthropicTools as sharedDeferredAnthropicTools,
|
|
70
|
+
hasAnthropicTurnScopedMessages,
|
|
68
71
|
requestAnthropicTools as sharedRequestAnthropicTools,
|
|
69
72
|
resolveAnthropicCacheTtls as resolveCacheTtls,
|
|
70
73
|
resolveAnthropicMessageCacheSlots,
|
|
@@ -72,6 +75,7 @@ import {
|
|
|
72
75
|
sharedCachePrefixBoundary,
|
|
73
76
|
toAnthropicMessages,
|
|
74
77
|
toAnthropicToolChoice,
|
|
78
|
+
withAnthropicTurnReminders,
|
|
75
79
|
} from './lib/anthropic-request-utils.mjs';
|
|
76
80
|
import { splitTailReminder } from './lib/tail-reminder-split.mjs';
|
|
77
81
|
import {
|
|
@@ -111,6 +115,7 @@ function buildOAuthBetaHeaders(body, { fastMode = false, toolSearch = false, mod
|
|
|
111
115
|
effort: shouldIncludeEffortBeta(model, opts),
|
|
112
116
|
serverFallback: body?.fallbacks === 'default',
|
|
113
117
|
thinkingDisplayUpdates: body?.thinking?.display === 'updates',
|
|
118
|
+
turnScopedSystem: hasAnthropicTurnScopedMessages(body?.messages),
|
|
114
119
|
});
|
|
115
120
|
}
|
|
116
121
|
|
|
@@ -223,12 +228,17 @@ function buildRequestBody(requestMessages, model, tools, sendOpts) {
|
|
|
223
228
|
// applied to the FINAL sanitized array by invariant, so block drops /
|
|
224
229
|
// inserts / reorders performed by the sanitizer can never move or delete a
|
|
225
230
|
// marked block. NEVER sanitize again after this (see send path).
|
|
226
|
-
|
|
227
|
-
|
|
231
|
+
// Turn-scoped tail reminders (tail-reminder-split.mjs): stored reminders are
|
|
232
|
+
// re-sent before their assistant turns, the current one ends the request.
|
|
233
|
+
const turnScoped = anthropicTurnScopedReminders('anthropic-oauth', model);
|
|
234
|
+
const lowerHistory = (history, projection) => {
|
|
235
|
+
const lowered = lowerAnthropicEffortHistory(
|
|
228
236
|
history.filter((m) => m.role !== 'system'),
|
|
229
237
|
(segment) => toAnthropicMessages(segment, requestTools),
|
|
230
238
|
projection
|
|
231
239
|
);
|
|
240
|
+
return turnScoped ? withAnthropicTurnReminders(lowered) : lowered;
|
|
241
|
+
};
|
|
232
242
|
const anthropicMessages = applyAnthropicCacheMarkers(
|
|
233
243
|
lowerHistory(chatMsgs, effortProjection),
|
|
234
244
|
messageCacheSlots,
|
|
@@ -240,7 +250,9 @@ function buildRequestBody(requestMessages, model, tools, sendOpts) {
|
|
|
240
250
|
const body = {
|
|
241
251
|
model,
|
|
242
252
|
max_tokens: maxTokens,
|
|
243
|
-
messages:
|
|
253
|
+
messages: turnScoped
|
|
254
|
+
? appendAnthropicTurnReminder(anthropicMessages, reminder)
|
|
255
|
+
: appendAnthropicTailReminder(anthropicMessages, reminder),
|
|
244
256
|
stream: true,
|
|
245
257
|
};
|
|
246
258
|
applyAnthropicServerFallback(body, model, {
|
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
} from './antigravity-oauth-tokens.mjs';
|
|
24
24
|
|
|
25
25
|
const ANTIGRAVITY_MODEL_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
26
|
-
|
|
26
|
+
// v2: rows keep the gateway-declared per-model `maxOutputTokens`.
|
|
27
|
+
const ANTIGRAVITY_MODEL_CACHE_VERSION = 2;
|
|
27
28
|
const FETCH_MODELS_TIMEOUT_MS = 15_000;
|
|
28
29
|
// Tier labels the gateway puts in parentheses, in effort order.
|
|
29
30
|
const TIER_EFFORTS = { 'extra low': 'minimal', minimal: 'minimal', low: 'low', medium: 'medium', high: 'high' };
|
|
@@ -145,10 +146,21 @@ export function normalizeAntigravityCatalog(rawModels) {
|
|
|
145
146
|
const key = familyId(base);
|
|
146
147
|
let family = families.get(key);
|
|
147
148
|
if (!family) {
|
|
148
|
-
family = {
|
|
149
|
+
family = {
|
|
150
|
+
base,
|
|
151
|
+
contextWindow: 0,
|
|
152
|
+
maxOutputTokens: 0,
|
|
153
|
+
supportsVision: false,
|
|
154
|
+
supportsReasoning: false,
|
|
155
|
+
wires: new Map(),
|
|
156
|
+
};
|
|
149
157
|
families.set(key, family);
|
|
150
158
|
}
|
|
151
159
|
family.contextWindow = Math.max(family.contextWindow, contextWindow);
|
|
160
|
+
// Wire ids sharing one record keep the smallest declared output limit, so
|
|
161
|
+
// the ceiling holds for every id the record can resolve to.
|
|
162
|
+
const declaredOutput = Math.floor(Number(entry.maxOutputTokens));
|
|
163
|
+
if (declaredOutput > 0) family.maxOutputTokens = Math.min(family.maxOutputTokens || declaredOutput, declaredOutput);
|
|
152
164
|
family.supportsVision ||= entry.supportsImages === true;
|
|
153
165
|
family.supportsReasoning ||= entry.supportsThinking === true;
|
|
154
166
|
const slot = effort || '';
|
|
@@ -177,6 +189,7 @@ export function normalizeAntigravityCatalog(rawModels) {
|
|
|
177
189
|
provider: 'antigravity-oauth',
|
|
178
190
|
...(kind ? { family: kind } : {}),
|
|
179
191
|
contextWindow: family.contextWindow,
|
|
192
|
+
...(family.maxOutputTokens ? { maxOutputTokens: family.maxOutputTokens } : {}),
|
|
180
193
|
supportsVision: family.supportsVision,
|
|
181
194
|
supportsReasoning: family.supportsReasoning,
|
|
182
195
|
supportsFunctionCalling: true,
|
|
@@ -210,6 +223,19 @@ export function antigravityModelFamily(model, models = antigravityCatalogModels(
|
|
|
210
223
|
return typeof record?.family === 'string' && record.family ? record.family : null;
|
|
211
224
|
}
|
|
212
225
|
|
|
226
|
+
/**
|
|
227
|
+
* The output-token limit the gateway declared for a picker id or one of its
|
|
228
|
+
* wire ids, or null when the catalog row carries none (the curated fallback
|
|
229
|
+
* list and caches written before the field existed declare none).
|
|
230
|
+
*/
|
|
231
|
+
export function antigravityOutputLimit(model, models = antigravityCatalogModels()) {
|
|
232
|
+
const id = String(model || '');
|
|
233
|
+
const wires = (entry) => (entry?.wire && typeof entry.wire === 'object' ? Object.values(entry.wire) : [entry?.wire]);
|
|
234
|
+
const record = models.find((entry) => entry?.id === id) || models.find((entry) => wires(entry).includes(id));
|
|
235
|
+
const limit = Math.floor(Number(record?.maxOutputTokens));
|
|
236
|
+
return limit > 0 ? limit : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
213
239
|
/**
|
|
214
240
|
* Turn a picker model + effort into the wire id the gateway accepts. Tiered
|
|
215
241
|
* families consume the effort (the id encodes it); a bare id whose record
|
|
@@ -232,7 +258,7 @@ export function resolveAntigravityWireModel(model, effort, models = antigravityC
|
|
|
232
258
|
return { model: wire, effort: record.reasoningLevels?.length ? effort : null };
|
|
233
259
|
}
|
|
234
260
|
|
|
235
|
-
function quotaRow(label, bucket) {
|
|
261
|
+
function quotaRow(label, bucket, scope) {
|
|
236
262
|
if (!bucket || typeof bucket !== 'object') return null;
|
|
237
263
|
if (typeof bucket.remainingFraction !== 'number' || !Number.isFinite(bucket.remainingFraction)) return null;
|
|
238
264
|
const usedPct = Math.round(Math.max(0, Math.min(100, (1 - bucket.remainingFraction) * 100)) * 100) / 100;
|
|
@@ -242,6 +268,7 @@ function quotaRow(label, bucket) {
|
|
|
242
268
|
usedPct,
|
|
243
269
|
...(Number.isFinite(resetAt) && resetAt > 0 ? { resetAt } : {}),
|
|
244
270
|
source: 'antigravity-quota-summary',
|
|
271
|
+
...scope,
|
|
245
272
|
};
|
|
246
273
|
}
|
|
247
274
|
|
|
@@ -254,11 +281,21 @@ const ARGON_WEEKLY_BUCKET_ID = 'gemini-argon-wtu-weekly';
|
|
|
254
281
|
const THIRD_PARTY_5H_BUCKET_ID = '3p-5h';
|
|
255
282
|
const THIRD_PARTY_WEEKLY_BUCKET_ID = '3p-weekly';
|
|
256
283
|
|
|
284
|
+
// The model ids each separate bucket meters: the "Claude and GPT models"
|
|
285
|
+
// group (Claude Opus, Claude Sonnet, GPT-OSS) and the Gemini 4 Argon group.
|
|
286
|
+
const THIRD_PARTY_MODELS = Object.freeze(['claude', 'gpt']);
|
|
287
|
+
const ARGON_MODELS = Object.freeze(['argon']);
|
|
288
|
+
|
|
257
289
|
/**
|
|
258
290
|
* Gemini 5H/7D rows from retrieveUserQuotaSummary, the separate weekly limit
|
|
259
291
|
* of the Gemini 4 Argon group (its own group with only a weekly bucket), and
|
|
260
292
|
* the Claude/GPT-OSS 5H/7D pair. Rows are selected by bucket id and window.
|
|
261
293
|
* Missing fields stay omitted.
|
|
294
|
+
*
|
|
295
|
+
* Each group's buckets gate its own models only: the 3p pair gates Claude and
|
|
296
|
+
* GPT-OSS, the Argon bucket gates Argon, and the shared Gemini pair gates every
|
|
297
|
+
* model no other bucket in this summary claims, so a full Claude/GPT window
|
|
298
|
+
* leaves Gemini serving and the reverse.
|
|
262
299
|
*/
|
|
263
300
|
export function antigravityQuotaWindows(summary) {
|
|
264
301
|
const groups = (Array.isArray(summary?.groups) ? summary.groups : []).filter(
|
|
@@ -268,11 +305,18 @@ export function antigravityQuotaWindows(summary) {
|
|
|
268
305
|
const bucket = (id, window) =>
|
|
269
306
|
buckets.find((entry) => entry && typeof entry === 'object' && entry.bucketId === id && entry.window === window) ??
|
|
270
307
|
null;
|
|
308
|
+
const reported = (...ids) => buckets.some((entry) => ids.includes(entry?.bucketId));
|
|
309
|
+
const claimed = [
|
|
310
|
+
...(reported(THIRD_PARTY_5H_BUCKET_ID, THIRD_PARTY_WEEKLY_BUCKET_ID) ? THIRD_PARTY_MODELS : []),
|
|
311
|
+
...(reported(ARGON_WEEKLY_BUCKET_ID) ? ARGON_MODELS : []),
|
|
312
|
+
];
|
|
313
|
+
const gemini = claimed.length ? { except: claimed } : {};
|
|
314
|
+
const thirdParty = { models: [...THIRD_PARTY_MODELS] };
|
|
271
315
|
return [
|
|
272
|
-
quotaRow('5H', bucket(GEMINI_5H_BUCKET_ID, '5h')),
|
|
273
|
-
quotaRow('7D', bucket(GEMINI_WEEKLY_BUCKET_ID, 'weekly')),
|
|
274
|
-
quotaRow('7D Argon', bucket(ARGON_WEEKLY_BUCKET_ID, 'weekly')),
|
|
275
|
-
quotaRow('5H Claude/GPT', bucket(THIRD_PARTY_5H_BUCKET_ID, '5h')),
|
|
276
|
-
quotaRow('7D Claude/GPT', bucket(THIRD_PARTY_WEEKLY_BUCKET_ID, 'weekly')),
|
|
316
|
+
quotaRow('5H', bucket(GEMINI_5H_BUCKET_ID, '5h'), gemini),
|
|
317
|
+
quotaRow('7D', bucket(GEMINI_WEEKLY_BUCKET_ID, 'weekly'), gemini),
|
|
318
|
+
quotaRow('7D Argon', bucket(ARGON_WEEKLY_BUCKET_ID, 'weekly'), { models: [...ARGON_MODELS] }),
|
|
319
|
+
quotaRow('5H Claude/GPT', bucket(THIRD_PARTY_5H_BUCKET_ID, '5h'), thirdParty),
|
|
320
|
+
quotaRow('7D Claude/GPT', bucket(THIRD_PARTY_WEEKLY_BUCKET_ID, 'weekly'), thirdParty),
|
|
277
321
|
].filter(Boolean);
|
|
278
322
|
}
|
|
@@ -18,6 +18,8 @@ import { buildAntigravityRequest, isAntigravityClaude as isClaudeModel } from '.
|
|
|
18
18
|
import { createPassthroughSignal } from '../stall-policy.mjs';
|
|
19
19
|
import { preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
20
20
|
import { createAntigravityStreamCollector } from './antigravity-stream.mjs';
|
|
21
|
+
import { createGeminiReasoningRelay } from './gemini-reasoning-relay.mjs';
|
|
22
|
+
import { splitTailReminder } from './lib/tail-reminder-split.mjs';
|
|
21
23
|
import { createAntigravityRequest } from './antigravity-transport.mjs';
|
|
22
24
|
import { finalizeAntigravityTurn } from './antigravity-response.mjs';
|
|
23
25
|
import {
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
fetchAvailableModels,
|
|
42
44
|
fetchUserQuotaSummary,
|
|
43
45
|
normalizeAntigravityCatalog,
|
|
46
|
+
antigravityOutputLimit,
|
|
44
47
|
resolveAntigravityWireModel,
|
|
45
48
|
} from './antigravity-oauth-catalog.mjs';
|
|
46
49
|
|
|
@@ -109,7 +112,7 @@ export class AntigravityOAuthProvider {
|
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
_buildBody(messages, model, tools, opts) {
|
|
112
|
-
const body = buildAntigravityRequest(messages, model, tools, opts, this._projectId);
|
|
115
|
+
const body = buildAntigravityRequest(messages, model, tools, opts, this._projectId, antigravityOutputLimit(model));
|
|
113
116
|
dumpAntigravityRequest(body);
|
|
114
117
|
return body;
|
|
115
118
|
}
|
|
@@ -131,6 +134,7 @@ export class AntigravityOAuthProvider {
|
|
|
131
134
|
// Relayed through opts so the callback keeps its original receiver.
|
|
132
135
|
const onTextDelta = typeof opts.onTextDelta === 'function' ? (text) => opts.onTextDelta(text) : null;
|
|
133
136
|
const onStreamDelta = typeof opts.onStreamDelta === 'function' ? opts.onStreamDelta : null;
|
|
137
|
+
const reasoning = createGeminiReasoningRelay(opts);
|
|
134
138
|
// Per-turn stream record: live text mirror, ordered parts, native/leaked
|
|
135
139
|
// tool calls and the exposure marks a failed attempt carries
|
|
136
140
|
// (antigravity-stream.mjs).
|
|
@@ -140,6 +144,8 @@ export class AntigravityOAuthProvider {
|
|
|
140
144
|
onToolCall,
|
|
141
145
|
onTextDelta,
|
|
142
146
|
onStreamDelta,
|
|
147
|
+
onReasoningDelta: reasoning.onReasoningDelta,
|
|
148
|
+
retractReasoning: reasoning.retract,
|
|
143
149
|
});
|
|
144
150
|
if (signal?.aborted) {
|
|
145
151
|
const reason = signal.reason;
|
|
@@ -181,6 +187,9 @@ export class AntigravityOAuthProvider {
|
|
|
181
187
|
collector,
|
|
182
188
|
sleepFn: this.config.sleepFn,
|
|
183
189
|
firstByte: antigravityFirstBytePolicy(useModel),
|
|
190
|
+
// The tail reminder is not history: an empty answer right after a tool
|
|
191
|
+
// result is the model's answer, whatever reminder rides behind it.
|
|
192
|
+
afterToolResult: splitTailReminder(messages).messages.at(-1)?.role === 'tool',
|
|
184
193
|
});
|
|
185
194
|
try {
|
|
186
195
|
try {
|
|
@@ -3,10 +3,17 @@ import { createProviderReplay, providerReplayItems } from './lib/provider-replay
|
|
|
3
3
|
import { toGeminiContents, toGeminiRequestTools, toGeminiToolConfig } from './gemini-schema.mjs';
|
|
4
4
|
import { geminiThinkingConfig } from './gemini-thinking.mjs';
|
|
5
5
|
import { isGeminiFamilyModel } from './gemini-family.mjs';
|
|
6
|
+
import { geminiMaxOutputTokens } from './gemini-output-limit.mjs';
|
|
6
7
|
import { splitTailReminder } from './lib/tail-reminder-split.mjs';
|
|
7
8
|
|
|
8
9
|
const THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator';
|
|
10
|
+
// Used only when the gateway declared no output limit for a Claude id (the
|
|
11
|
+
// gateway enforces Claude's limit per model: one token over it is a 400).
|
|
9
12
|
const CLAUDE_MAX_OUTPUT_TOKENS = 64000;
|
|
13
|
+
const claudeMaxOutputTokens = (declared) => {
|
|
14
|
+
const limit = Math.floor(Number(declared));
|
|
15
|
+
return limit > 0 ? limit : CLAUDE_MAX_OUTPUT_TOKENS;
|
|
16
|
+
};
|
|
10
17
|
|
|
11
18
|
// Claude models behind the gateway speak a different thinking/replay dialect
|
|
12
19
|
// (token budgets, anthropic-beta header, their own signatures). The roster
|
|
@@ -190,7 +197,7 @@ function claudeGatewayLevel(value) {
|
|
|
190
197
|
|
|
191
198
|
// An explicit token budget is sent as-is; otherwise the chosen effort rides
|
|
192
199
|
// on thinkingLevel with the wire id unchanged.
|
|
193
|
-
function claudeThinkingConfig(opts) {
|
|
200
|
+
function claudeThinkingConfig(opts, cap) {
|
|
194
201
|
const budget = opts.thinkingBudget ?? opts.thinkingBudgetTokens;
|
|
195
202
|
const thinkingConfig = { includeThoughts: true };
|
|
196
203
|
if (budget == null) {
|
|
@@ -199,8 +206,8 @@ function claudeThinkingConfig(opts) {
|
|
|
199
206
|
return thinkingConfig;
|
|
200
207
|
}
|
|
201
208
|
const value = Number(budget);
|
|
202
|
-
if (!Number.isInteger(value) || value < 1024 || value >=
|
|
203
|
-
throw new TypeError(
|
|
209
|
+
if (!Number.isInteger(value) || value < 1024 || value >= cap) {
|
|
210
|
+
throw new TypeError(`Antigravity Claude thinkingBudget must be an integer from 1024 to ${cap - 1}.`);
|
|
204
211
|
}
|
|
205
212
|
thinkingConfig.thinkingBudget = value;
|
|
206
213
|
return thinkingConfig;
|
|
@@ -220,7 +227,9 @@ function antigravityGeminiThinkingConfig(model, opts) {
|
|
|
220
227
|
});
|
|
221
228
|
}
|
|
222
229
|
|
|
223
|
-
|
|
230
|
+
// `outputLimit` is the output-token limit the gateway declared for this wire id
|
|
231
|
+
// (the catalog row's `maxOutputTokens`), or null when it declared none.
|
|
232
|
+
export function buildAntigravityRequest(requestMessages, model, tools, opts = {}, projectId, outputLimit = null) {
|
|
224
233
|
// The tail reminder is not history: the tool-loop closure below must see the
|
|
225
234
|
// real tail, so the reminder is appended to the wire contents last.
|
|
226
235
|
const { messages, reminder } = splitTailReminder(requestMessages);
|
|
@@ -232,10 +241,17 @@ export function buildAntigravityRequest(requestMessages, model, tools, opts = {}
|
|
|
232
241
|
const history = toGeminiContents(signatureSafeMessages(chatMessages, model), model);
|
|
233
242
|
if (!history.length) throw new Error('No messages to send');
|
|
234
243
|
const claude = isAntigravityClaude(model);
|
|
235
|
-
const
|
|
244
|
+
const claudeCap = claudeMaxOutputTokens(outputLimit);
|
|
245
|
+
const thinkingConfig = claude ? claudeThinkingConfig(opts, claudeCap) : antigravityGeminiThinkingConfig(model, opts);
|
|
236
246
|
const contents = claude && claudeThinkingEnabled(model, thinkingConfig) ? closeUnthoughtToolLoop(history) : history;
|
|
247
|
+
// Gemini wire ids carry the model's output ceiling (thoughts count against
|
|
248
|
+
// it); Claude is always sent a cap (the declared limit, else a fixed one);
|
|
249
|
+
// GPT-OSS keeps the plain config its gateway accepts.
|
|
250
|
+
let maxOutputTokens;
|
|
251
|
+
if (claude) maxOutputTokens = claudeCap;
|
|
252
|
+
else if (isGeminiFamilyModel(model)) maxOutputTokens = geminiMaxOutputTokens(model, opts, null, outputLimit);
|
|
237
253
|
const generationConfig = {
|
|
238
|
-
...(
|
|
254
|
+
...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
|
|
239
255
|
...(thinkingConfig ? { thinkingConfig } : {}),
|
|
240
256
|
};
|
|
241
257
|
const request = { contents: reminder ? [...contents, { role: 'user', parts: [{ text: reminder }] }] : contents };
|
|
@@ -23,10 +23,21 @@ export function normalizeAntigravityUsage(um) {
|
|
|
23
23
|
(um.promptTokenCount || um.prompt_token_count || 0) +
|
|
24
24
|
(um.toolUsePromptTokenCount || um.tool_use_prompt_token_count || 0);
|
|
25
25
|
const cachedTokens = um.cachedContentTokenCount || um.cached_content_token_count || 0;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const thoughtTokens = um.thoughtsTokenCount ?? um.thoughts_token_count;
|
|
27
|
+
// Thought tokens are billed and capped as output.
|
|
28
|
+
const outputTokens = (um.candidatesTokenCount || um.candidates_token_count || 0) + (Number(thoughtTokens) || 0);
|
|
29
|
+
const reportedTotal = Number(um.totalTokenCount ?? um.total_token_count);
|
|
30
|
+
return {
|
|
31
|
+
inputTokens,
|
|
32
|
+
outputTokens,
|
|
33
|
+
cachedTokens,
|
|
34
|
+
promptTokens: inputTokens,
|
|
35
|
+
// Reasoning is reported only when the gateway reported thoughts (a missing
|
|
36
|
+
// count is not zero).
|
|
37
|
+
...(typeof thoughtTokens === 'number' ? { reasoningTokens: thoughtTokens } : {}),
|
|
38
|
+
totalTokens: reportedTotal > 0 ? reportedTotal : inputTokens + outputTokens,
|
|
39
|
+
raw: um,
|
|
40
|
+
};
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
/**
|