@openai/agents-core 0.2.1 → 0.3.1

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 (87) hide show
  1. package/dist/editor.d.ts +38 -0
  2. package/dist/editor.js +3 -0
  3. package/dist/editor.js.map +1 -0
  4. package/dist/editor.mjs +2 -0
  5. package/dist/editor.mjs.map +1 -0
  6. package/dist/extensions/handoffFilters.js +4 -0
  7. package/dist/extensions/handoffFilters.js.map +1 -1
  8. package/dist/extensions/handoffFilters.mjs +4 -0
  9. package/dist/extensions/handoffFilters.mjs.map +1 -1
  10. package/dist/index.d.ts +8 -2
  11. package/dist/index.js +8 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +3 -1
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/items.d.ts +518 -4
  16. package/dist/items.js +22 -1
  17. package/dist/items.js.map +1 -1
  18. package/dist/items.mjs +22 -1
  19. package/dist/items.mjs.map +1 -1
  20. package/dist/memory/memorySession.d.ts +22 -0
  21. package/dist/memory/memorySession.js +64 -0
  22. package/dist/memory/memorySession.js.map +1 -0
  23. package/dist/memory/memorySession.mjs +60 -0
  24. package/dist/memory/memorySession.mjs.map +1 -0
  25. package/dist/memory/session.d.ts +36 -0
  26. package/dist/memory/session.js +3 -0
  27. package/dist/memory/session.js.map +1 -0
  28. package/dist/memory/session.mjs +2 -0
  29. package/dist/memory/session.mjs.map +1 -0
  30. package/dist/metadata.js +2 -2
  31. package/dist/metadata.mjs +2 -2
  32. package/dist/model.d.ts +10 -2
  33. package/dist/run.d.ts +88 -8
  34. package/dist/run.js +859 -347
  35. package/dist/run.js.map +1 -1
  36. package/dist/run.mjs +859 -347
  37. package/dist/run.mjs.map +1 -1
  38. package/dist/runContext.js +2 -2
  39. package/dist/runContext.js.map +1 -1
  40. package/dist/runContext.mjs +2 -2
  41. package/dist/runContext.mjs.map +1 -1
  42. package/dist/runImplementation.d.ts +37 -3
  43. package/dist/runImplementation.js +1116 -319
  44. package/dist/runImplementation.js.map +1 -1
  45. package/dist/runImplementation.mjs +1106 -317
  46. package/dist/runImplementation.mjs.map +1 -1
  47. package/dist/runState.d.ts +4453 -785
  48. package/dist/runState.js +62 -3
  49. package/dist/runState.js.map +1 -1
  50. package/dist/runState.mjs +62 -3
  51. package/dist/runState.mjs.map +1 -1
  52. package/dist/shell.d.ts +36 -0
  53. package/dist/shell.js +3 -0
  54. package/dist/shell.js.map +1 -0
  55. package/dist/shell.mjs +2 -0
  56. package/dist/shell.mjs.map +1 -0
  57. package/dist/tool.d.ts +62 -1
  58. package/dist/tool.js +30 -0
  59. package/dist/tool.js.map +1 -1
  60. package/dist/tool.mjs +28 -0
  61. package/dist/tool.mjs.map +1 -1
  62. package/dist/types/aliases.d.ts +3 -3
  63. package/dist/types/protocol.d.ts +5470 -1519
  64. package/dist/types/protocol.js +74 -1
  65. package/dist/types/protocol.js.map +1 -1
  66. package/dist/types/protocol.mjs +73 -0
  67. package/dist/types/protocol.mjs.map +1 -1
  68. package/dist/utils/applyDiff.d.ts +9 -0
  69. package/dist/utils/applyDiff.js +275 -0
  70. package/dist/utils/applyDiff.js.map +1 -0
  71. package/dist/utils/applyDiff.mjs +272 -0
  72. package/dist/utils/applyDiff.mjs.map +1 -0
  73. package/dist/utils/index.d.ts +1 -0
  74. package/dist/utils/index.js +3 -1
  75. package/dist/utils/index.js.map +1 -1
  76. package/dist/utils/index.mjs +1 -0
  77. package/dist/utils/index.mjs.map +1 -1
  78. package/dist/utils/serialize.js +12 -0
  79. package/dist/utils/serialize.js.map +1 -1
  80. package/dist/utils/serialize.mjs +12 -0
  81. package/dist/utils/serialize.mjs.map +1 -1
  82. package/dist/utils/smartString.d.ts +9 -0
  83. package/dist/utils/smartString.js +15 -0
  84. package/dist/utils/smartString.js.map +1 -1
  85. package/dist/utils/smartString.mjs +14 -3
  86. package/dist/utils/smartString.mjs.map +1 -1
  87. package/package.json +3 -3
@@ -6,15 +6,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.AgentToolUseTracker = exports.nextStepSchema = void 0;
7
7
  exports.processModelResponse = processModelResponse;
8
8
  exports.maybeResetToolChoice = maybeResetToolChoice;
9
- exports.executeInterruptedToolsAndSideEffects = executeInterruptedToolsAndSideEffects;
10
- exports.executeToolsAndSideEffects = executeToolsAndSideEffects;
9
+ exports.resolveInterruptedTurn = resolveInterruptedTurn;
10
+ exports.resolveTurnAfterModelResponse = resolveTurnAfterModelResponse;
11
11
  exports.getToolCallOutputItem = getToolCallOutputItem;
12
12
  exports.executeFunctionToolCalls = executeFunctionToolCalls;
13
+ exports.executeShellActions = executeShellActions;
14
+ exports.executeApplyPatchOperations = executeApplyPatchOperations;
13
15
  exports.executeComputerActions = executeComputerActions;
14
16
  exports.executeHandoffCalls = executeHandoffCalls;
15
17
  exports.checkForFinalOutputFromTools = checkForFinalOutputFromTools;
16
18
  exports.streamStepItemsToRunResult = streamStepItemsToRunResult;
17
19
  exports.addStepToRunResult = addStepToRunResult;
20
+ exports.toInputItemList = toInputItemList;
21
+ exports.extractOutputItemsFromRunItems = extractOutputItemsFromRunItems;
22
+ exports.saveToSession = saveToSession;
23
+ exports.saveStreamInputToSession = saveStreamInputToSession;
24
+ exports.saveStreamResultToSession = saveStreamResultToSession;
25
+ exports.prepareInputItemsWithSession = prepareInputItemsWithSession;
18
26
  const agent_1 = require("./agent.js");
19
27
  const errors_1 = require("./errors.js");
20
28
  const handoff_1 = require("./handoff.js");
@@ -24,25 +32,75 @@ const messages_1 = require("./utils/messages.js");
24
32
  const createSpans_1 = require("./tracing/createSpans.js");
25
33
  const tools_1 = require("./utils/tools.js");
26
34
  const base64_1 = require("./utils/base64.js");
35
+ const smartString_1 = require("./utils/smartString.js");
27
36
  const safeExecute_1 = require("./utils/safeExecute.js");
28
37
  const context_1 = require("./tracing/context.js");
29
38
  const events_1 = require("./events.js");
30
39
  const zod_1 = require("zod");
31
- const smartString_1 = require("./utils/smartString.js");
32
40
  const utils_1 = require("./utils/index.js");
41
+ function isApprovalItemLike(value) {
42
+ if (!value || typeof value !== 'object') {
43
+ return false;
44
+ }
45
+ if (!('rawItem' in value)) {
46
+ return false;
47
+ }
48
+ const rawItem = value.rawItem;
49
+ if (!rawItem || typeof rawItem !== 'object') {
50
+ return false;
51
+ }
52
+ const itemType = rawItem.type;
53
+ return itemType === 'function_call' || itemType === 'hosted_tool_call';
54
+ }
55
+ function getApprovalIdentity(approval) {
56
+ const rawItem = approval.rawItem;
57
+ if (!rawItem) {
58
+ return undefined;
59
+ }
60
+ if (rawItem.type === 'function_call' && rawItem.callId) {
61
+ return `function_call:${rawItem.callId}`;
62
+ }
63
+ if ('callId' in rawItem && rawItem.callId) {
64
+ return `${rawItem.type}:${rawItem.callId}`;
65
+ }
66
+ const id = 'id' in rawItem ? rawItem.id : undefined;
67
+ if (id) {
68
+ return `${rawItem.type}:${id}`;
69
+ }
70
+ const providerData = typeof rawItem.providerData === 'object' && rawItem.providerData
71
+ ? rawItem.providerData
72
+ : undefined;
73
+ if (providerData?.id) {
74
+ return `${rawItem.type}:provider:${providerData.id}`;
75
+ }
76
+ const agentName = 'agent' in approval && approval.agent ? approval.agent.name : '';
77
+ try {
78
+ return `${agentName}:${rawItem.type}:${JSON.stringify(rawItem)}`;
79
+ }
80
+ catch {
81
+ return `${agentName}:${rawItem.type}`;
82
+ }
83
+ }
33
84
  /**
34
85
  * @internal
86
+ * Walks a raw model response and classifies each item so the runner can schedule follow-up work.
87
+ * Returns both the serializable RunItems (for history/streaming) and the actionable tool metadata.
35
88
  */
36
89
  function processModelResponse(modelResponse, agent, tools, handoffs) {
37
90
  const items = [];
38
91
  const runHandoffs = [];
39
92
  const runFunctions = [];
40
93
  const runComputerActions = [];
94
+ const runShellActions = [];
95
+ const runApplyPatchActions = [];
41
96
  const runMCPApprovalRequests = [];
42
97
  const toolsUsed = [];
43
98
  const handoffMap = new Map(handoffs.map((h) => [h.toolName, h]));
99
+ // Resolve tools upfront so we can look up the concrete handler in O(1) while iterating outputs.
44
100
  const functionMap = new Map(tools.filter((t) => t.type === 'function').map((t) => [t.name, t]));
45
101
  const computerTool = tools.find((t) => t.type === 'computer');
102
+ const shellTool = tools.find((t) => t.type === 'shell');
103
+ const applyPatchTool = tools.find((t) => t.type === 'apply_patch');
46
104
  const mcpToolMap = new Map(tools
47
105
  .filter((t) => t.type === 'hosted_tool' && t.providerData?.type === 'mcp')
48
106
  .map((t) => t)
@@ -112,6 +170,40 @@ function processModelResponse(modelResponse, agent, tools, handoffs) {
112
170
  computer: computerTool,
113
171
  });
114
172
  }
173
+ else if (output.type === 'shell_call') {
174
+ items.push(new items_1.RunToolCallItem(output, agent));
175
+ toolsUsed.push('shell');
176
+ if (!shellTool) {
177
+ (0, context_1.addErrorToCurrentSpan)({
178
+ message: 'Model produced shell action without a shell tool.',
179
+ data: {
180
+ agent_name: agent.name,
181
+ },
182
+ });
183
+ throw new errors_1.ModelBehaviorError('Model produced shell action without a shell tool.');
184
+ }
185
+ runShellActions.push({
186
+ toolCall: output,
187
+ shell: shellTool,
188
+ });
189
+ }
190
+ else if (output.type === 'apply_patch_call') {
191
+ items.push(new items_1.RunToolCallItem(output, agent));
192
+ toolsUsed.push('apply_patch');
193
+ if (!applyPatchTool) {
194
+ (0, context_1.addErrorToCurrentSpan)({
195
+ message: 'Model produced apply_patch action without an apply_patch tool.',
196
+ data: {
197
+ agent_name: agent.name,
198
+ },
199
+ });
200
+ throw new errors_1.ModelBehaviorError('Model produced apply_patch action without an apply_patch tool.');
201
+ }
202
+ runApplyPatchActions.push({
203
+ toolCall: output,
204
+ applyPatch: applyPatchTool,
205
+ });
206
+ }
115
207
  if (output.type !== 'function_call') {
116
208
  continue;
117
209
  }
@@ -148,13 +240,17 @@ function processModelResponse(modelResponse, agent, tools, handoffs) {
148
240
  handoffs: runHandoffs,
149
241
  functions: runFunctions,
150
242
  computerActions: runComputerActions,
243
+ shellActions: runShellActions,
244
+ applyPatchActions: runApplyPatchActions,
151
245
  mcpApprovalRequests: runMCPApprovalRequests,
152
246
  toolsUsed: toolsUsed,
153
247
  hasToolsOrApprovalsToRun() {
154
248
  return (runHandoffs.length > 0 ||
155
249
  runFunctions.length > 0 ||
156
250
  runMCPApprovalRequests.length > 0 ||
157
- runComputerActions.length > 0);
251
+ runComputerActions.length > 0 ||
252
+ runShellActions.length > 0 ||
253
+ runApplyPatchActions.length > 0);
158
254
  },
159
255
  };
160
256
  }
@@ -175,6 +271,10 @@ exports.nextStepSchema = zod_1.z.discriminatedUnion('type', [
175
271
  data: zod_1.z.record(zod_1.z.string(), zod_1.z.any()),
176
272
  }),
177
273
  ]);
274
+ /**
275
+ * Internal convenience wrapper that groups the outcome of a single agent turn. It lets the caller
276
+ * update the RunState in one shot and decide which step to execute next.
277
+ */
178
278
  class SingleStepResult {
179
279
  originalInput;
180
280
  modelResponse;
@@ -183,7 +283,7 @@ class SingleStepResult {
183
283
  nextStep;
184
284
  constructor(
185
285
  /**
186
- * The input items i.e. the items before run() was called. May be muted by handoff input filters
286
+ * The input items (i.e., the items before run() was called). May be mutated by handoff input filters.
187
287
  */
188
288
  originalInput,
189
289
  /**
@@ -217,6 +317,8 @@ class SingleStepResult {
217
317
  }
218
318
  /**
219
319
  * @internal
320
+ * Resets the tool choice when the agent is configured to prefer a fresh tool selection after
321
+ * any tool usage. This prevents the provider from reusing stale tool hints across turns.
220
322
  */
221
323
  function maybeResetToolChoice(agent, toolUseTracker, modelSettings) {
222
324
  if (agent.resetToolChoice && toolUseTracker.hasUsedTools(agent)) {
@@ -226,33 +328,104 @@ function maybeResetToolChoice(agent, toolUseTracker, modelSettings) {
226
328
  }
227
329
  /**
228
330
  * @internal
331
+ * Continues a turn that was previously interrupted waiting for tool approval. Executes the now
332
+ * approved tools and returns the resulting step transition.
229
333
  */
230
- async function executeInterruptedToolsAndSideEffects(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
334
+ async function resolveInterruptedTurn(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
231
335
  // call_ids for function tools
232
336
  const functionCallIds = originalPreStepItems
233
337
  .filter((item) => item instanceof items_1.RunToolApprovalItem &&
234
338
  'callId' in item.rawItem &&
235
339
  item.rawItem.type === 'function_call')
236
340
  .map((item) => item.rawItem.callId);
341
+ // We already persisted the turn once when the approval interrupt was raised, so the
342
+ // counter reflects the approval items as "flushed". When we resume the same turn we need
343
+ // to rewind it so the eventual tool output for this call is still written to the session.
344
+ const pendingApprovalItems = state
345
+ .getInterruptions()
346
+ .filter(isApprovalItemLike);
347
+ if (pendingApprovalItems.length > 0) {
348
+ const pendingApprovalIdentities = new Set();
349
+ for (const approval of pendingApprovalItems) {
350
+ const identity = getApprovalIdentity(approval);
351
+ if (identity) {
352
+ pendingApprovalIdentities.add(identity);
353
+ }
354
+ }
355
+ if (pendingApprovalIdentities.size > 0) {
356
+ let rewindCount = 0;
357
+ for (let index = originalPreStepItems.length - 1; index >= 0; index--) {
358
+ const item = originalPreStepItems[index];
359
+ if (!(item instanceof items_1.RunToolApprovalItem)) {
360
+ continue;
361
+ }
362
+ const identity = getApprovalIdentity(item);
363
+ if (!identity) {
364
+ continue;
365
+ }
366
+ if (!pendingApprovalIdentities.has(identity)) {
367
+ continue;
368
+ }
369
+ rewindCount++;
370
+ pendingApprovalIdentities.delete(identity);
371
+ if (pendingApprovalIdentities.size === 0) {
372
+ break;
373
+ }
374
+ }
375
+ // Persisting the approval request already advanced the counter once, so undo the increment
376
+ // to make sure we write the final tool output back to the session when the turn resumes.
377
+ if (rewindCount > 0) {
378
+ state._currentTurnPersistedItemCount = Math.max(0, state._currentTurnPersistedItemCount - rewindCount);
379
+ }
380
+ }
381
+ }
237
382
  // Run function tools that require approval after they get their approval results
238
383
  const functionToolRuns = processedResponse.functions.filter((run) => {
239
384
  return functionCallIds.includes(run.toolCall.callId);
240
385
  });
241
386
  const functionResults = await executeFunctionToolCalls(agent, functionToolRuns, runner, state);
242
- // Create the initial set of the output items
243
- const newItems = functionResults.map((r) => r.runItem);
387
+ // There is no built-in HITL approval surface for computer tools today, so every pending action
388
+ // is executed immediately when the turn resumes.
389
+ const computerResults = processedResponse.computerActions.length > 0
390
+ ? await executeComputerActions(agent, processedResponse.computerActions, runner, state._context)
391
+ : [];
392
+ // When resuming we receive the original RunItem references; suppress duplicates so history and streaming do not double-emit the same items.
393
+ const originalPreStepItemSet = new Set(originalPreStepItems);
394
+ const newItems = [];
395
+ const newItemsSet = new Set();
396
+ const appendIfNew = (item) => {
397
+ if (originalPreStepItemSet.has(item) || newItemsSet.has(item)) {
398
+ return;
399
+ }
400
+ newItems.push(item);
401
+ newItemsSet.add(item);
402
+ };
403
+ for (const result of functionResults) {
404
+ appendIfNew(result.runItem);
405
+ }
406
+ for (const result of computerResults) {
407
+ appendIfNew(result);
408
+ }
244
409
  // Run MCP tools that require approval after they get their approval results
245
410
  const mcpApprovalRuns = processedResponse.mcpApprovalRequests.filter((run) => {
246
411
  return (run.requestItem.type === 'tool_approval_item' &&
247
412
  run.requestItem.rawItem.type === 'hosted_tool_call' &&
248
413
  run.requestItem.rawItem.providerData?.type === 'mcp_approval_request');
249
414
  });
415
+ // Hosted MCP approvals may still be waiting on a human decision when the turn resumes.
416
+ const pendingHostedMCPApprovals = new Set();
417
+ const pendingHostedMCPApprovalIds = new Set();
418
+ // Keep track of approvals we still need to surface next turn so HITL flows can resume cleanly.
250
419
  for (const run of mcpApprovalRuns) {
251
420
  // the approval_request_id "mcpr_123..."
252
- const approvalRequestId = run.requestItem.rawItem.id;
421
+ const rawItem = run.requestItem.rawItem;
422
+ if (rawItem.type !== 'hosted_tool_call') {
423
+ continue;
424
+ }
425
+ const approvalRequestId = rawItem.id;
253
426
  const approved = state._context.isToolApproved({
254
427
  // Since this item name must be the same with the one sent from Responses API server
255
- toolName: run.requestItem.rawItem.name,
428
+ toolName: rawItem.name,
256
429
  callId: approvalRequestId,
257
430
  });
258
431
  if (typeof approved !== 'undefined') {
@@ -262,50 +435,101 @@ async function executeInterruptedToolsAndSideEffects(agent, originalInput, origi
262
435
  reason: undefined,
263
436
  };
264
437
  // Tell Responses API server the approval result in the next turn
265
- newItems.push(new items_1.RunToolCallItem({
438
+ const responseItem = new items_1.RunToolCallItem({
266
439
  type: 'hosted_tool_call',
267
440
  name: 'mcp_approval_response',
268
441
  providerData,
269
- }, agent));
442
+ }, agent);
443
+ appendIfNew(responseItem);
444
+ }
445
+ else {
446
+ pendingHostedMCPApprovals.add(run.requestItem);
447
+ pendingHostedMCPApprovalIds.add(approvalRequestId);
448
+ functionResults.push({
449
+ type: 'hosted_mcp_tool_approval',
450
+ tool: run.mcpTool,
451
+ runItem: run.requestItem,
452
+ });
453
+ appendIfNew(run.requestItem);
270
454
  }
271
455
  }
272
- const checkToolOutput = await checkForFinalOutputFromTools(agent, functionResults, state);
273
- // Exclude the tool approval items, which should not be sent to Responses API,
274
- // from the SingleStepResult's preStepItems
456
+ // Server-managed conversations rely on preStepItems to re-surface pending approvals.
457
+ // Keep unresolved hosted MCP approvals in place so HITL flows still have something to approve next turn.
458
+ // Drop resolved approval placeholders so they are not replayed on the next turn, but keep
459
+ // pending approvals in place to signal the outstanding work to the UI and session store.
275
460
  const preStepItems = originalPreStepItems.filter((item) => {
276
- return !(item instanceof items_1.RunToolApprovalItem);
461
+ if (!(item instanceof items_1.RunToolApprovalItem)) {
462
+ return true;
463
+ }
464
+ if (item.rawItem.type === 'hosted_tool_call' &&
465
+ item.rawItem.providerData?.type === 'mcp_approval_request') {
466
+ if (pendingHostedMCPApprovals.has(item)) {
467
+ return true;
468
+ }
469
+ const approvalRequestId = item.rawItem.id;
470
+ if (approvalRequestId) {
471
+ return pendingHostedMCPApprovalIds.has(approvalRequestId);
472
+ }
473
+ return false;
474
+ }
475
+ return false;
277
476
  });
278
- if (checkToolOutput.isFinalOutput) {
279
- runner.emit('agent_end', state._context, agent, checkToolOutput.finalOutput);
280
- agent.emit('agent_end', state._context, checkToolOutput.finalOutput);
281
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
282
- type: 'next_step_final_output',
283
- output: checkToolOutput.finalOutput,
284
- });
285
- }
286
- else if (checkToolOutput.isInterrupted) {
287
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
288
- type: 'next_step_interruption',
289
- data: {
290
- interruptions: checkToolOutput.interruptions,
291
- },
292
- });
477
+ const completedStep = await maybeCompleteTurnFromToolResults({
478
+ agent,
479
+ runner,
480
+ state,
481
+ functionResults,
482
+ originalInput,
483
+ newResponse,
484
+ preStepItems,
485
+ newItems,
486
+ });
487
+ if (completedStep) {
488
+ return completedStep;
293
489
  }
294
490
  // we only ran new tools and side effects. We need to run the rest of the agent
295
491
  return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
296
492
  }
297
493
  /**
298
494
  * @internal
495
+ * Executes every follow-up action the model requested (function tools, computer actions, MCP flows),
496
+ * appends their outputs to the run history, and determines the next step for the agent loop.
299
497
  */
300
- async function executeToolsAndSideEffects(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
498
+ async function resolveTurnAfterModelResponse(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
499
+ // Reuse the same array reference so we can compare object identity when deciding whether to
500
+ // append new items, ensuring we never double-stream existing RunItems.
301
501
  const preStepItems = originalPreStepItems;
302
- let newItems = processedResponse.newItems;
303
- const [functionResults, computerResults] = await Promise.all([
502
+ const seenItems = new Set(originalPreStepItems);
503
+ const newItems = [];
504
+ const appendIfNew = (item) => {
505
+ if (seenItems.has(item)) {
506
+ return;
507
+ }
508
+ newItems.push(item);
509
+ seenItems.add(item);
510
+ };
511
+ for (const item of processedResponse.newItems) {
512
+ appendIfNew(item);
513
+ }
514
+ // Run function tools and computer actions in parallel; neither depends on the other's side effects.
515
+ const [functionResults, computerResults, shellResults, applyPatchResults] = await Promise.all([
304
516
  executeFunctionToolCalls(agent, processedResponse.functions, runner, state),
305
517
  executeComputerActions(agent, processedResponse.computerActions, runner, state._context),
518
+ executeShellActions(agent, processedResponse.shellActions, runner, state._context),
519
+ executeApplyPatchOperations(agent, processedResponse.applyPatchActions, runner, state._context),
306
520
  ]);
307
- newItems = newItems.concat(functionResults.map((r) => r.runItem));
308
- newItems = newItems.concat(computerResults);
521
+ for (const result of functionResults) {
522
+ appendIfNew(result.runItem);
523
+ }
524
+ for (const item of computerResults) {
525
+ appendIfNew(item);
526
+ }
527
+ for (const item of shellResults) {
528
+ appendIfNew(item);
529
+ }
530
+ for (const item of applyPatchResults) {
531
+ appendIfNew(item);
532
+ }
309
533
  // run hosted MCP approval requests
310
534
  if (processedResponse.mcpApprovalRequests.length > 0) {
311
535
  for (const approvalRequest of processedResponse.mcpApprovalRequests) {
@@ -351,28 +575,26 @@ async function executeToolsAndSideEffects(agent, originalInput, originalPreStepI
351
575
  if (processedResponse.handoffs.length > 0) {
352
576
  return await executeHandoffCalls(agent, originalInput, preStepItems, newItems, newResponse, processedResponse.handoffs, runner, state._context);
353
577
  }
354
- const checkToolOutput = await checkForFinalOutputFromTools(agent, functionResults, state);
355
- if (checkToolOutput.isFinalOutput) {
356
- runner.emit('agent_end', state._context, agent, checkToolOutput.finalOutput);
357
- agent.emit('agent_end', state._context, checkToolOutput.finalOutput);
358
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
359
- type: 'next_step_final_output',
360
- output: checkToolOutput.finalOutput,
361
- });
362
- }
363
- else if (checkToolOutput.isInterrupted) {
364
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
365
- type: 'next_step_interruption',
366
- data: {
367
- interruptions: checkToolOutput.interruptions,
368
- },
369
- });
578
+ const completedStep = await maybeCompleteTurnFromToolResults({
579
+ agent,
580
+ runner,
581
+ state,
582
+ functionResults,
583
+ originalInput,
584
+ newResponse,
585
+ preStepItems,
586
+ newItems,
587
+ });
588
+ if (completedStep) {
589
+ return completedStep;
370
590
  }
371
591
  // If the model issued any tool calls or handoffs in this turn,
372
592
  // we must NOT treat any assistant message in the same turn as the final output.
373
593
  // We should run the loop again so the model can see the tool results and respond.
374
594
  const hadToolCallsOrActions = (processedResponse.functions?.length ?? 0) > 0 ||
375
595
  (processedResponse.computerActions?.length ?? 0) > 0 ||
596
+ (processedResponse.shellActions?.length ?? 0) > 0 ||
597
+ (processedResponse.applyPatchActions?.length ?? 0) > 0 ||
376
598
  (processedResponse.mcpApprovalRequests?.length ?? 0) > 0 ||
377
599
  (processedResponse.handoffs?.length ?? 0) > 0;
378
600
  if (hadToolCallsOrActions) {
@@ -388,6 +610,7 @@ async function executeToolsAndSideEffects(agent, originalInput, originalPreStepI
388
610
  if (typeof potentialFinalOutput === 'undefined') {
389
611
  return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
390
612
  }
613
+ // Keep looping if any tool output placeholders still require an approval follow-up.
391
614
  const hasPendingToolsOrApprovals = functionResults.some((result) => result.runItem instanceof items_1.RunToolApprovalItem);
392
615
  if (!hasPendingToolsOrApprovals) {
393
616
  if (agent.outputType === 'text') {
@@ -414,8 +637,32 @@ async function executeToolsAndSideEffects(agent, originalInput, originalPreStepI
414
637
  }
415
638
  return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
416
639
  }
640
+ // Consolidates the logic that determines whether tool results yielded a final answer,
641
+ // triggered an interruption, or require the agent loop to continue running.
642
+ async function maybeCompleteTurnFromToolResults({ agent, runner, state, functionResults, originalInput, newResponse, preStepItems, newItems, }) {
643
+ const toolOutcome = await checkForFinalOutputFromTools(agent, functionResults, state);
644
+ if (toolOutcome.isFinalOutput) {
645
+ runner.emit('agent_end', state._context, agent, toolOutcome.finalOutput);
646
+ agent.emit('agent_end', state._context, toolOutcome.finalOutput);
647
+ return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
648
+ type: 'next_step_final_output',
649
+ output: toolOutcome.finalOutput,
650
+ });
651
+ }
652
+ if (toolOutcome.isInterrupted) {
653
+ return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
654
+ type: 'next_step_interruption',
655
+ data: {
656
+ interruptions: toolOutcome.interruptions,
657
+ },
658
+ });
659
+ }
660
+ return null;
661
+ }
417
662
  /**
418
663
  * @internal
664
+ * Normalizes tool outputs once so downstream code works with fully structured protocol items.
665
+ * Doing this here keeps API surface stable even when providers add new shapes.
419
666
  */
420
667
  function getToolCallOutputItem(toolCall, output) {
421
668
  const maybeStructuredOutputs = normalizeStructuredToolOutputs(output);
@@ -440,283 +687,57 @@ function getToolCallOutputItem(toolCall, output) {
440
687
  },
441
688
  };
442
689
  }
443
- /**
444
- * Accepts whatever the tool returned and attempts to coerce it into the structured protocol
445
- * shapes we expose to downstream model adapters (input_text/input_image/input_file). Tools are
446
- * allowed to return either a single structured object or an array of them; anything else falls
447
- * back to the legacy string pipeline.
448
- */
449
- function normalizeStructuredToolOutputs(output) {
450
- if (Array.isArray(output)) {
451
- const structured = [];
452
- for (const item of output) {
453
- const normalized = normalizeStructuredToolOutput(item);
454
- if (!normalized) {
455
- return null;
456
- }
457
- structured.push(normalized);
458
- }
459
- return structured;
690
+ function normalizeFileValue(value) {
691
+ const directFile = value.file;
692
+ if (typeof directFile === 'string' && directFile.length > 0) {
693
+ return directFile;
460
694
  }
461
- const normalized = normalizeStructuredToolOutput(output);
462
- return normalized ? [normalized] : null;
695
+ const normalizedObject = normalizeFileObjectCandidate(directFile);
696
+ if (normalizedObject) {
697
+ return normalizedObject;
698
+ }
699
+ const legacyValue = normalizeLegacyFileValue(value);
700
+ if (legacyValue) {
701
+ return legacyValue;
702
+ }
703
+ return null;
463
704
  }
464
- /**
465
- * Best-effort normalization of a single tool output item. If the object already matches the
466
- * protocol shape we simply cast it; otherwise we copy the recognised fields into the canonical
467
- * structure. Returning null lets the caller know we should revert to plain-string handling.
468
- */
469
- function normalizeStructuredToolOutput(value) {
705
+ function normalizeFileObjectCandidate(value) {
470
706
  if (!isRecord(value)) {
471
707
  return null;
472
708
  }
473
- const type = value.type;
474
- if (type === 'text' && typeof value.text === 'string') {
475
- const output = { type: 'text', text: value.text };
476
- if (isRecord(value.providerData)) {
477
- output.providerData = value.providerData;
478
- }
479
- return output;
480
- }
481
- if (type === 'image') {
482
- const output = { type: 'image' };
483
- let imageString;
484
- let imageFileId;
485
- const fallbackImageMediaType = isNonEmptyString(value.mediaType)
486
- ? value.mediaType
487
- : undefined;
488
- const imageField = value.image;
489
- if (typeof imageField === 'string' && imageField.length > 0) {
490
- imageString = imageField;
491
- }
492
- else if (isRecord(imageField)) {
493
- const imageObj = imageField;
494
- const inlineMediaType = isNonEmptyString(imageObj.mediaType)
495
- ? imageObj.mediaType
496
- : fallbackImageMediaType;
497
- if (isNonEmptyString(imageObj.url)) {
498
- imageString = imageObj.url;
499
- }
500
- else if (isNonEmptyString(imageObj.data)) {
501
- imageString = toInlineImageString(imageObj.data, inlineMediaType);
502
- }
503
- else if (imageObj.data instanceof Uint8Array &&
504
- imageObj.data.length > 0) {
505
- imageString = toInlineImageString(imageObj.data, inlineMediaType);
506
- }
507
- if (!imageString) {
508
- const candidateId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
509
- (isNonEmptyString(imageObj.id) && imageObj.id) ||
510
- undefined;
511
- if (candidateId) {
512
- imageFileId = candidateId;
513
- }
514
- }
515
- }
516
- if (!imageString &&
517
- typeof value.imageUrl === 'string' &&
518
- value.imageUrl.length > 0) {
519
- imageString = value.imageUrl;
520
- }
521
- if (!imageFileId &&
522
- typeof value.fileId === 'string' &&
523
- value.fileId.length > 0) {
524
- imageFileId = value.fileId;
525
- }
526
- if (!imageString &&
527
- typeof value.data === 'string' &&
528
- value.data.length > 0) {
529
- imageString = fallbackImageMediaType
530
- ? toInlineImageString(value.data, fallbackImageMediaType)
531
- : value.data;
532
- }
533
- else if (!imageString &&
534
- value.data instanceof Uint8Array &&
535
- value.data.length > 0) {
536
- imageString = toInlineImageString(value.data, fallbackImageMediaType);
537
- }
538
- if (typeof value.detail === 'string' && value.detail.length > 0) {
539
- output.detail = value.detail;
540
- }
541
- if (imageString) {
542
- output.image = imageString;
543
- }
544
- else if (imageFileId) {
545
- output.image = { fileId: imageFileId };
546
- }
547
- else {
709
+ if ('data' in value && value.data !== undefined) {
710
+ const dataValue = value.data;
711
+ const hasStringData = typeof dataValue === 'string' && dataValue.length > 0;
712
+ const hasBinaryData = dataValue instanceof Uint8Array && dataValue.length > 0;
713
+ if (!hasStringData && !hasBinaryData) {
548
714
  return null;
549
715
  }
550
- if (isRecord(value.providerData)) {
551
- output.providerData = value.providerData;
716
+ if (!isNonEmptyString(value.mediaType) ||
717
+ !isNonEmptyString(value.filename)) {
718
+ return null;
552
719
  }
553
- return output;
720
+ return {
721
+ data: typeof dataValue === 'string' ? dataValue : new Uint8Array(dataValue),
722
+ mediaType: value.mediaType,
723
+ filename: value.filename,
724
+ };
554
725
  }
555
- if (type === 'file') {
556
- const fileValue = normalizeFileValue(value);
557
- if (!fileValue) {
558
- return null;
726
+ if (isNonEmptyString(value.url)) {
727
+ const result = { url: value.url };
728
+ if (isNonEmptyString(value.filename)) {
729
+ result.filename = value.filename;
559
730
  }
560
- const output = { type: 'file', file: fileValue };
561
- if (isRecord(value.providerData)) {
562
- output.providerData = value.providerData;
731
+ return result;
732
+ }
733
+ const referencedId = (isNonEmptyString(value.id) && value.id) ||
734
+ (isNonEmptyString(value.fileId) && value.fileId);
735
+ if (referencedId) {
736
+ const result = { id: referencedId };
737
+ if (isNonEmptyString(value.filename)) {
738
+ result.filename = value.filename;
563
739
  }
564
- return output;
565
- }
566
- return null;
567
- }
568
- /**
569
- * Translates the normalized tool output into the protocol `input_*` items. This is the last hop
570
- * before we hand the data to model-specific adapters, so we generate the exact schema expected by
571
- * the protocol definitions.
572
- */
573
- function convertStructuredToolOutputToInputItem(output) {
574
- if (output.type === 'text') {
575
- const result = {
576
- type: 'input_text',
577
- text: output.text,
578
- };
579
- if (output.providerData) {
580
- result.providerData = output.providerData;
581
- }
582
- return result;
583
- }
584
- if (output.type === 'image') {
585
- const result = { type: 'input_image' };
586
- if (typeof output.detail === 'string' && output.detail.length > 0) {
587
- result.detail = output.detail;
588
- }
589
- if (typeof output.image === 'string' && output.image.length > 0) {
590
- result.image = output.image;
591
- }
592
- else if (isRecord(output.image)) {
593
- const imageObj = output.image;
594
- const inlineMediaType = isNonEmptyString(imageObj.mediaType)
595
- ? imageObj.mediaType
596
- : undefined;
597
- if (isNonEmptyString(imageObj.url)) {
598
- result.image = imageObj.url;
599
- }
600
- else if (isNonEmptyString(imageObj.data)) {
601
- result.image =
602
- inlineMediaType && !imageObj.data.startsWith('data:')
603
- ? asDataUrl(imageObj.data, inlineMediaType)
604
- : imageObj.data;
605
- }
606
- else if (imageObj.data instanceof Uint8Array &&
607
- imageObj.data.length > 0) {
608
- const base64 = (0, base64_1.encodeUint8ArrayToBase64)(imageObj.data);
609
- result.image = asDataUrl(base64, inlineMediaType);
610
- }
611
- else {
612
- const referencedId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
613
- (isNonEmptyString(imageObj.id) && imageObj.id) ||
614
- undefined;
615
- if (referencedId) {
616
- result.image = { id: referencedId };
617
- }
618
- }
619
- }
620
- if (output.providerData) {
621
- result.providerData = output.providerData;
622
- }
623
- return result;
624
- }
625
- if (output.type === 'file') {
626
- const result = { type: 'input_file' };
627
- const fileValue = output.file;
628
- if (typeof fileValue === 'string') {
629
- result.file = fileValue;
630
- }
631
- else if (fileValue && typeof fileValue === 'object') {
632
- const record = fileValue;
633
- if ('data' in record && record.data) {
634
- const mediaType = record.mediaType ?? 'text/plain';
635
- if (typeof record.data === 'string') {
636
- result.file = asDataUrl(record.data, mediaType);
637
- }
638
- else {
639
- const base64 = (0, base64_1.encodeUint8ArrayToBase64)(record.data);
640
- result.file = asDataUrl(base64, mediaType);
641
- }
642
- }
643
- else if (typeof record.url === 'string' && record.url.length > 0) {
644
- result.file = { url: record.url };
645
- }
646
- else {
647
- const referencedId = (typeof record.id === 'string' &&
648
- record.id.length > 0 &&
649
- record.id) ||
650
- (typeof record.fileId === 'string' && record.fileId.length > 0
651
- ? record.fileId
652
- : undefined);
653
- if (referencedId) {
654
- result.file = { id: referencedId };
655
- }
656
- }
657
- if (typeof record.filename === 'string' && record.filename.length > 0) {
658
- result.filename = record.filename;
659
- }
660
- }
661
- if (output.providerData) {
662
- result.providerData = output.providerData;
663
- }
664
- return result;
665
- }
666
- const exhaustiveCheck = output;
667
- return exhaustiveCheck;
668
- }
669
- function normalizeFileValue(value) {
670
- const directFile = value.file;
671
- if (typeof directFile === 'string' && directFile.length > 0) {
672
- return directFile;
673
- }
674
- const normalizedObject = normalizeFileObjectCandidate(directFile);
675
- if (normalizedObject) {
676
- return normalizedObject;
677
- }
678
- const legacyValue = normalizeLegacyFileValue(value);
679
- if (legacyValue) {
680
- return legacyValue;
681
- }
682
- return null;
683
- }
684
- function normalizeFileObjectCandidate(value) {
685
- if (!isRecord(value)) {
686
- return null;
687
- }
688
- if ('data' in value && value.data !== undefined) {
689
- const dataValue = value.data;
690
- const hasStringData = typeof dataValue === 'string' && dataValue.length > 0;
691
- const hasBinaryData = dataValue instanceof Uint8Array && dataValue.length > 0;
692
- if (!hasStringData && !hasBinaryData) {
693
- return null;
694
- }
695
- if (!isNonEmptyString(value.mediaType) ||
696
- !isNonEmptyString(value.filename)) {
697
- return null;
698
- }
699
- return {
700
- data: typeof dataValue === 'string' ? dataValue : new Uint8Array(dataValue),
701
- mediaType: value.mediaType,
702
- filename: value.filename,
703
- };
704
- }
705
- if (isNonEmptyString(value.url)) {
706
- const result = { url: value.url };
707
- if (isNonEmptyString(value.filename)) {
708
- result.filename = value.filename;
709
- }
710
- return result;
711
- }
712
- const referencedId = (isNonEmptyString(value.id) && value.id) ||
713
- (isNonEmptyString(value.fileId) && value.fileId);
714
- if (referencedId) {
715
- const result = { id: referencedId };
716
- if (isNonEmptyString(value.filename)) {
717
- result.filename = value.filename;
718
- }
719
- return result;
740
+ return result;
720
741
  }
721
742
  return null;
722
743
  }
@@ -776,6 +797,8 @@ function asDataUrl(base64, mediaType) {
776
797
  }
777
798
  /**
778
799
  * @internal
800
+ * Runs every function tool call requested by the model and returns their outputs alongside
801
+ * the `RunItem` instances that should be appended to history.
779
802
  */
780
803
  async function executeFunctionToolCalls(agent, toolRuns, runner, state) {
781
804
  async function runSingleTool(toolRun) {
@@ -788,6 +811,7 @@ async function executeFunctionToolCalls(agent, toolRuns, runner, state) {
788
811
  parsedArgs = JSON.parse(parsedArgs);
789
812
  }
790
813
  }
814
+ // Some tools require a human or policy check before execution; defer until approval is recorded.
791
815
  const needsApproval = await toolRun.tool.needsApproval(state._context, parsedArgs, toolRun.toolCall.callId);
792
816
  if (needsApproval) {
793
817
  const approval = state._context.isToolApproved({
@@ -939,8 +963,210 @@ async function _runComputerActionAndScreenshot(computer, toolCall) {
939
963
  }
940
964
  throw new Error('Computer does not implement screenshot()');
941
965
  }
966
+ function toErrorMessage(error) {
967
+ if (error instanceof Error) {
968
+ return error.message || error.toString();
969
+ }
970
+ try {
971
+ return JSON.stringify(error);
972
+ }
973
+ catch {
974
+ return String(error);
975
+ }
976
+ }
977
+ async function executeShellActions(agent, actions, runner, runContext, customLogger = undefined) {
978
+ const _logger = customLogger ?? logger_1.default;
979
+ const results = [];
980
+ for (const action of actions) {
981
+ const shellTool = action.shell;
982
+ const toolCall = action.toolCall;
983
+ const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, shellTool.name);
984
+ const requiresApproval = await shellTool.needsApproval(runContext, toolCall.action, toolCall.callId);
985
+ if (requiresApproval) {
986
+ if (shellTool.onApproval) {
987
+ const decision = await shellTool.onApproval(runContext, approvalItem);
988
+ if (decision.approve === true) {
989
+ runContext.approveTool(approvalItem);
990
+ }
991
+ else if (decision.approve === false) {
992
+ runContext.rejectTool(approvalItem);
993
+ }
994
+ }
995
+ const approval = runContext.isToolApproved({
996
+ toolName: shellTool.name,
997
+ callId: toolCall.callId,
998
+ });
999
+ if (approval === false) {
1000
+ const response = 'Tool execution was not approved.';
1001
+ const rejectionOutput = {
1002
+ stdout: '',
1003
+ stderr: response,
1004
+ outcome: { type: 'exit', exitCode: null },
1005
+ };
1006
+ results.push(new items_1.RunToolCallOutputItem({
1007
+ type: 'shell_call_output',
1008
+ callId: toolCall.callId,
1009
+ output: [rejectionOutput],
1010
+ }, agent, response));
1011
+ continue;
1012
+ }
1013
+ if (approval !== true) {
1014
+ results.push(approvalItem);
1015
+ continue;
1016
+ }
1017
+ }
1018
+ runner.emit('agent_tool_start', runContext, agent, shellTool, {
1019
+ toolCall,
1020
+ });
1021
+ if (typeof agent.emit === 'function') {
1022
+ agent.emit('agent_tool_start', runContext, shellTool, { toolCall });
1023
+ }
1024
+ let shellOutputs;
1025
+ const providerMeta = {};
1026
+ let maxOutputLength;
1027
+ try {
1028
+ const shellResult = await shellTool.shell.run(toolCall.action);
1029
+ shellOutputs = shellResult.output ?? [];
1030
+ if (shellResult.providerData) {
1031
+ Object.assign(providerMeta, shellResult.providerData);
1032
+ }
1033
+ if (typeof shellResult.maxOutputLength === 'number') {
1034
+ maxOutputLength = shellResult.maxOutputLength;
1035
+ }
1036
+ }
1037
+ catch (err) {
1038
+ const errorText = toErrorMessage(err);
1039
+ shellOutputs = [
1040
+ {
1041
+ stdout: '',
1042
+ stderr: errorText,
1043
+ outcome: { type: 'exit', exitCode: null },
1044
+ },
1045
+ ];
1046
+ _logger.error('Failed to execute shell action:', err);
1047
+ }
1048
+ shellOutputs = shellOutputs ?? [];
1049
+ runner.emit('agent_tool_end', runContext, agent, shellTool, JSON.stringify(shellOutputs), {
1050
+ toolCall,
1051
+ });
1052
+ if (typeof agent.emit === 'function') {
1053
+ agent.emit('agent_tool_end', runContext, shellTool, JSON.stringify(shellOutputs), {
1054
+ toolCall,
1055
+ });
1056
+ }
1057
+ const rawItem = {
1058
+ type: 'shell_call_output',
1059
+ callId: toolCall.callId,
1060
+ output: shellOutputs ?? [],
1061
+ };
1062
+ if (typeof maxOutputLength === 'number') {
1063
+ rawItem.maxOutputLength = maxOutputLength;
1064
+ }
1065
+ if (Object.keys(providerMeta).length > 0) {
1066
+ rawItem.providerData = providerMeta;
1067
+ }
1068
+ results.push(new items_1.RunToolCallOutputItem(rawItem, agent, rawItem.output));
1069
+ }
1070
+ return results;
1071
+ }
1072
+ async function executeApplyPatchOperations(agent, actions, runner, runContext, customLogger = undefined) {
1073
+ const _logger = customLogger ?? logger_1.default;
1074
+ const results = [];
1075
+ for (const action of actions) {
1076
+ const applyPatchTool = action.applyPatch;
1077
+ const toolCall = action.toolCall;
1078
+ const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, applyPatchTool.name);
1079
+ const requiresApproval = await applyPatchTool.needsApproval(runContext, toolCall.operation, toolCall.callId);
1080
+ if (requiresApproval) {
1081
+ if (applyPatchTool.onApproval) {
1082
+ const decision = await applyPatchTool.onApproval(runContext, approvalItem);
1083
+ if (decision.approve === true) {
1084
+ runContext.approveTool(approvalItem);
1085
+ }
1086
+ else if (decision.approve === false) {
1087
+ runContext.rejectTool(approvalItem);
1088
+ }
1089
+ }
1090
+ const approval = runContext.isToolApproved({
1091
+ toolName: applyPatchTool.name,
1092
+ callId: toolCall.callId,
1093
+ });
1094
+ if (approval === false) {
1095
+ const response = 'Tool execution was not approved.';
1096
+ results.push(new items_1.RunToolCallOutputItem({
1097
+ type: 'apply_patch_call_output',
1098
+ callId: toolCall.callId,
1099
+ status: 'failed',
1100
+ output: response,
1101
+ }, agent, response));
1102
+ continue;
1103
+ }
1104
+ if (approval !== true) {
1105
+ results.push(approvalItem);
1106
+ continue;
1107
+ }
1108
+ }
1109
+ runner.emit('agent_tool_start', runContext, agent, applyPatchTool, {
1110
+ toolCall,
1111
+ });
1112
+ if (typeof agent.emit === 'function') {
1113
+ agent.emit('agent_tool_start', runContext, applyPatchTool, {
1114
+ toolCall,
1115
+ });
1116
+ }
1117
+ let status = 'completed';
1118
+ let output = '';
1119
+ try {
1120
+ let result;
1121
+ switch (toolCall.operation.type) {
1122
+ case 'create_file':
1123
+ result = await applyPatchTool.editor.createFile(toolCall.operation);
1124
+ break;
1125
+ case 'update_file':
1126
+ result = await applyPatchTool.editor.updateFile(toolCall.operation);
1127
+ break;
1128
+ case 'delete_file':
1129
+ result = await applyPatchTool.editor.deleteFile(toolCall.operation);
1130
+ break;
1131
+ default:
1132
+ throw new Error('Unsupported apply_patch operation');
1133
+ }
1134
+ if (result && typeof result.status === 'string') {
1135
+ status = result.status;
1136
+ }
1137
+ if (result && typeof result.output === 'string') {
1138
+ output = result.output;
1139
+ }
1140
+ }
1141
+ catch (err) {
1142
+ status = 'failed';
1143
+ output = toErrorMessage(err);
1144
+ _logger.error('Failed to execute apply_patch operation:', err);
1145
+ }
1146
+ runner.emit('agent_tool_end', runContext, agent, applyPatchTool, output, {
1147
+ toolCall,
1148
+ });
1149
+ if (typeof agent.emit === 'function') {
1150
+ agent.emit('agent_tool_end', runContext, applyPatchTool, output, {
1151
+ toolCall,
1152
+ });
1153
+ }
1154
+ const rawItem = {
1155
+ type: 'apply_patch_call_output',
1156
+ callId: toolCall.callId,
1157
+ status,
1158
+ };
1159
+ if (output) {
1160
+ rawItem.output = output;
1161
+ }
1162
+ results.push(new items_1.RunToolCallOutputItem(rawItem, agent, output));
1163
+ }
1164
+ return results;
1165
+ }
942
1166
  /**
943
1167
  * @internal
1168
+ * Executes any computer-use actions emitted by the model and returns the resulting items so the
1169
+ * run history reflects the computer session.
944
1170
  */
945
1171
  async function executeComputerActions(agent, actions, runner, runContext, customLogger = undefined) {
946
1172
  const _logger = customLogger ?? logger_1.default;
@@ -973,7 +1199,7 @@ async function executeComputerActions(agent, actions, runner, runContext, custom
973
1199
  toolCall,
974
1200
  });
975
1201
  }
976
- // Always return a screenshot as a base64 data URL
1202
+ // Return the screenshot as a data URL when available; fall back to an empty string on failures.
977
1203
  const imageUrl = output ? `data:image/png;base64,${output}` : '';
978
1204
  const rawItem = {
979
1205
  type: 'computer_call_result',
@@ -986,6 +1212,8 @@ async function executeComputerActions(agent, actions, runner, runContext, custom
986
1212
  }
987
1213
  /**
988
1214
  * @internal
1215
+ * Drives handoff calls by invoking the downstream agent and capturing any generated items so
1216
+ * the current agent can continue with the new context.
989
1217
  */
990
1218
  async function executeHandoffCalls(agent, originalInput, preStepItems, newStepItems, newResponse, runHandoffs, runner, runContext) {
991
1219
  newStepItems = [...newStepItems];
@@ -1054,6 +1282,8 @@ const NOT_FINAL_OUTPUT = {
1054
1282
  };
1055
1283
  /**
1056
1284
  * @internal
1285
+ * Determines whether tool executions produced a final agent output, triggered an interruption,
1286
+ * or whether the agent loop should continue collecting more responses.
1057
1287
  */
1058
1288
  async function checkForFinalOutputFromTools(agent, toolResults, state) {
1059
1289
  if (toolResults.length === 0) {
@@ -1181,4 +1411,571 @@ class AgentToolUseTracker {
1181
1411
  }
1182
1412
  }
1183
1413
  exports.AgentToolUseTracker = AgentToolUseTracker;
1414
+ /**
1415
+ * @internal
1416
+ * Convert a user-provided input into a list of input items.
1417
+ */
1418
+ function toInputItemList(input) {
1419
+ if (typeof input === 'string') {
1420
+ return [
1421
+ {
1422
+ type: 'message',
1423
+ role: 'user',
1424
+ content: input,
1425
+ },
1426
+ ];
1427
+ }
1428
+ return [...input];
1429
+ }
1430
+ /**
1431
+ * @internal
1432
+ * Extract model output items from run items, excluding tool approval items.
1433
+ */
1434
+ function extractOutputItemsFromRunItems(items) {
1435
+ return items
1436
+ .filter((item) => item.type !== 'tool_approval_item')
1437
+ .map((item) => item.rawItem);
1438
+ }
1439
+ function normalizeItemsForSessionPersistence(items) {
1440
+ // Persisted sessions must avoid raw binary so we convert every item into a JSON-safe shape before writing to storage.
1441
+ return items.map((item) => sanitizeValueForSession(stripTransientCallIds(item)));
1442
+ }
1443
+ function sanitizeValueForSession(value, context = {}) {
1444
+ if (value === null || value === undefined) {
1445
+ return value;
1446
+ }
1447
+ // Convert supported binary payloads into ArrayBuffer views before serialization.
1448
+ const binary = toUint8ArrayIfBinary(value);
1449
+ if (binary) {
1450
+ return toDataUrlFromBytes(binary, context.mediaType);
1451
+ }
1452
+ if (Array.isArray(value)) {
1453
+ return value.map((entry) => sanitizeValueForSession(entry, context));
1454
+ }
1455
+ if (!isPlainObject(value)) {
1456
+ return value;
1457
+ }
1458
+ const record = value;
1459
+ const result = {};
1460
+ const mediaType = typeof record.mediaType === 'string' && record.mediaType.length > 0
1461
+ ? record.mediaType
1462
+ : context.mediaType;
1463
+ for (const [key, entry] of Object.entries(record)) {
1464
+ // Propagate explicit media type only when walking into binary payload containers.
1465
+ const nextContext = key === 'data' || key === 'fileData' ? { mediaType } : context;
1466
+ result[key] = sanitizeValueForSession(entry, nextContext);
1467
+ }
1468
+ return result;
1469
+ }
1470
+ function toUint8ArrayIfBinary(value) {
1471
+ // Normalize the diverse binary containers we may receive into a shared Uint8Array view.
1472
+ if (value instanceof ArrayBuffer) {
1473
+ return new Uint8Array(value);
1474
+ }
1475
+ if ((0, smartString_1.isArrayBufferView)(value)) {
1476
+ const view = value;
1477
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
1478
+ }
1479
+ if ((0, smartString_1.isNodeBuffer)(value)) {
1480
+ const view = value;
1481
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
1482
+ }
1483
+ if ((0, smartString_1.isSerializedBufferSnapshot)(value)) {
1484
+ const snapshot = value;
1485
+ return Uint8Array.from(snapshot.data);
1486
+ }
1487
+ return undefined;
1488
+ }
1489
+ function toDataUrlFromBytes(bytes, mediaType) {
1490
+ // Convert binary payloads into a durable data URL so session files remain self-contained.
1491
+ const base64 = (0, base64_1.encodeUint8ArrayToBase64)(bytes);
1492
+ // Note that OpenAI Responses API never accepts application/octet-stream as a media type,
1493
+ // so we fall back to text/plain; that said, tools are supposed to return a valid media type when this utility is used.
1494
+ const type = mediaType && !mediaType.startsWith('data:') ? mediaType : 'text/plain';
1495
+ return `data:${type};base64,${base64}`;
1496
+ }
1497
+ function isPlainObject(value) {
1498
+ if (typeof value !== 'object' || value === null) {
1499
+ return false;
1500
+ }
1501
+ const proto = Object.getPrototypeOf(value);
1502
+ return proto === Object.prototype || proto === null;
1503
+ }
1504
+ function stripTransientCallIds(value) {
1505
+ if (value === null || value === undefined) {
1506
+ return value;
1507
+ }
1508
+ if (Array.isArray(value)) {
1509
+ return value.map((entry) => stripTransientCallIds(entry));
1510
+ }
1511
+ if (!isPlainObject(value)) {
1512
+ return value;
1513
+ }
1514
+ const record = value;
1515
+ const result = {};
1516
+ const isProtocolItem = typeof record.type === 'string' && record.type.length > 0;
1517
+ const shouldStripId = isProtocolItem && shouldStripIdForType(record.type);
1518
+ for (const [key, entry] of Object.entries(record)) {
1519
+ if (shouldStripId && key === 'id') {
1520
+ continue;
1521
+ }
1522
+ result[key] = stripTransientCallIds(entry);
1523
+ }
1524
+ return result;
1525
+ }
1526
+ function shouldStripIdForType(type) {
1527
+ switch (type) {
1528
+ case 'function_call':
1529
+ case 'function_call_result':
1530
+ return true;
1531
+ default:
1532
+ return false;
1533
+ }
1534
+ }
1535
+ /**
1536
+ * @internal
1537
+ * Persist full turn (input + outputs) for non-streaming runs.
1538
+ */
1539
+ // Persists the combination of user inputs (possibly filtered) and model outputs for a completed turn.
1540
+ async function saveToSession(session, sessionInputItems, result) {
1541
+ if (!session) {
1542
+ return;
1543
+ }
1544
+ const inputItems = sessionInputItems ?? [];
1545
+ const state = result.state;
1546
+ const alreadyPersisted = state._currentTurnPersistedItemCount ?? 0;
1547
+ // Persist only the portion of _generatedItems that has not yet been stored for this turn.
1548
+ const newRunItems = result.newItems.slice(alreadyPersisted);
1549
+ if (process.env.OPENAI_AGENTS__DEBUG_SAVE_SESSION) {
1550
+ console.debug('saveToSession:newRunItems', newRunItems.map((item) => item.type));
1551
+ }
1552
+ const outputItems = extractOutputItemsFromRunItems(newRunItems);
1553
+ const itemsToSave = [...inputItems, ...outputItems];
1554
+ if (itemsToSave.length === 0) {
1555
+ state._currentTurnPersistedItemCount =
1556
+ alreadyPersisted + newRunItems.length;
1557
+ return;
1558
+ }
1559
+ const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
1560
+ await session.addItems(sanitizedItems);
1561
+ state._currentTurnPersistedItemCount = alreadyPersisted + newRunItems.length;
1562
+ }
1563
+ /**
1564
+ * @internal
1565
+ * Persist only the user input for streaming runs at start.
1566
+ */
1567
+ // For streaming runs we persist user input as soon as it is sent so reconnections can resume.
1568
+ async function saveStreamInputToSession(session, sessionInputItems) {
1569
+ if (!session) {
1570
+ return;
1571
+ }
1572
+ if (!sessionInputItems || sessionInputItems.length === 0) {
1573
+ return;
1574
+ }
1575
+ const sanitizedInput = normalizeItemsForSessionPersistence(sessionInputItems);
1576
+ await session.addItems(sanitizedInput);
1577
+ }
1578
+ /**
1579
+ * @internal
1580
+ * Persist only the model outputs for streaming runs at the end of a turn.
1581
+ */
1582
+ // Complements saveStreamInputToSession by recording the streaming outputs at the end of the turn.
1583
+ async function saveStreamResultToSession(session, result) {
1584
+ if (!session) {
1585
+ return;
1586
+ }
1587
+ const state = result.state;
1588
+ const alreadyPersisted = state._currentTurnPersistedItemCount ?? 0;
1589
+ const newRunItems = result.newItems.slice(alreadyPersisted);
1590
+ const itemsToSave = extractOutputItemsFromRunItems(newRunItems);
1591
+ if (itemsToSave.length === 0) {
1592
+ state._currentTurnPersistedItemCount =
1593
+ alreadyPersisted + newRunItems.length;
1594
+ return;
1595
+ }
1596
+ const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
1597
+ await session.addItems(sanitizedItems);
1598
+ state._currentTurnPersistedItemCount = alreadyPersisted + newRunItems.length;
1599
+ }
1600
+ async function prepareInputItemsWithSession(input, session, sessionInputCallback, options) {
1601
+ if (!session) {
1602
+ return {
1603
+ preparedInput: input,
1604
+ sessionItems: undefined,
1605
+ };
1606
+ }
1607
+ const includeHistoryInPreparedInput = options?.includeHistoryInPreparedInput ?? true;
1608
+ const preserveDroppedNewItems = options?.preserveDroppedNewItems ?? false;
1609
+ const history = await session.getItems();
1610
+ const newInputItems = Array.isArray(input)
1611
+ ? [...input]
1612
+ : toInputItemList(input);
1613
+ if (!sessionInputCallback) {
1614
+ return {
1615
+ preparedInput: includeHistoryInPreparedInput
1616
+ ? [...history, ...newInputItems]
1617
+ : newInputItems,
1618
+ sessionItems: newInputItems,
1619
+ };
1620
+ }
1621
+ // Capture snapshots before invoking the callback so we can reason about the original state even
1622
+ // if the callback mutates the history array in-place.
1623
+ const historySnapshot = history.slice();
1624
+ const newInputSnapshot = newInputItems.slice();
1625
+ // Delegate history reconciliation to the user-supplied callback. It must return a concrete list
1626
+ // to keep downstream model requests well-typed.
1627
+ const combined = await sessionInputCallback(history, newInputItems);
1628
+ if (!Array.isArray(combined)) {
1629
+ throw new errors_1.UserError('Session input callback must return an array of AgentInputItem objects.');
1630
+ }
1631
+ const historyCounts = buildItemFrequencyMap(historySnapshot);
1632
+ const newInputCounts = buildItemFrequencyMap(newInputSnapshot);
1633
+ const historyRefs = buildItemReferenceMap(historySnapshot);
1634
+ const newInputRefs = buildItemReferenceMap(newInputSnapshot);
1635
+ const appended = [];
1636
+ for (const item of combined) {
1637
+ const key = sessionItemKey(item);
1638
+ if (consumeReference(newInputRefs, key, item)) {
1639
+ decrementCount(newInputCounts, key);
1640
+ appended.push(item);
1641
+ continue;
1642
+ }
1643
+ // Prioritize exact history matches before payload-based counts so callbacks that surface
1644
+ // history ahead of identical new inputs keep previously persisted items out of the new queue.
1645
+ if (consumeReference(historyRefs, key, item)) {
1646
+ decrementCount(historyCounts, key);
1647
+ continue;
1648
+ }
1649
+ const historyRemaining = historyCounts.get(key) ?? 0;
1650
+ if (historyRemaining > 0) {
1651
+ historyCounts.set(key, historyRemaining - 1);
1652
+ continue;
1653
+ }
1654
+ const newRemaining = newInputCounts.get(key) ?? 0;
1655
+ if (newRemaining > 0) {
1656
+ newInputCounts.set(key, newRemaining - 1);
1657
+ appended.push(item);
1658
+ continue;
1659
+ }
1660
+ appended.push(item);
1661
+ }
1662
+ // Preserve redacted inputs for model delivery when requested (e.g. server-managed histories).
1663
+ const preparedItems = includeHistoryInPreparedInput
1664
+ ? combined
1665
+ : appended.length > 0
1666
+ ? appended
1667
+ : preserveDroppedNewItems
1668
+ ? newInputSnapshot
1669
+ : [];
1670
+ return {
1671
+ preparedInput: preparedItems,
1672
+ // Respect callbacks that intentionally drop the latest inputs (e.g. to redact sensitive
1673
+ // values) by persisting only the items they kept in the combined array.
1674
+ sessionItems: appended,
1675
+ };
1676
+ }
1677
+ /**
1678
+ * Accepts whatever the tool returned and attempts to coerce it into the structured protocol
1679
+ * shapes we expose to downstream model adapters (input_text/input_image/input_file). Tools are
1680
+ * allowed to return either a single structured object or an array of them; anything else falls
1681
+ * back to the legacy string pipeline.
1682
+ */
1683
+ function normalizeStructuredToolOutputs(output) {
1684
+ if (Array.isArray(output)) {
1685
+ const structured = [];
1686
+ for (const item of output) {
1687
+ const normalized = normalizeStructuredToolOutput(item);
1688
+ if (!normalized) {
1689
+ return null;
1690
+ }
1691
+ structured.push(normalized);
1692
+ }
1693
+ return structured;
1694
+ }
1695
+ const normalized = normalizeStructuredToolOutput(output);
1696
+ return normalized ? [normalized] : null;
1697
+ }
1698
+ /**
1699
+ * Best-effort normalization of a single tool output item. If the object already matches the
1700
+ * protocol shape we simply cast it; otherwise we copy the recognised fields into the canonical
1701
+ * structure. Returning null lets the caller know we should revert to plain-string handling.
1702
+ */
1703
+ function normalizeStructuredToolOutput(value) {
1704
+ if (!isRecord(value)) {
1705
+ return null;
1706
+ }
1707
+ const type = value.type;
1708
+ if (type === 'text' && typeof value.text === 'string') {
1709
+ const output = { type: 'text', text: value.text };
1710
+ if (isRecord(value.providerData)) {
1711
+ output.providerData = value.providerData;
1712
+ }
1713
+ return output;
1714
+ }
1715
+ if (type === 'image') {
1716
+ const output = { type: 'image' };
1717
+ let imageString;
1718
+ let imageFileId;
1719
+ const fallbackImageMediaType = isNonEmptyString(value.mediaType)
1720
+ ? value.mediaType
1721
+ : undefined;
1722
+ const imageField = value.image;
1723
+ if (typeof imageField === 'string' && imageField.length > 0) {
1724
+ imageString = imageField;
1725
+ }
1726
+ else if (isRecord(imageField)) {
1727
+ const imageObj = imageField;
1728
+ const inlineMediaType = isNonEmptyString(imageObj.mediaType)
1729
+ ? imageObj.mediaType
1730
+ : fallbackImageMediaType;
1731
+ if (isNonEmptyString(imageObj.url)) {
1732
+ imageString = imageObj.url;
1733
+ }
1734
+ else if (isNonEmptyString(imageObj.data)) {
1735
+ imageString = toInlineImageString(imageObj.data, inlineMediaType);
1736
+ }
1737
+ else if (imageObj.data instanceof Uint8Array &&
1738
+ imageObj.data.length > 0) {
1739
+ imageString = toInlineImageString(imageObj.data, inlineMediaType);
1740
+ }
1741
+ if (!imageString) {
1742
+ const candidateId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
1743
+ (isNonEmptyString(imageObj.id) && imageObj.id) ||
1744
+ undefined;
1745
+ if (candidateId) {
1746
+ imageFileId = candidateId;
1747
+ }
1748
+ }
1749
+ }
1750
+ if (!imageString &&
1751
+ typeof value.imageUrl === 'string' &&
1752
+ value.imageUrl.length > 0) {
1753
+ imageString = value.imageUrl;
1754
+ }
1755
+ if (!imageFileId &&
1756
+ typeof value.fileId === 'string' &&
1757
+ value.fileId.length > 0) {
1758
+ imageFileId = value.fileId;
1759
+ }
1760
+ if (!imageString &&
1761
+ typeof value.data === 'string' &&
1762
+ value.data.length > 0) {
1763
+ imageString = fallbackImageMediaType
1764
+ ? toInlineImageString(value.data, fallbackImageMediaType)
1765
+ : value.data;
1766
+ }
1767
+ else if (!imageString &&
1768
+ value.data instanceof Uint8Array &&
1769
+ value.data.length > 0) {
1770
+ imageString = toInlineImageString(value.data, fallbackImageMediaType);
1771
+ }
1772
+ if (typeof value.detail === 'string' && value.detail.length > 0) {
1773
+ output.detail = value.detail;
1774
+ }
1775
+ if (imageString) {
1776
+ output.image = imageString;
1777
+ }
1778
+ else if (imageFileId) {
1779
+ output.image = { fileId: imageFileId };
1780
+ }
1781
+ else {
1782
+ return null;
1783
+ }
1784
+ if (isRecord(value.providerData)) {
1785
+ output.providerData = value.providerData;
1786
+ }
1787
+ return output;
1788
+ }
1789
+ if (type === 'file') {
1790
+ const fileValue = normalizeFileValue(value);
1791
+ if (!fileValue) {
1792
+ return null;
1793
+ }
1794
+ const output = { type: 'file', file: fileValue };
1795
+ if (isRecord(value.providerData)) {
1796
+ output.providerData = value.providerData;
1797
+ }
1798
+ return output;
1799
+ }
1800
+ return null;
1801
+ }
1802
+ /**
1803
+ * Translates the normalized tool output into the protocol `input_*` items. This is the last hop
1804
+ * before we hand the data to model-specific adapters, so we generate the exact schema expected by
1805
+ * the protocol definitions.
1806
+ */
1807
+ function convertStructuredToolOutputToInputItem(output) {
1808
+ if (output.type === 'text') {
1809
+ const result = {
1810
+ type: 'input_text',
1811
+ text: output.text,
1812
+ };
1813
+ if (output.providerData) {
1814
+ result.providerData = output.providerData;
1815
+ }
1816
+ return result;
1817
+ }
1818
+ if (output.type === 'image') {
1819
+ const result = { type: 'input_image' };
1820
+ if (typeof output.detail === 'string' && output.detail.length > 0) {
1821
+ result.detail = output.detail;
1822
+ }
1823
+ if (typeof output.image === 'string' && output.image.length > 0) {
1824
+ result.image = output.image;
1825
+ }
1826
+ else if (isRecord(output.image)) {
1827
+ const imageObj = output.image;
1828
+ const inlineMediaType = isNonEmptyString(imageObj.mediaType)
1829
+ ? imageObj.mediaType
1830
+ : undefined;
1831
+ if (isNonEmptyString(imageObj.url)) {
1832
+ result.image = imageObj.url;
1833
+ }
1834
+ else if (isNonEmptyString(imageObj.data)) {
1835
+ result.image =
1836
+ inlineMediaType && !imageObj.data.startsWith('data:')
1837
+ ? asDataUrl(imageObj.data, inlineMediaType)
1838
+ : imageObj.data;
1839
+ }
1840
+ else if (imageObj.data instanceof Uint8Array &&
1841
+ imageObj.data.length > 0) {
1842
+ const base64 = (0, base64_1.encodeUint8ArrayToBase64)(imageObj.data);
1843
+ result.image = asDataUrl(base64, inlineMediaType);
1844
+ }
1845
+ else {
1846
+ const referencedId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
1847
+ (isNonEmptyString(imageObj.id) && imageObj.id) ||
1848
+ undefined;
1849
+ if (referencedId) {
1850
+ result.image = { id: referencedId };
1851
+ }
1852
+ }
1853
+ }
1854
+ if (output.providerData) {
1855
+ result.providerData = output.providerData;
1856
+ }
1857
+ return result;
1858
+ }
1859
+ if (output.type === 'file') {
1860
+ const result = { type: 'input_file' };
1861
+ const fileValue = output.file;
1862
+ if (typeof fileValue === 'string') {
1863
+ result.file = fileValue;
1864
+ }
1865
+ else if (fileValue && typeof fileValue === 'object') {
1866
+ const record = fileValue;
1867
+ if ('data' in record && record.data) {
1868
+ const mediaType = record.mediaType ?? 'text/plain';
1869
+ if (typeof record.data === 'string') {
1870
+ result.file = asDataUrl(record.data, mediaType);
1871
+ }
1872
+ else {
1873
+ const base64 = (0, base64_1.encodeUint8ArrayToBase64)(record.data);
1874
+ result.file = asDataUrl(base64, mediaType);
1875
+ }
1876
+ }
1877
+ else if (typeof record.url === 'string' && record.url.length > 0) {
1878
+ result.file = { url: record.url };
1879
+ }
1880
+ else {
1881
+ const referencedId = (typeof record.id === 'string' &&
1882
+ record.id.length > 0 &&
1883
+ record.id) ||
1884
+ (typeof record.fileId === 'string' && record.fileId.length > 0
1885
+ ? record.fileId
1886
+ : undefined);
1887
+ if (referencedId) {
1888
+ result.file = { id: referencedId };
1889
+ }
1890
+ }
1891
+ if (typeof record.filename === 'string' && record.filename.length > 0) {
1892
+ result.filename = record.filename;
1893
+ }
1894
+ }
1895
+ if (output.providerData) {
1896
+ result.providerData = output.providerData;
1897
+ }
1898
+ return result;
1899
+ }
1900
+ const exhaustiveCheck = output;
1901
+ return exhaustiveCheck;
1902
+ }
1903
+ function buildItemFrequencyMap(items) {
1904
+ const counts = new Map();
1905
+ for (const item of items) {
1906
+ const key = sessionItemKey(item);
1907
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1908
+ }
1909
+ return counts;
1910
+ }
1911
+ function buildItemReferenceMap(items) {
1912
+ const refs = new Map();
1913
+ for (const item of items) {
1914
+ const key = sessionItemKey(item);
1915
+ const list = refs.get(key);
1916
+ if (list) {
1917
+ list.push(item);
1918
+ }
1919
+ else {
1920
+ refs.set(key, [item]);
1921
+ }
1922
+ }
1923
+ return refs;
1924
+ }
1925
+ function consumeReference(refs, key, target) {
1926
+ const candidates = refs.get(key);
1927
+ if (!candidates || candidates.length === 0) {
1928
+ return false;
1929
+ }
1930
+ const index = candidates.findIndex((candidate) => candidate === target);
1931
+ if (index === -1) {
1932
+ return false;
1933
+ }
1934
+ candidates.splice(index, 1);
1935
+ if (candidates.length === 0) {
1936
+ refs.delete(key);
1937
+ }
1938
+ return true;
1939
+ }
1940
+ function decrementCount(map, key) {
1941
+ const remaining = (map.get(key) ?? 0) - 1;
1942
+ if (remaining <= 0) {
1943
+ map.delete(key);
1944
+ }
1945
+ else {
1946
+ map.set(key, remaining);
1947
+ }
1948
+ }
1949
+ function sessionItemKey(item) {
1950
+ return JSON.stringify(item, sessionSerializationReplacer);
1951
+ }
1952
+ function sessionSerializationReplacer(_key, value) {
1953
+ if (value instanceof ArrayBuffer) {
1954
+ return {
1955
+ __type: 'ArrayBuffer',
1956
+ data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(value)),
1957
+ };
1958
+ }
1959
+ if ((0, smartString_1.isArrayBufferView)(value)) {
1960
+ const view = value;
1961
+ return {
1962
+ __type: view.constructor.name,
1963
+ data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
1964
+ };
1965
+ }
1966
+ if ((0, smartString_1.isNodeBuffer)(value)) {
1967
+ const view = value;
1968
+ return {
1969
+ __type: 'Buffer',
1970
+ data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
1971
+ };
1972
+ }
1973
+ if ((0, smartString_1.isSerializedBufferSnapshot)(value)) {
1974
+ return {
1975
+ __type: 'Buffer',
1976
+ data: (0, base64_1.encodeUint8ArrayToBase64)(Uint8Array.from(value.data)),
1977
+ };
1978
+ }
1979
+ return value;
1980
+ }
1184
1981
  //# sourceMappingURL=runImplementation.js.map