@bitkyc08/opencodex 2.79.0 → 2.81.0
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/README.md +3 -3
- package/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +10 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/autostart-health.ts +3 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +347 -199
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +16 -16
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-scope.ts +4 -1
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/combo-requested-effort.ts +14 -0
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +21 -18
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/desktop-startup.ts +100 -3
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
package/src/providers/quota.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { listCodexAuthAccountsSnapshot } from "../codex/auth-api";
|
|
2
|
+
import { isAnthropicInstanceId, configuredAnthropicInstance } from "./anthropic-instance";
|
|
3
|
+
import { fetchAnthropicAccountQuota } from "./quota/anthropic-account-quota";
|
|
2
4
|
import { resolveEnvValue } from "../config";
|
|
3
5
|
import { getAccountCredential, getAccountSet } from "../oauth/store";
|
|
4
6
|
import { apiKeyPoolEntryId } from "./api-keys";
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
accountReportCurrent,
|
|
19
21
|
AUTHORITATIVE_EMPTY_QUOTA,
|
|
20
22
|
bumpProviderQuotaInvalidationEpoch,
|
|
23
|
+
captureProviderReportEpochs, providerReportEpochChanged,
|
|
21
24
|
cacheKeyWithAggregationState,
|
|
22
25
|
getProviderQuotaReportCache,
|
|
23
26
|
hasCodexPoolProvider,
|
|
@@ -56,7 +59,6 @@ import {
|
|
|
56
59
|
} from "./quota/account-cache";
|
|
57
60
|
import {
|
|
58
61
|
fetchAnthropicQuota,
|
|
59
|
-
fetchAnthropicUsageQuota,
|
|
60
62
|
fetchChatGptForwardQuota,
|
|
61
63
|
fetchCursorQuota,
|
|
62
64
|
fetchKiroQuota,
|
|
@@ -68,11 +70,10 @@ import { fetchCommandCodeQuota, fetchKimiQuota, keyQuotaReaderForProvider } from
|
|
|
68
70
|
import { antigravityQuotaDiagnosticIdentity, fetchAntigravityQuota, probeAntigravityUsageQuota } from "./quota/antigravity";
|
|
69
71
|
import { persistKiroAccountState } from "./kiro-account-state-disk";
|
|
70
72
|
import { kiroProbeCurrent, kiroProbeIdentity } from "./quota/kiro-account-probe";
|
|
71
|
-
import { AnthropicQuotaProbeOwnershipError, anthropicCooldownFlightKey, assertAnthropicQuotaSendAllowed, probeAnthropicQuotaWithRecovery } from "./quota/anthropic-cooldown-recovery";
|
|
72
73
|
export type { ProviderQuota, ProviderQuotaCreditsUsd, ProviderQuotaWindow } from "./quota-types";
|
|
73
74
|
export { QUOTA_RESPONSE_MAX_BYTES } from "./quota-wire";
|
|
74
75
|
export {
|
|
75
|
-
clearProviderQuotaCache,
|
|
76
|
+
clearProviderQuotaCache, clearProviderQuotaCacheFor,
|
|
76
77
|
publishKeyReportForTests,
|
|
77
78
|
readProviderQuotaJsonForTests,
|
|
78
79
|
setProviderQuotaBeforePublishForTests,
|
|
@@ -86,7 +87,7 @@ export {
|
|
|
86
87
|
parseAnthropicRateLimitHeaders,
|
|
87
88
|
providerOAuthAccountQuotaMode,
|
|
88
89
|
readPassiveProviderAccountQuotas,
|
|
89
|
-
recordAnthropicAccountQuotaFromHeaders,
|
|
90
|
+
recordAnthropicAccountQuotaFromHeaders, recordAnthropicAccountQuotaFromHeadersForInstance,
|
|
90
91
|
recordPassiveAccountQuota,
|
|
91
92
|
reconcileProviderAccountQuotaRows,
|
|
92
93
|
resetProviderQuotaReconcileStateForTests,
|
|
@@ -121,7 +122,7 @@ async function maybeFetchProviderQuota(
|
|
|
121
122
|
return fetchChatGptForwardQuota(config, name, provider, forceRefresh, prefetchedCodexSnapshot);
|
|
122
123
|
}
|
|
123
124
|
if (provider.authMode === "oauth" && explicitAccountReader(name)) return await fetchExplicitCurrentQuota(name, provider, config);
|
|
124
|
-
if (provider.authMode === "oauth" && name
|
|
125
|
+
if (provider.authMode === "oauth" && configuredAnthropicInstance(config, name)) return fetchAnthropicQuota(name, config);
|
|
125
126
|
if (provider.authMode === "oauth" && name === "google-antigravity") return await fetchAntigravityQuota(name);
|
|
126
127
|
if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name);
|
|
127
128
|
// meta-muse: a device-logged-in account can be probed at the key endpoint; an
|
|
@@ -236,6 +237,7 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
|
|
|
236
237
|
const keyCandidate = cacheKeyWithAggregationState(config, prefetchedCodexSnapshot);
|
|
237
238
|
const key = typeof keyCandidate === "string" ? keyCandidate : await keyCandidate;
|
|
238
239
|
const writerGeneration = captureConfigGeneration();
|
|
240
|
+
const providerEpochs = captureProviderReportEpochs();
|
|
239
241
|
const now = Date.now();
|
|
240
242
|
// The cache fast path must not extend a preserved last-good row past its 30-minute bound:
|
|
241
243
|
// a row preserved at age 29:59 plus a full 5-minute TTL would otherwise serve until ~35min.
|
|
@@ -312,6 +314,8 @@ export async function fetchProviderQuotaReports(config: OcxConfig, forceRefresh
|
|
|
312
314
|
byProvider.delete(provider);
|
|
313
315
|
generationMismatchedProviders.delete(provider);
|
|
314
316
|
}
|
|
317
|
+
// A provider-scoped clear during this probe retires only that provider's row.
|
|
318
|
+
for (const provider of [...byProvider.keys()]) if (providerReportEpochChanged(providerEpochs, provider)) byProvider.delete(provider);
|
|
315
319
|
|
|
316
320
|
const response = { generatedAt: Date.now(), reports: [...byProvider.values()] };
|
|
317
321
|
// Commit only when this probe still holds authority (no clear/force superseded it).
|
|
@@ -415,27 +419,27 @@ async function fetchAccountQuota(
|
|
|
415
419
|
accountId: string,
|
|
416
420
|
forceRefresh: boolean,
|
|
417
421
|
providerConfig?: OcxProviderConfig,
|
|
422
|
+
liveConfig?: OcxConfig,
|
|
418
423
|
): Promise<AccountQuotaCacheEntry> {
|
|
419
424
|
if (accountQuotaProbeSkip(provider, accountId)) return accountQuotaProbeSkip(provider, accountId)!;
|
|
425
|
+
if (isAnthropicInstanceId(provider)) return fetchAnthropicAccountQuota(provider, accountId, forceRefresh, liveConfig);
|
|
420
426
|
if (explicitAccountReader(provider)) return fetchExplicitAccountQuota(provider, accountId, forceRefresh, providerConfig);
|
|
421
|
-
if (provider === "
|
|
427
|
+
if (provider === "kiro") hydrateAccountQuotaCache();
|
|
422
428
|
const key = accountCacheKey(provider, accountId);
|
|
423
429
|
const writerGeneration = captureConfigGeneration();
|
|
424
430
|
const kiroIdentity = provider === "kiro" ? kiroProbeIdentity(accountId) : undefined;
|
|
425
431
|
const cachedCandidate = accountQuotaCache.get(key);
|
|
426
|
-
const cached =
|
|
427
|
-
&& (provider !== "anthropic" || cachedCandidate?.isCurrent?.() !== false) ? cachedCandidate : undefined;
|
|
432
|
+
const cached = provider !== "kiro" || cachedCandidate?.identity === kiroIdentity ? cachedCandidate : undefined;
|
|
428
433
|
if (!forceRefresh && cached && Date.now() - cached.ts < ACCOUNT_QUOTA_TTL_MS) {
|
|
429
434
|
if (provider === "google-antigravity" && cached.quotaFailure && cached.quotaFailureIsCurrent?.() !== true) return { ...cached, quotaFailure: undefined };
|
|
430
|
-
return
|
|
435
|
+
return cached;
|
|
431
436
|
}
|
|
432
|
-
const flightKey =
|
|
433
|
-
const flightCurrent = () => provider !== "anthropic" || anthropicCooldownFlightKey(key, accountId) === flightKey;
|
|
437
|
+
const flightKey = key;
|
|
434
438
|
const joinable = accountQuotaInflight.get(flightKey);
|
|
435
439
|
if (joinable) {
|
|
436
440
|
const joined = await joinable;
|
|
437
441
|
const joinedCurrent = joined.isCurrent?.();
|
|
438
|
-
if (joinedCurrent === false
|
|
442
|
+
if (joinedCurrent === false) return { ts: Date.now(), quota: null, unavailable: true };
|
|
439
443
|
return provider !== "kiro" || joined.identity === kiroIdentity
|
|
440
444
|
? joined : fetchAccountQuota(provider, accountId, true, providerConfig);
|
|
441
445
|
}
|
|
@@ -449,14 +453,10 @@ async function fetchAccountQuota(
|
|
|
449
453
|
};
|
|
450
454
|
const diagnosticFields = () => quotaFailure && quotaFailureIsCurrent() ? { quotaFailure, quotaFailureIsCurrent } : {};
|
|
451
455
|
const unavailable = (): AccountQuotaCacheEntry => {
|
|
452
|
-
if (!flightCurrent()) return { ts: Date.now(), quota: null, unavailable: true };
|
|
453
|
-
const previous = accountQuotaCache.get(key);
|
|
454
|
-
const retained = provider === "anthropic" && previous?.isCurrent?.() === false ? undefined : previous;
|
|
455
456
|
const entry: AccountQuotaCacheEntry = {
|
|
456
457
|
ts: Date.now(),
|
|
457
|
-
quota:
|
|
458
|
+
quota: cached?.quota ?? null,
|
|
458
459
|
unavailable: true,
|
|
459
|
-
...(provider === "anthropic" && retained?.isCurrent ? { isCurrent: retained.isCurrent } : {}),
|
|
460
460
|
...(provider === "kiro" ? { identity: kiroIdentity } : {}),
|
|
461
461
|
...diagnosticFields(),
|
|
462
462
|
};
|
|
@@ -470,14 +470,12 @@ async function fetchAccountQuota(
|
|
|
470
470
|
try {
|
|
471
471
|
if (provider === "google-antigravity") diagnosticIdentity = antigravityQuotaDiagnosticIdentity(accountId);
|
|
472
472
|
let quota: ProviderQuota | null;
|
|
473
|
-
let anthropicCurrent: (() => boolean) | undefined;
|
|
474
473
|
let kiroSnapshot: KiroUsageSnapshot | null = null;
|
|
475
474
|
if (provider === "kiro") {
|
|
476
475
|
kiroSnapshot = await fetchKiroUsageSnapshot(await kiroUsageContextForAccount(accountId));
|
|
477
476
|
quota = kiroSnapshot?.quota ?? null;
|
|
478
477
|
} else {
|
|
479
478
|
const token = await getTokenForAccountQuotaProbe(provider, accountId);
|
|
480
|
-
if (!flightCurrent()) throw new AnthropicQuotaProbeOwnershipError("anthropic quota flight is stale");
|
|
481
479
|
if (provider === "google-antigravity") {
|
|
482
480
|
// Per-account Gem/Cla windows (#1082). The project id is part of the stored
|
|
483
481
|
// credential; without it the probe cannot be made, and that is "unavailable",
|
|
@@ -488,22 +486,14 @@ async function fetchAccountQuota(
|
|
|
488
486
|
const result = await probeAntigravityUsageQuota(token, credential.projectId);
|
|
489
487
|
quota = result.kind === "available" ? result.quota : null;
|
|
490
488
|
if (result.kind === "unavailable") quotaFailure = result.failure;
|
|
491
|
-
} else if (provider === "anthropic") {
|
|
492
|
-
const result = await probeAnthropicQuotaWithRecovery(accountId, token,
|
|
493
|
-
fresh => { assertAnthropicQuotaSendAllowed(accountId, token); return fetchAnthropicUsageQuota(token, fresh); },
|
|
494
|
-
() => mayCommitAccountQuotaKey(key, writerGeneration));
|
|
495
|
-
if (result && !result.isCurrent()) throw new AnthropicQuotaProbeOwnershipError("anthropic quota probe lost publication ownership");
|
|
496
|
-
quota = result?.quota ?? null;
|
|
497
|
-
anthropicCurrent = result?.isCurrent;
|
|
498
489
|
} else {
|
|
499
490
|
return { ts: Date.now(), quota: null, unavailable: true };
|
|
500
491
|
}
|
|
501
492
|
}
|
|
502
493
|
if (!quota) return unavailable();
|
|
503
494
|
const entry: AccountQuotaCacheEntry = {
|
|
504
|
-
ts: Date.now(), quota
|
|
495
|
+
ts: Date.now(), quota,
|
|
505
496
|
...(provider === "kiro" ? { identity: kiroIdentity } : {}),
|
|
506
|
-
...(anthropicCurrent ? { isCurrent: anthropicCurrent } : {}),
|
|
507
497
|
};
|
|
508
498
|
if (mayCommitAccountQuotaKey(key, writerGeneration)
|
|
509
499
|
&& (provider !== "kiro" || kiroProbeCurrent(accountId, kiroIdentity))) {
|
|
@@ -513,7 +503,6 @@ async function fetchAccountQuota(
|
|
|
513
503
|
}
|
|
514
504
|
return entry;
|
|
515
505
|
} catch (error) {
|
|
516
|
-
if (provider === "anthropic" && error instanceof AnthropicQuotaProbeOwnershipError) return { ts: Date.now(), quota: null, unavailable: true };
|
|
517
506
|
if (provider === "google-antigravity") quotaFailure = "account_unavailable";
|
|
518
507
|
return unavailable();
|
|
519
508
|
}
|
|
@@ -531,20 +520,21 @@ export async function fetchProviderAccountQuotas(
|
|
|
531
520
|
provider: string,
|
|
532
521
|
forceRefresh = false,
|
|
533
522
|
providerConfig?: OcxProviderConfig,
|
|
523
|
+
liveConfig?: OcxConfig,
|
|
534
524
|
): Promise<ProviderAccountQuota[]> {
|
|
535
525
|
if (!supportsPerAccountQuota(provider)) return [];
|
|
536
526
|
const set = getAccountSet(provider);
|
|
537
527
|
if (!set) return [];
|
|
538
528
|
return mapQuotaRoster(set.accounts, async account => {
|
|
539
|
-
const entry = await fetchAccountQuota(provider, account.id, forceRefresh, providerConfig);
|
|
529
|
+
const entry = await fetchAccountQuota(provider, account.id, forceRefresh, providerConfig, liveConfig);
|
|
540
530
|
const result: ProviderAccountQuota = {
|
|
541
531
|
accountId: account.id,
|
|
542
|
-
quota: provider
|
|
532
|
+
quota: isAnthropicInstanceId(provider) ? normalizeAnthropicQuota(entry.quota, Date.now()) : entry.quota,
|
|
543
533
|
...(entry.unavailable ? { unavailable: true as const } : {}),
|
|
544
534
|
...(entry.unavailable && entry.quotaFailure && entry.quotaFailureIsCurrent?.() === true ? { quotaFailure: entry.quotaFailure } : {}),
|
|
545
535
|
};
|
|
546
536
|
if (entry.quotaFailureIsCurrent) Object.defineProperty(result, "quotaFailureIsCurrent", { value: entry.quotaFailureIsCurrent });
|
|
547
|
-
if (provider
|
|
537
|
+
if (isAnthropicInstanceId(provider) && entry.isCurrent) Object.defineProperty(result, "isCurrent", { value: entry.isCurrent });
|
|
548
538
|
if (!explicitAccountReader(provider)) return result;
|
|
549
539
|
const identity = entry.identity;
|
|
550
540
|
Object.defineProperty(result, "isCurrent", { value: () => {
|
|
@@ -14,6 +14,7 @@ import { cursorFastCapableBases } from "../../adapters/cursor/catalog";
|
|
|
14
14
|
import { COMMAND_CODE_MODEL_REASONING_EFFORTS } from "../command-code-efforts";
|
|
15
15
|
import { isCanonicalOpenRouterTarget } from "../openrouter-routing";
|
|
16
16
|
import type { ProviderRegistryEntry } from "./types";
|
|
17
|
+
import type { AnthropicInstanceId } from "../anthropic-instance-id";
|
|
17
18
|
import { ANTHROPIC_FAST_MODE_BETA } from "../anthropic-fast";
|
|
18
19
|
import {
|
|
19
20
|
ANTHROPIC_MODELS,
|
|
@@ -109,6 +110,37 @@ const ANTHROPIC_FAST_MODELS: Readonly<Record<string, boolean>> = Object.freeze({
|
|
|
109
110
|
const ANTHROPIC_FAST_TIER_DESCRIPTION =
|
|
110
111
|
"Claude fast mode: faster output at 2x price; needs usage credits (subscription) or fast-mode access (API)";
|
|
111
112
|
|
|
113
|
+
/** Each instance owns its entire seed, including nested model lists and Fast wire maps. */
|
|
114
|
+
function anthropicOAuthEntry(id: AnthropicInstanceId, label: string): ProviderRegistryEntry {
|
|
115
|
+
return structuredClone<ProviderRegistryEntry>({
|
|
116
|
+
id,
|
|
117
|
+
label,
|
|
118
|
+
adapter: "anthropic",
|
|
119
|
+
baseUrl: "https://api.anthropic.com",
|
|
120
|
+
authKind: "oauth",
|
|
121
|
+
allowBaseUrlOverride: true,
|
|
122
|
+
featured: true,
|
|
123
|
+
oauthId: id,
|
|
124
|
+
oauthFamily: "anthropic",
|
|
125
|
+
jawcodeBundle: "anthropic",
|
|
126
|
+
note: id === "anthropic" ? "Log in with your Claude account"
|
|
127
|
+
: "Independent Claude account pool — log in with a separate Claude account",
|
|
128
|
+
models: [...ANTHROPIC_MODELS],
|
|
129
|
+
modelContextWindows: { ...ANTHROPIC_MODEL_CONTEXT_WINDOWS },
|
|
130
|
+
modelInputModalities: { ...ANTHROPIC_MODEL_INPUT_MODALITIES },
|
|
131
|
+
modelReasoningEfforts: { ...ANTHROPIC_MODEL_REASONING_EFFORTS },
|
|
132
|
+
// Codex omits max_output_tokens; the fallback 8192 truncates long answers.
|
|
133
|
+
defaultMaxOutputTokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS,
|
|
134
|
+
modelMaxOutputTokens: { ...ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS },
|
|
135
|
+
defaultModel: "claude-sonnet-5",
|
|
136
|
+
// Subscription /fast remains opt-in: it draws usage credits at 2x price.
|
|
137
|
+
fastWire: ANTHROPIC_FAST_WIRE,
|
|
138
|
+
modelSupportsServiceTier: { ...ANTHROPIC_FAST_MODELS },
|
|
139
|
+
fastTierDescription: ANTHROPIC_FAST_TIER_DESCRIPTION,
|
|
140
|
+
fastOptIn: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
113
145
|
{
|
|
114
146
|
id: "openai",
|
|
@@ -209,7 +241,7 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
209
241
|
// (it is the current catalog, so its default ordering wins), then the ids
|
|
210
242
|
// only the old devin entry carried. Degraded-mode seed only either way —
|
|
211
243
|
// `liveModels` discovers the account's real roster.
|
|
212
|
-
models: ["swe-2", "swe-1-7", "gpt-5-6-sol", "gpt-6-astra", "gpt-6-1-sol", "gpt-6-sol", "gpt-6-luna", "claude-opus-5-5", "claude-opus-5", "claude-fable-5-1", "claude-sonnet-5-5", "claude-sonnet-5", "glm-5-3", "kimi-k3", "gemini-3-8-flash", "grok-4-6", "grok-4-7", "swe-1-7-lightning", "gpt-5-6-luna", "gpt-5-6-terra", "claude-opus-4-8", "glm-5-2", "kimi-k2-7", "grok-4-5"],
|
|
244
|
+
models: ["swe-2", "swe-1-7", "gpt-5-6-sol", "gpt-6-astra", "gpt-6-1-sol", "gpt-6-sol", "gpt-6-luna", "claude-opus-5-5", "claude-opus-5", "claude-fable-5-1", "claude-sonnet-5-5", "claude-haiku-5-5", "claude-sonnet-5", "glm-5-3", "kimi-k3", "gemini-3-8-flash", "grok-4-6", "grok-4-7", "swe-1-7-lightning", "gpt-5-6-luna", "gpt-5-6-terra", "claude-opus-4-8", "glm-5-2", "kimi-k2-7", "grok-4-5"],
|
|
213
245
|
liveModels: true,
|
|
214
246
|
defaultModel: "swe-2",
|
|
215
247
|
modelContextWindows: DEVIN_MODEL_CONTEXT_WINDOWS,
|
|
@@ -490,35 +522,8 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
|
|
|
490
522
|
modelReasoningEfforts: ORCAROUTER_MODEL_REASONING_EFFORTS,
|
|
491
523
|
note: "Connect your OrcaRouter account with OAuth 2.0 + PKCE; the issued API key is stored in OpenCodex's existing credential store.",
|
|
492
524
|
},
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
label: "Anthropic Claude",
|
|
496
|
-
adapter: "anthropic",
|
|
497
|
-
baseUrl: "https://api.anthropic.com",
|
|
498
|
-
authKind: "oauth",
|
|
499
|
-
allowBaseUrlOverride: true,
|
|
500
|
-
featured: true,
|
|
501
|
-
oauthId: "anthropic",
|
|
502
|
-
jawcodeBundle: "anthropic",
|
|
503
|
-
note: "Log in with your Claude account",
|
|
504
|
-
models: [...ANTHROPIC_MODELS],
|
|
505
|
-
modelContextWindows: { ...ANTHROPIC_MODEL_CONTEXT_WINDOWS },
|
|
506
|
-
modelInputModalities: { ...ANTHROPIC_MODEL_INPUT_MODALITIES },
|
|
507
|
-
modelReasoningEfforts: { ...ANTHROPIC_MODEL_REASONING_EFFORTS },
|
|
508
|
-
// Codex omits max_output_tokens; without a provider budget the Anthropic adapter
|
|
509
|
-
// falls back to 8192, which truncates long answers with stop_reason=max_tokens.
|
|
510
|
-
defaultMaxOutputTokens: ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS,
|
|
511
|
-
modelMaxOutputTokens: { ...ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS },
|
|
512
|
-
defaultModel: "claude-sonnet-5",
|
|
513
|
-
// Claude fast mode on the subscription lane (Claude Code `/fast`): the OAuth route accepts
|
|
514
|
-
// `speed` and gates it on account entitlement (usage credits / org enablement), probed live
|
|
515
|
-
// 2026-09-23 (devlog/_plan/260923_anthropic_fast_speed/020_probe-evidence.md).
|
|
516
|
-
// Off until the operator opts in: fast mode draws usage credits at 2x price.
|
|
517
|
-
fastWire: ANTHROPIC_FAST_WIRE,
|
|
518
|
-
modelSupportsServiceTier: { ...ANTHROPIC_FAST_MODELS },
|
|
519
|
-
fastTierDescription: ANTHROPIC_FAST_TIER_DESCRIPTION,
|
|
520
|
-
fastOptIn: true,
|
|
521
|
-
},
|
|
525
|
+
anthropicOAuthEntry("anthropic", "Anthropic Claude"),
|
|
526
|
+
anthropicOAuthEntry("anthropic2", "Anthropic · Pool 2"),
|
|
522
527
|
{
|
|
523
528
|
id: "anthropic-apikey",
|
|
524
529
|
label: "Anthropic (API key)",
|
|
@@ -12,8 +12,10 @@ import type { ProviderModelDiscoverySpec } from "./types";
|
|
|
12
12
|
// 260929 Claude Sonnet 5.5 (`claude-sonnet-5-5`, released 2026-09-28): 1M context / 128K output /
|
|
13
13
|
// adaptive thinking / effort low..max with a high default, same price as Sonnet 5, per the Sonnet 5.5
|
|
14
14
|
// overview, migration guide, effort and pricing pages (platform.claude.com).
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// 261008 Claude Haiku 5.5: 1M / 128K, adaptive thinking, effort low..max (default medium),
|
|
16
|
+
// per platform.claude.com Haiku 5.5 overview, migration guide and pricing page.
|
|
17
|
+
export const ANTHROPIC_MODELS = ["claude-fable-5-1", "claude-fable-5", "claude-sonnet-5-5", "claude-sonnet-5", "claude-opus-5-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-5-5", "claude-haiku-4-5"];
|
|
18
|
+
export const ANTHROPIC_MODEL_CONTEXT_WINDOWS: Record<string, number> = { "claude-fable-5-1": 1_000_000, "claude-sonnet-5-5": 1_000_000, "claude-sonnet-5": 1_000_000, "claude-fable-5": 1_000_000, "claude-opus-5-5": 1_000_000, "claude-opus-5": 1_000_000, "claude-opus-4-8": 1_000_000, "claude-opus-4-7": 1_000_000, "claude-opus-4-6": 1_000_000, "claude-sonnet-4-6": 1_000_000, "claude-haiku-5-5": 1_000_000, "claude-haiku-4-5": 200_000 };
|
|
17
19
|
// All seeded Claude models support vision: https://platform.claude.com/docs/en/models/overview
|
|
18
20
|
export const ANTHROPIC_MODEL_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntries(
|
|
19
21
|
ANTHROPIC_MODELS.map(id => [id, ["text", "image"]]),
|
|
@@ -40,6 +42,7 @@ export const ANTHROPIC_DEFAULT_MAX_OUTPUT_TOKENS = 64_000;
|
|
|
40
42
|
* headroom arithmetic wrong.
|
|
41
43
|
*/
|
|
42
44
|
export const ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS: Record<string, number> = {
|
|
45
|
+
"claude-haiku-5-5": 128_000,
|
|
43
46
|
"claude-fable-5-1": 128_000,
|
|
44
47
|
"claude-fable-5": 128_000,
|
|
45
48
|
"claude-sonnet-5-5": 128_000,
|
|
@@ -70,8 +73,8 @@ export const ANTHROPIC_MODEL_MAX_OUTPUT_TOKENS: Record<string, number> = {
|
|
|
70
73
|
* do what it says:
|
|
71
74
|
* - `minimal`: `adaptiveEffort` rewrites it to `low` (the adaptive wire 400s on it), so
|
|
72
75
|
* it is not a distinct setting.
|
|
73
|
-
* - `none`:
|
|
74
|
-
*
|
|
76
|
+
* - `none`: thinking-off support differs by family (`anthropic-model-contract.ts`);
|
|
77
|
+
* Haiku 5.5 accepts it without effort, while Fable rejects one outright.
|
|
75
78
|
* - `ultra`: not an Anthropic concept, and it is degraded to `max` at the request
|
|
76
79
|
* boundary anyway (src/responses/parser.ts).
|
|
77
80
|
*/
|
|
@@ -361,6 +361,8 @@ export interface ProviderRegistryEntry {
|
|
|
361
361
|
thinkingBudgetModels?: string[];
|
|
362
362
|
escapeBuiltinToolNames?: boolean;
|
|
363
363
|
oauthId?: string;
|
|
364
|
+
/** OAuth implementation family; instance identity remains the exact id/oauthId. */
|
|
365
|
+
oauthFamily?: "anthropic";
|
|
364
366
|
virtualModels?: Record<string, { wireModelId: string; reasoningMode: "pro" }>;
|
|
365
367
|
modelMaxInputTokens?: Record<string, number>;
|
|
366
368
|
jawcodeBundle?: string;
|
|
@@ -373,6 +375,7 @@ export interface ProviderRegistryEntry {
|
|
|
373
375
|
|
|
374
376
|
export type ProviderConfigSeed = Pick<
|
|
375
377
|
OcxProviderConfig,
|
|
378
|
+
"anthropicOAuthInstance" |
|
|
376
379
|
"adapter" | "baseUrl" | "apiKeyTransport" | "responsesPath" | "chatCompletionsPath" | "authMode" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip" | "defaultModel" | "models"
|
|
377
380
|
| "liveModels" | "contextWindow" | "modelContextWindows" | "modelInputModalities"
|
|
378
381
|
| "modelDisplayNames"
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
} from "./registry/types";
|
|
8
8
|
import { PROVIDER_REGISTRY_CORE } from "./registry/entries-core";
|
|
9
9
|
import { PROVIDER_REGISTRY_EXTENDED } from "./registry/entries-extended";
|
|
10
|
-
|
|
10
|
+
import { anthropicInstanceRowShapeMatches } from "./anthropic-instance-id";
|
|
11
11
|
export type {
|
|
12
12
|
ProviderAuthKind,
|
|
13
13
|
MetadataModelIdNormalize,
|
|
@@ -102,10 +102,11 @@ export function normalizedProviderEndpoint(value: string): string {
|
|
|
102
102
|
*/
|
|
103
103
|
export function providerMatchesRegistryTransport(
|
|
104
104
|
id: string,
|
|
105
|
-
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode">>,
|
|
105
|
+
provider: Pick<OcxProviderConfig, "baseUrl" | "adapter"> & Partial<Pick<OcxProviderConfig, "authMode" | "anthropicOAuthInstance">>,
|
|
106
106
|
): boolean {
|
|
107
107
|
const entry = getProviderRegistryEntry(id);
|
|
108
108
|
if (!entry) return false;
|
|
109
|
+
if (id === "anthropic2") return entry.authKind === "oauth" && entry.oauthId === id && entry.oauthFamily === "anthropic" && anthropicInstanceRowShapeMatches(id, provider);
|
|
109
110
|
if (entry.authKind !== "key" || entry.preserveCustomDestination !== true) return true;
|
|
110
111
|
// The opt-in is intentionally limited to fixed key destinations. Fail closed if a future
|
|
111
112
|
// registry edit combines it with an override/template despite the registry parity tests.
|
|
@@ -115,7 +116,6 @@ export function providerMatchesRegistryTransport(
|
|
|
115
116
|
if (provider.authMode !== undefined && provider.authMode !== "key") return false;
|
|
116
117
|
return normalizedProviderEndpoint(provider.baseUrl) === normalizedProviderEndpoint(entry.baseUrl);
|
|
117
118
|
}
|
|
118
|
-
|
|
119
119
|
/**
|
|
120
120
|
* Resolve the registry entry a configured provider actually points at, by TRANSPORT
|
|
121
121
|
* rather than by name.
|
|
@@ -72,6 +72,7 @@ export interface ResponseSpillPayload {
|
|
|
72
72
|
*/
|
|
73
73
|
providerOutputStart?: number;
|
|
74
74
|
providers?: OcxProviderContinuationState;
|
|
75
|
+
unforcedStoreFalse?: boolean;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export interface ResponseSpillRef {
|
|
@@ -496,6 +497,7 @@ function serializedSpill(
|
|
|
496
497
|
items: state.items,
|
|
497
498
|
...(state.providerOutputStart !== undefined ? { providerOutputStart: state.providerOutputStart } : {}),
|
|
498
499
|
...(state.providers ? { providers: state.providers } : {}),
|
|
500
|
+
...(state.unforcedStoreFalse ? { unforcedStoreFalse: state.unforcedStoreFalse } : {}),
|
|
499
501
|
};
|
|
500
502
|
const serialized = JSON.stringify(payload);
|
|
501
503
|
if (serialized === undefined) throw new Error("Response spill serialization failed");
|
|
@@ -549,12 +551,14 @@ function validPayload(value: unknown, responseId: string): value is ResponseSpil
|
|
|
549
551
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
550
552
|
const payload = value as Record<string, unknown>;
|
|
551
553
|
const keys = Object.keys(payload);
|
|
552
|
-
if (keys.some(key => !["version", "responseId", "createdAt", "clientThreadId", "items", "providerOutputStart", "providers"].includes(key))) return false;
|
|
554
|
+
if (keys.some(key => !["version", "responseId", "createdAt", "clientThreadId", "items", "providerOutputStart", "providers", "unforcedStoreFalse"].includes(key))) return false;
|
|
553
555
|
if (payload.version !== 1 || payload.responseId !== responseId) return false;
|
|
554
556
|
if (typeof payload.createdAt !== "number" || !Number.isFinite(payload.createdAt)) return false;
|
|
555
557
|
if (payload.clientThreadId !== undefined
|
|
556
558
|
&& (typeof payload.clientThreadId !== "string" || payload.clientThreadId.trim().length === 0)) return false;
|
|
559
|
+
if (payload.unforcedStoreFalse !== undefined && typeof payload.unforcedStoreFalse !== "boolean") return false;
|
|
557
560
|
if (!Array.isArray(payload.items)) return false;
|
|
561
|
+
if (payload.unforcedStoreFalse === true && payload.providerOutputStart === undefined) return false;
|
|
558
562
|
// A malformed boundary must degrade to "never skip", never to a bad index: reject the
|
|
559
563
|
// payload outright so materialization treats it as corrupt rather than trusting it.
|
|
560
564
|
if (payload.providerOutputStart !== undefined) {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { StoredResponseState } from "../state";
|
|
2
|
+
import type { ResponseSpillWriteStatus, ResponseSpillWriteFailureCode, ResponseSpillWriteFailureOrigin } from "./spill-failure";
|
|
3
|
+
|
|
4
|
+
export interface ResponseStateMetrics {
|
|
5
|
+
count: number;
|
|
6
|
+
residentCount: number;
|
|
7
|
+
spillStubCount: number;
|
|
8
|
+
tombstoneCount: number;
|
|
9
|
+
totalBytes: number;
|
|
10
|
+
spillPayloadBytes: number;
|
|
11
|
+
largestBytes: number;
|
|
12
|
+
oldestAgeMs: number;
|
|
13
|
+
spillWrites: number;
|
|
14
|
+
spillWriteFailures: number;
|
|
15
|
+
spillWriteStatus: ResponseSpillWriteStatus;
|
|
16
|
+
spillWriteConsecutiveFailures: number;
|
|
17
|
+
spillLastWriteFailureCode: ResponseSpillWriteFailureCode | null;
|
|
18
|
+
spillLastWriteFailureOrigin: ResponseSpillWriteFailureOrigin | null;
|
|
19
|
+
spillAclRetryReturnedTimeouts: number;
|
|
20
|
+
spillAclTimeoutMemoRefusals: number;
|
|
21
|
+
spillLastWriteFailureAt: number | null;
|
|
22
|
+
spillLastWriteSuccessAt: number | null;
|
|
23
|
+
spillReadFailures: number;
|
|
24
|
+
replayScopeMismatchDrops: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function computeResponseStateMetrics(params: {
|
|
28
|
+
states: Iterable<StoredResponseState>;
|
|
29
|
+
stateCount: number;
|
|
30
|
+
now: number;
|
|
31
|
+
totalBytes: number;
|
|
32
|
+
spillWrites: number;
|
|
33
|
+
spillWriteFailures: number;
|
|
34
|
+
spillWriteConsecutiveFailures: number;
|
|
35
|
+
spillLastFailureCode: ResponseSpillWriteFailureCode | null;
|
|
36
|
+
spillLastFailureOrigin: ResponseSpillWriteFailureOrigin | null;
|
|
37
|
+
spillLastSuccessAt: number | null;
|
|
38
|
+
spillAclRetryReturnedTimeouts: number;
|
|
39
|
+
spillAclTimeoutMemoRefusals: number;
|
|
40
|
+
spillLastFailureAt: number | null;
|
|
41
|
+
spillReadFailures: number;
|
|
42
|
+
replayScopeMismatchDrops: number;
|
|
43
|
+
}): ResponseStateMetrics {
|
|
44
|
+
let largestBytes = 0;
|
|
45
|
+
let oldestCreatedAt = params.now;
|
|
46
|
+
let residentCount = 0;
|
|
47
|
+
let spillStubCount = 0;
|
|
48
|
+
let tombstoneCount = 0;
|
|
49
|
+
let spillPayloadBytes = 0;
|
|
50
|
+
for (const state of params.states) {
|
|
51
|
+
const bytes = state.sizeBytes;
|
|
52
|
+
if (bytes > largestBytes) largestBytes = bytes;
|
|
53
|
+
if (state.createdAt < oldestCreatedAt) oldestCreatedAt = state.createdAt;
|
|
54
|
+
if (state.kind === "resident") {
|
|
55
|
+
residentCount += 1;
|
|
56
|
+
} else if (state.kind === "spill") {
|
|
57
|
+
spillStubCount += 1;
|
|
58
|
+
spillPayloadBytes += state.spill.payloadBytes;
|
|
59
|
+
} else tombstoneCount += 1;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
count: params.stateCount,
|
|
63
|
+
residentCount,
|
|
64
|
+
spillStubCount,
|
|
65
|
+
tombstoneCount,
|
|
66
|
+
totalBytes: params.totalBytes,
|
|
67
|
+
spillPayloadBytes,
|
|
68
|
+
largestBytes,
|
|
69
|
+
oldestAgeMs: params.stateCount > 0 ? params.now - oldestCreatedAt : 0,
|
|
70
|
+
spillWrites: params.spillWrites,
|
|
71
|
+
spillWriteFailures: params.spillWriteFailures,
|
|
72
|
+
spillWriteStatus: params.spillWriteConsecutiveFailures > 0
|
|
73
|
+
? "degraded"
|
|
74
|
+
: params.spillLastSuccessAt !== null
|
|
75
|
+
? "healthy"
|
|
76
|
+
: "initial",
|
|
77
|
+
spillWriteConsecutiveFailures: params.spillWriteConsecutiveFailures,
|
|
78
|
+
spillLastWriteFailureCode: params.spillLastFailureCode,
|
|
79
|
+
spillLastWriteFailureOrigin: params.spillLastFailureOrigin,
|
|
80
|
+
spillAclRetryReturnedTimeouts: params.spillAclRetryReturnedTimeouts,
|
|
81
|
+
spillAclTimeoutMemoRefusals: params.spillAclTimeoutMemoRefusals,
|
|
82
|
+
spillLastWriteFailureAt: params.spillLastFailureAt,
|
|
83
|
+
spillLastWriteSuccessAt: params.spillLastSuccessAt,
|
|
84
|
+
spillReadFailures: params.spillReadFailures,
|
|
85
|
+
replayScopeMismatchDrops: params.replayScopeMismatchDrops,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -24,6 +24,7 @@ interface LegacySnapshotState {
|
|
|
24
24
|
providers?: OcxProviderContinuationState;
|
|
25
25
|
conversationId?: unknown;
|
|
26
26
|
cursorCheckpointUsable?: unknown;
|
|
27
|
+
unforcedStoreFalse?: unknown;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function isSpillRef(value: unknown): value is ResponseSpillRef {
|
|
@@ -51,6 +52,9 @@ export function loadSnapshotEntry(id: string, value: unknown, store: SnapshotLoa
|
|
|
51
52
|
? raw as number
|
|
52
53
|
: undefined;
|
|
53
54
|
};
|
|
55
|
+
// Restricted retention requires proven provider output; legacy unrestricted rows stay readable.
|
|
56
|
+
if (rec.unforcedStoreFalse === true
|
|
57
|
+
&& anchorFor(rec.kind === "spill" ? Number.MAX_SAFE_INTEGER : Array.isArray(rec.items) ? rec.items.length : 0) === undefined) return;
|
|
54
58
|
if (rec.kind === "spill") {
|
|
55
59
|
if (!isSpillRef(rec.spill)) return;
|
|
56
60
|
const base: Omit<SpilledResponseState, "sizeBytes"> = {
|
|
@@ -61,6 +65,7 @@ export function loadSnapshotEntry(id: string, value: unknown, store: SnapshotLoa
|
|
|
61
65
|
// here; the spill payload validator re-checks it against the real array.
|
|
62
66
|
...(anchorFor(Number.MAX_SAFE_INTEGER) !== undefined ? { providerOutputStart: anchorFor(Number.MAX_SAFE_INTEGER) } : {}),
|
|
63
67
|
...(rec.providers ? { providers: rec.providers } : {}),
|
|
68
|
+
...(rec.unforcedStoreFalse === true ? { unforcedStoreFalse: true } : {}),
|
|
64
69
|
spill: rec.spill,
|
|
65
70
|
};
|
|
66
71
|
store.replaceMapEntry(id, { ...base, sizeBytes: store.stubSize(id, base) });
|
|
@@ -88,6 +93,7 @@ export function loadSnapshotEntry(id: string, value: unknown, store: SnapshotLoa
|
|
|
88
93
|
items: rec.items,
|
|
89
94
|
...(anchorFor(rec.items.length) !== undefined ? { providerOutputStart: anchorFor(rec.items.length) } : {}),
|
|
90
95
|
...(providers ? { providers } : {}),
|
|
96
|
+
...(rec.unforcedStoreFalse === true ? { unforcedStoreFalse: true } : {}),
|
|
91
97
|
});
|
|
92
98
|
if (!resident) {
|
|
93
99
|
store.replaceMapEntry(id, store.tombstone(id, rec.createdAt));
|
|
@@ -191,6 +191,7 @@ function spillPayloadForResident(candidate: ResidentResponseState): Parameters<t
|
|
|
191
191
|
items: candidate.items,
|
|
192
192
|
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
193
193
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
194
|
+
...(candidate.unforcedStoreFalse ? { unforcedStoreFalse: candidate.unforcedStoreFalse } : {}),
|
|
194
195
|
};
|
|
195
196
|
}
|
|
196
197
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed provenance and replay gating for unforced store:false client tool call responses.
|
|
3
|
+
*
|
|
4
|
+
* An unforced store:false response is cached exclusively to allow the client to send a matching
|
|
5
|
+
* function/custom tool output continuation on the next turn. Normal replay, text continuation, or
|
|
6
|
+
* mismatched output without a matching pending call fails replay and requires the client to
|
|
7
|
+
* supply the full conversation.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function hasPendingClientToolCall(output: readonly unknown[]): boolean {
|
|
11
|
+
return output.some(item =>
|
|
12
|
+
!!item && typeof item === "object" && clientToolOutputType((item as { type?: unknown }).type) !== undefined,
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function allowsUnforcedStoreFalseReplay(
|
|
17
|
+
stored: readonly unknown[],
|
|
18
|
+
providerOutputStart: number | undefined,
|
|
19
|
+
clientInput: readonly unknown[],
|
|
20
|
+
carried: number,
|
|
21
|
+
): boolean {
|
|
22
|
+
if (typeof providerOutputStart !== "number" || !Number.isSafeInteger(providerOutputStart)
|
|
23
|
+
|| providerOutputStart < 0 || providerOutputStart > stored.length) return false;
|
|
24
|
+
const anchor = providerOutputStart;
|
|
25
|
+
const pendingCallIds = new Map<string, string>();
|
|
26
|
+
for (const item of stored.slice(anchor)) {
|
|
27
|
+
if (item && typeof item === "object") {
|
|
28
|
+
const outputType = clientToolOutputType((item as { type?: unknown }).type);
|
|
29
|
+
const callId = (item as { call_id?: unknown }).call_id;
|
|
30
|
+
if (outputType && typeof callId === "string" && callId) pendingCallIds.set(callId, outputType);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (let i = 0; i < carried; i++) {
|
|
34
|
+
const item = clientInput[i];
|
|
35
|
+
if (item && typeof item === "object") {
|
|
36
|
+
const callId = (item as { call_id?: unknown }).call_id;
|
|
37
|
+
if (typeof callId === "string" && pendingCallIds.get(callId) === (item as { type?: unknown }).type) pendingCallIds.delete(callId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return clientInput.slice(carried).some(item => {
|
|
41
|
+
if (item && typeof item === "object") {
|
|
42
|
+
const callId = (item as { call_id?: unknown }).call_id;
|
|
43
|
+
return typeof callId === "string" && pendingCallIds.has(callId)
|
|
44
|
+
&& pendingCallIds.get(callId) === (item as { type?: unknown }).type;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Closed call/output pairs supported by the client-owned continuation path. */
|
|
51
|
+
function clientToolOutputType(type: unknown): string | undefined {
|
|
52
|
+
if (type === "function_call") return "function_call_output";
|
|
53
|
+
if (type === "custom_tool_call") return "custom_tool_call_output";
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|