@opengeni/worker-bundle 1.0.0-canary.0 → 1.0.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/dist/activities/agent-run-admission.d.ts +3 -1
- package/dist/activities/agent-turn/admission.d.ts +24 -3
- package/dist/activities/agent-turn/agent-build.d.ts +4 -0
- package/dist/activities/agent-turn/claude-usage-observer.d.ts +58 -0
- package/dist/activities/agent-turn/code-search.d.ts +60 -0
- package/dist/activities/agent-turn/codex-capacity.d.ts +13 -0
- package/dist/activities/agent-turn/codex-plan-entitlement.d.ts +105 -0
- package/dist/activities/agent-turn/codex.d.ts +3 -2
- package/dist/activities/agent-turn/compaction-prep.d.ts +21 -5
- package/dist/activities/agent-turn/credential-leases.d.ts +19 -33
- package/dist/activities/agent-turn/credential-restriction.d.ts +3 -0
- package/dist/activities/agent-turn/errors.d.ts +82 -11
- package/dist/activities/agent-turn/failure-settlement.d.ts +6 -3
- package/dist/activities/agent-turn/final-reply.d.ts +18 -0
- package/dist/activities/agent-turn/finalization-monitor.d.ts +17 -0
- package/dist/activities/agent-turn/finalization.d.ts +4 -2
- package/dist/activities/agent-turn/github-repository-bindings.d.ts +2 -0
- package/dist/activities/agent-turn/governance-model.d.ts +9 -2
- package/dist/activities/agent-turn/history-sink.d.ts +2 -0
- package/dist/activities/agent-turn/history.d.ts +2 -0
- package/dist/activities/agent-turn/input-wait-reply.d.ts +41 -0
- package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
- package/dist/activities/agent-turn/model-call-admission.d.ts +15 -0
- package/dist/activities/agent-turn/model-usage.d.ts +6 -0
- package/dist/activities/agent-turn/model-wire-dispatch.d.ts +15 -0
- package/dist/activities/agent-turn/optional-repositories.d.ts +59 -0
- package/dist/activities/agent-turn/provider-dispatch-barrier.d.ts +12 -1
- package/dist/activities/agent-turn/quiescence.d.ts +7 -7
- package/dist/activities/agent-turn/recovery-warning.d.ts +9 -0
- package/dist/activities/agent-turn/refresh-credentials.d.ts +6 -0
- package/dist/activities/agent-turn/run-credentials.d.ts +8 -3
- package/dist/activities/agent-turn/sandbox-establish.d.ts +2 -1
- package/dist/activities/agent-turn/sandbox-provision.d.ts +3 -1
- package/dist/activities/agent-turn/sandbox-route.d.ts +3 -20
- package/dist/activities/agent-turn/sandbox-runtime.d.ts +2 -2
- package/dist/activities/agent-turn/session-title.d.ts +34 -2
- package/dist/activities/agent-turn/skill-checkout.d.ts +25 -6
- package/dist/activities/agent-turn/skill-read.d.ts +70 -2
- package/dist/activities/agent-turn/skill-remove.d.ts +14 -0
- package/dist/activities/agent-turn/skill-search.d.ts +3 -1
- package/dist/activities/agent-turn/skill-tools.d.ts +15 -2
- package/dist/activities/agent-turn/skill-transfer.d.ts +18 -6
- package/dist/activities/agent-turn/stream-attempt.d.ts +7 -3
- package/dist/activities/agent-turn/subscription-lease.d.ts +38 -0
- package/dist/activities/agent-turn/tool-environment.d.ts +56 -6
- package/dist/activities/agent-turn/tool-policy.d.ts +43 -0
- package/dist/activities/agent-turn/turn-context.d.ts +29 -0
- package/dist/activities/agent-turn/turn-sandbox-access.d.ts +3 -0
- package/dist/activities/agent-turn/xai-capacity.d.ts +2 -1
- package/dist/activities/automations.d.ts +3 -1
- package/dist/activities/browser-deadline-checkpoint.d.ts +29 -0
- package/dist/activities/codemode-dispatcher.d.ts +9 -2
- package/dist/activities/codex-capacity.d.ts +18 -2
- package/dist/activities/codex-rotation.d.ts +7 -1
- package/dist/activities/context-compaction.d.ts +16 -2
- package/dist/activities/gmail-files.d.ts +32 -0
- package/dist/activities/goals.d.ts +8 -0
- package/dist/activities/knowledge-indexing.d.ts +19 -0
- package/dist/activities/mcp-account-routes.d.ts +3 -0
- package/dist/activities/mcp-credentials.d.ts +12 -13
- package/dist/activities/mcp-operation-authority.d.ts +0 -5
- package/dist/activities/model-call-fact-reconciler.d.ts +34 -0
- package/dist/activities/open-suffix-resume.d.ts +13 -0
- package/dist/activities/parent-wake.d.ts +6 -12
- package/dist/activities/retained-screenshots.d.ts +7 -2
- package/dist/activities/rig-verification.d.ts +2 -3
- package/dist/activities/run-credential-renewal.d.ts +1 -0
- package/dist/activities/run-credentials.d.ts +15 -3
- package/dist/activities/sandbox-images.d.ts +1 -1
- package/dist/activities/sandbox-lease.d.ts +47 -9
- package/dist/activities/scheduled-tasks.d.ts +4 -35
- package/dist/activities/session-state.d.ts +70 -3
- package/dist/activities/types.d.ts +55 -6
- package/dist/activities/workspace-capture.d.ts +2 -0
- package/dist/activities/workspace-credential-provider.d.ts +46 -0
- package/dist/{activities-control-HMFGSKC5.js → activities-control-BNPTRTOE.js} +1896 -418
- package/dist/activities-control-BNPTRTOE.js.map +1 -0
- package/dist/activities-control.d.ts +182 -4
- package/dist/{activities-turn-AP67JADS.js → activities-turn-5LGZOSVU.js} +4476 -1327
- package/dist/activities-turn-5LGZOSVU.js.map +1 -0
- package/dist/activities.d.ts +182 -4
- package/dist/artifact-materializer-entry.js +5 -2
- package/dist/artifact-materializer-entry.js.map +1 -1
- package/dist/artifact-outbox-entry.js +5 -2
- package/dist/artifact-outbox-entry.js.map +1 -1
- package/dist/browser-deadline-checkpoint-contract.d.ts +2 -0
- package/dist/{chunk-A4XQWVZK.js → chunk-3ZTRWDMN.js} +307 -4
- package/dist/chunk-3ZTRWDMN.js.map +1 -0
- package/dist/{chunk-RAMNYSAW.js → chunk-4ER3YXOT.js} +22 -4
- package/dist/chunk-4ER3YXOT.js.map +1 -0
- package/dist/{chunk-CYITU5UH.js → chunk-LJX7Z2KP.js} +1445 -343
- package/dist/chunk-LJX7Z2KP.js.map +1 -0
- package/dist/editable-artifact-materializer-service.d.ts +2 -2
- package/dist/editable-artifact-outbox-service.d.ts +2 -2
- package/dist/host-export-pump.d.ts +3 -1
- package/dist/host-export.d.ts +1 -1
- package/dist/host-export.js +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/index.d.ts +95 -3
- package/dist/index.js +137 -43
- package/dist/index.js.map +1 -1
- package/dist/observability-metrics.d.ts +118 -8
- package/dist/retained-process-diagnostics.d.ts +4 -0
- package/dist/retained-process-retry.d.ts +7 -0
- package/dist/sandbox-resume.d.ts +55 -13
- package/dist/sandbox-routing.d.ts +5 -3
- package/dist/sandbox-snapshot-diagnostics.d.ts +20 -0
- package/dist/session-event-fanout.d.ts +6 -0
- package/dist/warm-capture-holder.d.ts +3 -0
- package/dist/worker-service-lifecycle.d.ts +12 -0
- package/dist/workflow-bundle.js +273 -41
- package/dist/workflows/activities.d.ts +1 -1
- package/dist/workflows/browser-deadline-checkpoint.d.ts +9 -0
- package/dist/workflows/scheduled-tasks.d.ts +4 -0
- package/dist/workflows/session.d.ts +36 -0
- package/dist/workflows.d.ts +1 -0
- package/package.json +21 -19
- package/src/activities/agent-run-admission.ts +17 -1
- package/src/activities/agent-turn/admission.ts +66 -3
- package/src/activities/agent-turn/agent-build.ts +84 -10
- package/src/activities/agent-turn/claim.ts +186 -43
- package/src/activities/agent-turn/claude-usage-observer.ts +221 -0
- package/src/activities/agent-turn/code-search.ts +275 -0
- package/src/activities/agent-turn/codex-capacity.ts +172 -8
- package/src/activities/agent-turn/codex-plan-entitlement.ts +254 -0
- package/src/activities/agent-turn/codex.ts +13 -5
- package/src/activities/agent-turn/compaction-prep.ts +170 -102
- package/src/activities/agent-turn/credential-leases.ts +122 -216
- package/src/activities/agent-turn/credential-restriction.ts +14 -0
- package/src/activities/agent-turn/errors.ts +647 -48
- package/src/activities/agent-turn/failure-settlement.ts +682 -130
- package/src/activities/agent-turn/final-reply.ts +46 -0
- package/src/activities/agent-turn/finalization-monitor.ts +120 -0
- package/src/activities/agent-turn/finalization.ts +146 -43
- package/src/activities/agent-turn/github-repository-bindings.ts +3 -1
- package/src/activities/agent-turn/governance-model.ts +69 -28
- package/src/activities/agent-turn/history-sink.ts +16 -2
- package/src/activities/agent-turn/history.ts +38 -11
- package/src/activities/agent-turn/input-wait-reply.ts +103 -0
- package/src/activities/agent-turn/knowledge-source-tools.ts +2 -0
- package/src/activities/agent-turn/media-artifacts.ts +2 -1
- package/src/activities/agent-turn/model-call-admission.ts +94 -0
- package/src/activities/agent-turn/model-usage.ts +64 -3
- package/src/activities/agent-turn/model-wire-dispatch.ts +27 -0
- package/src/activities/agent-turn/optional-repositories.ts +224 -0
- package/src/activities/agent-turn/provider-dispatch-barrier.ts +22 -0
- package/src/activities/agent-turn/quiescence.ts +10 -7
- package/src/activities/agent-turn/recovery-warning.ts +16 -0
- package/src/activities/agent-turn/refresh-credentials.ts +44 -0
- package/src/activities/agent-turn/run-credentials.ts +116 -55
- package/src/activities/agent-turn/run.ts +320 -93
- package/src/activities/agent-turn/sandbox-establish.ts +84 -8
- package/src/activities/agent-turn/sandbox-provision.ts +3 -1
- package/src/activities/agent-turn/sandbox-route.ts +4 -21
- package/src/activities/agent-turn/sandbox-runtime.ts +343 -18
- package/src/activities/agent-turn/session-title.ts +81 -5
- package/src/activities/agent-turn/skill-checkout.ts +111 -24
- package/src/activities/agent-turn/skill-install.ts +1 -1
- package/src/activities/agent-turn/skill-read.ts +374 -28
- package/src/activities/agent-turn/skill-remove.ts +46 -0
- package/src/activities/agent-turn/skill-save.ts +1 -1
- package/src/activities/agent-turn/skill-search.ts +10 -1
- package/src/activities/agent-turn/skill-selection.ts +4 -0
- package/src/activities/agent-turn/skill-tools.ts +112 -18
- package/src/activities/agent-turn/skill-transfer.ts +68 -23
- package/src/activities/agent-turn/stream-attempt.ts +313 -77
- package/src/activities/agent-turn/subscription-lease.ts +115 -0
- package/src/activities/agent-turn/tool-environment.ts +316 -167
- package/src/activities/agent-turn/tool-policy.ts +80 -1
- package/src/activities/agent-turn/turn-context.ts +35 -0
- package/src/activities/agent-turn/turn-sandbox-access.ts +7 -1
- package/src/activities/agent-turn/xai-capacity.ts +94 -51
- package/src/activities/automations.ts +76 -4
- package/src/activities/browser-deadline-checkpoint.ts +279 -0
- package/src/activities/capabilities.ts +108 -11
- package/src/activities/codemode-dispatcher.ts +36 -8
- package/src/activities/codex-capacity.ts +60 -25
- package/src/activities/codex-rotation.ts +39 -17
- package/src/activities/context-compaction.ts +75 -18
- package/src/activities/gmail-files.ts +88 -0
- package/src/activities/goals.ts +116 -121
- package/src/activities/google-drive-publication.ts +1 -3
- package/src/activities/image-generation-operation.ts +1 -1
- package/src/activities/knowledge-indexing.ts +269 -15
- package/src/activities/knowledge-source-sync.ts +10 -0
- package/src/activities/mcp-account-routes.ts +17 -0
- package/src/activities/mcp-credentials.ts +83 -194
- package/src/activities/mcp-operation-authority.ts +16 -41
- package/src/activities/model-call-fact-reconciler.ts +112 -0
- package/src/activities/open-suffix-resume.ts +15 -2
- package/src/activities/parent-wake.ts +46 -21
- package/src/activities/retained-screenshots.ts +82 -11
- package/src/activities/rig-verification.ts +38 -37
- package/src/activities/run-credential-renewal.ts +13 -1
- package/src/activities/run-credentials.ts +59 -17
- package/src/activities/run-input.ts +25 -16
- package/src/activities/sandbox-images.ts +0 -1
- package/src/activities/sandbox-lease.ts +798 -82
- package/src/activities/scheduled-tasks.ts +550 -178
- package/src/activities/session-state.ts +245 -6
- package/src/activities/site-auth-maintenance.ts +3 -0
- package/src/activities/types.ts +72 -5
- package/src/activities/workflow-wake.ts +2 -17
- package/src/activities/workspace-capture.ts +13 -2
- package/src/activities/workspace-credential-provider.ts +283 -0
- package/src/activities-control.ts +4 -0
- package/src/activities-turn.ts +5 -0
- package/src/activities.ts +13 -0
- package/src/activity-services.ts +16 -4
- package/src/browser-deadline-checkpoint-contract.ts +5 -0
- package/src/editable-artifact-hint-broker.ts +3 -0
- package/src/editable-artifact-materializer-service.ts +7 -3
- package/src/editable-artifact-outbox-service.ts +4 -4
- package/src/github-rest-mcp.ts +1 -2
- package/src/host-export-pump.ts +26 -4
- package/src/host-export.ts +3 -0
- package/src/http.ts +4 -2
- package/src/index.ts +99 -37
- package/src/observability-metrics.ts +510 -5
- package/src/personal-github-git-credentials.ts +2 -1
- package/src/pr-review-credentials.ts +1 -3
- package/src/retained-process-diagnostics.ts +50 -0
- package/src/retained-process-retry.ts +36 -0
- package/src/sandbox-resume.ts +388 -101
- package/src/sandbox-routing.ts +150 -25
- package/src/sandbox-snapshot-diagnostics.ts +112 -0
- package/src/session-event-fanout.ts +23 -0
- package/src/warm-capture-holder.ts +15 -0
- package/src/worker-service-lifecycle.ts +53 -0
- package/src/workflows/activities.ts +16 -1
- package/src/workflows/browser-deadline-checkpoint.ts +50 -0
- package/src/workflows/file-upload-reaper.ts +2 -0
- package/src/workflows/scheduled-tasks.ts +26 -13
- package/src/workflows/session.ts +242 -22
- package/src/workflows.ts +4 -0
- package/dist/activities/agent-turn/knowledge-sources.d.ts +0 -20
- package/dist/activities/packs.d.ts +0 -55
- package/dist/activities-control-HMFGSKC5.js.map +0 -1
- package/dist/activities-turn-AP67JADS.js.map +0 -1
- package/dist/chunk-A4XQWVZK.js.map +0 -1
- package/dist/chunk-CYITU5UH.js.map +0 -1
- package/dist/chunk-RAMNYSAW.js.map +0 -1
- package/src/activities/agent-turn/knowledge-sources.ts +0 -86
- package/src/activities/packs.ts +0 -166
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { bindMcpTelemetry, measureMcpPhase, withMcpCallIdentity } from "@opengeni/observability";
|
|
2
3
|
import {
|
|
3
4
|
AttemptToolApprovalRequiredError,
|
|
4
5
|
AttemptToolCatalogStaleError,
|
|
@@ -16,6 +17,8 @@ import {
|
|
|
16
17
|
CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT,
|
|
17
18
|
type CodemodeOperation,
|
|
18
19
|
type SessionEvent,
|
|
20
|
+
type ToolDisplayMetadata,
|
|
21
|
+
type ToolFamily,
|
|
19
22
|
} from "@opengeni/contracts";
|
|
20
23
|
import {
|
|
21
24
|
cancelQueuedCodemodeOperationsForAttempt,
|
|
@@ -70,6 +73,9 @@ export class CodemodeAttemptDispatcher {
|
|
|
70
73
|
private readonly turnSignal?: AbortSignal,
|
|
71
74
|
private readonly maxConcurrentCalls = CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT,
|
|
72
75
|
timings: CodemodeDispatcherTimings = {},
|
|
76
|
+
private readonly toolDisplayMetadata?: (modelName: string) => ToolDisplayMetadata | undefined,
|
|
77
|
+
/** Content-free analytics family for a catalog entry; event enrichment only. */
|
|
78
|
+
private readonly toolFamily?: (entry: AttemptToolCatalogEntry) => ToolFamily | null,
|
|
73
79
|
) {
|
|
74
80
|
if (
|
|
75
81
|
environment.catalog.accountId !== scope.accountId ||
|
|
@@ -107,7 +113,7 @@ export class CodemodeAttemptDispatcher {
|
|
|
107
113
|
throw new Error("Codemode dispatcher cannot be restarted");
|
|
108
114
|
this.unsubscribe = this.bus.subscribeRequests(
|
|
109
115
|
codemodeDispatchSubject(this.scope.workspaceId, this.scope.attemptId),
|
|
110
|
-
async (payload) => {
|
|
116
|
+
bindMcpTelemetry(async (payload) => {
|
|
111
117
|
const request = decodeCodemodeDispatchRequest(payload);
|
|
112
118
|
if (request.catalogDigest !== this.environment.catalog.digest || this.closing) {
|
|
113
119
|
return encodeCodemodeDispatchAck({
|
|
@@ -136,11 +142,11 @@ export class CodemodeAttemptDispatcher {
|
|
|
136
142
|
claimLeaseMs: this.claimLeaseMs,
|
|
137
143
|
});
|
|
138
144
|
if (claim.status === "claimed") {
|
|
139
|
-
const execution =
|
|
140
|
-
(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
145
|
+
const execution = withMcpCallIdentity(claim.operation.operationId, () =>
|
|
146
|
+
this.execute(claim.operation, claim.claimId, claim.reclaimed),
|
|
147
|
+
).finally(() => {
|
|
148
|
+
this.inFlight.delete(request.operationId);
|
|
149
|
+
});
|
|
144
150
|
this.inFlight.set(request.operationId, execution);
|
|
145
151
|
return encodeCodemodeDispatchAck({
|
|
146
152
|
version: 1,
|
|
@@ -169,7 +175,7 @@ export class CodemodeAttemptDispatcher {
|
|
|
169
175
|
} finally {
|
|
170
176
|
this.pendingClaims -= 1;
|
|
171
177
|
}
|
|
172
|
-
},
|
|
178
|
+
}),
|
|
173
179
|
);
|
|
174
180
|
}
|
|
175
181
|
|
|
@@ -248,7 +254,11 @@ export class CodemodeAttemptDispatcher {
|
|
|
248
254
|
);
|
|
249
255
|
return;
|
|
250
256
|
}
|
|
251
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
!(await measureMcpPhase("event_persistence", () =>
|
|
259
|
+
this.ensureToolCallCreated(operation, entry, reclaimed),
|
|
260
|
+
))
|
|
261
|
+
) {
|
|
252
262
|
await this.failBeforeExecution(
|
|
253
263
|
operation.operationId,
|
|
254
264
|
claimId,
|
|
@@ -351,6 +361,7 @@ export class CodemodeAttemptDispatcher {
|
|
|
351
361
|
return true;
|
|
352
362
|
}
|
|
353
363
|
}
|
|
364
|
+
const toolFamily = this.toolFamily?.(entry) ?? null;
|
|
354
365
|
try {
|
|
355
366
|
const created = await appendAndPublishTurnEventsFenced(
|
|
356
367
|
this.db,
|
|
@@ -371,9 +382,14 @@ export class CodemodeAttemptDispatcher {
|
|
|
371
382
|
payload: {
|
|
372
383
|
id: operation.operationId,
|
|
373
384
|
name: entry.modelName,
|
|
385
|
+
display: this.toolDisplayMetadata?.(entry.modelName) ?? {
|
|
386
|
+
toolName: entry.identity.toolName,
|
|
387
|
+
...(entry.title ? { title: entry.title } : {}),
|
|
388
|
+
},
|
|
374
389
|
arguments: operation.arguments,
|
|
375
390
|
origin: "codemode",
|
|
376
391
|
subjectId: operation.caller.subjectId,
|
|
392
|
+
...(toolFamily ? { toolFamily } : {}),
|
|
377
393
|
raw: {
|
|
378
394
|
type: "codemode_call",
|
|
379
395
|
serverId: operation.identity.serverId,
|
|
@@ -411,6 +427,18 @@ export class CodemodeAttemptDispatcher {
|
|
|
411
427
|
errorCode: string;
|
|
412
428
|
errorMessage: string;
|
|
413
429
|
},
|
|
430
|
+
): Promise<void> {
|
|
431
|
+
return withMcpCallIdentity(operation.operationId, () =>
|
|
432
|
+
measureMcpPhase("event_persistence", () =>
|
|
433
|
+
this.persistSettlement(operation, claimId, settlement),
|
|
434
|
+
),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private async persistSettlement(
|
|
439
|
+
operation: CodemodeOperation,
|
|
440
|
+
claimId: string,
|
|
441
|
+
settlement: Parameters<CodemodeAttemptDispatcher["settleWithOutput"]>[2],
|
|
414
442
|
): Promise<void> {
|
|
415
443
|
const result = await settleCodemodeOperationWithOutput(this.db, {
|
|
416
444
|
...this.scope,
|
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
fetchCodexUsageForAccount,
|
|
4
4
|
getCodexCapacityWaitForSession,
|
|
5
5
|
getXaiCapacityWaitForSession,
|
|
6
|
+
getClaudeCapacityWaitForSession,
|
|
7
|
+
resolveClaudeWaiterSubject,
|
|
8
|
+
reconcileClaudeCapacityWait as reconcileClaudeCapacityWaitDb,
|
|
6
9
|
resolveXaiWaiterSubject,
|
|
7
10
|
listCodexAccountStatuses,
|
|
8
11
|
listPendingCodexCapacityWakeTargets,
|
|
@@ -16,6 +19,7 @@ import { refreshExhaustedXaiQuota } from "./xai-quota";
|
|
|
16
19
|
import {
|
|
17
20
|
authoritativeCodexCapacityResetAt,
|
|
18
21
|
codexAccountNeedsLiveCapacityRefresh,
|
|
22
|
+
codexAccountServesModel,
|
|
19
23
|
isCodexCredentialEligible,
|
|
20
24
|
isCodexCredentialHealthy,
|
|
21
25
|
selectCodexCredentialLeaseForTurn,
|
|
@@ -90,8 +94,14 @@ export async function signalPendingCodexCapacityWakeTargets(
|
|
|
90
94
|
|
|
91
95
|
export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnostic = never>(
|
|
92
96
|
context: CodexCapacitySelectionContext<TPolicyScope, TUnavailableDiagnostic>,
|
|
97
|
+
now = new Date(),
|
|
93
98
|
): ReturnType<Parameters<typeof reconcileCodexCapacityWaitDb>[2]> {
|
|
94
|
-
|
|
99
|
+
context = {
|
|
100
|
+
...context,
|
|
101
|
+
accounts: context.accounts.filter(
|
|
102
|
+
(account) => !context.modelId || codexAccountServesModel(account, context.modelId, now),
|
|
103
|
+
),
|
|
104
|
+
};
|
|
95
105
|
const selected = selectCodexCredentialLeaseForTurn({
|
|
96
106
|
context,
|
|
97
107
|
sessionId: context.sessionId,
|
|
@@ -114,8 +124,12 @@ export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnost
|
|
|
114
124
|
credentialId: selected.credentialId,
|
|
115
125
|
diagnostic: {
|
|
116
126
|
connectedCount: context.accounts.length,
|
|
117
|
-
eligibleCount: context.accounts.filter(
|
|
118
|
-
|
|
127
|
+
eligibleCount: context.accounts.filter(
|
|
128
|
+
(account) =>
|
|
129
|
+
(account.id === selected.credentialId ||
|
|
130
|
+
!context.failedCredentialIds?.includes(account.id)) &&
|
|
131
|
+
isCodexCredentialEligible(account, now),
|
|
132
|
+
).length,
|
|
119
133
|
},
|
|
120
134
|
};
|
|
121
135
|
}
|
|
@@ -134,8 +148,7 @@ export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnost
|
|
|
134
148
|
account.status === "active" &&
|
|
135
149
|
account.allocatorEnabled &&
|
|
136
150
|
account.exhaustedKind === "quota" &&
|
|
137
|
-
account.exhaustedUntil !== null
|
|
138
|
-
account.exhaustedUntil > now,
|
|
151
|
+
account.exhaustedUntil !== null,
|
|
139
152
|
);
|
|
140
153
|
const policyAccount = capacityAccounts[0] ?? null;
|
|
141
154
|
const mutationOnlyStatusBlock =
|
|
@@ -162,7 +175,7 @@ export function codexCapacityDecision<TPolicyScope = never, TUnavailableDiagnost
|
|
|
162
175
|
kind: "unavailable",
|
|
163
176
|
earliestResetAt: authoritativeReset,
|
|
164
177
|
resetKind:
|
|
165
|
-
selected.decision.kind === "none" ||
|
|
178
|
+
(selected.decision.kind === "none" && !hasReconcilableQuotaCooldown && !authoritativeReset) ||
|
|
166
179
|
selected.decision.kind === "allocatorDisabled" ||
|
|
167
180
|
mutationOnlyStatusBlock
|
|
168
181
|
? "mutation_only"
|
|
@@ -191,6 +204,16 @@ export async function armAndReconcileCodexCapacityWait(
|
|
|
191
204
|
options: { onArmed?: () => void } = {},
|
|
192
205
|
) {
|
|
193
206
|
const armed = await armCodexCapacityWait(services.db, input);
|
|
207
|
+
if (armed.action === "stopped") {
|
|
208
|
+
options.onArmed?.();
|
|
209
|
+
await publishDurableSessionEvents(
|
|
210
|
+
services.bus,
|
|
211
|
+
input.workspaceId,
|
|
212
|
+
input.sessionId,
|
|
213
|
+
armed.events,
|
|
214
|
+
);
|
|
215
|
+
return armed;
|
|
216
|
+
}
|
|
194
217
|
if (armed.action !== "waiting") return armed;
|
|
195
218
|
|
|
196
219
|
options.onArmed?.();
|
|
@@ -219,8 +242,9 @@ export async function armAndReconcileCodexCapacityWait(
|
|
|
219
242
|
async function refreshCapacityMetadata(
|
|
220
243
|
services: ControlActivityServices,
|
|
221
244
|
workspaceId: string,
|
|
245
|
+
turnId: string,
|
|
222
246
|
): Promise<void> {
|
|
223
|
-
const accounts = await listCodexAccountStatuses(services.db, workspaceId).catch(() => []);
|
|
247
|
+
const accounts = await listCodexAccountStatuses(services.db, workspaceId, turnId).catch(() => []);
|
|
224
248
|
const now = new Date();
|
|
225
249
|
const stale = accounts.filter(
|
|
226
250
|
(account) =>
|
|
@@ -231,7 +255,14 @@ async function refreshCapacityMetadata(
|
|
|
231
255
|
await refreshCodexUsageAndRepairCapacityWaiters(
|
|
232
256
|
stale.map(
|
|
233
257
|
(account) => () =>
|
|
234
|
-
fetchCodexUsageForAccount(
|
|
258
|
+
fetchCodexUsageForAccount(
|
|
259
|
+
services.db,
|
|
260
|
+
services.settings,
|
|
261
|
+
workspaceId,
|
|
262
|
+
account.id,
|
|
263
|
+
undefined,
|
|
264
|
+
turnId,
|
|
265
|
+
),
|
|
235
266
|
),
|
|
236
267
|
() => signalPendingCodexCapacityWakeTargets(services, workspaceId),
|
|
237
268
|
);
|
|
@@ -248,10 +279,18 @@ export function createCodexCapacityActivities(services: () => Promise<ControlAct
|
|
|
248
279
|
const xaiWaiter = codexWaiter
|
|
249
280
|
? null
|
|
250
281
|
: await getXaiCapacityWaitForSession(db, input.workspaceId, input.sessionId);
|
|
251
|
-
const
|
|
282
|
+
const claudeWaiter =
|
|
283
|
+
codexWaiter || xaiWaiter
|
|
284
|
+
? null
|
|
285
|
+
: await getClaudeCapacityWaitForSession(db, input.workspaceId, input.sessionId);
|
|
286
|
+
const waiter = codexWaiter ?? xaiWaiter ?? claudeWaiter;
|
|
252
287
|
return waiter
|
|
253
288
|
? {
|
|
254
|
-
...(xaiWaiter
|
|
289
|
+
...(xaiWaiter
|
|
290
|
+
? { provider: "xai" as const }
|
|
291
|
+
: claudeWaiter
|
|
292
|
+
? { provider: "claude" as const }
|
|
293
|
+
: {}),
|
|
255
294
|
waiterId: waiter.id,
|
|
256
295
|
generation: waiter.generation,
|
|
257
296
|
// A capacity mutation may have committed while its Temporal signal
|
|
@@ -271,21 +310,17 @@ export function createCodexCapacityActivities(services: () => Promise<ControlAct
|
|
|
271
310
|
input: ReconcileCodexCapacityWaitInput,
|
|
272
311
|
): Promise<ReconcileCodexCapacityWaitResult> {
|
|
273
312
|
const resolved = await services();
|
|
274
|
-
if (input.provider === "xai") {
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
);
|
|
313
|
+
if (input.provider === "xai" || input.provider === "claude") {
|
|
314
|
+
const claude = input.provider === "claude";
|
|
315
|
+
const getWaiter = claude ? getClaudeCapacityWaitForSession : getXaiCapacityWaitForSession;
|
|
316
|
+
const resolveAuthority = claude ? resolveClaudeWaiterSubject : resolveXaiWaiterSubject;
|
|
317
|
+
const reconcile = claude ? reconcileClaudeCapacityWaitDb : reconcileXaiCapacityWaitDb;
|
|
318
|
+
const current = await getWaiter(resolved.db, input.workspaceId, input.sessionId);
|
|
280
319
|
if (!current || current.id !== input.waiterId || current.generation !== input.generation) {
|
|
281
320
|
return { action: "stale" };
|
|
282
321
|
}
|
|
283
|
-
const authority = await
|
|
284
|
-
|
|
285
|
-
input.workspaceId,
|
|
286
|
-
input.sessionId,
|
|
287
|
-
);
|
|
288
|
-
if (authority)
|
|
322
|
+
const authority = await resolveAuthority(resolved.db, input.workspaceId, input.sessionId);
|
|
323
|
+
if (authority && !claude)
|
|
289
324
|
await refreshExhaustedXaiQuota({
|
|
290
325
|
db: resolved.db,
|
|
291
326
|
settings: resolved.settings,
|
|
@@ -296,7 +331,7 @@ export function createCodexCapacityActivities(services: () => Promise<ControlAct
|
|
|
296
331
|
subjectId: authority.subjectId,
|
|
297
332
|
authoritySnapshot: authority.snapshot,
|
|
298
333
|
});
|
|
299
|
-
const result = await
|
|
334
|
+
const result = await reconcile(resolved.db, {
|
|
300
335
|
accountId: input.accountId,
|
|
301
336
|
workspaceId: input.workspaceId,
|
|
302
337
|
sessionId: input.sessionId,
|
|
@@ -314,7 +349,7 @@ export function createCodexCapacityActivities(services: () => Promise<ControlAct
|
|
|
314
349
|
if (result.action === "waiting") {
|
|
315
350
|
return {
|
|
316
351
|
action: "waiting",
|
|
317
|
-
provider:
|
|
352
|
+
provider: input.provider,
|
|
318
353
|
waiterId: result.waiter.id,
|
|
319
354
|
generation: result.waiter.generation,
|
|
320
355
|
nextCheckAt: result.waiter.nextCheckAt.toISOString(),
|
|
@@ -338,7 +373,7 @@ export function createCodexCapacityActivities(services: () => Promise<ControlAct
|
|
|
338
373
|
if (boundedRefreshAttempted) {
|
|
339
374
|
// This is a bounded secret-safe control-plane quota refresh. It creates no
|
|
340
375
|
// turn, model call, user message, schedule, or entitlement action.
|
|
341
|
-
await refreshCapacityMetadata(resolved, input.workspaceId);
|
|
376
|
+
await refreshCapacityMetadata(resolved, input.workspaceId, current.blockedTurnId);
|
|
342
377
|
}
|
|
343
378
|
const result = await reconcileCodexCapacityWaitDb(
|
|
344
379
|
resolved.db,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { subscriptionAccountShardIndex } from "@opengeni/config";
|
|
1
2
|
// Multi-account P3 — the PURE rotation ranker. Zero I/O: no provider calls, no
|
|
2
3
|
// decrypts, no db. It consumes the already-loaded, metadata-only account list
|
|
3
4
|
// (cached usage columns + the exhausted_until cooldown column) and returns the
|
|
@@ -12,23 +13,38 @@ import type {
|
|
|
12
13
|
CodexLeaseAccountStatus,
|
|
13
14
|
CodexPinSource,
|
|
14
15
|
} from "@opengeni/db";
|
|
16
|
+
import { codexPlanExcludesModel, connectionModelAllowed } from "@opengeni/db";
|
|
15
17
|
|
|
16
18
|
export type CodexRotationAccount = CodexAccountStatus | CodexLeaseAccountStatus;
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Model-scoped allocation filter: user connection policy plus proven plan
|
|
22
|
+
* entitlement. A plan exclusion applies only until it expires (one request
|
|
23
|
+
* then re-probes the account) or a different plan is observed.
|
|
24
|
+
*/
|
|
25
|
+
export function codexAccountServesModel(
|
|
26
|
+
account: Pick<CodexRotationAccount, "allowedModelIds" | "planType" | "planEntitlementExclusion">,
|
|
27
|
+
modelId: string,
|
|
28
|
+
now: Date,
|
|
29
|
+
): boolean {
|
|
30
|
+
return (
|
|
31
|
+
connectionModelAllowed(account.allowedModelIds, modelId) &&
|
|
32
|
+
!codexPlanExcludesModel(account, modelId, now)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
/** Capacity metadata worth an authoritative live read before/all through an idle. */
|
|
19
37
|
export function codexAccountNeedsLiveCapacityRefresh(
|
|
20
38
|
account: Pick<
|
|
21
39
|
CodexAccountStatus,
|
|
22
40
|
"primaryUsedPercent" | "secondaryUsedPercent" | "exhaustedUntil" | "exhaustedKind"
|
|
23
41
|
>,
|
|
24
|
-
|
|
42
|
+
_now: Date,
|
|
25
43
|
): boolean {
|
|
26
44
|
return (
|
|
27
45
|
(account.primaryUsedPercent ?? 0) >= CODEX_USAGE_EXHAUSTED_PCT ||
|
|
28
46
|
(account.secondaryUsedPercent ?? 0) >= CODEX_USAGE_EXHAUSTED_PCT ||
|
|
29
|
-
(account.exhaustedKind === "quota" &&
|
|
30
|
-
account.exhaustedUntil !== null &&
|
|
31
|
-
account.exhaustedUntil > now)
|
|
47
|
+
(account.exhaustedKind === "quota" && account.exhaustedUntil !== null)
|
|
32
48
|
);
|
|
33
49
|
}
|
|
34
50
|
|
|
@@ -208,17 +224,6 @@ export function isCodexAccountEligible(acct: CodexRotationAccount, now: Date): b
|
|
|
208
224
|
return isCodexCredentialEligible(acct, now);
|
|
209
225
|
}
|
|
210
226
|
|
|
211
|
-
/** Deterministic 32-bit FNV-1a over a UTF-16 code-unit stream. Pure, allocation-free. */
|
|
212
|
-
function fnv1a32(input: string): number {
|
|
213
|
-
let hash = 0x811c9dc5;
|
|
214
|
-
for (let i = 0; i < input.length; i++) {
|
|
215
|
-
hash ^= input.charCodeAt(i);
|
|
216
|
-
// 32-bit FNV prime multiply via Math.imul; `>>> 0` keeps it unsigned.
|
|
217
|
-
hash = Math.imul(hash, 0x01000193);
|
|
218
|
-
}
|
|
219
|
-
return hash >>> 0;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
227
|
/**
|
|
223
228
|
* Session-sharded HOME account (AM-6): the deterministic account a session runs on
|
|
224
229
|
* under the "sharded" strategy — `stableAccountList[ hash(sessionId) % N ]` over the
|
|
@@ -248,7 +253,7 @@ export function shardCredentialForSession(args: {
|
|
|
248
253
|
if (eligibles.length === 0) {
|
|
249
254
|
return null;
|
|
250
255
|
}
|
|
251
|
-
const index =
|
|
256
|
+
const index = subscriptionAccountShardIndex(sessionId, eligibles.length);
|
|
252
257
|
return eligibles[index]!.id;
|
|
253
258
|
}
|
|
254
259
|
|
|
@@ -524,8 +529,19 @@ export function selectCodexCredentialLeaseForTurn<
|
|
|
524
529
|
sessionLastCredentialId: string | null;
|
|
525
530
|
now: Date;
|
|
526
531
|
}): CodexTurnLeaseSelection {
|
|
527
|
-
const {
|
|
532
|
+
const { activeCredentialId, rotationEnabled, rotationStrategy, existingCredentialId } =
|
|
528
533
|
args.context;
|
|
534
|
+
if (args.context.failoverExhausted) {
|
|
535
|
+
return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
|
|
536
|
+
}
|
|
537
|
+
// User model policy and proven plan entitlement both remove an account for
|
|
538
|
+
// this model only. A plan exclusion is provider truth, never user policy; it
|
|
539
|
+
// becomes inert when a different plan is observed or its TTL elapses.
|
|
540
|
+
const accounts = args.context.modelId
|
|
541
|
+
? args.context.accounts.filter((account) =>
|
|
542
|
+
codexAccountServesModel(account, args.context.modelId!, args.now),
|
|
543
|
+
)
|
|
544
|
+
: args.context.accounts;
|
|
529
545
|
const failedCredentialIds = new Set(args.context.failedCredentialIds ?? []);
|
|
530
546
|
// Normalized: rotation-enabled always behaves as sharded (see
|
|
531
547
|
// effectiveRotationStrategy); the stored value is only ever legacy residue.
|
|
@@ -564,6 +580,9 @@ export function selectCodexCredentialLeaseForTurn<
|
|
|
564
580
|
if (!pinned) {
|
|
565
581
|
return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
|
|
566
582
|
}
|
|
583
|
+
if (failedCredentialIds.has(pinned.id)) {
|
|
584
|
+
return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
|
|
585
|
+
}
|
|
567
586
|
if (!pinned.allocatorEnabled) {
|
|
568
587
|
return {
|
|
569
588
|
credentialId: null,
|
|
@@ -633,6 +652,9 @@ export function selectCodexCredentialLeaseForTurn<
|
|
|
633
652
|
if (!active) {
|
|
634
653
|
return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
|
|
635
654
|
}
|
|
655
|
+
if (failedCredentialIds.has(active.id)) {
|
|
656
|
+
return { credentialId: null, decision: { kind: "none" }, advanceActivePointer: false };
|
|
657
|
+
}
|
|
636
658
|
if (!active.allocatorEnabled) {
|
|
637
659
|
return {
|
|
638
660
|
credentialId: null,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readReasoningConfiguration } from "@opengeni/codex";
|
|
1
2
|
import {
|
|
2
3
|
applyContextCompaction,
|
|
3
4
|
getActiveSessionHistoryItemsPaged,
|
|
@@ -8,7 +9,8 @@ import {
|
|
|
8
9
|
import {
|
|
9
10
|
EmptyCompactionSummaryError,
|
|
10
11
|
REMOTE_COMPACTION_V2_IMPLEMENTATION,
|
|
11
|
-
|
|
12
|
+
compactionSummaryOutputTokens,
|
|
13
|
+
buildSummaryItem,
|
|
12
14
|
buildCompactionReplacementHistory,
|
|
13
15
|
buildRemoteV2ReplacementHistory,
|
|
14
16
|
compactionThresholdTokens,
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
sanitizeHistoryItemsForModel,
|
|
22
24
|
summarizeForCompaction,
|
|
23
25
|
type CompactionItem,
|
|
26
|
+
type CompactionProviderRejection,
|
|
24
27
|
} from "@opengeni/runtime";
|
|
25
28
|
import { contextInputBudgetTokens, type Settings } from "@opengeni/config";
|
|
26
29
|
import type { SessionEvent } from "@opengeni/contracts";
|
|
@@ -56,7 +59,13 @@ export type MaybeCompactResult =
|
|
|
56
59
|
* Codex, call Codex `/codex/responses` with `compaction_trigger` and persist the
|
|
57
60
|
* opaque compaction item. Fail closed — never silently fall back to portable.
|
|
58
61
|
*/
|
|
59
|
-
export type CompactionSummarizer = (
|
|
62
|
+
export type CompactionSummarizer = ((
|
|
63
|
+
settings: Settings,
|
|
64
|
+
input: CompactionItem[],
|
|
65
|
+
) => Promise<string>) & {
|
|
66
|
+
/** Model-visible instructions and tool schemas outside the history estimate. */
|
|
67
|
+
estimatePrefixTokens?: () => number;
|
|
68
|
+
};
|
|
60
69
|
|
|
61
70
|
/** Returns the opaque Codex remote compaction v2 item. */
|
|
62
71
|
export type RemoteCompactionV2Requester = (
|
|
@@ -79,7 +88,7 @@ export async function maybeCompactContext(
|
|
|
79
88
|
// Injectable for tests; defaults to the real provider-aware model call.
|
|
80
89
|
summarize: CompactionSummarizer = (s, m) =>
|
|
81
90
|
summarizeForCompaction(s, m, {
|
|
82
|
-
maxOutputTokens:
|
|
91
|
+
maxOutputTokens: compactionSummaryOutputTokens(s.contextWindowTokens),
|
|
83
92
|
}),
|
|
84
93
|
// Operator-forced (the /compact command): bypass the budget trigger and
|
|
85
94
|
// compact now if there is anything to summarize. Structural guards still hold.
|
|
@@ -260,6 +269,12 @@ export async function settleFailedContextCompactionLandmark(
|
|
|
260
269
|
options: {
|
|
261
270
|
clearRequestedCompaction?: boolean;
|
|
262
271
|
publishLiveEvents?: (events: SessionEvent[]) => Promise<void>;
|
|
272
|
+
/**
|
|
273
|
+
* Closed identifier record of a definitive provider rejection. Carried on
|
|
274
|
+
* the visible `compaction.skipped` landmark so the timeline can name the
|
|
275
|
+
* rejected field instead of offering a retry that cannot succeed.
|
|
276
|
+
*/
|
|
277
|
+
providerRejection?: CompactionProviderRejection | null;
|
|
263
278
|
} = {},
|
|
264
279
|
): Promise<Extract<MaybeCompactResult, { compacted: false }>> {
|
|
265
280
|
const settled = await settleSkippedAfterStart(db, scope, options, "summarization_failed");
|
|
@@ -287,10 +302,12 @@ async function settleSkippedAfterStart(
|
|
|
287
302
|
},
|
|
288
303
|
options: {
|
|
289
304
|
clearRequestedCompaction?: boolean;
|
|
305
|
+
providerRejection?: CompactionProviderRejection | null;
|
|
290
306
|
},
|
|
291
307
|
reason:
|
|
292
308
|
| "no_history"
|
|
293
309
|
| "replacement_not_smaller"
|
|
310
|
+
| "replacement_exceeds_model_budget"
|
|
294
311
|
| "replacement_unchanged"
|
|
295
312
|
| "summarization_failed",
|
|
296
313
|
): Promise<Extract<MaybeCompactResult, { compacted: false }>> {
|
|
@@ -304,6 +321,7 @@ async function settleSkippedAfterStart(
|
|
|
304
321
|
// an operator `/compact` flag — auto/overflow never set one.
|
|
305
322
|
requirePendingRequest: false,
|
|
306
323
|
clearRequestedCompaction,
|
|
324
|
+
...(options.providerRejection ? { providerRejection: options.providerRejection } : {}),
|
|
307
325
|
});
|
|
308
326
|
if (!skipped.recorded) {
|
|
309
327
|
throw new TurnAttemptFencedError(
|
|
@@ -370,7 +388,10 @@ async function compactContextRemoteV2(
|
|
|
370
388
|
);
|
|
371
389
|
const estimatedTokensAfter = estimateTokens(await projectForWire(replacementHistory));
|
|
372
390
|
const replacementFingerprint = compactionReplacementFingerprint(replacementHistory);
|
|
373
|
-
const
|
|
391
|
+
const summaryIndex =
|
|
392
|
+
replacementHistory.length -
|
|
393
|
+
(readReasoningConfiguration(replacementHistory.at(-1) ?? {}) ? 2 : 1);
|
|
394
|
+
const tailItem = replacementHistory[summaryIndex];
|
|
374
395
|
if (!tailItem) {
|
|
375
396
|
throw new EmptyCompactionSummaryError({
|
|
376
397
|
stage: "remote_v2_replacement",
|
|
@@ -384,7 +405,10 @@ async function compactContextRemoteV2(
|
|
|
384
405
|
turnId: scope.turnId,
|
|
385
406
|
expectedExecutionGeneration: scope.executionGeneration,
|
|
386
407
|
expectedAttemptId: scope.attemptId,
|
|
387
|
-
replacementItems: replacementHistory.slice(0,
|
|
408
|
+
replacementItems: replacementHistory.slice(0, summaryIndex),
|
|
409
|
+
...(replacementHistory.length > summaryIndex + 1
|
|
410
|
+
? { trailingItems: replacementHistory.slice(summaryIndex + 1) }
|
|
411
|
+
: {}),
|
|
388
412
|
summaryItem: tailItem as Record<string, unknown>,
|
|
389
413
|
...(options.clearRequestedCompaction ? { clearRequestedCompaction: true } : {}),
|
|
390
414
|
eventPayload: {
|
|
@@ -439,15 +463,29 @@ async function compactContextPortable(
|
|
|
439
463
|
const summarized = await summarizeWithCodexOverflowTrimming(summarize, settings, items);
|
|
440
464
|
const summaryBody = summarized.summaryBody;
|
|
441
465
|
const retainedTokens = await retentionTokenCounts(canonicalItems, projectForWire);
|
|
466
|
+
const outputReserve = compactionSummaryOutputTokens(settings.contextWindowTokens);
|
|
467
|
+
const structuralBudget = Math.min(
|
|
468
|
+
contextInputBudgetTokens(settings) || settings.contextWindowTokens - outputReserve,
|
|
469
|
+
settings.contextWindowTokens - outputReserve,
|
|
470
|
+
);
|
|
471
|
+
const prefixTokens = Math.max(0, Math.ceil(summarize.estimatePrefixTokens?.() ?? 0));
|
|
472
|
+
const summaryTokens = estimateTokens([buildSummaryItem(summaryBody)]);
|
|
442
473
|
const replacementHistory = buildCompactionReplacementHistory(
|
|
443
474
|
canonicalItems,
|
|
444
475
|
summaryBody,
|
|
445
476
|
(item) => retainedTokens.get(item) ?? estimateTokens([item]),
|
|
477
|
+
Math.min(outputReserve, Math.max(0, structuralBudget - prefixTokens - summaryTokens)),
|
|
446
478
|
);
|
|
447
479
|
const estimatedTokensAfter = estimateTokens(await projectForWire(replacementHistory));
|
|
480
|
+
if (estimatedTokensAfter + prefixTokens > structuralBudget) {
|
|
481
|
+
return await settleSkippedAfterStart(db, scope, options, "replacement_exceeds_model_budget");
|
|
482
|
+
}
|
|
448
483
|
const replacementFingerprint = compactionReplacementFingerprint(replacementHistory);
|
|
449
484
|
const previousReplacementFingerprint = latestCompactionReplacementFingerprint(canonicalItems);
|
|
450
|
-
const
|
|
485
|
+
const summaryIndex =
|
|
486
|
+
replacementHistory.length -
|
|
487
|
+
(readReasoningConfiguration(replacementHistory.at(-1) ?? {}) ? 2 : 1);
|
|
488
|
+
const summaryItem = replacementHistory[summaryIndex];
|
|
451
489
|
if (!summaryItem) {
|
|
452
490
|
// Started already fanout; settle visibly so the landmark cannot stick on
|
|
453
491
|
// "Compacting…". This is not an operator-request clear path.
|
|
@@ -466,7 +504,10 @@ async function compactContextPortable(
|
|
|
466
504
|
turnId: scope.turnId,
|
|
467
505
|
expectedExecutionGeneration: scope.executionGeneration,
|
|
468
506
|
expectedAttemptId: scope.attemptId,
|
|
469
|
-
replacementItems: replacementHistory.slice(0,
|
|
507
|
+
replacementItems: replacementHistory.slice(0, summaryIndex),
|
|
508
|
+
...(replacementHistory.length > summaryIndex + 1
|
|
509
|
+
? { trailingItems: replacementHistory.slice(summaryIndex + 1) }
|
|
510
|
+
: {}),
|
|
470
511
|
summaryItem: summaryItem as Record<string, unknown>,
|
|
471
512
|
...(options.clearRequestedCompaction ? { clearRequestedCompaction: true } : {}),
|
|
472
513
|
eventPayload: {
|
|
@@ -498,7 +539,7 @@ async function compactContextPortable(
|
|
|
498
539
|
};
|
|
499
540
|
}
|
|
500
541
|
|
|
501
|
-
async function summarizeWithCodexOverflowTrimming(
|
|
542
|
+
export async function summarizeWithCodexOverflowTrimming(
|
|
502
543
|
summarize: CompactionSummarizer,
|
|
503
544
|
settings: Settings,
|
|
504
545
|
activeHistory: CompactionItem[],
|
|
@@ -509,17 +550,32 @@ async function summarizeWithCodexOverflowTrimming(
|
|
|
509
550
|
}> {
|
|
510
551
|
// Codex's estimator is intentionally coarse. Keep the explicit checkpoint
|
|
511
552
|
// request below both the effective input window and the raw window minus the
|
|
512
|
-
// requested summary
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
const summaryAwareBudget = Math.max(
|
|
553
|
+
// requested summary. Preserve the full portable history copy on the first
|
|
554
|
+
// call whenever it fits; only trim further after an actual provider overflow.
|
|
555
|
+
// Durable active history remains untouched until applyContextCompaction.
|
|
556
|
+
const summaryAwareBudget = Math.max(
|
|
557
|
+
0,
|
|
558
|
+
settings.contextWindowTokens - compactionSummaryOutputTokens(settings.contextWindowTokens),
|
|
559
|
+
);
|
|
516
560
|
const configuredInputBudget = contextInputBudgetTokens(settings);
|
|
517
561
|
const structuralBudget = Math.min(
|
|
518
562
|
configuredInputBudget > 0 ? configuredInputBudget : summaryAwareBudget,
|
|
519
563
|
summaryAwareBudget,
|
|
520
564
|
);
|
|
521
|
-
const
|
|
565
|
+
const prefixTokens = Math.max(0, Math.ceil(summarize.estimatePrefixTokens?.() ?? 0));
|
|
566
|
+
const initialBudget = Math.max(0, structuralBudget - prefixTokens);
|
|
522
567
|
let preparation = prepareCompactionPromptInput(activeHistory, initialBudget);
|
|
568
|
+
// A checkpoint prompt without source history cannot summarize that history.
|
|
569
|
+
// Do not let a plausible-sounding reply replace durable active context.
|
|
570
|
+
const requireHistory = () => {
|
|
571
|
+
if (activeHistory.length > 0 && preparation.input.length === 1) {
|
|
572
|
+
throw new EmptyCompactionSummaryError({
|
|
573
|
+
stage: "portable_input_budget",
|
|
574
|
+
reason: "no_history_fit",
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
requireHistory();
|
|
523
579
|
try {
|
|
524
580
|
return {
|
|
525
581
|
summaryBody: await summarize(settings, preparation.input),
|
|
@@ -529,15 +585,16 @@ async function summarizeWithCodexOverflowTrimming(
|
|
|
529
585
|
} catch (error) {
|
|
530
586
|
if (!isContextWindowExceeded(error)) throw error;
|
|
531
587
|
// The provider is more authoritative than the byte/4 estimate. Refit once
|
|
532
|
-
// to
|
|
588
|
+
// to 40% of both the available target and the actual prepared estimate;
|
|
533
589
|
// then fail terminally with prior history intact. Never issue one failing
|
|
534
|
-
// request per oldest item. The
|
|
535
|
-
// provider can count slightly more than twice the byte/4 estimate
|
|
536
|
-
//
|
|
590
|
+
// request per oldest item. The retry is bounded, not a guarantee: the
|
|
591
|
+
// provider can count slightly more than twice the byte/4 estimate. The
|
|
592
|
+
// prepared prefix has already been reserved from the available budget.
|
|
537
593
|
const retryBudget = Math.floor(
|
|
538
|
-
Math.min(initialBudget * 0.
|
|
594
|
+
Math.min(initialBudget * 0.4, preparation.estimatedInputTokens * 0.4),
|
|
539
595
|
);
|
|
540
596
|
preparation = prepareCompactionPromptInput(activeHistory, retryBudget);
|
|
597
|
+
requireHistory();
|
|
541
598
|
return {
|
|
542
599
|
summaryBody: await summarize(settings, preparation.input),
|
|
543
600
|
preparation,
|