@opengeni/worker-bundle 2.0.1 → 2.0.3-canary.36079201993001
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-turn/failure-settlement.d.ts +1 -1
- package/dist/activities/agent-turn/history.d.ts +2 -0
- package/dist/activities/agent-turn/media-artifacts.d.ts +5 -3
- package/dist/activities/context-compaction.d.ts +10 -2
- package/dist/activities/retained-screenshots.d.ts +7 -2
- package/dist/activities/sandbox-lease.d.ts +3 -4
- package/dist/{activities-control-QA6OLRQY.js → activities-control-IVTL723D.js} +204 -23
- package/dist/activities-control-IVTL723D.js.map +1 -0
- package/dist/{activities-turn-YVVKCGAK.js → activities-turn-AJHDYNZU.js} +180 -58
- package/dist/activities-turn-AJHDYNZU.js.map +1 -0
- package/dist/{chunk-M3NVMF2V.js → chunk-JMO5ZUW3.js} +14 -7
- package/dist/{chunk-M3NVMF2V.js.map → chunk-JMO5ZUW3.js.map} +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/workflow-bundle.js +4 -1
- package/package.json +19 -19
- package/src/activities/agent-turn/compaction-prep.ts +52 -16
- package/src/activities/agent-turn/errors.ts +8 -3
- package/src/activities/agent-turn/failure-settlement.ts +40 -8
- package/src/activities/agent-turn/history.ts +14 -1
- package/src/activities/agent-turn/media-artifacts.ts +2 -1
- package/src/activities/agent-turn/sandbox-runtime.ts +25 -9
- package/src/activities/agent-turn/stream-attempt.ts +13 -2
- package/src/activities/context-compaction.ts +49 -14
- package/src/activities/knowledge-indexing.ts +133 -6
- package/src/activities/retained-screenshots.ts +82 -11
- package/src/activities/sandbox-lease.ts +141 -21
- package/src/sandbox-resume.ts +6 -0
- package/src/workflows/activities.ts +14 -1
- package/dist/activities-control-QA6OLRQY.js.map +0 -1
- package/dist/activities-turn-YVVKCGAK.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/worker-bundle",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3-canary.36079201993001",
|
|
4
4
|
"description": "OpenGeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,24 +54,24 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@llamaindex/liteparse": "2.14.2",
|
|
57
|
-
"@opengeni/agent-proto": "^0.6.0",
|
|
58
|
-
"@opengeni/capabilities": "^0.3.4",
|
|
59
|
-
"@opengeni/codemode": "^0.6.
|
|
60
|
-
"@opengeni/codex": "^0.2.
|
|
61
|
-
"@opengeni/config": "^2.1.
|
|
62
|
-
"@opengeni/contracts": "^5.1.
|
|
63
|
-
"@opengeni/core": "^4.0.
|
|
64
|
-
"@opengeni/db": "^6.0.
|
|
65
|
-
"@opengeni/documents": "^0.8.
|
|
66
|
-
"@opengeni/events": "^0.4.
|
|
67
|
-
"@opengeni/github": "^0.7.
|
|
68
|
-
"@opengeni/network": "^0.3.1",
|
|
69
|
-
"@opengeni/observability": "^0.8.
|
|
70
|
-
"@opengeni/runtime": "^4.0.
|
|
71
|
-
"@opengeni/sdk": "^7.1.
|
|
72
|
-
"@opengeni/storage": "^0.2.
|
|
73
|
-
"@opengeni/tool-gateway": "^0.1.
|
|
74
|
-
"@opengeni/xai-subscription": "^0.1.4",
|
|
57
|
+
"@opengeni/agent-proto": "^0.6.0-canary.36079201993001",
|
|
58
|
+
"@opengeni/capabilities": "^0.3.4-canary.36079201993001",
|
|
59
|
+
"@opengeni/codemode": "^0.6.2-canary.36079201993001",
|
|
60
|
+
"@opengeni/codex": "^0.2.26-canary.36079201993001",
|
|
61
|
+
"@opengeni/config": "^2.1.1-canary.36079201993001",
|
|
62
|
+
"@opengeni/contracts": "^5.1.1-canary.36079201993001",
|
|
63
|
+
"@opengeni/core": "^4.0.3-canary.36079201993001",
|
|
64
|
+
"@opengeni/db": "^6.0.3-canary.36079201993001",
|
|
65
|
+
"@opengeni/documents": "^0.8.34-canary.36079201993001",
|
|
66
|
+
"@opengeni/events": "^0.4.32-canary.36079201993001",
|
|
67
|
+
"@opengeni/github": "^0.7.16-canary.36079201993001",
|
|
68
|
+
"@opengeni/network": "^0.3.1-canary.36079201993001",
|
|
69
|
+
"@opengeni/observability": "^0.8.32-canary.36079201993001",
|
|
70
|
+
"@opengeni/runtime": "^4.0.3-canary.36079201993001",
|
|
71
|
+
"@opengeni/sdk": "^7.1.1-canary.36079201993001",
|
|
72
|
+
"@opengeni/storage": "^0.2.133-canary.36079201993001",
|
|
73
|
+
"@opengeni/tool-gateway": "^0.1.13-canary.36079201993001",
|
|
74
|
+
"@opengeni/xai-subscription": "^0.1.4-canary.36079201993001",
|
|
75
75
|
"@temporalio/activity": "^1.17.0",
|
|
76
76
|
"@temporalio/client": "^1.17.0",
|
|
77
77
|
"@temporalio/worker": "^1.17.0",
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
compactionThresholdTokens,
|
|
12
12
|
CompactionNeededError,
|
|
13
13
|
compactionProviderRejection,
|
|
14
|
-
|
|
14
|
+
estimateSerializedValueTokens,
|
|
15
|
+
compactionSummaryOutputTokens,
|
|
15
16
|
type ModelResponseUsage,
|
|
16
17
|
} from "@opengeni/runtime";
|
|
17
18
|
import { type Settings } from "@opengeni/config";
|
|
@@ -168,6 +169,13 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
|
|
|
168
169
|
const remotePrefix: RemoteCompactionPrefix = {
|
|
169
170
|
agent: null,
|
|
170
171
|
};
|
|
172
|
+
const portableResponsesNeedsAgentPrefix =
|
|
173
|
+
resolvedModel?.provider.api === "responses" &&
|
|
174
|
+
!(billingState.isCodexTurn && session.codexCompactionMode === "remote_v2");
|
|
175
|
+
const preparedPortableRequest = () => {
|
|
176
|
+
if (!remotePrefix.agent) throw new Error("Compaction agent is unavailable");
|
|
177
|
+
return preparedCompactionRequest(remotePrefix.agent);
|
|
178
|
+
};
|
|
171
179
|
|
|
172
180
|
const promptCacheKey = acceptsPromptCacheKeyForTurn(resolvedModel) ? input.sessionId : undefined;
|
|
173
181
|
const compactionUsageState = createCompactionModelUsageEventState(claimedModelUsageSourceKeys);
|
|
@@ -200,8 +208,8 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
|
|
|
200
208
|
contextContributions: eventing.companyBrainContextContributions,
|
|
201
209
|
});
|
|
202
210
|
};
|
|
203
|
-
const compactionSummarizerFor = (systemInstructions?: string) =>
|
|
204
|
-
resolvedModel
|
|
211
|
+
const compactionSummarizerFor = (systemInstructions?: string): CompactionSummarizer => {
|
|
212
|
+
const summarize: CompactionSummarizer = resolvedModel
|
|
205
213
|
? (s: Settings, m: Array<Record<string, unknown>>) =>
|
|
206
214
|
withProviderRequestContext(() =>
|
|
207
215
|
summarizeContextForCompaction(s, m, {
|
|
@@ -209,20 +217,37 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
|
|
|
209
217
|
provider: resolvedModel.provider,
|
|
210
218
|
api: resolvedModel.provider.api,
|
|
211
219
|
model: turnExecutionPolicy.upstreamModelId,
|
|
212
|
-
maxOutputTokens:
|
|
220
|
+
maxOutputTokens: compactionSummaryOutputTokens(s.contextWindowTokens),
|
|
221
|
+
...(cancellationSignal ? { signal: cancellationSignal } : {}),
|
|
213
222
|
onUsage: recordCompactionUsage,
|
|
214
223
|
...(systemInstructions ? { systemInstructions } : {}),
|
|
215
224
|
...(promptCacheKey ? { promptCacheKey } : {}),
|
|
225
|
+
...(portableResponsesNeedsAgentPrefix
|
|
226
|
+
? { preparedRequest: preparedPortableRequest() }
|
|
227
|
+
: {}),
|
|
216
228
|
}),
|
|
217
229
|
)
|
|
218
230
|
: (s: Settings, m: Array<Record<string, unknown>>) =>
|
|
219
231
|
summarizeContextForCompaction(s, m, {
|
|
220
232
|
model: turnExecutionPolicy.upstreamModelId,
|
|
221
|
-
maxOutputTokens:
|
|
233
|
+
maxOutputTokens: compactionSummaryOutputTokens(s.contextWindowTokens),
|
|
234
|
+
...(cancellationSignal ? { signal: cancellationSignal } : {}),
|
|
222
235
|
onUsage: recordCompactionUsage,
|
|
223
236
|
...(systemInstructions ? { systemInstructions } : {}),
|
|
224
237
|
...(promptCacheKey ? { promptCacheKey } : {}),
|
|
225
238
|
});
|
|
239
|
+
summarize.estimatePrefixTokens = () => {
|
|
240
|
+
if (resolvedModel?.provider.api === "chat") {
|
|
241
|
+
return estimateSerializedValueTokens(systemInstructions ?? "");
|
|
242
|
+
}
|
|
243
|
+
const prepared = portableResponsesNeedsAgentPrefix ? preparedPortableRequest() : null;
|
|
244
|
+
return (
|
|
245
|
+
estimateSerializedValueTokens(prepared?.systemInstructions ?? systemInstructions ?? "") +
|
|
246
|
+
(prepared ? estimateSerializedValueTokens(prepared.tools) : 0)
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
return summarize;
|
|
250
|
+
};
|
|
226
251
|
// Prompt-cache prefix for remote_v2 MUST match ordinary turns:
|
|
227
252
|
// tools → instructions → history. Filled after buildAgent for every
|
|
228
253
|
// compact path (including operator /compact, which now builds the agent
|
|
@@ -265,13 +290,9 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
|
|
|
265
290
|
...(remoteCompactionRequester ? { requestRemoteCompactionV2: remoteCompactionRequester } : {}),
|
|
266
291
|
} as const;
|
|
267
292
|
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// - remote_v2: fall through to prepareTools/buildAgent so the compact
|
|
272
|
-
// request reuses the ordinary tools→instructions cache prefix, then
|
|
273
|
-
// settle without inference. (Requester is also wired for Codex portable
|
|
274
|
-
// turns but unused there — gate on the frozen session mode.)
|
|
293
|
+
// Responses compaction, portable or remote, prepares the ordinary agent
|
|
294
|
+
// request first so tool schemas and instructions match the warm cache prefix.
|
|
295
|
+
// Chat providers keep the standalone portable maintenance path.
|
|
275
296
|
const compactionOnlyTurn = turn.source === "compaction";
|
|
276
297
|
const remoteV2CompactionNeedsAgentPrefix =
|
|
277
298
|
Boolean(remoteCompactionRequester) && session.codexCompactionMode === "remote_v2";
|
|
@@ -308,7 +329,11 @@ export async function prepareCompaction(deps: CompactionPrepDeps): Promise<Compa
|
|
|
308
329
|
control.activityStatus = "idle";
|
|
309
330
|
return claimedResult({ status: "idle" });
|
|
310
331
|
};
|
|
311
|
-
if (
|
|
332
|
+
if (
|
|
333
|
+
compactionOnlyTurn &&
|
|
334
|
+
!remoteV2CompactionNeedsAgentPrefix &&
|
|
335
|
+
!portableResponsesNeedsAgentPrefix
|
|
336
|
+
) {
|
|
312
337
|
const compactionInstructions = appendWorkspaceMemory(
|
|
313
338
|
appendSessionInstructions(
|
|
314
339
|
appendWorkspaceGovernance(
|
|
@@ -466,6 +491,7 @@ export async function runPostAgentCompaction(
|
|
|
466
491
|
claimedResult,
|
|
467
492
|
turn,
|
|
468
493
|
session,
|
|
494
|
+
resolvedModel,
|
|
469
495
|
remotePrefix,
|
|
470
496
|
remoteCompactionRequester,
|
|
471
497
|
publishCompactionLiveEvents,
|
|
@@ -480,10 +506,13 @@ export async function runPostAgentCompaction(
|
|
|
480
506
|
} = deps;
|
|
481
507
|
|
|
482
508
|
const agentInstructions = typeof agent.instructions === "string" ? agent.instructions : "";
|
|
509
|
+
const preparedPortable =
|
|
510
|
+
resolvedModel?.provider.api === "responses" &&
|
|
511
|
+
!(remoteCompactionRequester && session.codexCompactionMode === "remote_v2");
|
|
483
512
|
const compactSummarizer = compactionSummarizerFor(
|
|
484
513
|
agentInstructions.trim() ? agentInstructions : undefined,
|
|
485
514
|
);
|
|
486
|
-
if (remoteCompactionRequester) {
|
|
515
|
+
if (remoteCompactionRequester || preparedPortable) {
|
|
487
516
|
// The prefix is captured only after this agent passes normal SDK preparation.
|
|
488
517
|
remotePrefix.agent = agent;
|
|
489
518
|
}
|
|
@@ -491,7 +520,11 @@ export async function runPostAgentCompaction(
|
|
|
491
520
|
if (compactionOnlyTurn) {
|
|
492
521
|
const requested = await isSessionCompactionRequested(db, input.workspaceId, input.sessionId);
|
|
493
522
|
let outcome: Awaited<ReturnType<typeof maybeCompactContext>> | null = null;
|
|
494
|
-
if (
|
|
523
|
+
if (
|
|
524
|
+
requested &&
|
|
525
|
+
(preparedPortable ||
|
|
526
|
+
(remoteCompactionRequester && session.codexCompactionMode === "remote_v2"))
|
|
527
|
+
) {
|
|
495
528
|
queuePreparedCompaction(
|
|
496
529
|
agent,
|
|
497
530
|
new CompactionNeededError({
|
|
@@ -610,7 +643,10 @@ export async function runPostAgentCompaction(
|
|
|
610
643
|
return { exit: claimedResult({ status: "idle" }) };
|
|
611
644
|
}
|
|
612
645
|
|
|
613
|
-
if (
|
|
646
|
+
if (
|
|
647
|
+
preparedPortable ||
|
|
648
|
+
(remoteCompactionRequester && session.codexCompactionMode === "remote_v2")
|
|
649
|
+
) {
|
|
614
650
|
const forced = await isSessionCompactionRequested(db, input.workspaceId, input.sessionId);
|
|
615
651
|
const thresholdTokens = compactionThresholdTokens(eventing.modelRunSettings);
|
|
616
652
|
if (
|
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
type CompactionProviderRejection,
|
|
17
17
|
isMcpRequestTimeoutError,
|
|
18
18
|
isMcpTransportConnectivityError,
|
|
19
|
-
|
|
19
|
+
isModalTaskExecStartPreDispatchUnavailableError,
|
|
20
|
+
isRoutingMutationOutcomeUnknownError,
|
|
20
21
|
RoutingWorkspaceRootChangedError,
|
|
21
22
|
SandboxMaterializationVerificationError,
|
|
22
23
|
materializationVerificationDiagnostic,
|
|
@@ -119,6 +120,7 @@ export function providerRecoveryResult(input: {
|
|
|
119
120
|
input.failureCode === "sandbox_command_start_unavailable" ||
|
|
120
121
|
input.failureCode === "mcp_transport_timeout" ||
|
|
121
122
|
input.failureCode === "mcp_transport_unavailable" ||
|
|
123
|
+
input.failureCode === "turn_execution_policy_definition_mismatch" ||
|
|
122
124
|
input.failureCode === POST_COMPACTION_CONTINUATION_EMPTY_CODE
|
|
123
125
|
? Math.max(
|
|
124
126
|
providerDelay ?? 0,
|
|
@@ -1021,10 +1023,13 @@ function baseAgentRunFailurePayload(
|
|
|
1021
1023
|
retryable: true,
|
|
1022
1024
|
};
|
|
1023
1025
|
}
|
|
1024
|
-
if (
|
|
1026
|
+
if (
|
|
1027
|
+
!isRoutingMutationOutcomeUnknownError(error) &&
|
|
1028
|
+
isModalTaskExecStartPreDispatchUnavailableError(error)
|
|
1029
|
+
) {
|
|
1025
1030
|
return {
|
|
1026
1031
|
error:
|
|
1027
|
-
"The managed sandbox command
|
|
1032
|
+
"The managed sandbox command router was not ready before the command was sent. The same turn will retry after a short delay.",
|
|
1028
1033
|
code: "sandbox_command_start_unavailable",
|
|
1029
1034
|
retryable: true,
|
|
1030
1035
|
};
|
|
@@ -16,14 +16,14 @@ import {
|
|
|
16
16
|
} from "@opengeni/db";
|
|
17
17
|
import { publishDurableSessionEvents } from "@opengeni/events";
|
|
18
18
|
import { maxTurnsExceededRunState } from "@opengeni/runtime";
|
|
19
|
-
import { CancelledFailure } from "@temporalio/activity";
|
|
19
|
+
import { ApplicationFailure, CancelledFailure } from "@temporalio/activity";
|
|
20
20
|
import {
|
|
21
21
|
authoritativeCodexCapacityResetAt,
|
|
22
22
|
classifyCodexPin,
|
|
23
23
|
selectCodexCredentialLeaseForTurn,
|
|
24
24
|
type CodexRotationStrategy,
|
|
25
25
|
} from "../codex-rotation";
|
|
26
|
-
import type
|
|
26
|
+
import { TurnExecutionPolicyDefinitionMismatchError, type Settings } from "@opengeni/config";
|
|
27
27
|
import {
|
|
28
28
|
classifyCodexEncryptedArtifactRejection,
|
|
29
29
|
classifyCodexUsageLimitError,
|
|
@@ -1499,10 +1499,28 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1499
1499
|
// truth, recover this SAME accepted turn, then let the workflow re-claim
|
|
1500
1500
|
// it after a pacing delay. This is independent of goal state and never
|
|
1501
1501
|
// relies on a synthetic continuation prompt.
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1502
|
+
// A rolling-deployment definition mismatch is a separate configuration
|
|
1503
|
+
// class: only the exact typed setup error can use this checkpoint before
|
|
1504
|
+
// eventing exists. No generic setup/credential failure gains retry authority.
|
|
1505
|
+
const earlyDefinitionMismatch =
|
|
1506
|
+
error instanceof TurnExecutionPolicyDefinitionMismatchError &&
|
|
1507
|
+
!attempt.modelRequestStarted &&
|
|
1508
|
+
!eventing.turnStartedPublished &&
|
|
1509
|
+
!!attempt.turnId &&
|
|
1510
|
+
!!attempt.triggerEventId &&
|
|
1511
|
+
attempt.executionGeneration > 0;
|
|
1512
|
+
let failure = (
|
|
1513
|
+
earlyDefinitionMismatch
|
|
1514
|
+
? { error: error.message, code: error.code, retryable: true }
|
|
1515
|
+
: agentRunFailurePayload(error, {
|
|
1516
|
+
isCodexTurn: billingState.isCodexTurn,
|
|
1517
|
+
})
|
|
1518
|
+
) as ReturnType<typeof agentRunFailurePayload>;
|
|
1519
|
+
if (
|
|
1520
|
+
attempt.turnId &&
|
|
1521
|
+
(earlyDefinitionMismatch ||
|
|
1522
|
+
(failure.retryable && eventing.publish && eventing.turnStartedPublished))
|
|
1523
|
+
) {
|
|
1506
1524
|
const nextProviderRecoveryCount = attempt.providerRecoveryCount + 1;
|
|
1507
1525
|
const recoveryResult = providerRecoveryResult({
|
|
1508
1526
|
failureCode: failure.code,
|
|
@@ -1511,8 +1529,10 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1511
1529
|
});
|
|
1512
1530
|
try {
|
|
1513
1531
|
if (recoveryResult.status === "recovering") {
|
|
1514
|
-
|
|
1515
|
-
|
|
1532
|
+
if (!earlyDefinitionMismatch) {
|
|
1533
|
+
await flushRuntimeBatcher();
|
|
1534
|
+
await historySink.reconcileConversationTruth({ requireDurable: true });
|
|
1535
|
+
}
|
|
1516
1536
|
const recovery = await requestSessionTurnRecovery(db, input.workspaceId, {
|
|
1517
1537
|
sessionId: input.sessionId,
|
|
1518
1538
|
turnId: attempt.turnId,
|
|
@@ -1540,6 +1560,18 @@ export async function settleTurnFailure(deps: TurnFailureDeps): Promise<RunAgent
|
|
|
1540
1560
|
return claimedResult(recoveryResult);
|
|
1541
1561
|
}
|
|
1542
1562
|
failure = providerRecoveryExhaustedFailure(failure, recoveryResult);
|
|
1563
|
+
if (earlyDefinitionMismatch) {
|
|
1564
|
+
// Setup has no eventing sink yet. Carry only the fixed, safe diagnostic
|
|
1565
|
+
// through Temporal into exact-attempt workflow failure settlement.
|
|
1566
|
+
control.activityStatus = "failed";
|
|
1567
|
+
control.turnMetricOutcome = "failed";
|
|
1568
|
+
control.activityError = error;
|
|
1569
|
+
throw ApplicationFailure.create({
|
|
1570
|
+
message: `${error.message}. Automatic same-turn configuration recovery exhausted after ${recoveryResult.providerRecoveryCount} retries.`,
|
|
1571
|
+
type: "TurnExecutionPolicyDefinitionMismatchError",
|
|
1572
|
+
nonRetryable: true,
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1543
1575
|
} catch (recoveryError) {
|
|
1544
1576
|
const escaped =
|
|
1545
1577
|
recoveryResult.status === "recovering"
|
|
@@ -186,7 +186,20 @@ export function toolCallProducesRetainableSessionImage(name: string | null): boo
|
|
|
186
186
|
return (
|
|
187
187
|
name === "computer_screenshot" ||
|
|
188
188
|
name === "view_image" ||
|
|
189
|
-
name === "interaction__computer_observe"
|
|
189
|
+
name === "interaction__computer_observe" ||
|
|
190
|
+
retainableBrowserScreenshotToolCall(name)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Browser interactions can carry an image block, including opt-in observe frames. */
|
|
195
|
+
export function retainableBrowserScreenshotToolCall(name: string | null): boolean {
|
|
196
|
+
return (
|
|
197
|
+
name === "browser_screenshot" ||
|
|
198
|
+
name === "interaction__browser_screenshot" ||
|
|
199
|
+
name === "browser_observe" ||
|
|
200
|
+
name === "interaction__browser_observe" ||
|
|
201
|
+
name === "browser_act" ||
|
|
202
|
+
name === "interaction__browser_act"
|
|
190
203
|
);
|
|
191
204
|
}
|
|
192
205
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
type TurnToolCancellationFence,
|
|
13
13
|
} from "@opengeni/runtime";
|
|
14
14
|
import type { Settings } from "@opengeni/config";
|
|
15
|
-
import type { RetainedArtifactMetadata } from "@opengeni/contracts";
|
|
15
|
+
import type { RetainedArtifactMetadata, RetainedSessionScreenshotKind } from "@opengeni/contracts";
|
|
16
16
|
import type { ResumedTurnSandbox } from "../../sandbox-resume";
|
|
17
17
|
import type { SharedActivityServices } from "../types";
|
|
18
18
|
import {
|
|
@@ -79,6 +79,7 @@ export class TurnMediaArtifacts {
|
|
|
79
79
|
sandboxFileDownloadBackend: Settings["sandboxBackend"];
|
|
80
80
|
nativeImageGenerationRetention: NativeImageGenerationRetention | null = null;
|
|
81
81
|
readonly retainedSessionImageCallIds = new Set<string>();
|
|
82
|
+
readonly retainedSessionImageKindsByCallId = new Map<string, RetainedSessionScreenshotKind>();
|
|
82
83
|
|
|
83
84
|
constructor(private readonly deps: TurnMediaArtifactDeps) {
|
|
84
85
|
this.sandboxFileDownloadBackend = deps.getModelRunSettings().sandboxBackend;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
advanceWorkspaceGeneration,
|
|
3
3
|
verifyWorkspaceMutationSettlement,
|
|
4
|
-
|
|
4
|
+
heartbeatLeaseHolderStatus,
|
|
5
5
|
readLease,
|
|
6
6
|
accrueWarmSeconds,
|
|
7
7
|
SandboxWorkspaceMutationFencedError,
|
|
@@ -526,12 +526,15 @@ export function createSandboxTurnRuntime(deps: SandboxTurnRuntimeDeps) {
|
|
|
526
526
|
// home turn that degraded to the cloud group box (swap-away / flag-off), that
|
|
527
527
|
// is the deployment default (modal), so the fallback box is warm-metered at
|
|
528
528
|
// the cloud rate instead of selfhosted's rate-0 (which would under-bill).
|
|
529
|
-
const warmRate =
|
|
530
|
-
settings
|
|
531
|
-
|
|
532
|
-
|
|
529
|
+
const warmRate =
|
|
530
|
+
settings.sandboxWarmBillingMode === "usage_only"
|
|
531
|
+
? 0
|
|
532
|
+
: sandboxWarmRateMicrosPerSecond(
|
|
533
|
+
settings,
|
|
534
|
+
warmBackend ?? (sandbox.established.backendId as Settings["sandboxBackend"]),
|
|
535
|
+
);
|
|
533
536
|
sandboxState.leaseHeartbeatTimer = setInterval(() => {
|
|
534
|
-
void
|
|
537
|
+
void heartbeatLeaseHolderStatus(db, {
|
|
535
538
|
accountId: input.accountId,
|
|
536
539
|
workspaceId: input.workspaceId,
|
|
537
540
|
sandboxGroupId: heartbeatGroupId,
|
|
@@ -539,9 +542,17 @@ export function createSandboxTurnRuntime(deps: SandboxTurnRuntimeDeps) {
|
|
|
539
542
|
holderId: heartbeatHolderId,
|
|
540
543
|
leaseTtlMs: settings.sandboxLeaseTtlMs,
|
|
541
544
|
expectedEpoch: heartbeatEpoch,
|
|
545
|
+
billingMode: settings.sandboxWarmBillingMode,
|
|
542
546
|
})
|
|
543
|
-
.then(async (
|
|
544
|
-
if (
|
|
547
|
+
.then(async (status) => {
|
|
548
|
+
if (status.fence === "funding") {
|
|
549
|
+
stopLeaseHeartbeat();
|
|
550
|
+
sandboxRotationController.abort(
|
|
551
|
+
new Error("Insufficient OpenGeni credits to extend paid sandbox compute"),
|
|
552
|
+
);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (status.leaseExtended) return;
|
|
545
556
|
const rotation = await beginRotationPreemption(sandbox, heartbeatEpoch, heartbeatGroupId);
|
|
546
557
|
if (rotation === "not_rotating") {
|
|
547
558
|
// The holder was reaped, the exact attempt closed, the epoch was
|
|
@@ -557,9 +568,14 @@ export function createSandboxTurnRuntime(deps: SandboxTurnRuntimeDeps) {
|
|
|
557
568
|
sandboxGroupId: heartbeatGroupId,
|
|
558
569
|
expectedEpoch: heartbeatEpoch,
|
|
559
570
|
warmRateMicrosPerSecond: warmRate,
|
|
571
|
+
billingMode: settings.sandboxWarmBillingMode,
|
|
560
572
|
subjectId: input.sessionId,
|
|
561
573
|
})
|
|
562
|
-
.then((result) =>
|
|
574
|
+
.then((result) => {
|
|
575
|
+
if (settings.sandboxWarmBillingMode === "credits") {
|
|
576
|
+
recordCreditMicros(observability, "usage", result.costMicros);
|
|
577
|
+
}
|
|
578
|
+
})
|
|
563
579
|
.catch(() => undefined);
|
|
564
580
|
// MID-SESSION snapshot (sandbox-file-persistence): while the turn holds
|
|
565
581
|
// the box, fold a fresh /workspace snapshot onto the lease every
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
import {
|
|
62
62
|
compactRetainedScreenshotHistory,
|
|
63
63
|
sdkEventContainsInlineImage,
|
|
64
|
-
|
|
64
|
+
retainSessionScreenshot,
|
|
65
65
|
typedScreenshotFromSdkEvent,
|
|
66
66
|
unavailableRetainedSessionImage,
|
|
67
67
|
} from "../retained-screenshots";
|
|
@@ -108,6 +108,7 @@ import {
|
|
|
108
108
|
import {
|
|
109
109
|
pendingToolCallFromSdkEvent,
|
|
110
110
|
toolCallProducesRetainableSessionImage,
|
|
111
|
+
retainableBrowserScreenshotToolCall,
|
|
111
112
|
completedToolCallFromSdkEvent,
|
|
112
113
|
} from "./history";
|
|
113
114
|
import { checkpointHistoryBeforeProviderDispatch } from "./provider-dispatch-barrier";
|
|
@@ -1125,6 +1126,12 @@ export async function runTurnStreamAttempt(
|
|
|
1125
1126
|
currentToolBatchCallIds.add(pendingToolCall.callId);
|
|
1126
1127
|
if (toolCallProducesRetainableSessionImage(pendingToolCall.callName)) {
|
|
1127
1128
|
media.retainedSessionImageCallIds.add(pendingToolCall.callId);
|
|
1129
|
+
if (retainableBrowserScreenshotToolCall(pendingToolCall.callName)) {
|
|
1130
|
+
media.retainedSessionImageKindsByCallId.set(
|
|
1131
|
+
pendingToolCall.callId,
|
|
1132
|
+
"browser_screenshot",
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1128
1135
|
}
|
|
1129
1136
|
}
|
|
1130
1137
|
const completedToolCall = completedToolCallFromSdkEvent(durableSdkEvent);
|
|
@@ -1147,6 +1154,7 @@ export async function runTurnStreamAttempt(
|
|
|
1147
1154
|
toolCallId: completedToolCall.callId,
|
|
1148
1155
|
toolOutputId: completedToolCall.callId,
|
|
1149
1156
|
reason: "unsupported",
|
|
1157
|
+
kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId),
|
|
1150
1158
|
});
|
|
1151
1159
|
media.retainedScreenshotReceiptsByCallId.set(
|
|
1152
1160
|
completedToolCall.callId,
|
|
@@ -1169,12 +1177,13 @@ export async function runTurnStreamAttempt(
|
|
|
1169
1177
|
toolCallId: typedScreenshot.callId,
|
|
1170
1178
|
toolOutputId: typedScreenshot.toolOutputId,
|
|
1171
1179
|
reason: "pending",
|
|
1180
|
+
kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId),
|
|
1172
1181
|
});
|
|
1173
1182
|
media.retainedScreenshotReceiptsByCallId.set(
|
|
1174
1183
|
completedToolCall.callId,
|
|
1175
1184
|
retainedScreenshotMetadata,
|
|
1176
1185
|
);
|
|
1177
|
-
retainedScreenshotMetadata = await
|
|
1186
|
+
retainedScreenshotMetadata = await retainSessionScreenshot({
|
|
1178
1187
|
db,
|
|
1179
1188
|
objectStorage,
|
|
1180
1189
|
accountId: input.accountId,
|
|
@@ -1183,6 +1192,7 @@ export async function runTurnStreamAttempt(
|
|
|
1183
1192
|
turnId: activeTurnId,
|
|
1184
1193
|
attemptId: input.attemptId,
|
|
1185
1194
|
output: typedScreenshot,
|
|
1195
|
+
kind: media.retainedSessionImageKindsByCallId.get(completedToolCall.callId),
|
|
1186
1196
|
});
|
|
1187
1197
|
media.retainedScreenshotReceiptsByCallId.set(
|
|
1188
1198
|
completedToolCall.callId,
|
|
@@ -1339,6 +1349,7 @@ export async function runTurnStreamAttempt(
|
|
|
1339
1349
|
}
|
|
1340
1350
|
for (const callId of stableToolCallIdsToClear) {
|
|
1341
1351
|
media.retainedSessionImageCallIds.delete(callId);
|
|
1352
|
+
media.retainedSessionImageKindsByCallId.delete(callId);
|
|
1342
1353
|
}
|
|
1343
1354
|
}
|
|
1344
1355
|
}
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
EmptyCompactionSummaryError,
|
|
11
11
|
REMOTE_COMPACTION_V2_IMPLEMENTATION,
|
|
12
|
-
|
|
12
|
+
compactionSummaryOutputTokens,
|
|
13
|
+
buildSummaryItem,
|
|
13
14
|
buildCompactionReplacementHistory,
|
|
14
15
|
buildRemoteV2ReplacementHistory,
|
|
15
16
|
compactionThresholdTokens,
|
|
@@ -58,7 +59,13 @@ export type MaybeCompactResult =
|
|
|
58
59
|
* Codex, call Codex `/codex/responses` with `compaction_trigger` and persist the
|
|
59
60
|
* opaque compaction item. Fail closed — never silently fall back to portable.
|
|
60
61
|
*/
|
|
61
|
-
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
|
+
};
|
|
62
69
|
|
|
63
70
|
/** Returns the opaque Codex remote compaction v2 item. */
|
|
64
71
|
export type RemoteCompactionV2Requester = (
|
|
@@ -81,7 +88,7 @@ export async function maybeCompactContext(
|
|
|
81
88
|
// Injectable for tests; defaults to the real provider-aware model call.
|
|
82
89
|
summarize: CompactionSummarizer = (s, m) =>
|
|
83
90
|
summarizeForCompaction(s, m, {
|
|
84
|
-
maxOutputTokens:
|
|
91
|
+
maxOutputTokens: compactionSummaryOutputTokens(s.contextWindowTokens),
|
|
85
92
|
}),
|
|
86
93
|
// Operator-forced (the /compact command): bypass the budget trigger and
|
|
87
94
|
// compact now if there is anything to summarize. Structural guards still hold.
|
|
@@ -300,6 +307,7 @@ async function settleSkippedAfterStart(
|
|
|
300
307
|
reason:
|
|
301
308
|
| "no_history"
|
|
302
309
|
| "replacement_not_smaller"
|
|
310
|
+
| "replacement_exceeds_model_budget"
|
|
303
311
|
| "replacement_unchanged"
|
|
304
312
|
| "summarization_failed",
|
|
305
313
|
): Promise<Extract<MaybeCompactResult, { compacted: false }>> {
|
|
@@ -455,12 +463,23 @@ async function compactContextPortable(
|
|
|
455
463
|
const summarized = await summarizeWithCodexOverflowTrimming(summarize, settings, items);
|
|
456
464
|
const summaryBody = summarized.summaryBody;
|
|
457
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)]);
|
|
458
473
|
const replacementHistory = buildCompactionReplacementHistory(
|
|
459
474
|
canonicalItems,
|
|
460
475
|
summaryBody,
|
|
461
476
|
(item) => retainedTokens.get(item) ?? estimateTokens([item]),
|
|
477
|
+
Math.min(outputReserve, Math.max(0, structuralBudget - prefixTokens - summaryTokens)),
|
|
462
478
|
);
|
|
463
479
|
const estimatedTokensAfter = estimateTokens(await projectForWire(replacementHistory));
|
|
480
|
+
if (estimatedTokensAfter + prefixTokens > structuralBudget) {
|
|
481
|
+
return await settleSkippedAfterStart(db, scope, options, "replacement_exceeds_model_budget");
|
|
482
|
+
}
|
|
464
483
|
const replacementFingerprint = compactionReplacementFingerprint(replacementHistory);
|
|
465
484
|
const previousReplacementFingerprint = latestCompactionReplacementFingerprint(canonicalItems);
|
|
466
485
|
const summaryIndex =
|
|
@@ -520,7 +539,7 @@ async function compactContextPortable(
|
|
|
520
539
|
};
|
|
521
540
|
}
|
|
522
541
|
|
|
523
|
-
async function summarizeWithCodexOverflowTrimming(
|
|
542
|
+
export async function summarizeWithCodexOverflowTrimming(
|
|
524
543
|
summarize: CompactionSummarizer,
|
|
525
544
|
settings: Settings,
|
|
526
545
|
activeHistory: CompactionItem[],
|
|
@@ -531,17 +550,32 @@ async function summarizeWithCodexOverflowTrimming(
|
|
|
531
550
|
}> {
|
|
532
551
|
// Codex's estimator is intentionally coarse. Keep the explicit checkpoint
|
|
533
552
|
// request below both the effective input window and the raw window minus the
|
|
534
|
-
// requested summary
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
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
|
+
);
|
|
538
560
|
const configuredInputBudget = contextInputBudgetTokens(settings);
|
|
539
561
|
const structuralBudget = Math.min(
|
|
540
562
|
configuredInputBudget > 0 ? configuredInputBudget : summaryAwareBudget,
|
|
541
563
|
summaryAwareBudget,
|
|
542
564
|
);
|
|
543
|
-
const
|
|
565
|
+
const prefixTokens = Math.max(0, Math.ceil(summarize.estimatePrefixTokens?.() ?? 0));
|
|
566
|
+
const initialBudget = Math.max(0, structuralBudget - prefixTokens);
|
|
544
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();
|
|
545
579
|
try {
|
|
546
580
|
return {
|
|
547
581
|
summaryBody: await summarize(settings, preparation.input),
|
|
@@ -551,15 +585,16 @@ async function summarizeWithCodexOverflowTrimming(
|
|
|
551
585
|
} catch (error) {
|
|
552
586
|
if (!isContextWindowExceeded(error)) throw error;
|
|
553
587
|
// The provider is more authoritative than the byte/4 estimate. Refit once
|
|
554
|
-
// to
|
|
588
|
+
// to 40% of both the available target and the actual prepared estimate;
|
|
555
589
|
// then fail terminally with prior history intact. Never issue one failing
|
|
556
|
-
// request per oldest item. The
|
|
557
|
-
// provider can count slightly more than twice the byte/4 estimate
|
|
558
|
-
//
|
|
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.
|
|
559
593
|
const retryBudget = Math.floor(
|
|
560
|
-
Math.min(initialBudget * 0.
|
|
594
|
+
Math.min(initialBudget * 0.4, preparation.estimatedInputTokens * 0.4),
|
|
561
595
|
);
|
|
562
596
|
preparation = prepareCompactionPromptInput(activeHistory, retryBudget);
|
|
597
|
+
requireHistory();
|
|
563
598
|
return {
|
|
564
599
|
summaryBody: await summarize(settings, preparation.input),
|
|
565
600
|
preparation,
|