@opengeni/worker-bundle 1.2.1-canary.37222965366001 → 1.4.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.
Files changed (39) hide show
  1. package/dist/activities/agent-turn/errors.d.ts +1 -13
  2. package/dist/activities/agent-turn/subscription-lease.d.ts +1 -0
  3. package/dist/activities/agent-turn/tool-policy.d.ts +10 -1
  4. package/dist/activities/agent-turn/web-search.d.ts +33 -0
  5. package/dist/activities/codemode-dispatcher.d.ts +6 -2
  6. package/dist/activities/open-suffix-resume.d.ts +2 -0
  7. package/dist/activities/programmatic-approvals.d.ts +27 -0
  8. package/dist/activities/run-input.d.ts +2 -0
  9. package/dist/{activities-control-QSDPXLLD.js → activities-control-D5KVEPHI.js} +3 -3
  10. package/dist/{activities-turn-BMY3CDKL.js → activities-turn-4Z3M7Z3F.js} +554 -113
  11. package/dist/activities-turn-4Z3M7Z3F.js.map +1 -0
  12. package/dist/{chunk-JKMG5ZHH.js → chunk-BMTWULF7.js} +115 -31
  13. package/dist/chunk-BMTWULF7.js.map +1 -0
  14. package/dist/{chunk-XONQEVAD.js → chunk-FF4HPY7L.js} +1 -1
  15. package/dist/chunk-FF4HPY7L.js.map +1 -0
  16. package/dist/index.js +2 -2
  17. package/dist/observability-metrics.d.ts +1 -1
  18. package/package.json +21 -21
  19. package/src/activities/agent-turn/agent-build.ts +6 -4
  20. package/src/activities/agent-turn/errors.ts +82 -3
  21. package/src/activities/agent-turn/governance-model.ts +8 -8
  22. package/src/activities/agent-turn/sandbox-runtime.ts +16 -0
  23. package/src/activities/agent-turn/stream-attempt.ts +100 -21
  24. package/src/activities/agent-turn/subscription-lease.ts +10 -1
  25. package/src/activities/agent-turn/tool-environment.ts +20 -0
  26. package/src/activities/agent-turn/tool-policy.ts +26 -5
  27. package/src/activities/agent-turn/web-search.ts +252 -0
  28. package/src/activities/codemode-dispatcher.ts +145 -7
  29. package/src/activities/connector-attachments.ts +3 -1
  30. package/src/activities/open-suffix-resume.ts +107 -86
  31. package/src/activities/programmatic-approvals.ts +52 -0
  32. package/src/activities/run-input.ts +4 -1
  33. package/src/activity-services.ts +8 -0
  34. package/src/observability-metrics.ts +1 -0
  35. package/src/sandbox-routing.ts +58 -24
  36. package/dist/activities-turn-BMY3CDKL.js.map +0 -1
  37. package/dist/chunk-JKMG5ZHH.js.map +0 -1
  38. package/dist/chunk-XONQEVAD.js.map +0 -1
  39. /package/dist/{activities-control-QSDPXLLD.js.map → activities-control-D5KVEPHI.js.map} +0 -0
@@ -99,7 +99,7 @@ import {
99
99
  withFirstPartyTools,
100
100
  wrapLazyTurnBoxWithRouting,
101
101
  wrapTurnBoxWithRouting
102
- } from "./chunk-JKMG5ZHH.js";
102
+ } from "./chunk-BMTWULF7.js";
103
103
  import {
104
104
  StreamTimingMetrics,
105
105
  makeMachineOpObserver,
@@ -132,7 +132,7 @@ import {
132
132
  runtimeMetricsHooksForObservability,
133
133
  sandboxAutomaticRecoveryOutcome,
134
134
  turnLifecycleMetricsFor
135
- } from "./chunk-XONQEVAD.js";
135
+ } from "./chunk-FF4HPY7L.js";
136
136
  import {
137
137
  __export
138
138
  } from "./chunk-PZ5AY32C.js";
@@ -1193,12 +1193,14 @@ import {
1193
1193
  } from "@opengeni/db";
1194
1194
 
1195
1195
  // src/activities/agent-turn/subscription-lease.ts
1196
+ var HEARTBEAT_INTERVAL_MS = 6e4;
1196
1197
  var SubscriptionTurnLease = class {
1197
1198
  constructor(leaseDeps) {
1198
1199
  this.leaseDeps = leaseDeps;
1199
1200
  if (!Number.isFinite(leaseDeps.ttlMs) || leaseDeps.ttlMs <= 0)
1200
1201
  throw new Error("Subscription lease TTL must be positive");
1201
1202
  this.now = leaseDeps.now ?? (() => performance.now());
1203
+ this.renewalIntervalMs = Math.min(HEARTBEAT_INTERVAL_MS, leaseDeps.ttlMs / 5);
1202
1204
  }
1203
1205
  held = false;
1204
1206
  lost = false;
@@ -1209,6 +1211,7 @@ var SubscriptionTurnLease = class {
1209
1211
  heartbeatInFlight = false;
1210
1212
  heartbeatTimer;
1211
1213
  now;
1214
+ renewalIntervalMs;
1212
1215
  expired = (deadline) => deadline === null || !Number.isFinite(deadline) || this.now() >= deadline;
1213
1216
  markLost = (reason) => {
1214
1217
  if (this.lost) return;
@@ -1231,6 +1234,8 @@ var SubscriptionTurnLease = class {
1231
1234
  return;
1232
1235
  }
1233
1236
  if (this.heartbeatInFlight) return;
1237
+ const renewAt = this.confirmedUntilMs - this.leaseDeps.ttlMs + this.renewalIntervalMs;
1238
+ if (this.now() < renewAt) return;
1234
1239
  this.heartbeatInFlight = true;
1235
1240
  const priorDeadline = this.confirmedUntilMs;
1236
1241
  const startedAt = this.now();
@@ -1257,7 +1262,7 @@ var SubscriptionTurnLease = class {
1257
1262
  };
1258
1263
  startHeartbeat = () => {
1259
1264
  if (!this.leaseDeps.getTurnId() || this.heartbeatTimer) return;
1260
- this.heartbeatTimer = setInterval(() => void this.renew("timer"), 6e4);
1265
+ this.heartbeatTimer = setInterval(() => void this.renew("timer"), HEARTBEAT_INTERVAL_MS);
1261
1266
  this.heartbeatTimer.unref?.();
1262
1267
  };
1263
1268
  stopHeartbeat = () => {
@@ -7654,6 +7659,7 @@ import {
7654
7659
  sessionTurnHasFinalReplyNudge,
7655
7660
  getSessionHumanInputRequest,
7656
7661
  getWorkspace,
7662
+ listTurnCodemodeApprovals,
7657
7663
  recordUsageEvent as recordUsageEvent3,
7658
7664
  registerPendingSessionToolCall,
7659
7665
  recordPendingSessionToolCallResult as recordPendingSessionToolCallResult2,
@@ -8080,6 +8086,34 @@ async function retentionTokenCounts(items, project2) {
8080
8086
  return counts;
8081
8087
  }
8082
8088
 
8089
+ // src/activities/programmatic-approvals.ts
8090
+ function programmaticApproval(operation) {
8091
+ return {
8092
+ id: operation.operationId,
8093
+ name: operation.identity.toolName,
8094
+ source: "codemode",
8095
+ operationId: operation.operationId,
8096
+ requestId: operation.approvalRequestId
8097
+ };
8098
+ }
8099
+ function programmaticContinuationNote(operations) {
8100
+ if (operations.length === 0) return void 0;
8101
+ return [
8102
+ "Stored programmatic operations (durable server receipts):",
8103
+ ...operations.map(
8104
+ (operation) => `${operation.operationId}: ${operation.state}${operation.errorCode ? ` (${operation.errorCode})` : ""}`
8105
+ ),
8106
+ "Approval resumes the stored operation, not the earlier program's JavaScript stack. Read results with the current Codemode client's status/resume handle. Do not resubmit arguments for completed, running, waiting, or uncertain operations."
8107
+ ].join("\n");
8108
+ }
8109
+ async function recoverProgrammaticOperationsBeforeFirstRequest(input) {
8110
+ if (input.approvalDecisionId === void 0 && !await input.hasUnfinishedOperations()) {
8111
+ return [];
8112
+ }
8113
+ await input.toolPreparationReady;
8114
+ return await input.resumeApproved(input.approvalDecisionId) ?? [];
8115
+ }
8116
+
8083
8117
  // src/activities/agent-turn/turn-sandbox-access.ts
8084
8118
  function ownedTurnSandboxForAgent(sandboxState) {
8085
8119
  return sandboxState.lazyOwnedSandbox ?? sandboxState.resolvedSandbox?.established;
@@ -8263,7 +8297,7 @@ async function resultItemForOpenSuffixMember(input) {
8263
8297
  }
8264
8298
  }
8265
8299
  async function settleOpenSuffixResumeIfNeeded(input) {
8266
- if (input.trigger.type !== "user.approvalDecision" && input.trigger.type !== "user.humanInputResponse") {
8300
+ if (input.trigger.type !== "user.approvalDecision" && input.trigger.type !== "user.humanInputResponse" && !input.additionalApprovals?.length) {
8267
8301
  return { action: "continue" };
8268
8302
  }
8269
8303
  const rows = await listTurnOpenSuffixToolCalls(
@@ -8272,7 +8306,7 @@ async function settleOpenSuffixResumeIfNeeded(input) {
8272
8306
  input.sessionId,
8273
8307
  input.turnId
8274
8308
  );
8275
- if (rows.length === 0) {
8309
+ if (rows.length === 0 && !input.additionalApprovals?.length) {
8276
8310
  return { action: "continue" };
8277
8311
  }
8278
8312
  const callId = matchingOpenSuffixCallId({
@@ -8280,96 +8314,108 @@ async function settleOpenSuffixResumeIfNeeded(input) {
8280
8314
  humanInputToolCallId: input.humanInputResume?.toolCallId
8281
8315
  });
8282
8316
  const target = resolveOpenSuffixResumeTarget(rows, callId);
8283
- if (!target || !callId) {
8317
+ if ((!target || !callId) && !input.programmaticApprovalAcknowledged && (input.trigger.type === "user.approvalDecision" || input.trigger.type === "user.humanInputResponse" || !input.additionalApprovals?.length)) {
8284
8318
  throw new Error("Open suffix resume event does not match an interruption");
8285
8319
  }
8286
- const truncationTokens = input.modelToolOutputTruncationTokens ?? target.modelToolOutputTruncationTokens ?? void 0;
8287
- let resultItem = target.resultItem;
8288
- let eventOutput = resultItem?.output;
8289
- if (!resultItem) {
8290
- const member = await resultItemForOpenSuffixMember({
8291
- agent: input.agent,
8292
- row: target,
8293
- trigger: input.trigger,
8294
- humanInputResume: input.humanInputResume
8295
- });
8296
- resultItem = member.resultItem;
8297
- eventOutput = member.eventOutput;
8298
- const recorded = await recordPendingSessionToolCallResult(input.db, {
8299
- accountId: input.accountId,
8300
- workspaceId: input.workspaceId,
8301
- sessionId: input.sessionId,
8302
- turnId: input.turnId,
8303
- executionGeneration: input.executionGeneration,
8304
- attemptId: input.attemptId,
8305
- callId: target.callId,
8306
- ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8307
- resultItem,
8308
- eventOutput
8309
- });
8310
- if (!recorded.accepted) {
8311
- return { action: "cancelled" };
8312
- }
8313
- if (!recorded.recorded) {
8314
- const latest = resolveOpenSuffixResumeTarget(
8315
- await listTurnOpenSuffixToolCalls(
8316
- input.db,
8317
- input.workspaceId,
8318
- input.sessionId,
8319
- input.turnId
8320
- ),
8321
- callId
8322
- );
8323
- resultItem = latest?.resultItem ?? resultItem;
8324
- } else if (input.publish) {
8325
- await input.publish([
8326
- {
8327
- type: "agent.toolCall.output",
8328
- payload: { id: target.callId, output: eventOutput }
8329
- }
8330
- ]);
8320
+ if (target && callId) {
8321
+ const truncationTokens = input.modelToolOutputTruncationTokens ?? target.modelToolOutputTruncationTokens ?? void 0;
8322
+ let resultItem = target.resultItem;
8323
+ let eventOutput = resultItem?.output;
8324
+ if (!resultItem) {
8325
+ const member = await resultItemForOpenSuffixMember({
8326
+ agent: input.agent,
8327
+ row: target,
8328
+ trigger: input.trigger,
8329
+ humanInputResume: input.humanInputResume
8330
+ });
8331
+ resultItem = member.resultItem;
8332
+ eventOutput = member.eventOutput;
8333
+ const recorded = await recordPendingSessionToolCallResult(input.db, {
8334
+ accountId: input.accountId,
8335
+ workspaceId: input.workspaceId,
8336
+ sessionId: input.sessionId,
8337
+ turnId: input.turnId,
8338
+ executionGeneration: input.executionGeneration,
8339
+ attemptId: input.attemptId,
8340
+ callId: target.callId,
8341
+ ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8342
+ resultItem,
8343
+ eventOutput
8344
+ });
8345
+ if (!recorded.accepted) {
8346
+ return { action: "cancelled" };
8347
+ }
8348
+ if (!recorded.recorded) {
8349
+ const latest = resolveOpenSuffixResumeTarget(
8350
+ await listTurnOpenSuffixToolCalls(
8351
+ input.db,
8352
+ input.workspaceId,
8353
+ input.sessionId,
8354
+ input.turnId
8355
+ ),
8356
+ callId
8357
+ );
8358
+ resultItem = latest?.resultItem ?? resultItem;
8359
+ } else if (input.publish) {
8360
+ await input.publish([
8361
+ {
8362
+ type: "agent.toolCall.output",
8363
+ payload: { id: target.callId, output: eventOutput }
8364
+ }
8365
+ ]);
8366
+ }
8331
8367
  }
8332
- }
8333
- if (!resultItem) {
8334
- throw new Error(`Open suffix resume for ${target.callId} has no result item`);
8335
- }
8336
- const history = await getActiveSessionHistoryItems(input.db, input.workspaceId, input.sessionId);
8337
- if (!openSuffixPairPresentInHistory(
8338
- history.map((row) => row.item),
8339
- callId
8340
- )) {
8341
- const historyItems = openSuffixHistoryItems(target, resultItem);
8342
- if (historyItems.length === 0) {
8343
- throw new Error(`Open suffix resume for ${target.callId} produced no paired history`);
8368
+ if (!resultItem) {
8369
+ throw new Error(`Open suffix resume for ${target.callId} has no result item`);
8344
8370
  }
8345
- const nextPosition = await nextSessionHistoryPosition(
8371
+ const history = await getActiveSessionHistoryItems(
8346
8372
  input.db,
8347
8373
  input.workspaceId,
8348
8374
  input.sessionId
8349
8375
  );
8350
- const appended = await appendSessionHistoryItems2(input.db, {
8351
- accountId: input.accountId,
8352
- workspaceId: input.workspaceId,
8353
- sessionId: input.sessionId,
8354
- turnId: input.turnId,
8355
- expectedExecutionGeneration: input.executionGeneration,
8356
- expectedAttemptId: input.attemptId,
8357
- ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8358
- items: historyItems.map((item, offset) => ({
8359
- position: nextPosition + offset,
8360
- item
8361
- }))
8362
- });
8363
- if (!appended) {
8364
- return { action: "cancelled" };
8376
+ if (!openSuffixPairPresentInHistory(
8377
+ history.map((row) => row.item),
8378
+ callId
8379
+ )) {
8380
+ const historyItems = openSuffixHistoryItems(target, resultItem);
8381
+ if (historyItems.length === 0) {
8382
+ throw new Error(`Open suffix resume for ${target.callId} produced no paired history`);
8383
+ }
8384
+ const nextPosition = await nextSessionHistoryPosition(
8385
+ input.db,
8386
+ input.workspaceId,
8387
+ input.sessionId
8388
+ );
8389
+ const appended = await appendSessionHistoryItems2(input.db, {
8390
+ accountId: input.accountId,
8391
+ workspaceId: input.workspaceId,
8392
+ sessionId: input.sessionId,
8393
+ turnId: input.turnId,
8394
+ expectedExecutionGeneration: input.executionGeneration,
8395
+ expectedAttemptId: input.attemptId,
8396
+ ...truncationTokens !== void 0 ? { modelToolOutputTruncationTokens: truncationTokens } : {},
8397
+ items: historyItems.map((item, offset) => ({
8398
+ position: nextPosition + offset,
8399
+ item
8400
+ }))
8401
+ });
8402
+ if (!appended) {
8403
+ return { action: "cancelled" };
8404
+ }
8365
8405
  }
8366
8406
  }
8367
8407
  const remaining = (await listTurnOpenSuffixToolCalls(input.db, input.workspaceId, input.sessionId, input.turnId)).filter((row) => row.resultItem == null);
8368
- if (remaining.length === 0) {
8408
+ if (remaining.length === 0 && !input.additionalApprovals?.length) {
8369
8409
  return { action: "continue" };
8370
8410
  }
8371
- const requiresActionApprovals = remainingPendingApprovalsFromSuffix(remaining);
8372
- const pendingApprovals = remainingRunStatePendingApprovalsFromSuffix(remaining);
8411
+ const requiresActionApprovals = [
8412
+ ...remainingPendingApprovalsFromSuffix(remaining),
8413
+ ...input.additionalApprovals ?? []
8414
+ ];
8415
+ const pendingApprovals = [
8416
+ ...remainingRunStatePendingApprovalsFromSuffix(remaining),
8417
+ ...input.additionalApprovals ?? []
8418
+ ];
8373
8419
  const settled = await input.settle({
8374
8420
  events: [
8375
8421
  ...requiresActionApprovals.length > 0 ? [
@@ -8997,6 +9043,8 @@ async function runTurnStreamAttempt(deps) {
8997
9043
  await parallelSessionTitle?.cancel();
8998
9044
  };
8999
9045
  let runInput = null;
9046
+ let codemodeContinuationNote;
9047
+ let programmaticApprovalAcknowledged = false;
9000
9048
  const prepareRunAttemptInput = async () => {
9001
9049
  const historyPreparationStartedAt = performance.now();
9002
9050
  let historyPreparationOutcome = "completed";
@@ -9009,6 +9057,8 @@ async function runTurnStreamAttempt(deps) {
9009
9057
  subjectId: fileAuthoritySubjectId
9010
9058
  },
9011
9059
  recovering: turn.executionGeneration > 1,
9060
+ programmaticApprovalAcknowledged,
9061
+ ...codemodeContinuationNote ? { codemodeContinuationNote } : {},
9012
9062
  ...unavailableSandboxFilesNote ? { unavailableSandboxFilesNote } : {},
9013
9063
  ...runCredentialsNote ? { runCredentialsNote } : {},
9014
9064
  ...mcpAvailabilityNote ? { mcpAvailabilityNote } : {},
@@ -9967,10 +10017,17 @@ async function runTurnStreamAttempt(deps) {
9967
10017
  });
9968
10018
  }
9969
10019
  }
9970
- if (eventing.stream.interruptions.length > 0) {
10020
+ const programmaticPending = (await listTurnCodemodeApprovals(db, {
10021
+ accountId: input.accountId,
10022
+ workspaceId: input.workspaceId,
10023
+ sessionId: input.sessionId,
10024
+ turnId: activeTurnId
10025
+ })).filter((operation) => operation.state === "waiting_for_approval").map(programmaticApproval);
10026
+ if (eventing.stream.interruptions.length > 0 || programmaticPending.length > 0) {
9971
10027
  await eventing.preparedTools?.inputWaitYield?.sealForSettlement(runtimeCancellationSignal);
9972
10028
  await historySink.reconcileConversationTruth({ requireDurable: true });
9973
- const approvals = runtime.serializeApprovals(eventing.stream.interruptions);
10029
+ const sdkApprovals = runtime.serializeApprovals(eventing.stream.interruptions);
10030
+ const approvals = [...sdkApprovals, ...programmaticPending];
9974
10031
  const humanInputInterruptions = runtime.serializeHumanInputRequests?.(eventing.stream.interruptions) ?? [];
9975
10032
  const interactionInterventionInterruptions = runtime.serializeInteractionInterventionRequests?.(eventing.stream.interruptions) ?? [];
9976
10033
  const latestWorkspace = await getWorkspace(db, input.workspaceId);
@@ -10042,11 +10099,14 @@ async function runTurnStreamAttempt(deps) {
10042
10099
  const interruptionCallIds = interruptionCallIdsFromPause({
10043
10100
  humanInputRequests,
10044
10101
  interactionInterventionRequests,
10045
- pendingApprovals
10102
+ pendingApprovals: [
10103
+ ...sdkApprovals,
10104
+ ...interactionInterventionInterruptions.map((interruption) => interruption.approval)
10105
+ ]
10046
10106
  });
10047
10107
  assertOpenSuffixResumable(suffixMembers, interruptionCallIds);
10048
10108
  const suffixByCallId = new Map(suffixMembers.map((member) => [member.callId, member]));
10049
- const attached = await attachOpenSuffixToPendingToolCalls(db, {
10109
+ const attached = interruptionCallIds.length === 0 ? { accepted: true } : await attachOpenSuffixToPendingToolCalls(db, {
10050
10110
  accountId: input.accountId,
10051
10111
  workspaceId: input.workspaceId,
10052
10112
  sessionId: input.sessionId,
@@ -10203,7 +10263,41 @@ async function runTurnStreamAttempt(deps) {
10203
10263
  control.activityStatus = "idle";
10204
10264
  return claimedResult({ status: "idle" });
10205
10265
  };
10266
+ const programmaticRecoveryStartedAt = performance.now();
10267
+ let programmaticRecoveryOutcome = "failed";
10268
+ const codemodeOperations = await recoverProgrammaticOperationsBeforeFirstRequest({
10269
+ approvalDecisionId: trigger.type === "user.approvalDecision" ? trigger.payload.approvalId : void 0,
10270
+ hasUnfinishedOperations: async () => (await listTurnCodemodeApprovals(db, {
10271
+ accountId: input.accountId,
10272
+ workspaceId: input.workspaceId,
10273
+ sessionId: input.sessionId,
10274
+ turnId: activeTurnId
10275
+ })).length > 0,
10276
+ toolPreparationReady: eventing.toolPreparationReady,
10277
+ resumeApproved: (decisionOperationId) => eventing.codemodeDispatcher?.resumeApproved(
10278
+ `sandbox:${input.attemptId}`,
10279
+ decisionOperationId
10280
+ )
10281
+ }).then((operations) => {
10282
+ programmaticRecoveryOutcome = "completed";
10283
+ return operations;
10284
+ }).finally(() => {
10285
+ recordTurnStartupPhase(observability, {
10286
+ phase: "programmatic_operation_recovery",
10287
+ provider: turnExecutionPolicy.providerId,
10288
+ backend: activeSandboxBackend ?? groupBoxBackend,
10289
+ outcome: programmaticRecoveryOutcome,
10290
+ durationSeconds: (performance.now() - programmaticRecoveryStartedAt) / 1e3
10291
+ });
10292
+ });
10293
+ codemodeContinuationNote = programmaticContinuationNote(codemodeOperations);
10294
+ programmaticApprovalAcknowledged = trigger.type === "user.approvalDecision" && codemodeOperations.some(
10295
+ (operation) => operation.operationId === trigger.payload.approvalId
10296
+ );
10297
+ const pendingProgrammaticApprovals = codemodeOperations.filter((operation) => operation.state === "waiting_for_approval").map(programmaticApproval);
10206
10298
  const openSuffixResume = await settleOpenSuffixResumeIfNeeded({
10299
+ additionalApprovals: pendingProgrammaticApprovals,
10300
+ programmaticApprovalAcknowledged,
10207
10301
  db,
10208
10302
  agent,
10209
10303
  accountId: input.accountId,
@@ -12187,6 +12281,7 @@ import {
12187
12281
  desktopCapableBackend
12188
12282
  } from "@opengeni/runtime";
12189
12283
  import {
12284
+ configuredModelForAcceptedTurnExecutionPolicy,
12190
12285
  isDirectOpenAiApiBaseUrl
12191
12286
  } from "@opengeni/config";
12192
12287
 
@@ -12463,6 +12558,16 @@ function shouldDeferNonEagerToolPreparation(args) {
12463
12558
  args.lazyToolTransport && args.progressiveDisclosureEnabled && args.triggerKind === "next" && (args.triggerType === "user.message" || args.triggerType === "system.update.delivered")
12464
12559
  );
12465
12560
  }
12561
+ function resolveAcceptedTurnModel(runtime, settings, policy) {
12562
+ const current = runtime.resolveTurnModel(settings, policy.productModelId);
12563
+ if (!current) return null;
12564
+ const configured = configuredModelForAcceptedTurnExecutionPolicy(
12565
+ current.configured,
12566
+ current.provider,
12567
+ policy
12568
+ );
12569
+ return configured === current.configured ? current : { ...current, configured };
12570
+ }
12466
12571
  function hostedWebSearchForTurn(resolvedModel, deploymentWebSearchEnabled) {
12467
12572
  return resolvedModel?.configured.hostedWebSearch ?? deploymentWebSearchEnabled;
12468
12573
  }
@@ -12486,9 +12591,8 @@ function modelSupportsImageInputForTurn(resolvedModel) {
12486
12591
  return resolvedModel === null || resolvedModel.configured.capabilities?.inputModalities.includes("image") !== false;
12487
12592
  }
12488
12593
  function modelAttachmentInputPolicyForTurn(resolvedModel) {
12489
- const typedTransport = resolvedModel === null || resolvedModel.provider.api === "responses" || resolvedModel.provider.api === "anthropic-messages";
12490
12594
  return {
12491
- supportsImageInput: typedTransport && modelSupportsImageInputForTurn(resolvedModel),
12595
+ supportsImageInput: modelSupportsImageInputForTurn(resolvedModel),
12492
12596
  inputFileMediaTypes: []
12493
12597
  };
12494
12598
  }
@@ -12654,10 +12758,7 @@ async function prepareGovernanceAndModel(deps) {
12654
12758
  baseRunSettings
12655
12759
  )
12656
12760
  );
12657
- const resolvedModel = runtime.resolveTurnModel(
12658
- capabilitySettings,
12659
- turnExecutionPolicy.productModelId
12660
- );
12761
+ const resolvedModel = resolveAcceptedTurnModel(runtime, capabilitySettings, turnExecutionPolicy);
12661
12762
  const providerApi = resolvedModel?.provider.api ?? "responses";
12662
12763
  const nativeImageProviderBinding = providerApi === "responses" ? openAiHostedImageProviderBindingForTurn(capabilitySettings, resolvedModel) : null;
12663
12764
  const lazyToolTransport = lazyToolTransportForTurn(resolvedModel);
@@ -13417,6 +13518,7 @@ import {
13417
13518
  readLease as readLease2,
13418
13519
  accrueWarmSeconds,
13419
13520
  SandboxWorkspaceMutationFencedError,
13521
+ SandboxWorkspaceMutationOutputRejectedError,
13420
13522
  retainWorkspaceMutationProcess,
13421
13523
  retainedProviderCommandPersistence,
13422
13524
  SandboxRetainedProcessPromotionFencedError
@@ -13424,6 +13526,7 @@ import {
13424
13526
  import { createProviderCommandRetainer } from "@opengeni/db/retained-provider-commands";
13425
13527
  import {
13426
13528
  RoutingMutationOutcomeUnknownError,
13529
+ RoutingMutationOutputRejectedError,
13427
13530
  ProviderCommandStartOutcomeUnknownError,
13428
13531
  ProviderCommandObservationUnavailableError,
13429
13532
  isModalCommandStartOutcomeUnknownError as isModalCommandStartOutcomeUnknownError2,
@@ -13836,6 +13939,17 @@ function createSandboxTurnRuntime(deps) {
13836
13939
  try {
13837
13940
  await settleMutation("resolved");
13838
13941
  } catch (settlementError) {
13942
+ if (settlementError instanceof SandboxWorkspaceMutationOutputRejectedError && settlementError.matchesPhysicalSettlement({
13943
+ accountId: identity.accountId,
13944
+ workspaceId: identity.workspaceId,
13945
+ admission,
13946
+ operation,
13947
+ outcome: "resolved"
13948
+ })) {
13949
+ throw new RoutingMutationOutputRejectedError(operation, settlementError.code, {
13950
+ cause: settlementError
13951
+ });
13952
+ }
13839
13953
  throw new RoutingMutationOutcomeUnknownError(
13840
13954
  operation,
13841
13955
  `Platform workspace mutation "${operation}" returned from the provider but lost its durable settlement fence; its outcome is unknown and it was not replayed`,
@@ -32710,7 +32824,8 @@ import {
32710
32824
  } from "@opengeni/runtime";
32711
32825
  import {
32712
32826
  ChannelAPartialMutationError as ChannelAPartialMutationError2,
32713
- RoutingMutationOutcomeUnknownError as RoutingMutationOutcomeUnknownError2
32827
+ RoutingMutationOutcomeUnknownError as RoutingMutationOutcomeUnknownError2,
32828
+ isRoutingMutationOutputRejectedError
32714
32829
  } from "@opengeni/runtime/sandbox";
32715
32830
  import { connectorAttachmentSandboxPath as connectorAttachmentSandboxPath2 } from "@opengeni/runtime";
32716
32831
  var ConnectorAttachmentMaterializationError = class extends Error {
@@ -32819,7 +32934,7 @@ async function materializeConnectorAttachmentsInChannel(channel, request, option
32819
32934
  attachments: receipts
32820
32935
  });
32821
32936
  } catch (error51) {
32822
- if (error51 instanceof ChannelAPartialMutationError2 || error51 instanceof RoutingMutationOutcomeUnknownError2) {
32937
+ if (error51 instanceof ChannelAPartialMutationError2 || error51 instanceof RoutingMutationOutcomeUnknownError2 || isRoutingMutationOutputRejectedError(error51)) {
32823
32938
  throw error51;
32824
32939
  }
32825
32940
  throw new ConnectorAttachmentMaterializationError();
@@ -32894,7 +33009,12 @@ import { allowedFirstPartyMcpToolsForSession } from "@opengeni/config";
32894
33009
 
32895
33010
  // src/activities/codemode-dispatcher.ts
32896
33011
  import { randomUUID as randomUUID3 } from "crypto";
32897
- import { bindMcpTelemetry, measureMcpPhase as measureMcpPhase2, withMcpCallIdentity as withMcpCallIdentity2 } from "@opengeni/observability";
33012
+ import {
33013
+ bindMcpTelemetry,
33014
+ measureMcpPhase as measureMcpPhase2,
33015
+ withMcpCallIdentity as withMcpCallIdentity2,
33016
+ recordToolApproval
33017
+ } from "@opengeni/observability";
32898
33018
  import {
32899
33019
  AttemptToolApprovalRequiredError,
32900
33020
  AttemptToolCatalogStaleError,
@@ -32907,10 +33027,16 @@ import {
32907
33027
  import {
32908
33028
  CODEMODE_CLAIM_LEASE_MS,
32909
33029
  CODEMODE_CLAIM_HEARTBEAT_MS,
32910
- CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT
33030
+ CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT,
33031
+ CodemodeOperation
32911
33032
  } from "@opengeni/contracts";
32912
33033
  import {
32913
33034
  cancelQueuedCodemodeOperationsForAttempt,
33035
+ waitForCodemodeApproval,
33036
+ bindCodemodePreparation,
33037
+ listTurnCodemodeApprovals as listTurnCodemodeApprovals2,
33038
+ adoptCodemodeApprovalWithOutput,
33039
+ readTurnCodemodeOperation,
32914
33040
  claimCodemodeOperation,
32915
33041
  failCodemodeOperation,
32916
33042
  getSessionEventByClientEventId,
@@ -32924,7 +33050,7 @@ import {
32924
33050
  publishDurableSessionEvents as publishDurableSessionEvents8
32925
33051
  } from "@opengeni/events";
32926
33052
  var CodemodeAttemptDispatcher = class {
32927
- constructor(db, bus, environment, scope, turnSignal, maxConcurrentCalls = CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT, timings = {}, toolDisplayMetadata, toolFamily) {
33053
+ constructor(db, bus, environment, scope, turnSignal, maxConcurrentCalls = CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT, timings = {}, toolDisplayMetadata, toolFamily, approvalWait) {
32928
33054
  this.db = db;
32929
33055
  this.bus = bus;
32930
33056
  this.environment = environment;
@@ -32933,6 +33059,7 @@ var CodemodeAttemptDispatcher = class {
32933
33059
  this.maxConcurrentCalls = maxConcurrentCalls;
32934
33060
  this.toolDisplayMetadata = toolDisplayMetadata;
32935
33061
  this.toolFamily = toolFamily;
33062
+ this.approvalWait = approvalWait;
32936
33063
  if (environment.catalog.accountId !== scope.accountId || environment.catalog.workspaceId !== scope.workspaceId || environment.catalog.sessionId !== scope.sessionId || environment.catalog.turnId !== scope.turnId || environment.catalog.attemptId !== scope.attemptId || environment.catalog.executionGeneration !== scope.executionGeneration) {
32937
33064
  throw new Error("Codemode dispatcher scope does not match its tool environment");
32938
33065
  }
@@ -33022,6 +33149,76 @@ var CodemodeAttemptDispatcher = class {
33022
33149
  })
33023
33150
  );
33024
33151
  }
33152
+ async resumeApproved(callerSubjectId, decisionOperationId) {
33153
+ const operations = await listTurnCodemodeApprovals2(this.db, this.scope);
33154
+ if (decisionOperationId && // Model-tool approval IDs are opaque strings. Only programmatic UUIDs
33155
+ // can address the Codemode operation journal.
33156
+ CodemodeOperation.shape.operationId.safeParse(decisionOperationId).success && !operations.some((operation) => operation.operationId === decisionOperationId)) {
33157
+ const decided = await readTurnCodemodeOperation(this.db, {
33158
+ ...this.scope,
33159
+ callerSubjectId,
33160
+ operationId: decisionOperationId
33161
+ });
33162
+ if (decided) operations.push(decided);
33163
+ }
33164
+ for (const operation of operations) {
33165
+ if (!["waiting_for_approval", "queued", "running"].includes(operation.state)) continue;
33166
+ let effectDigest = null;
33167
+ try {
33168
+ effectDigest = this.environment.effectDigest(operation.identity);
33169
+ } catch {
33170
+ }
33171
+ const continuation = await adoptCodemodeApprovalWithOutput(this.db, {
33172
+ ...this.scope,
33173
+ operationId: operation.operationId,
33174
+ callerSubjectId,
33175
+ catalogDigest: this.environment.catalog.digest,
33176
+ effectDigest
33177
+ });
33178
+ await publishDurableSessionEvents8(
33179
+ this.bus,
33180
+ this.scope.workspaceId,
33181
+ this.scope.sessionId,
33182
+ continuation.events
33183
+ );
33184
+ const adopted = continuation.operation;
33185
+ if (adopted && adopted.state !== operation.state) {
33186
+ const outcome = adopted.state === "queued" ? "resumed" : adopted.state === "outcome_unknown" ? "unknown" : adopted.errorCode === "approval_stale" ? "stale" : "rejected";
33187
+ recordToolApproval(
33188
+ outcome,
33189
+ "continuation",
33190
+ operation.state === "waiting_for_approval" ? Math.max(0, Date.now() - Date.parse(operation.updatedAt)) : void 0
33191
+ );
33192
+ }
33193
+ if (adopted?.state !== "queued") continue;
33194
+ const claim = await claimCodemodeOperation(this.db, {
33195
+ ...this.scope,
33196
+ operationId: adopted.operationId,
33197
+ claimId: randomUUID3(),
33198
+ catalogDigest: this.environment.catalog.digest,
33199
+ claimLeaseMs: this.claimLeaseMs
33200
+ });
33201
+ if (claim.status === "claimed") {
33202
+ const execution = this.execute(claim.operation, claim.claimId, true);
33203
+ this.inFlight.set(adopted.operationId, execution);
33204
+ try {
33205
+ await execution;
33206
+ } finally {
33207
+ this.inFlight.delete(adopted.operationId);
33208
+ }
33209
+ }
33210
+ }
33211
+ const receipts = [];
33212
+ for (const operation of operations) {
33213
+ const receipt = await readTurnCodemodeOperation(this.db, {
33214
+ ...this.scope,
33215
+ callerSubjectId,
33216
+ operationId: operation.operationId
33217
+ });
33218
+ if (receipt) receipts.push(receipt);
33219
+ }
33220
+ return receipts;
33221
+ }
33025
33222
  async close(reason = "Execution attempt ended") {
33026
33223
  if (this.closing) {
33027
33224
  await Promise.allSettled([...this.inFlight.values()]);
@@ -33102,18 +33299,51 @@ var CodemodeAttemptDispatcher = class {
33102
33299
  return;
33103
33300
  }
33104
33301
  let preparedCall;
33302
+ let releaseWait;
33105
33303
  try {
33304
+ if (operation.durableApproval) {
33305
+ if (!this.approvalWait) throw new Error("Durable approval yield is unavailable");
33306
+ releaseWait = this.approvalWait.beginWait();
33307
+ if (!await bindCodemodePreparation(this.db, {
33308
+ ...this.scope,
33309
+ operationId: operation.operationId,
33310
+ claimId,
33311
+ effectDigest: this.environment.effectDigest(operation.identity)
33312
+ }))
33313
+ throw new Error("Operation preparation is stale");
33314
+ }
33106
33315
  preparedCall = await this.environment.prepareCall(
33107
33316
  {
33108
33317
  operationId: operation.operationId,
33109
- catalogDigest: operation.catalogDigest,
33318
+ catalogDigest: this.environment.catalog.digest,
33110
33319
  identity: operation.identity,
33111
33320
  arguments: operation.arguments,
33112
- caller: operation.caller
33321
+ caller: {
33322
+ kind: "codemode",
33323
+ subjectId: operation.caller.subjectId === `sandbox:${operation.attemptId}` ? `sandbox:${this.scope.attemptId}` : operation.caller.subjectId
33324
+ }
33113
33325
  },
33114
- { signal }
33326
+ {
33327
+ signal,
33328
+ ...operation.durableApproval ? { transportMeta: { durableApproval: true } } : {}
33329
+ }
33115
33330
  );
33331
+ if (preparedCall.waitingForApproval) {
33332
+ const waiting = await waitForCodemodeApproval(this.db, {
33333
+ ...this.scope,
33334
+ operationId: operation.operationId,
33335
+ claimId,
33336
+ ...preparedCall.waitingForApproval,
33337
+ effectDigest: preparedCall.effectDigest
33338
+ });
33339
+ if (!waiting) throw new Error("Approval could not be bound to the operation");
33340
+ recordToolApproval("waiting");
33341
+ releaseWait?.(true);
33342
+ return;
33343
+ }
33344
+ releaseWait?.(false);
33116
33345
  } catch (error51) {
33346
+ releaseWait?.(false);
33117
33347
  await this.settleWithOutput(operation, claimId, {
33118
33348
  state: "failed",
33119
33349
  errorCode: errorCode(error51, signal, false),
@@ -33244,6 +33474,7 @@ var CodemodeAttemptDispatcher = class {
33244
33474
  settlement
33245
33475
  });
33246
33476
  if (!result.committed || result.events.length === 0) return;
33477
+ recordToolApproval(settlement.state === "outcome_unknown" ? "unknown" : settlement.state);
33247
33478
  await publishDurableSessionEvents8(
33248
33479
  this.bus,
33249
33480
  this.scope.workspaceId,
@@ -33270,7 +33501,7 @@ function codemodeToolCallCreatedClientEventId(operationId) {
33270
33501
  function assertMatchingCodemodeToolCallCreated(event, operation, entry, scope) {
33271
33502
  const payload = recordValue(event.payload);
33272
33503
  const raw = recordValue(payload?.raw);
33273
- if (event.type !== "agent.toolCall.created" || event.turnId !== scope.turnId || event.turnGeneration !== scope.executionGeneration || event.turnAttemptId !== scope.attemptId || event.turnAssociation !== "current" || payload?.id !== operation.operationId || payload?.name !== entry.modelName || payload?.origin !== "codemode" || payload?.subjectId !== operation.caller.subjectId || raw?.type !== "codemode_call" || raw?.serverId !== operation.identity.serverId || raw?.toolName !== operation.identity.toolName || raw?.catalogDigest !== operation.catalogDigest) {
33504
+ if (event.type !== "agent.toolCall.created" || event.turnId !== scope.turnId || event.turnGeneration !== operation.executionGeneration || event.turnAttemptId !== operation.attemptId || event.turnAssociation !== "current" || payload?.id !== operation.operationId || payload?.name !== entry.modelName || payload?.origin !== "codemode" || payload?.subjectId !== operation.caller.subjectId || raw?.type !== "codemode_call" || raw?.serverId !== operation.identity.serverId || raw?.toolName !== operation.identity.toolName || raw?.catalogDigest !== operation.catalogDigest) {
33274
33505
  throw new Error("Codemode tool-call creation id is bound to incompatible durable evidence");
33275
33506
  }
33276
33507
  }
@@ -33317,7 +33548,8 @@ import {
33317
33548
  resolveTurnToolPolicy,
33318
33549
  scheduledTurnMcpServerIds,
33319
33550
  hasPermission as hasPermission2,
33320
- requireExplicitPermissionDelegation
33551
+ requireExplicitPermissionDelegation,
33552
+ createWebSearchBilling
33321
33553
  } from "@opengeni/core";
33322
33554
  import { SandboxChannelAService as SandboxChannelAService3 } from "@opengeni/runtime/sandbox";
33323
33555
  import { sandboxRunAs as sandboxRunAs4 } from "@opengeni/runtime";
@@ -33639,6 +33871,200 @@ function codeSearchToolDefinitions(input) {
33639
33871
  ];
33640
33872
  }
33641
33873
 
33874
+ // src/activities/agent-turn/web-search.ts
33875
+ import {
33876
+ WEB_FETCH_TOOL_NAME,
33877
+ WEB_SEARCH_TOOL_NAME,
33878
+ webSearchCallPricing,
33879
+ webSearchCreditMicros,
33880
+ webSearchProviderConfig,
33881
+ webSearchToolPlan
33882
+ } from "@opengeni/config";
33883
+ import {
33884
+ WebSearchBillingRefusedError
33885
+ } from "@opengeni/core";
33886
+ import {
33887
+ WEB_FETCH_TOOL_DESCRIPTION,
33888
+ WEB_FETCH_RETAINED_MAX_CHARS,
33889
+ WEB_SEARCH_TOOL_DESCRIPTION,
33890
+ WebSearchProviderError,
33891
+ WebToolArgumentError,
33892
+ createWebFetchProvider,
33893
+ createWebSearchProvider,
33894
+ parseWebFetchArguments,
33895
+ parseWebSearchArguments,
33896
+ renderWebPageWindow,
33897
+ renderWebSearchResults,
33898
+ webFetchInputSchema,
33899
+ webSearchInputSchema
33900
+ } from "@opengeni/runtime/web-search";
33901
+ function turnWebSearchPlan(resolvedModel, settings) {
33902
+ return webSearchToolPlan(settings, {
33903
+ hostedWebSearch: hostedWebSearchForTurn(resolvedModel, settings.webSearchEnabled),
33904
+ // SuperGrok's native search is added by its transport, not as an agent
33905
+ // tool, so provider tools would only duplicate it.
33906
+ transportHostedSearch: resolvedModel?.provider.kind === "xai-subscription"
33907
+ });
33908
+ }
33909
+ var PAGE_CACHE_ENTRIES = 16;
33910
+ function textResult2(text, isError) {
33911
+ return { isError, content: [{ type: "text", text }] };
33912
+ }
33913
+ function settledCost(operationId, operation, provider, pricing, reportedCostMicros) {
33914
+ const basis = pricing.explicit ? "configured_price" : reportedCostMicros !== void 0 ? "provider_reported" : "list_price";
33915
+ const providerMicros = basis === "provider_reported" ? reportedCostMicros : pricing.providerMicros;
33916
+ return {
33917
+ operationId,
33918
+ operation,
33919
+ provider,
33920
+ providerMicros,
33921
+ creditMicros: webSearchCreditMicros(providerMicros, pricing.marginBps),
33922
+ marginBps: pricing.marginBps,
33923
+ basis
33924
+ };
33925
+ }
33926
+ function providerFailure(error51) {
33927
+ return `${error51.message}${error51.retryable ? ". You may retry shortly." : "."}`;
33928
+ }
33929
+ function webSearchToolDefinitions(input) {
33930
+ const config2 = webSearchProviderConfig(input.settings);
33931
+ if (!config2 || input.tools.length === 0) return [];
33932
+ const adapterInput = {
33933
+ timeoutMs: config2.timeoutMs,
33934
+ ...input.fetch ? { fetch: input.fetch } : {}
33935
+ };
33936
+ const settle = async (cost) => {
33937
+ try {
33938
+ await input.billing.settle(input.scope, cost);
33939
+ } catch (error51) {
33940
+ input.observability.warn("web search usage settlement failed", {
33941
+ operation: cost.operation,
33942
+ provider: cost.provider,
33943
+ error: error51 instanceof Error ? error51.message : String(error51)
33944
+ });
33945
+ }
33946
+ };
33947
+ const definitions = [];
33948
+ if (input.tools.includes(WEB_SEARCH_TOOL_NAME)) {
33949
+ const provider = createWebSearchProvider({ ...adapterInput, endpoint: config2.search });
33950
+ const pricing = webSearchCallPricing(config2, "search");
33951
+ definitions.push({
33952
+ identity: { serverId: "opengeni", toolName: WEB_SEARCH_TOOL_NAME },
33953
+ modelName: WEB_SEARCH_TOOL_NAME,
33954
+ codemodePath: ["opengeni", WEB_SEARCH_TOOL_NAME],
33955
+ title: "Search the web",
33956
+ description: WEB_SEARCH_TOOL_DESCRIPTION,
33957
+ inputSchema: webSearchInputSchema,
33958
+ annotations: {
33959
+ title: "Search the web",
33960
+ readOnlyHint: true,
33961
+ destructiveHint: false,
33962
+ idempotentHint: true,
33963
+ openWorldHint: true
33964
+ },
33965
+ source: "opengeni",
33966
+ approval: "none",
33967
+ execute: async (args, context) => {
33968
+ let request;
33969
+ try {
33970
+ request = parseWebSearchArguments(args);
33971
+ await input.billing.admit(input.scope, pricing.providerMicros);
33972
+ } catch (error51) {
33973
+ if (error51 instanceof WebToolArgumentError || error51 instanceof WebSearchBillingRefusedError)
33974
+ return textResult2(error51.message, true);
33975
+ throw error51;
33976
+ }
33977
+ try {
33978
+ const response = await provider.search(request, { signal: context.signal });
33979
+ await settle(
33980
+ settledCost(
33981
+ context.operationId,
33982
+ "search",
33983
+ config2.search.provider,
33984
+ pricing,
33985
+ response.reportedCostMicros
33986
+ )
33987
+ );
33988
+ return textResult2(renderWebSearchResults(request.query, response.results), false);
33989
+ } catch (error51) {
33990
+ if (context.signal?.aborted) throw error51;
33991
+ if (error51 instanceof WebSearchProviderError) {
33992
+ return textResult2(`Web search failed: ${providerFailure(error51)}`, true);
33993
+ }
33994
+ throw error51;
33995
+ }
33996
+ }
33997
+ });
33998
+ }
33999
+ if (input.tools.includes(WEB_FETCH_TOOL_NAME) && config2.fetch) {
34000
+ const fetchEndpoint = config2.fetch;
34001
+ const provider = createWebFetchProvider({ ...adapterInput, endpoint: fetchEndpoint });
34002
+ const pricing = webSearchCallPricing(config2, "fetch");
34003
+ const pages = /* @__PURE__ */ new Map();
34004
+ definitions.push({
34005
+ identity: { serverId: "opengeni", toolName: WEB_FETCH_TOOL_NAME },
34006
+ modelName: WEB_FETCH_TOOL_NAME,
34007
+ codemodePath: ["opengeni", WEB_FETCH_TOOL_NAME],
34008
+ title: "Read a web page",
34009
+ description: WEB_FETCH_TOOL_DESCRIPTION,
34010
+ inputSchema: webFetchInputSchema,
34011
+ annotations: {
34012
+ title: "Read a web page",
34013
+ readOnlyHint: true,
34014
+ destructiveHint: false,
34015
+ idempotentHint: true,
34016
+ openWorldHint: true
34017
+ },
34018
+ source: "opengeni",
34019
+ approval: "none",
34020
+ execute: async (args, context) => {
34021
+ let request;
34022
+ try {
34023
+ request = parseWebFetchArguments(args);
34024
+ } catch (error51) {
34025
+ if (error51 instanceof WebToolArgumentError) return textResult2(error51.message, true);
34026
+ throw error51;
34027
+ }
34028
+ const cached2 = pages.get(request.url);
34029
+ if (cached2) return textResult2(renderWebPageWindow(cached2, request), false);
34030
+ try {
34031
+ await input.billing.admit(input.scope, pricing.providerMicros);
34032
+ } catch (error51) {
34033
+ if (error51 instanceof WebSearchBillingRefusedError) return textResult2(error51.message, true);
34034
+ throw error51;
34035
+ }
34036
+ try {
34037
+ const page = await provider.fetch({ url: request.url }, { signal: context.signal });
34038
+ await settle(
34039
+ settledCost(
34040
+ context.operationId,
34041
+ "fetch",
34042
+ fetchEndpoint.provider,
34043
+ pricing,
34044
+ page.reportedCostMicros
34045
+ )
34046
+ );
34047
+ if (pages.size >= PAGE_CACHE_ENTRIES) {
34048
+ pages.delete(pages.keys().next().value);
34049
+ }
34050
+ pages.set(request.url, {
34051
+ ...page,
34052
+ content: page.content.slice(0, WEB_FETCH_RETAINED_MAX_CHARS + 1)
34053
+ });
34054
+ return textResult2(renderWebPageWindow(page, request), false);
34055
+ } catch (error51) {
34056
+ if (context.signal?.aborted) throw error51;
34057
+ if (error51 instanceof WebSearchProviderError) {
34058
+ return textResult2(`Web fetch failed: ${providerFailure(error51)}`, true);
34059
+ }
34060
+ throw error51;
34061
+ }
34062
+ }
34063
+ });
34064
+ }
34065
+ return definitions;
34066
+ }
34067
+
33642
34068
  // src/activities/agent-turn/skill-tools.ts
33643
34069
  import { createHash as createHash13 } from "crypto";
33644
34070
  import {
@@ -33808,14 +34234,14 @@ async function readResult(files, identity, args, context, activeHistory) {
33808
34234
  if (args.listFiles === true) {
33809
34235
  return {
33810
34236
  kind: "list",
33811
- result: textResult2({ ...identity, ...listSkillPaths(files, PORTABLE_SKILL_MAX_FILES) })
34237
+ result: textResult3({ ...identity, ...listSkillPaths(files, PORTABLE_SKILL_MAX_FILES) })
33812
34238
  };
33813
34239
  }
33814
34240
  const selected = readSkillFiles(files, args.paths);
33815
34241
  if (args.paths === void 0 && context.caller.kind === "model" && activeHistory && await inActiveHistory(activeHistory, identity, selected.files[0])) {
33816
34242
  return {
33817
34243
  kind: "already_in_context",
33818
- result: textResult2({
34244
+ result: textResult3({
33819
34245
  ...identity,
33820
34246
  alreadyInContext: true,
33821
34247
  message: `Already in context: SKILL.md of ${JSON.stringify(args.skill)}${identity.revisionId ? ` revision ${identity.revisionId}` : ""} was returned earlier in this conversation and is unchanged. Use that copy. Re-read only if you need a fresh copy: call skill_read with paths ["SKILL.md"].`
@@ -33823,12 +34249,12 @@ async function readResult(files, identity, args, context, activeHistory) {
33823
34249
  };
33824
34250
  }
33825
34251
  if (args.paths !== void 0) {
33826
- return { kind: "files", result: textResult2({ ...identity, ...selected }) };
34252
+ return { kind: "files", result: textResult3({ ...identity, ...selected }) };
33827
34253
  }
33828
34254
  const scripts = skillScriptIndex(files);
33829
34255
  return {
33830
34256
  kind: "full",
33831
- result: textResult2({ ...identity, ...selected, ...scripts ?? {} })
34257
+ result: textResult3({ ...identity, ...selected, ...scripts ?? {} })
33832
34258
  };
33833
34259
  }
33834
34260
  function observe(telemetry, read) {
@@ -33865,7 +34291,7 @@ function withSkillUse(read, origin, identity, telemetry) {
33865
34291
  return read.result;
33866
34292
  }
33867
34293
  }
33868
- function textResult2(output) {
34294
+ function textResult3(output) {
33869
34295
  return {
33870
34296
  isError: false,
33871
34297
  content: [{ type: "text", text: JSON.stringify(output) }],
@@ -35329,6 +35755,19 @@ async function prepareTurnToolRuntime(deps) {
35329
35755
  const attemptToolFamilies = resolveAgentToolFamilies(session.agent, {
35330
35756
  hasSkills: skillCatalog.length > 0
35331
35757
  });
35758
+ const webSearchTools = webSearchToolDefinitions({
35759
+ settings: runSettings,
35760
+ tools: turnWebSearchPlan(resolvedModel, runSettings).providerTools,
35761
+ scope: {
35762
+ accountId: input.accountId,
35763
+ workspaceId: input.workspaceId,
35764
+ sessionId: input.sessionId,
35765
+ turnId: turn.id,
35766
+ attemptId: input.attemptId
35767
+ },
35768
+ billing: createWebSearchBilling({ db, settings: runSettings }),
35769
+ observability
35770
+ });
35332
35771
  const attemptToolDefinitions = [
35333
35772
  ...operationReadStore ? [
35334
35773
  createOperationReadAttemptToolDefinition({
@@ -35407,7 +35846,8 @@ async function prepareTurnToolRuntime(deps) {
35407
35846
  ...interactionInterventionResume ? { interventionResume: interactionInterventionResume } : {}
35408
35847
  }),
35409
35848
  ...googleDrivePublicationTool && googleDrivePublicationAllowed ? [googleDrivePublicationTool] : [],
35410
- ...codeSearchTools
35849
+ ...codeSearchTools,
35850
+ ...webSearchTools
35411
35851
  ].filter((tool) => attemptToolFamilies.allowsFunctionTool(tool.modelName));
35412
35852
  recordTurnStartupPhase(observability, {
35413
35853
  phase: "tool_context_preparation",
@@ -35634,7 +36074,8 @@ async function prepareTurnToolRuntime(deps) {
35634
36074
  void 0,
35635
36075
  {},
35636
36076
  (name) => mcpToolDisplayMetadata(tools.mcpServers, name),
35637
- (entry) => toolFamilyForCatalogIdentity(entry, runSettings.mcpServers)
36077
+ (entry) => toolFamilyForCatalogIdentity(entry, runSettings.mcpServers),
36078
+ tools.inputWaitYield
35638
36079
  );
35639
36080
  eventing.codemodeDispatcher.start();
35640
36081
  };
@@ -37225,7 +37666,7 @@ async function buildTurnAgent(deps) {
37225
37666
  variableNames: Object.keys(workspaceVariableSet.values)
37226
37667
  }
37227
37668
  } : {};
37228
- const hostedWebSearch = hostedWebSearchForTurn(resolvedModel, runSettings.webSearchEnabled);
37669
+ const hostedWebSearch = turnWebSearchPlan(resolvedModel, runSettings).hostedWebSearch;
37229
37670
  const resolveImageReferences = async (references) => await resolveImageGenerationReferencesForTool({
37230
37671
  db,
37231
37672
  objectStorage,
@@ -37668,8 +38109,8 @@ async function buildTurnAgent(deps) {
37668
38109
  // tool. Gateway Responses routes likewise expose ordinary function
37669
38110
  // tools, not OpenAI-hosted sandbox tools. Tell buildAgent to use
37670
38111
  // function apply_patch and wrap successful view_image results as
37671
- // typed input_image content. Chat wires have no proven typed image
37672
- // result transport and therefore receive no view_image tool.
38112
+ // typed input_image content. The Chat adapter projects tool images
38113
+ // into a labelled image envelope after the paired tool results.
37673
38114
  structuredToolTransport: structuredToolTransportForTurn(resolvedModel),
37674
38115
  ...promptCacheKey ? { promptCacheKey } : {}
37675
38116
  } : (
@@ -40507,4 +40948,4 @@ export {
40507
40948
  createTurnActivities,
40508
40949
  createTurnActivitiesFromServices
40509
40950
  };
40510
- //# sourceMappingURL=activities-turn-BMY3CDKL.js.map
40951
+ //# sourceMappingURL=activities-turn-4Z3M7Z3F.js.map