@openai/agents-core 0.3.6 → 0.3.8

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 (180) hide show
  1. package/dist/errors.d.ts +40 -0
  2. package/dist/errors.js +38 -1
  3. package/dist/errors.js.map +1 -1
  4. package/dist/errors.mjs +34 -0
  5. package/dist/errors.mjs.map +1 -1
  6. package/dist/index.d.ts +6 -2
  7. package/dist/index.js +13 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/metadata.js +2 -2
  12. package/dist/metadata.mjs +2 -2
  13. package/dist/result.d.ts +17 -0
  14. package/dist/result.js +71 -24
  15. package/dist/result.js.map +1 -1
  16. package/dist/result.mjs +69 -22
  17. package/dist/result.mjs.map +1 -1
  18. package/dist/run.d.ts +14 -47
  19. package/dist/run.js +421 -994
  20. package/dist/run.js.map +1 -1
  21. package/dist/run.mjs +405 -978
  22. package/dist/run.mjs.map +1 -1
  23. package/dist/runState.d.ts +1286 -172
  24. package/dist/runState.js +146 -16
  25. package/dist/runState.js.map +1 -1
  26. package/dist/runState.mjs +142 -12
  27. package/dist/runState.mjs.map +1 -1
  28. package/dist/runner/constants.d.ts +1 -0
  29. package/dist/runner/constants.js +6 -0
  30. package/dist/runner/constants.js.map +1 -0
  31. package/dist/runner/constants.mjs +3 -0
  32. package/dist/runner/constants.mjs.map +1 -0
  33. package/dist/runner/conversation.d.ts +85 -0
  34. package/dist/runner/conversation.js +275 -0
  35. package/dist/runner/conversation.js.map +1 -0
  36. package/dist/runner/conversation.mjs +269 -0
  37. package/dist/runner/conversation.mjs.map +1 -0
  38. package/dist/runner/guardrails.d.ts +23 -0
  39. package/dist/runner/guardrails.js +174 -0
  40. package/dist/runner/guardrails.js.map +1 -0
  41. package/dist/runner/guardrails.mjs +166 -0
  42. package/dist/runner/guardrails.mjs.map +1 -0
  43. package/dist/runner/items.d.ts +18 -0
  44. package/dist/runner/items.js +89 -0
  45. package/dist/runner/items.js.map +1 -0
  46. package/dist/runner/items.mjs +79 -0
  47. package/dist/runner/items.mjs.map +1 -0
  48. package/dist/runner/mcpApprovals.d.ts +25 -0
  49. package/dist/runner/mcpApprovals.js +66 -0
  50. package/dist/runner/mcpApprovals.js.map +1 -0
  51. package/dist/runner/mcpApprovals.mjs +63 -0
  52. package/dist/runner/mcpApprovals.mjs.map +1 -0
  53. package/dist/runner/modelOutputs.d.ts +10 -0
  54. package/dist/runner/modelOutputs.js +206 -0
  55. package/dist/runner/modelOutputs.js.map +1 -0
  56. package/dist/runner/modelOutputs.mjs +203 -0
  57. package/dist/runner/modelOutputs.mjs.map +1 -0
  58. package/dist/runner/modelPreparation.d.ts +8 -0
  59. package/dist/runner/modelPreparation.js +41 -0
  60. package/dist/runner/modelPreparation.js.map +1 -0
  61. package/dist/runner/modelPreparation.mjs +38 -0
  62. package/dist/runner/modelPreparation.mjs.map +1 -0
  63. package/dist/runner/modelSettings.d.ts +20 -0
  64. package/dist/runner/modelSettings.js +97 -0
  65. package/dist/runner/modelSettings.js.map +1 -0
  66. package/dist/runner/modelSettings.mjs +92 -0
  67. package/dist/runner/modelSettings.mjs.map +1 -0
  68. package/dist/runner/runLoop.d.ts +32 -0
  69. package/dist/runner/runLoop.js +62 -0
  70. package/dist/runner/runLoop.js.map +1 -0
  71. package/dist/runner/runLoop.mjs +57 -0
  72. package/dist/runner/runLoop.mjs.map +1 -0
  73. package/dist/runner/sessionPersistence.d.ts +26 -0
  74. package/dist/runner/sessionPersistence.js +441 -0
  75. package/dist/runner/sessionPersistence.js.map +1 -0
  76. package/dist/runner/sessionPersistence.mjs +431 -0
  77. package/dist/runner/sessionPersistence.mjs.map +1 -0
  78. package/dist/runner/steps.d.ts +48 -0
  79. package/dist/runner/steps.js +40 -0
  80. package/dist/runner/steps.js.map +1 -0
  81. package/dist/runner/steps.mjs +36 -0
  82. package/dist/runner/steps.mjs.map +1 -0
  83. package/dist/runner/streaming.d.ts +9 -0
  84. package/dist/runner/streaming.js +74 -0
  85. package/dist/runner/streaming.js.map +1 -0
  86. package/dist/runner/streaming.mjs +65 -0
  87. package/dist/runner/streaming.mjs.map +1 -0
  88. package/dist/runner/toolExecution.d.ts +15 -0
  89. package/dist/runner/toolExecution.js +997 -0
  90. package/dist/runner/toolExecution.js.map +1 -0
  91. package/dist/runner/toolExecution.mjs +984 -0
  92. package/dist/runner/toolExecution.mjs.map +1 -0
  93. package/dist/runner/toolUseTracker.d.ts +9 -0
  94. package/dist/runner/toolUseTracker.js +34 -0
  95. package/dist/runner/toolUseTracker.js.map +1 -0
  96. package/dist/runner/toolUseTracker.mjs +30 -0
  97. package/dist/runner/toolUseTracker.mjs.map +1 -0
  98. package/dist/runner/tracing.d.ts +23 -0
  99. package/dist/runner/tracing.js +45 -0
  100. package/dist/runner/tracing.js.map +1 -0
  101. package/dist/runner/tracing.mjs +41 -0
  102. package/dist/runner/tracing.mjs.map +1 -0
  103. package/dist/runner/turnPreparation.d.ts +30 -0
  104. package/dist/runner/turnPreparation.js +80 -0
  105. package/dist/runner/turnPreparation.js.map +1 -0
  106. package/dist/runner/turnPreparation.mjs +74 -0
  107. package/dist/runner/turnPreparation.mjs.map +1 -0
  108. package/dist/runner/turnResolution.d.ts +3 -0
  109. package/dist/runner/turnResolution.js +531 -0
  110. package/dist/runner/turnResolution.js.map +1 -0
  111. package/dist/runner/turnResolution.mjs +526 -0
  112. package/dist/runner/turnResolution.mjs.map +1 -0
  113. package/dist/runner/types.d.ts +66 -0
  114. package/dist/runner/types.js +3 -0
  115. package/dist/runner/types.js.map +1 -0
  116. package/dist/runner/types.mjs +2 -0
  117. package/dist/runner/types.mjs.map +1 -0
  118. package/dist/shims/mcp-server/node.js +76 -30
  119. package/dist/shims/mcp-server/node.js.map +1 -1
  120. package/dist/shims/mcp-server/node.mjs +76 -30
  121. package/dist/shims/mcp-server/node.mjs.map +1 -1
  122. package/dist/tool.d.ts +28 -2
  123. package/dist/tool.js +7 -1
  124. package/dist/tool.js.map +1 -1
  125. package/dist/tool.mjs +8 -2
  126. package/dist/tool.mjs.map +1 -1
  127. package/dist/toolGuardrail.d.ts +101 -0
  128. package/dist/toolGuardrail.js +58 -0
  129. package/dist/toolGuardrail.js.map +1 -0
  130. package/dist/toolGuardrail.mjs +51 -0
  131. package/dist/toolGuardrail.mjs.map +1 -0
  132. package/dist/tracing/config.d.ts +3 -0
  133. package/dist/tracing/config.js +3 -0
  134. package/dist/tracing/config.js.map +1 -0
  135. package/dist/tracing/config.mjs +2 -0
  136. package/dist/tracing/config.mjs.map +1 -0
  137. package/dist/tracing/context.d.ts +2 -0
  138. package/dist/tracing/context.js +95 -24
  139. package/dist/tracing/context.js.map +1 -1
  140. package/dist/tracing/context.mjs +95 -24
  141. package/dist/tracing/context.mjs.map +1 -1
  142. package/dist/tracing/createSpans.d.ts +11 -11
  143. package/dist/tracing/index.d.ts +2 -0
  144. package/dist/tracing/index.js.map +1 -1
  145. package/dist/tracing/index.mjs.map +1 -1
  146. package/dist/tracing/provider.js +54 -4
  147. package/dist/tracing/provider.js.map +1 -1
  148. package/dist/tracing/provider.mjs +54 -4
  149. package/dist/tracing/provider.mjs.map +1 -1
  150. package/dist/tracing/spans.d.ts +2 -0
  151. package/dist/tracing/spans.js +6 -0
  152. package/dist/tracing/spans.js.map +1 -1
  153. package/dist/tracing/spans.mjs +6 -0
  154. package/dist/tracing/spans.mjs.map +1 -1
  155. package/dist/tracing/traces.d.ts +11 -1
  156. package/dist/tracing/traces.js +15 -2
  157. package/dist/tracing/traces.js.map +1 -1
  158. package/dist/tracing/traces.mjs +15 -2
  159. package/dist/tracing/traces.mjs.map +1 -1
  160. package/dist/types/protocol.d.ts +11 -0
  161. package/dist/types/protocol.js +1 -0
  162. package/dist/types/protocol.js.map +1 -1
  163. package/dist/types/protocol.mjs +1 -0
  164. package/dist/types/protocol.mjs.map +1 -1
  165. package/dist/utils/binary.d.ts +6 -0
  166. package/dist/utils/binary.js +53 -0
  167. package/dist/utils/binary.js.map +1 -0
  168. package/dist/utils/binary.mjs +49 -0
  169. package/dist/utils/binary.mjs.map +1 -0
  170. package/dist/utils/toolGuardrails.d.ts +24 -0
  171. package/dist/utils/toolGuardrails.js +58 -0
  172. package/dist/utils/toolGuardrails.js.map +1 -0
  173. package/dist/utils/toolGuardrails.mjs +54 -0
  174. package/dist/utils/toolGuardrails.mjs.map +1 -0
  175. package/package.json +4 -3
  176. package/dist/runImplementation.d.ts +0 -161
  177. package/dist/runImplementation.js +0 -2054
  178. package/dist/runImplementation.js.map +0 -1
  179. package/dist/runImplementation.mjs +0 -2028
  180. package/dist/runImplementation.mjs.map +0 -1
@@ -1,2028 +0,0 @@
1
- import { consumeAgentToolRunResult, } from "./agent.mjs";
2
- import { ModelBehaviorError, ToolCallError, UserError } from "./errors.mjs";
3
- import { getTransferMessage } from "./handoff.mjs";
4
- import { RunHandoffCallItem, RunHandoffOutputItem, RunMessageOutputItem, RunReasoningItem, RunToolApprovalItem, RunToolCallItem, RunToolCallOutputItem, } from "./items.mjs";
5
- import logger from "./logger.mjs";
6
- import { resolveComputer, } from "./tool.mjs";
7
- import { getLastTextFromOutputMessage } from "./utils/messages.mjs";
8
- import { withFunctionSpan, withHandoffSpan } from "./tracing/createSpans.mjs";
9
- import { getSchemaAndParserFromInputType } from "./utils/tools.mjs";
10
- import { encodeUint8ArrayToBase64 } from "./utils/base64.mjs";
11
- import { isArrayBufferView, isNodeBuffer, isSerializedBufferSnapshot, toSmartString, } from "./utils/smartString.mjs";
12
- import { safeExecute } from "./utils/safeExecute.mjs";
13
- import { addErrorToCurrentSpan } from "./tracing/context.mjs";
14
- import { RunItemStreamEvent } from "./events.mjs";
15
- import { z } from 'zod';
16
- import { isZodObject } from "./utils/index.mjs";
17
- import { isOpenAIResponsesCompactionAwareSession, } from "./memory/session.mjs";
18
- import { Usage } from "./usage.mjs";
19
- function isApprovalItemLike(value) {
20
- if (!value || typeof value !== 'object') {
21
- return false;
22
- }
23
- if (!('rawItem' in value)) {
24
- return false;
25
- }
26
- const rawItem = value.rawItem;
27
- if (!rawItem || typeof rawItem !== 'object') {
28
- return false;
29
- }
30
- const itemType = rawItem.type;
31
- return itemType === 'function_call' || itemType === 'hosted_tool_call';
32
- }
33
- function getApprovalIdentity(approval) {
34
- const rawItem = approval.rawItem;
35
- if (!rawItem) {
36
- return undefined;
37
- }
38
- if (rawItem.type === 'function_call' && rawItem.callId) {
39
- return `function_call:${rawItem.callId}`;
40
- }
41
- if ('callId' in rawItem && rawItem.callId) {
42
- return `${rawItem.type}:${rawItem.callId}`;
43
- }
44
- const id = 'id' in rawItem ? rawItem.id : undefined;
45
- if (id) {
46
- return `${rawItem.type}:${id}`;
47
- }
48
- const providerData = typeof rawItem.providerData === 'object' && rawItem.providerData
49
- ? rawItem.providerData
50
- : undefined;
51
- if (providerData?.id) {
52
- return `${rawItem.type}:provider:${providerData.id}`;
53
- }
54
- const agentName = 'agent' in approval && approval.agent ? approval.agent.name : '';
55
- try {
56
- return `${agentName}:${rawItem.type}:${JSON.stringify(rawItem)}`;
57
- }
58
- catch {
59
- return `${agentName}:${rawItem.type}`;
60
- }
61
- }
62
- function formatFinalOutputTypeError(error) {
63
- // Surface structured output validation hints without echoing potentially large or sensitive payloads.
64
- try {
65
- if (error instanceof z.ZodError) {
66
- const issue = error.issues[0];
67
- if (issue) {
68
- const issuePathParts = Array.isArray(issue.path) ? issue.path : [];
69
- const issuePath = issuePathParts.length > 0
70
- ? issuePathParts.map((part) => String(part)).join('.')
71
- : '(root)';
72
- const message = truncateForDeveloper(issue.message ?? '');
73
- return `Invalid output type: final assistant output failed schema validation at "${issuePath}" (${message}).`;
74
- }
75
- return 'Invalid output type: final assistant output failed schema validation.';
76
- }
77
- if (error instanceof Error && error.message) {
78
- return `Invalid output type: ${truncateForDeveloper(error.message)}`;
79
- }
80
- }
81
- catch {
82
- // Swallow formatting errors so we can return a generic message below.
83
- }
84
- return 'Invalid output type: final assistant output did not match the expected schema.';
85
- }
86
- function truncateForDeveloper(message, maxLength = 160) {
87
- const trimmed = message.trim();
88
- if (!trimmed) {
89
- return 'Schema validation failed.';
90
- }
91
- if (trimmed.length <= maxLength) {
92
- return trimmed;
93
- }
94
- return `${trimmed.slice(0, maxLength - 3)}...`;
95
- }
96
- /**
97
- * @internal
98
- * Walks a raw model response and classifies each item so the runner can schedule follow-up work.
99
- * Returns both the serializable RunItems (for history/streaming) and the actionable tool metadata.
100
- */
101
- export function processModelResponse(modelResponse, agent, tools, handoffs) {
102
- const items = [];
103
- const runHandoffs = [];
104
- const runFunctions = [];
105
- const runComputerActions = [];
106
- const runShellActions = [];
107
- const runApplyPatchActions = [];
108
- const runMCPApprovalRequests = [];
109
- const toolsUsed = [];
110
- const handoffMap = new Map(handoffs.map((h) => [h.toolName, h]));
111
- // Resolve tools upfront so we can look up the concrete handler in O(1) while iterating outputs.
112
- const functionMap = new Map(tools.filter((t) => t.type === 'function').map((t) => [t.name, t]));
113
- const computerTool = tools.find((t) => t.type === 'computer');
114
- const shellTool = tools.find((t) => t.type === 'shell');
115
- const applyPatchTool = tools.find((t) => t.type === 'apply_patch');
116
- const mcpToolMap = new Map(tools
117
- .filter((t) => t.type === 'hosted_tool' && t.providerData?.type === 'mcp')
118
- .map((t) => t)
119
- .map((t) => [t.providerData.server_label, t]));
120
- for (const output of modelResponse.output) {
121
- if (output.type === 'message') {
122
- if (output.role === 'assistant') {
123
- items.push(new RunMessageOutputItem(output, agent));
124
- }
125
- }
126
- else if (output.type === 'hosted_tool_call') {
127
- items.push(new RunToolCallItem(output, agent));
128
- const toolName = output.name;
129
- toolsUsed.push(toolName);
130
- if (output.providerData?.type === 'mcp_approval_request' ||
131
- output.name === 'mcp_approval_request') {
132
- // Hosted remote MCP server's approval process
133
- const providerData = output.providerData;
134
- const mcpServerLabel = providerData.server_label;
135
- const mcpServerTool = mcpToolMap.get(mcpServerLabel);
136
- if (typeof mcpServerTool === 'undefined') {
137
- const message = `MCP server (${mcpServerLabel}) not found in Agent (${agent.name})`;
138
- addErrorToCurrentSpan({
139
- message,
140
- data: { mcp_server_label: mcpServerLabel },
141
- });
142
- throw new ModelBehaviorError(message);
143
- }
144
- // Do this approval later:
145
- // We support both onApproval callback (like the Python SDK does) and HITL patterns.
146
- const approvalItem = new RunToolApprovalItem({
147
- type: 'hosted_tool_call',
148
- // We must use this name to align with the name sent from the servers
149
- name: providerData.name,
150
- id: providerData.id,
151
- status: 'in_progress',
152
- providerData,
153
- }, agent);
154
- runMCPApprovalRequests.push({
155
- requestItem: approvalItem,
156
- mcpTool: mcpServerTool,
157
- });
158
- if (!mcpServerTool.providerData.on_approval) {
159
- // When onApproval function exists, it confirms the approval right after this.
160
- // Thus, this approval item must be appended only for the next turn interruption patterns.
161
- items.push(approvalItem);
162
- }
163
- }
164
- }
165
- else if (output.type === 'reasoning') {
166
- items.push(new RunReasoningItem(output, agent));
167
- }
168
- else if (output.type === 'computer_call') {
169
- items.push(new RunToolCallItem(output, agent));
170
- toolsUsed.push('computer_use');
171
- if (!computerTool) {
172
- addErrorToCurrentSpan({
173
- message: 'Model produced computer action without a computer tool.',
174
- data: {
175
- agent_name: agent.name,
176
- },
177
- });
178
- throw new ModelBehaviorError('Model produced computer action without a computer tool.');
179
- }
180
- runComputerActions.push({
181
- toolCall: output,
182
- computer: computerTool,
183
- });
184
- }
185
- else if (output.type === 'shell_call') {
186
- items.push(new RunToolCallItem(output, agent));
187
- toolsUsed.push('shell');
188
- if (!shellTool) {
189
- addErrorToCurrentSpan({
190
- message: 'Model produced shell action without a shell tool.',
191
- data: {
192
- agent_name: agent.name,
193
- },
194
- });
195
- throw new ModelBehaviorError('Model produced shell action without a shell tool.');
196
- }
197
- runShellActions.push({
198
- toolCall: output,
199
- shell: shellTool,
200
- });
201
- }
202
- else if (output.type === 'apply_patch_call') {
203
- items.push(new RunToolCallItem(output, agent));
204
- toolsUsed.push('apply_patch');
205
- if (!applyPatchTool) {
206
- addErrorToCurrentSpan({
207
- message: 'Model produced apply_patch action without an apply_patch tool.',
208
- data: {
209
- agent_name: agent.name,
210
- },
211
- });
212
- throw new ModelBehaviorError('Model produced apply_patch action without an apply_patch tool.');
213
- }
214
- runApplyPatchActions.push({
215
- toolCall: output,
216
- applyPatch: applyPatchTool,
217
- });
218
- }
219
- if (output.type !== 'function_call') {
220
- continue;
221
- }
222
- toolsUsed.push(output.name);
223
- const handoff = handoffMap.get(output.name);
224
- if (handoff) {
225
- items.push(new RunHandoffCallItem(output, agent));
226
- runHandoffs.push({
227
- toolCall: output,
228
- handoff: handoff,
229
- });
230
- }
231
- else {
232
- const functionTool = functionMap.get(output.name);
233
- if (!functionTool) {
234
- addErrorToCurrentSpan({
235
- message: `Tool ${output.name} not found in agent ${agent.name}.`,
236
- data: {
237
- tool_name: output.name,
238
- agent_name: agent.name,
239
- },
240
- });
241
- throw new ModelBehaviorError(`Tool ${output.name} not found in agent ${agent.name}.`);
242
- }
243
- items.push(new RunToolCallItem(output, agent));
244
- runFunctions.push({
245
- toolCall: output,
246
- tool: functionTool,
247
- });
248
- }
249
- }
250
- return {
251
- newItems: items,
252
- handoffs: runHandoffs,
253
- functions: runFunctions,
254
- computerActions: runComputerActions,
255
- shellActions: runShellActions,
256
- applyPatchActions: runApplyPatchActions,
257
- mcpApprovalRequests: runMCPApprovalRequests,
258
- toolsUsed: toolsUsed,
259
- hasToolsOrApprovalsToRun() {
260
- return (runHandoffs.length > 0 ||
261
- runFunctions.length > 0 ||
262
- runMCPApprovalRequests.length > 0 ||
263
- runComputerActions.length > 0 ||
264
- runShellActions.length > 0 ||
265
- runApplyPatchActions.length > 0);
266
- },
267
- };
268
- }
269
- export const nextStepSchema = z.discriminatedUnion('type', [
270
- z.object({
271
- type: z.literal('next_step_handoff'),
272
- newAgent: z.any(),
273
- }),
274
- z.object({
275
- type: z.literal('next_step_final_output'),
276
- output: z.string(),
277
- }),
278
- z.object({
279
- type: z.literal('next_step_run_again'),
280
- }),
281
- z.object({
282
- type: z.literal('next_step_interruption'),
283
- data: z.record(z.string(), z.any()),
284
- }),
285
- ]);
286
- /**
287
- * Internal convenience wrapper that groups the outcome of a single agent turn. It lets the caller
288
- * update the RunState in one shot and decide which step to execute next.
289
- */
290
- class SingleStepResult {
291
- originalInput;
292
- modelResponse;
293
- preStepItems;
294
- newStepItems;
295
- nextStep;
296
- constructor(
297
- /**
298
- * The input items (i.e., the items before run() was called). May be mutated by handoff input filters.
299
- */
300
- originalInput,
301
- /**
302
- * The model response for the current step
303
- */
304
- modelResponse,
305
- /**
306
- * The items before the current step was executed
307
- */
308
- preStepItems,
309
- /**
310
- * The items after the current step was executed
311
- */
312
- newStepItems,
313
- /**
314
- * The next step to execute
315
- */
316
- nextStep) {
317
- this.originalInput = originalInput;
318
- this.modelResponse = modelResponse;
319
- this.preStepItems = preStepItems;
320
- this.newStepItems = newStepItems;
321
- this.nextStep = nextStep;
322
- }
323
- /**
324
- * The items generated during the agent run (i.e. everything generated after originalInput)
325
- */
326
- get generatedItems() {
327
- return this.preStepItems.concat(this.newStepItems);
328
- }
329
- }
330
- /**
331
- * @internal
332
- * Resets the tool choice when the agent is configured to prefer a fresh tool selection after
333
- * any tool usage. This prevents the provider from reusing stale tool hints across turns.
334
- */
335
- export function maybeResetToolChoice(agent, toolUseTracker, modelSettings) {
336
- if (agent.resetToolChoice && toolUseTracker.hasUsedTools(agent)) {
337
- return { ...modelSettings, toolChoice: undefined };
338
- }
339
- return modelSettings;
340
- }
341
- /**
342
- * @internal
343
- * Continues a turn that was previously interrupted waiting for tool approval. Executes the now
344
- * approved tools and returns the resulting step transition.
345
- */
346
- export async function resolveInterruptedTurn(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
347
- // call_ids for function tools
348
- const functionCallIds = originalPreStepItems
349
- .filter((item) => item instanceof RunToolApprovalItem &&
350
- 'callId' in item.rawItem &&
351
- item.rawItem.type === 'function_call')
352
- .map((item) => item.rawItem.callId);
353
- // We already persisted the turn once when the approval interrupt was raised, so the
354
- // counter reflects the approval items as "flushed". When we resume the same turn we need
355
- // to rewind it so the eventual tool output for this call is still written to the session.
356
- const pendingApprovalItems = state
357
- .getInterruptions()
358
- .filter(isApprovalItemLike);
359
- if (pendingApprovalItems.length > 0) {
360
- const pendingApprovalIdentities = new Set();
361
- for (const approval of pendingApprovalItems) {
362
- const identity = getApprovalIdentity(approval);
363
- if (identity) {
364
- pendingApprovalIdentities.add(identity);
365
- }
366
- }
367
- if (pendingApprovalIdentities.size > 0) {
368
- let rewindCount = 0;
369
- for (let index = originalPreStepItems.length - 1; index >= 0; index--) {
370
- const item = originalPreStepItems[index];
371
- if (!(item instanceof RunToolApprovalItem)) {
372
- continue;
373
- }
374
- const identity = getApprovalIdentity(item);
375
- if (!identity) {
376
- continue;
377
- }
378
- if (!pendingApprovalIdentities.has(identity)) {
379
- continue;
380
- }
381
- rewindCount++;
382
- pendingApprovalIdentities.delete(identity);
383
- if (pendingApprovalIdentities.size === 0) {
384
- break;
385
- }
386
- }
387
- // Persisting the approval request already advanced the counter once, so undo the increment
388
- // to make sure we write the final tool output back to the session when the turn resumes.
389
- if (rewindCount > 0) {
390
- state._currentTurnPersistedItemCount = Math.max(0, state._currentTurnPersistedItemCount - rewindCount);
391
- }
392
- }
393
- }
394
- // Run function tools that require approval after they get their approval results
395
- const functionToolRuns = processedResponse.functions.filter((run) => {
396
- return functionCallIds.includes(run.toolCall.callId);
397
- });
398
- const functionResults = await executeFunctionToolCalls(agent, functionToolRuns, runner, state);
399
- // There is no built-in HITL approval surface for computer tools today, so every pending action
400
- // is executed immediately when the turn resumes.
401
- const computerResults = processedResponse.computerActions.length > 0
402
- ? await executeComputerActions(agent, processedResponse.computerActions, runner, state._context)
403
- : [];
404
- // When resuming we receive the original RunItem references; suppress duplicates so history and streaming do not double-emit the same items.
405
- const originalPreStepItemSet = new Set(originalPreStepItems);
406
- const newItems = [];
407
- const newItemsSet = new Set();
408
- const appendIfNew = (item) => {
409
- if (originalPreStepItemSet.has(item) || newItemsSet.has(item)) {
410
- return;
411
- }
412
- newItems.push(item);
413
- newItemsSet.add(item);
414
- };
415
- for (const result of functionResults) {
416
- appendIfNew(result.runItem);
417
- }
418
- for (const result of computerResults) {
419
- appendIfNew(result);
420
- }
421
- // Run MCP tools that require approval after they get their approval results
422
- const mcpApprovalRuns = processedResponse.mcpApprovalRequests.filter((run) => {
423
- return (run.requestItem.type === 'tool_approval_item' &&
424
- run.requestItem.rawItem.type === 'hosted_tool_call' &&
425
- run.requestItem.rawItem.providerData?.type === 'mcp_approval_request');
426
- });
427
- // Hosted MCP approvals may still be waiting on a human decision when the turn resumes.
428
- const pendingHostedMCPApprovals = new Set();
429
- const pendingHostedMCPApprovalIds = new Set();
430
- // Keep track of approvals we still need to surface next turn so HITL flows can resume cleanly.
431
- for (const run of mcpApprovalRuns) {
432
- // the approval_request_id "mcpr_123..."
433
- const rawItem = run.requestItem.rawItem;
434
- if (rawItem.type !== 'hosted_tool_call') {
435
- continue;
436
- }
437
- const approvalRequestId = rawItem.id;
438
- const approved = state._context.isToolApproved({
439
- // Since this item name must be the same with the one sent from Responses API server
440
- toolName: rawItem.name,
441
- callId: approvalRequestId,
442
- });
443
- if (typeof approved !== 'undefined') {
444
- const providerData = {
445
- approve: approved,
446
- approval_request_id: approvalRequestId,
447
- reason: undefined,
448
- };
449
- // Tell Responses API server the approval result in the next turn
450
- const responseItem = new RunToolCallItem({
451
- type: 'hosted_tool_call',
452
- name: 'mcp_approval_response',
453
- providerData,
454
- }, agent);
455
- appendIfNew(responseItem);
456
- }
457
- else {
458
- pendingHostedMCPApprovals.add(run.requestItem);
459
- pendingHostedMCPApprovalIds.add(approvalRequestId);
460
- functionResults.push({
461
- type: 'hosted_mcp_tool_approval',
462
- tool: run.mcpTool,
463
- runItem: run.requestItem,
464
- });
465
- appendIfNew(run.requestItem);
466
- }
467
- }
468
- // Server-managed conversations rely on preStepItems to re-surface pending approvals.
469
- // Keep unresolved hosted MCP approvals in place so HITL flows still have something to approve next turn.
470
- // Drop resolved approval placeholders so they are not replayed on the next turn, but keep
471
- // pending approvals in place to signal the outstanding work to the UI and session store.
472
- const preStepItems = originalPreStepItems.filter((item) => {
473
- if (!(item instanceof RunToolApprovalItem)) {
474
- return true;
475
- }
476
- if (item.rawItem.type === 'hosted_tool_call' &&
477
- item.rawItem.providerData?.type === 'mcp_approval_request') {
478
- if (pendingHostedMCPApprovals.has(item)) {
479
- return true;
480
- }
481
- const approvalRequestId = item.rawItem.id;
482
- if (approvalRequestId) {
483
- return pendingHostedMCPApprovalIds.has(approvalRequestId);
484
- }
485
- return false;
486
- }
487
- return false;
488
- });
489
- const completedStep = await maybeCompleteTurnFromToolResults({
490
- agent,
491
- runner,
492
- state,
493
- functionResults,
494
- originalInput,
495
- newResponse,
496
- preStepItems,
497
- newItems,
498
- });
499
- if (completedStep) {
500
- return completedStep;
501
- }
502
- // we only ran new tools and side effects. We need to run the rest of the agent
503
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
504
- }
505
- /**
506
- * @internal
507
- * Executes every follow-up action the model requested (function tools, computer actions, MCP flows),
508
- * appends their outputs to the run history, and determines the next step for the agent loop.
509
- */
510
- export async function resolveTurnAfterModelResponse(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
511
- // Reuse the same array reference so we can compare object identity when deciding whether to
512
- // append new items, ensuring we never double-stream existing RunItems.
513
- const preStepItems = originalPreStepItems;
514
- const seenItems = new Set(originalPreStepItems);
515
- const newItems = [];
516
- const appendIfNew = (item) => {
517
- if (seenItems.has(item)) {
518
- return;
519
- }
520
- newItems.push(item);
521
- seenItems.add(item);
522
- };
523
- for (const item of processedResponse.newItems) {
524
- appendIfNew(item);
525
- }
526
- // Run function tools and computer actions in parallel; neither depends on the other's side effects.
527
- const [functionResults, computerResults, shellResults, applyPatchResults] = await Promise.all([
528
- executeFunctionToolCalls(agent, processedResponse.functions, runner, state),
529
- executeComputerActions(agent, processedResponse.computerActions, runner, state._context),
530
- executeShellActions(agent, processedResponse.shellActions, runner, state._context),
531
- executeApplyPatchOperations(agent, processedResponse.applyPatchActions, runner, state._context),
532
- ]);
533
- for (const result of functionResults) {
534
- appendIfNew(result.runItem);
535
- }
536
- for (const item of computerResults) {
537
- appendIfNew(item);
538
- }
539
- for (const item of shellResults) {
540
- appendIfNew(item);
541
- }
542
- for (const item of applyPatchResults) {
543
- appendIfNew(item);
544
- }
545
- // run hosted MCP approval requests
546
- if (processedResponse.mcpApprovalRequests.length > 0) {
547
- for (const approvalRequest of processedResponse.mcpApprovalRequests) {
548
- const toolData = approvalRequest.mcpTool
549
- .providerData;
550
- const requestData = approvalRequest.requestItem.rawItem
551
- .providerData;
552
- if (toolData.on_approval) {
553
- // synchronously handle the approval process here
554
- const approvalResult = await toolData.on_approval(state._context, approvalRequest.requestItem);
555
- const approvalResponseData = {
556
- approve: approvalResult.approve,
557
- approval_request_id: requestData.id,
558
- reason: approvalResult.reason,
559
- };
560
- newItems.push(new RunToolCallItem({
561
- type: 'hosted_tool_call',
562
- name: 'mcp_approval_response',
563
- providerData: approvalResponseData,
564
- }, agent));
565
- }
566
- else {
567
- // receive a user's approval on the next turn
568
- newItems.push(approvalRequest.requestItem);
569
- const approvalItem = {
570
- type: 'hosted_mcp_tool_approval',
571
- tool: approvalRequest.mcpTool,
572
- runItem: new RunToolApprovalItem({
573
- type: 'hosted_tool_call',
574
- name: requestData.name,
575
- id: requestData.id,
576
- arguments: requestData.arguments,
577
- status: 'in_progress',
578
- providerData: requestData,
579
- }, agent),
580
- };
581
- functionResults.push(approvalItem);
582
- // newItems.push(approvalItem.runItem);
583
- }
584
- }
585
- }
586
- // process handoffs
587
- if (processedResponse.handoffs.length > 0) {
588
- return await executeHandoffCalls(agent, originalInput, preStepItems, newItems, newResponse, processedResponse.handoffs, runner, state._context);
589
- }
590
- const completedStep = await maybeCompleteTurnFromToolResults({
591
- agent,
592
- runner,
593
- state,
594
- functionResults,
595
- originalInput,
596
- newResponse,
597
- preStepItems,
598
- newItems,
599
- });
600
- if (completedStep) {
601
- return completedStep;
602
- }
603
- // If the model issued any tool calls or handoffs in this turn,
604
- // we must NOT treat any assistant message in the same turn as the final output.
605
- // We should run the loop again so the model can see the tool results and respond.
606
- const hadToolCallsOrActions = (processedResponse.functions?.length ?? 0) > 0 ||
607
- (processedResponse.computerActions?.length ?? 0) > 0 ||
608
- (processedResponse.shellActions?.length ?? 0) > 0 ||
609
- (processedResponse.applyPatchActions?.length ?? 0) > 0 ||
610
- (processedResponse.mcpApprovalRequests?.length ?? 0) > 0 ||
611
- (processedResponse.handoffs?.length ?? 0) > 0;
612
- if (hadToolCallsOrActions) {
613
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
614
- }
615
- // No tool calls/actions in this turn; safe to consider a plain assistant message as final.
616
- const messageItems = newItems.filter((item) => item instanceof RunMessageOutputItem);
617
- // we will use the last content output as the final output
618
- const potentialFinalOutput = messageItems.length > 0
619
- ? getLastTextFromOutputMessage(messageItems[messageItems.length - 1].rawItem)
620
- : undefined;
621
- // if there is no output we just run again
622
- if (typeof potentialFinalOutput === 'undefined') {
623
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
624
- }
625
- // Keep looping if any tool output placeholders still require an approval follow-up.
626
- const hasPendingToolsOrApprovals = functionResults.some((result) => result.runItem instanceof RunToolApprovalItem);
627
- if (!hasPendingToolsOrApprovals) {
628
- if (agent.outputType === 'text') {
629
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
630
- type: 'next_step_final_output',
631
- output: potentialFinalOutput,
632
- });
633
- }
634
- if (agent.outputType !== 'text' && potentialFinalOutput) {
635
- // Structured output schema => always leads to a final output if we have text.
636
- const { parser } = getSchemaAndParserFromInputType(agent.outputType, 'final_output');
637
- const [error] = await safeExecute(() => parser(potentialFinalOutput));
638
- if (error) {
639
- const outputErrorMessage = formatFinalOutputTypeError(error);
640
- addErrorToCurrentSpan({
641
- message: outputErrorMessage,
642
- data: {
643
- error: String(error),
644
- },
645
- });
646
- throw new ModelBehaviorError(outputErrorMessage);
647
- }
648
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_final_output', output: potentialFinalOutput });
649
- }
650
- }
651
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
652
- }
653
- // Consolidates the logic that determines whether tool results yielded a final answer,
654
- // triggered an interruption, or require the agent loop to continue running.
655
- async function maybeCompleteTurnFromToolResults({ agent, runner, state, functionResults, originalInput, newResponse, preStepItems, newItems, }) {
656
- const toolOutcome = await checkForFinalOutputFromTools(agent, functionResults, state);
657
- if (toolOutcome.isFinalOutput) {
658
- runner.emit('agent_end', state._context, agent, toolOutcome.finalOutput);
659
- agent.emit('agent_end', state._context, toolOutcome.finalOutput);
660
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
661
- type: 'next_step_final_output',
662
- output: toolOutcome.finalOutput,
663
- });
664
- }
665
- if (toolOutcome.isInterrupted) {
666
- return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
667
- type: 'next_step_interruption',
668
- data: {
669
- interruptions: toolOutcome.interruptions,
670
- },
671
- });
672
- }
673
- return null;
674
- }
675
- /**
676
- * @internal
677
- * Normalizes tool outputs once so downstream code works with fully structured protocol items.
678
- * Doing this here keeps API surface stable even when providers add new shapes.
679
- */
680
- export function getToolCallOutputItem(toolCall, output) {
681
- const maybeStructuredOutputs = normalizeStructuredToolOutputs(output);
682
- if (maybeStructuredOutputs) {
683
- const structuredItems = maybeStructuredOutputs.map(convertStructuredToolOutputToInputItem);
684
- return {
685
- type: 'function_call_result',
686
- name: toolCall.name,
687
- callId: toolCall.callId,
688
- status: 'completed',
689
- output: structuredItems,
690
- };
691
- }
692
- return {
693
- type: 'function_call_result',
694
- name: toolCall.name,
695
- callId: toolCall.callId,
696
- status: 'completed',
697
- output: {
698
- type: 'text',
699
- text: toSmartString(output),
700
- },
701
- };
702
- }
703
- function normalizeFileValue(value) {
704
- const directFile = value.file;
705
- if (typeof directFile === 'string' && directFile.length > 0) {
706
- return directFile;
707
- }
708
- const normalizedObject = normalizeFileObjectCandidate(directFile);
709
- if (normalizedObject) {
710
- return normalizedObject;
711
- }
712
- const legacyValue = normalizeLegacyFileValue(value);
713
- if (legacyValue) {
714
- return legacyValue;
715
- }
716
- return null;
717
- }
718
- function normalizeFileObjectCandidate(value) {
719
- if (!isRecord(value)) {
720
- return null;
721
- }
722
- if ('data' in value && value.data !== undefined) {
723
- const dataValue = value.data;
724
- const hasStringData = typeof dataValue === 'string' && dataValue.length > 0;
725
- const hasBinaryData = dataValue instanceof Uint8Array && dataValue.length > 0;
726
- if (!hasStringData && !hasBinaryData) {
727
- return null;
728
- }
729
- if (!isNonEmptyString(value.mediaType) ||
730
- !isNonEmptyString(value.filename)) {
731
- return null;
732
- }
733
- return {
734
- data: typeof dataValue === 'string' ? dataValue : new Uint8Array(dataValue),
735
- mediaType: value.mediaType,
736
- filename: value.filename,
737
- };
738
- }
739
- if (isNonEmptyString(value.url)) {
740
- const result = { url: value.url };
741
- if (isNonEmptyString(value.filename)) {
742
- result.filename = value.filename;
743
- }
744
- return result;
745
- }
746
- const referencedId = (isNonEmptyString(value.id) && value.id) ||
747
- (isNonEmptyString(value.fileId) && value.fileId);
748
- if (referencedId) {
749
- const result = { id: referencedId };
750
- if (isNonEmptyString(value.filename)) {
751
- result.filename = value.filename;
752
- }
753
- return result;
754
- }
755
- return null;
756
- }
757
- function normalizeLegacyFileValue(value) {
758
- const filename = typeof value.filename === 'string' && value.filename.length > 0
759
- ? value.filename
760
- : undefined;
761
- const mediaType = typeof value.mediaType === 'string' && value.mediaType.length > 0
762
- ? value.mediaType
763
- : undefined;
764
- if (typeof value.fileData === 'string' && value.fileData.length > 0) {
765
- if (!mediaType || !filename) {
766
- return null;
767
- }
768
- return { data: value.fileData, mediaType, filename };
769
- }
770
- if (value.fileData instanceof Uint8Array && value.fileData.length > 0) {
771
- if (!mediaType || !filename) {
772
- return null;
773
- }
774
- return { data: new Uint8Array(value.fileData), mediaType, filename };
775
- }
776
- if (typeof value.fileUrl === 'string' && value.fileUrl.length > 0) {
777
- const result = { url: value.fileUrl };
778
- if (filename) {
779
- result.filename = filename;
780
- }
781
- return result;
782
- }
783
- if (typeof value.fileId === 'string' && value.fileId.length > 0) {
784
- const result = { id: value.fileId };
785
- if (filename) {
786
- result.filename = filename;
787
- }
788
- return result;
789
- }
790
- return null;
791
- }
792
- function isRecord(value) {
793
- return typeof value === 'object' && value !== null;
794
- }
795
- function isNonEmptyString(value) {
796
- return typeof value === 'string' && value.length > 0;
797
- }
798
- function toInlineImageString(data, mediaType) {
799
- if (typeof data === 'string') {
800
- if (mediaType && !data.startsWith('data:')) {
801
- return asDataUrl(data, mediaType);
802
- }
803
- return data;
804
- }
805
- const base64 = encodeUint8ArrayToBase64(data);
806
- return asDataUrl(base64, mediaType);
807
- }
808
- function asDataUrl(base64, mediaType) {
809
- return mediaType ? `data:${mediaType};base64,${base64}` : base64;
810
- }
811
- /**
812
- * @internal
813
- * Runs every function tool call requested by the model and returns their outputs alongside
814
- * the `RunItem` instances that should be appended to history.
815
- */
816
- export async function executeFunctionToolCalls(agent, toolRuns, runner, state) {
817
- async function runSingleTool(toolRun) {
818
- let parsedArgs = toolRun.toolCall.arguments;
819
- if (toolRun.tool.parameters) {
820
- if (isZodObject(toolRun.tool.parameters)) {
821
- parsedArgs = toolRun.tool.parameters.parse(parsedArgs);
822
- }
823
- else {
824
- parsedArgs = JSON.parse(parsedArgs);
825
- }
826
- }
827
- // Some tools require a human or policy check before execution; defer until approval is recorded.
828
- const needsApproval = await toolRun.tool.needsApproval(state._context, parsedArgs, toolRun.toolCall.callId);
829
- if (needsApproval) {
830
- const approval = state._context.isToolApproved({
831
- toolName: toolRun.tool.name,
832
- callId: toolRun.toolCall.callId,
833
- });
834
- if (approval === false) {
835
- // rejected
836
- return withFunctionSpan(async (span) => {
837
- const response = 'Tool execution was not approved.';
838
- span.setError({
839
- message: response,
840
- data: {
841
- tool_name: toolRun.tool.name,
842
- error: `Tool execution for ${toolRun.toolCall.callId} was manually rejected by user.`,
843
- },
844
- });
845
- span.spanData.output = response;
846
- return {
847
- type: 'function_output',
848
- tool: toolRun.tool,
849
- output: response,
850
- runItem: new RunToolCallOutputItem(getToolCallOutputItem(toolRun.toolCall, response), agent, response),
851
- };
852
- }, {
853
- data: {
854
- name: toolRun.tool.name,
855
- },
856
- });
857
- }
858
- if (approval !== true) {
859
- // this approval process needs to be done in the next turn
860
- return {
861
- type: 'function_approval',
862
- tool: toolRun.tool,
863
- runItem: new RunToolApprovalItem(toolRun.toolCall, agent),
864
- };
865
- }
866
- }
867
- return withFunctionSpan(async (span) => {
868
- if (runner.config.traceIncludeSensitiveData) {
869
- span.spanData.input = toolRun.toolCall.arguments;
870
- }
871
- try {
872
- runner.emit('agent_tool_start', state._context, agent, toolRun.tool, {
873
- toolCall: toolRun.toolCall,
874
- });
875
- agent.emit('agent_tool_start', state._context, toolRun.tool, {
876
- toolCall: toolRun.toolCall,
877
- });
878
- const toolOutput = await toolRun.tool.invoke(state._context, toolRun.toolCall.arguments, { toolCall: toolRun.toolCall });
879
- // Use string data for tracing and event emitter
880
- const stringResult = toSmartString(toolOutput);
881
- runner.emit('agent_tool_end', state._context, agent, toolRun.tool, stringResult, { toolCall: toolRun.toolCall });
882
- agent.emit('agent_tool_end', state._context, toolRun.tool, stringResult, { toolCall: toolRun.toolCall });
883
- if (runner.config.traceIncludeSensitiveData) {
884
- span.spanData.output = stringResult;
885
- }
886
- const functionResult = {
887
- type: 'function_output',
888
- tool: toolRun.tool,
889
- output: toolOutput,
890
- runItem: new RunToolCallOutputItem(getToolCallOutputItem(toolRun.toolCall, toolOutput), agent, toolOutput),
891
- };
892
- const nestedRunResult = consumeAgentToolRunResult(toolRun.toolCall);
893
- if (nestedRunResult) {
894
- functionResult.agentRunResult = nestedRunResult;
895
- const nestedInterruptions = nestedRunResult.interruptions;
896
- if (nestedInterruptions.length > 0) {
897
- functionResult.interruptions = nestedInterruptions;
898
- }
899
- }
900
- return functionResult;
901
- }
902
- catch (error) {
903
- span.setError({
904
- message: 'Error running tool',
905
- data: {
906
- tool_name: toolRun.tool.name,
907
- error: String(error),
908
- },
909
- });
910
- // Emit agent_tool_end even on error to maintain consistent event lifecycle
911
- const errorResult = String(error);
912
- runner.emit('agent_tool_end', state._context, agent, toolRun.tool, errorResult, {
913
- toolCall: toolRun.toolCall,
914
- });
915
- agent.emit('agent_tool_end', state._context, toolRun.tool, errorResult, {
916
- toolCall: toolRun.toolCall,
917
- });
918
- throw error;
919
- }
920
- }, {
921
- data: {
922
- name: toolRun.tool.name,
923
- },
924
- });
925
- }
926
- try {
927
- const results = await Promise.all(toolRuns.map(runSingleTool));
928
- return results;
929
- }
930
- catch (e) {
931
- throw new ToolCallError(`Failed to run function tools: ${e}`, e, state);
932
- }
933
- }
934
- /**
935
- * @internal
936
- */
937
- // Internal helper: dispatch a computer action and return a screenshot (sync/async)
938
- async function _runComputerActionAndScreenshot(computer, toolCall) {
939
- const action = toolCall.action;
940
- let screenshot;
941
- // Dispatch based on action type string (assume action.type exists)
942
- switch (action.type) {
943
- case 'click':
944
- await computer.click(action.x, action.y, action.button);
945
- break;
946
- case 'double_click':
947
- await computer.doubleClick(action.x, action.y);
948
- break;
949
- case 'drag':
950
- await computer.drag(action.path.map((p) => [p.x, p.y]));
951
- break;
952
- case 'keypress':
953
- await computer.keypress(action.keys);
954
- break;
955
- case 'move':
956
- await computer.move(action.x, action.y);
957
- break;
958
- case 'screenshot':
959
- screenshot = await computer.screenshot();
960
- break;
961
- case 'scroll':
962
- await computer.scroll(action.x, action.y, action.scroll_x, action.scroll_y);
963
- break;
964
- case 'type':
965
- await computer.type(action.text);
966
- break;
967
- case 'wait':
968
- await computer.wait();
969
- break;
970
- default:
971
- action; // ensures that we handle every action we know of
972
- // Unknown action, just take screenshot
973
- break;
974
- }
975
- if (typeof screenshot !== 'undefined') {
976
- return screenshot;
977
- }
978
- // Always return screenshot as base64 string
979
- if (typeof computer.screenshot === 'function') {
980
- screenshot = await computer.screenshot();
981
- if (typeof screenshot !== 'undefined') {
982
- return screenshot;
983
- }
984
- }
985
- throw new Error('Computer does not implement screenshot()');
986
- }
987
- function toErrorMessage(error) {
988
- if (error instanceof Error) {
989
- return error.message || error.toString();
990
- }
991
- try {
992
- return JSON.stringify(error);
993
- }
994
- catch {
995
- return String(error);
996
- }
997
- }
998
- export async function executeShellActions(agent, actions, runner, runContext, customLogger = undefined) {
999
- const _logger = customLogger ?? logger;
1000
- const results = [];
1001
- for (const action of actions) {
1002
- const shellTool = action.shell;
1003
- const toolCall = action.toolCall;
1004
- const approvalItem = new RunToolApprovalItem(toolCall, agent, shellTool.name);
1005
- const requiresApproval = await shellTool.needsApproval(runContext, toolCall.action, toolCall.callId);
1006
- if (requiresApproval) {
1007
- if (shellTool.onApproval) {
1008
- const decision = await shellTool.onApproval(runContext, approvalItem);
1009
- if (decision.approve === true) {
1010
- runContext.approveTool(approvalItem);
1011
- }
1012
- else if (decision.approve === false) {
1013
- runContext.rejectTool(approvalItem);
1014
- }
1015
- }
1016
- const approval = runContext.isToolApproved({
1017
- toolName: shellTool.name,
1018
- callId: toolCall.callId,
1019
- });
1020
- if (approval === false) {
1021
- const response = 'Tool execution was not approved.';
1022
- const rejectionOutput = {
1023
- stdout: '',
1024
- stderr: response,
1025
- outcome: { type: 'exit', exitCode: null },
1026
- };
1027
- results.push(new RunToolCallOutputItem({
1028
- type: 'shell_call_output',
1029
- callId: toolCall.callId,
1030
- output: [rejectionOutput],
1031
- }, agent, response));
1032
- continue;
1033
- }
1034
- if (approval !== true) {
1035
- results.push(approvalItem);
1036
- continue;
1037
- }
1038
- }
1039
- runner.emit('agent_tool_start', runContext, agent, shellTool, {
1040
- toolCall,
1041
- });
1042
- if (typeof agent.emit === 'function') {
1043
- agent.emit('agent_tool_start', runContext, shellTool, { toolCall });
1044
- }
1045
- let shellOutputs;
1046
- const providerMeta = {};
1047
- let maxOutputLength;
1048
- try {
1049
- const shellResult = await shellTool.shell.run(toolCall.action);
1050
- shellOutputs = shellResult.output ?? [];
1051
- if (shellResult.providerData) {
1052
- Object.assign(providerMeta, shellResult.providerData);
1053
- }
1054
- if (typeof shellResult.maxOutputLength === 'number') {
1055
- maxOutputLength = shellResult.maxOutputLength;
1056
- }
1057
- }
1058
- catch (err) {
1059
- const errorText = toErrorMessage(err);
1060
- shellOutputs = [
1061
- {
1062
- stdout: '',
1063
- stderr: errorText,
1064
- outcome: { type: 'exit', exitCode: null },
1065
- },
1066
- ];
1067
- _logger.error('Failed to execute shell action:', err);
1068
- }
1069
- shellOutputs = shellOutputs ?? [];
1070
- runner.emit('agent_tool_end', runContext, agent, shellTool, JSON.stringify(shellOutputs), {
1071
- toolCall,
1072
- });
1073
- if (typeof agent.emit === 'function') {
1074
- agent.emit('agent_tool_end', runContext, shellTool, JSON.stringify(shellOutputs), {
1075
- toolCall,
1076
- });
1077
- }
1078
- const rawItem = {
1079
- type: 'shell_call_output',
1080
- callId: toolCall.callId,
1081
- output: shellOutputs ?? [],
1082
- };
1083
- if (typeof maxOutputLength === 'number') {
1084
- rawItem.maxOutputLength = maxOutputLength;
1085
- }
1086
- if (Object.keys(providerMeta).length > 0) {
1087
- rawItem.providerData = providerMeta;
1088
- }
1089
- results.push(new RunToolCallOutputItem(rawItem, agent, rawItem.output));
1090
- }
1091
- return results;
1092
- }
1093
- export async function executeApplyPatchOperations(agent, actions, runner, runContext, customLogger = undefined) {
1094
- const _logger = customLogger ?? logger;
1095
- const results = [];
1096
- for (const action of actions) {
1097
- const applyPatchTool = action.applyPatch;
1098
- const toolCall = action.toolCall;
1099
- const approvalItem = new RunToolApprovalItem(toolCall, agent, applyPatchTool.name);
1100
- const requiresApproval = await applyPatchTool.needsApproval(runContext, toolCall.operation, toolCall.callId);
1101
- if (requiresApproval) {
1102
- if (applyPatchTool.onApproval) {
1103
- const decision = await applyPatchTool.onApproval(runContext, approvalItem);
1104
- if (decision.approve === true) {
1105
- runContext.approveTool(approvalItem);
1106
- }
1107
- else if (decision.approve === false) {
1108
- runContext.rejectTool(approvalItem);
1109
- }
1110
- }
1111
- const approval = runContext.isToolApproved({
1112
- toolName: applyPatchTool.name,
1113
- callId: toolCall.callId,
1114
- });
1115
- if (approval === false) {
1116
- const response = 'Tool execution was not approved.';
1117
- results.push(new RunToolCallOutputItem({
1118
- type: 'apply_patch_call_output',
1119
- callId: toolCall.callId,
1120
- status: 'failed',
1121
- output: response,
1122
- }, agent, response));
1123
- continue;
1124
- }
1125
- if (approval !== true) {
1126
- results.push(approvalItem);
1127
- continue;
1128
- }
1129
- }
1130
- runner.emit('agent_tool_start', runContext, agent, applyPatchTool, {
1131
- toolCall,
1132
- });
1133
- if (typeof agent.emit === 'function') {
1134
- agent.emit('agent_tool_start', runContext, applyPatchTool, {
1135
- toolCall,
1136
- });
1137
- }
1138
- let status = 'completed';
1139
- let output = '';
1140
- try {
1141
- let result;
1142
- switch (toolCall.operation.type) {
1143
- case 'create_file':
1144
- result = await applyPatchTool.editor.createFile(toolCall.operation);
1145
- break;
1146
- case 'update_file':
1147
- result = await applyPatchTool.editor.updateFile(toolCall.operation);
1148
- break;
1149
- case 'delete_file':
1150
- result = await applyPatchTool.editor.deleteFile(toolCall.operation);
1151
- break;
1152
- default:
1153
- throw new Error('Unsupported apply_patch operation');
1154
- }
1155
- if (result && typeof result.status === 'string') {
1156
- status = result.status;
1157
- }
1158
- if (result && typeof result.output === 'string') {
1159
- output = result.output;
1160
- }
1161
- }
1162
- catch (err) {
1163
- status = 'failed';
1164
- output = toErrorMessage(err);
1165
- _logger.error('Failed to execute apply_patch operation:', err);
1166
- }
1167
- runner.emit('agent_tool_end', runContext, agent, applyPatchTool, output, {
1168
- toolCall,
1169
- });
1170
- if (typeof agent.emit === 'function') {
1171
- agent.emit('agent_tool_end', runContext, applyPatchTool, output, {
1172
- toolCall,
1173
- });
1174
- }
1175
- const rawItem = {
1176
- type: 'apply_patch_call_output',
1177
- callId: toolCall.callId,
1178
- status,
1179
- };
1180
- if (output) {
1181
- rawItem.output = output;
1182
- }
1183
- results.push(new RunToolCallOutputItem(rawItem, agent, output));
1184
- }
1185
- return results;
1186
- }
1187
- /**
1188
- * @internal
1189
- * Executes any computer-use actions emitted by the model and returns the resulting items so the
1190
- * run history reflects the computer session.
1191
- */
1192
- export async function executeComputerActions(agent, actions, runner, runContext, customLogger = undefined) {
1193
- const _logger = customLogger ?? logger;
1194
- const results = [];
1195
- for (const action of actions) {
1196
- const toolCall = action.toolCall;
1197
- // Hooks: on_tool_start (global + agent)
1198
- runner.emit('agent_tool_start', runContext, agent, action.computer, {
1199
- toolCall,
1200
- });
1201
- if (typeof agent.emit === 'function') {
1202
- agent.emit('agent_tool_start', runContext, action.computer, { toolCall });
1203
- }
1204
- // Run the action and get screenshot
1205
- let output;
1206
- try {
1207
- const computer = await resolveComputer({
1208
- tool: action.computer,
1209
- runContext,
1210
- });
1211
- output = await _runComputerActionAndScreenshot(computer, toolCall);
1212
- }
1213
- catch (err) {
1214
- _logger.error('Failed to execute computer action:', err);
1215
- output = '';
1216
- }
1217
- // Hooks: on_tool_end (global + agent)
1218
- runner.emit('agent_tool_end', runContext, agent, action.computer, output, {
1219
- toolCall,
1220
- });
1221
- if (typeof agent.emit === 'function') {
1222
- agent.emit('agent_tool_end', runContext, action.computer, output, {
1223
- toolCall,
1224
- });
1225
- }
1226
- // Return the screenshot as a data URL when available; fall back to an empty string on failures.
1227
- const imageUrl = output ? `data:image/png;base64,${output}` : '';
1228
- const rawItem = {
1229
- type: 'computer_call_result',
1230
- callId: toolCall.callId,
1231
- output: { type: 'computer_screenshot', data: imageUrl },
1232
- };
1233
- results.push(new RunToolCallOutputItem(rawItem, agent, imageUrl));
1234
- }
1235
- return results;
1236
- }
1237
- /**
1238
- * @internal
1239
- * Drives handoff calls by invoking the downstream agent and capturing any generated items so
1240
- * the current agent can continue with the new context.
1241
- */
1242
- export async function executeHandoffCalls(agent, originalInput, preStepItems, newStepItems, newResponse, runHandoffs, runner, runContext) {
1243
- newStepItems = [...newStepItems];
1244
- if (runHandoffs.length === 0) {
1245
- logger.warn('Incorrectly called executeHandoffCalls with no handoffs. This should not happen. Moving on.');
1246
- return new SingleStepResult(originalInput, newResponse, preStepItems, newStepItems, { type: 'next_step_run_again' });
1247
- }
1248
- if (runHandoffs.length > 1) {
1249
- // multiple handoffs. Ignoring all but the first one by adding reject responses for those
1250
- const outputMessage = 'Multiple handoffs detected, ignoring this one.';
1251
- for (let i = 1; i < runHandoffs.length; i++) {
1252
- newStepItems.push(new RunToolCallOutputItem(getToolCallOutputItem(runHandoffs[i].toolCall, outputMessage), agent, outputMessage));
1253
- }
1254
- }
1255
- const actualHandoff = runHandoffs[0];
1256
- return withHandoffSpan(async (handoffSpan) => {
1257
- const handoff = actualHandoff.handoff;
1258
- const newAgent = await handoff.onInvokeHandoff(runContext, actualHandoff.toolCall.arguments);
1259
- handoffSpan.spanData.to_agent = newAgent.name;
1260
- if (runHandoffs.length > 1) {
1261
- const requestedAgents = runHandoffs.map((h) => h.handoff.agentName);
1262
- handoffSpan.setError({
1263
- message: 'Multiple handoffs requested',
1264
- data: {
1265
- requested_agents: requestedAgents,
1266
- },
1267
- });
1268
- }
1269
- newStepItems.push(new RunHandoffOutputItem(getToolCallOutputItem(actualHandoff.toolCall, getTransferMessage(newAgent)), agent, newAgent));
1270
- runner.emit('agent_handoff', runContext, agent, newAgent);
1271
- agent.emit('agent_handoff', runContext, newAgent);
1272
- const inputFilter = handoff.inputFilter ?? runner.config.handoffInputFilter;
1273
- if (inputFilter) {
1274
- logger.debug('Filtering inputs for handoff');
1275
- if (typeof inputFilter !== 'function') {
1276
- handoffSpan.setError({
1277
- message: 'Invalid input filter',
1278
- data: {
1279
- details: 'not callable',
1280
- },
1281
- });
1282
- }
1283
- const handoffInputData = {
1284
- inputHistory: Array.isArray(originalInput)
1285
- ? [...originalInput]
1286
- : originalInput,
1287
- preHandoffItems: [...preStepItems],
1288
- newItems: [...newStepItems],
1289
- runContext,
1290
- };
1291
- const filtered = inputFilter(handoffInputData);
1292
- originalInput = filtered.inputHistory;
1293
- preStepItems = filtered.preHandoffItems;
1294
- newStepItems = filtered.newItems;
1295
- }
1296
- return new SingleStepResult(originalInput, newResponse, preStepItems, newStepItems, { type: 'next_step_handoff', newAgent });
1297
- }, {
1298
- data: {
1299
- from_agent: agent.name,
1300
- },
1301
- });
1302
- }
1303
- const NOT_FINAL_OUTPUT = {
1304
- isFinalOutput: false,
1305
- isInterrupted: undefined,
1306
- };
1307
- /**
1308
- * @internal
1309
- * Determines whether tool executions produced a final agent output, triggered an interruption,
1310
- * or whether the agent loop should continue collecting more responses.
1311
- */
1312
- export async function checkForFinalOutputFromTools(agent, toolResults, state) {
1313
- if (toolResults.length === 0) {
1314
- return NOT_FINAL_OUTPUT;
1315
- }
1316
- const interruptions = [];
1317
- for (const result of toolResults) {
1318
- if (result.runItem instanceof RunToolApprovalItem) {
1319
- interruptions.push(result.runItem);
1320
- }
1321
- if (result.type === 'function_output') {
1322
- if (Array.isArray(result.interruptions)) {
1323
- interruptions.push(...result.interruptions);
1324
- }
1325
- else if (result.agentRunResult) {
1326
- const nestedInterruptions = result.agentRunResult.interruptions;
1327
- if (nestedInterruptions.length > 0) {
1328
- interruptions.push(...nestedInterruptions);
1329
- }
1330
- }
1331
- }
1332
- }
1333
- if (interruptions.length > 0) {
1334
- return {
1335
- isFinalOutput: false,
1336
- isInterrupted: true,
1337
- interruptions,
1338
- };
1339
- }
1340
- if (agent.toolUseBehavior === 'run_llm_again') {
1341
- return NOT_FINAL_OUTPUT;
1342
- }
1343
- const firstToolResult = toolResults[0];
1344
- if (agent.toolUseBehavior === 'stop_on_first_tool') {
1345
- if (firstToolResult?.type === 'function_output') {
1346
- const stringOutput = toSmartString(firstToolResult.output);
1347
- return {
1348
- isFinalOutput: true,
1349
- isInterrupted: undefined,
1350
- finalOutput: stringOutput,
1351
- };
1352
- }
1353
- return NOT_FINAL_OUTPUT;
1354
- }
1355
- const toolUseBehavior = agent.toolUseBehavior;
1356
- if (typeof toolUseBehavior === 'object') {
1357
- const stoppingTool = toolResults.find((r) => toolUseBehavior.stopAtToolNames.includes(r.tool.name));
1358
- if (stoppingTool?.type === 'function_output') {
1359
- const stringOutput = toSmartString(stoppingTool.output);
1360
- return {
1361
- isFinalOutput: true,
1362
- isInterrupted: undefined,
1363
- finalOutput: stringOutput,
1364
- };
1365
- }
1366
- return NOT_FINAL_OUTPUT;
1367
- }
1368
- if (typeof toolUseBehavior === 'function') {
1369
- return toolUseBehavior(state._context, toolResults);
1370
- }
1371
- throw new UserError(`Invalid toolUseBehavior: ${toolUseBehavior}`, state);
1372
- }
1373
- function getRunItemStreamEventName(item) {
1374
- if (item instanceof RunMessageOutputItem) {
1375
- return 'message_output_created';
1376
- }
1377
- if (item instanceof RunHandoffCallItem) {
1378
- return 'handoff_requested';
1379
- }
1380
- if (item instanceof RunHandoffOutputItem) {
1381
- return 'handoff_occurred';
1382
- }
1383
- if (item instanceof RunToolCallItem) {
1384
- return 'tool_called';
1385
- }
1386
- if (item instanceof RunToolCallOutputItem) {
1387
- return 'tool_output';
1388
- }
1389
- if (item instanceof RunReasoningItem) {
1390
- return 'reasoning_item_created';
1391
- }
1392
- if (item instanceof RunToolApprovalItem) {
1393
- return 'tool_approval_requested';
1394
- }
1395
- return undefined;
1396
- }
1397
- function enqueueRunItemStreamEvent(result, item) {
1398
- const itemName = getRunItemStreamEventName(item);
1399
- if (!itemName) {
1400
- logger.warn('Unknown item type: ', item);
1401
- return;
1402
- }
1403
- result._addItem(new RunItemStreamEvent(itemName, item));
1404
- }
1405
- export function streamStepItemsToRunResult(result, items) {
1406
- // Preserve the order in which items were generated by enqueueing each one
1407
- // immediately on the streamed result.
1408
- for (const item of items) {
1409
- enqueueRunItemStreamEvent(result, item);
1410
- }
1411
- }
1412
- export function addStepToRunResult(result, step, options) {
1413
- // skipItems contains run items that were already streamed so we avoid
1414
- // enqueueing duplicate events for the same instance.
1415
- const skippedItems = options?.skipItems;
1416
- for (const item of step.newStepItems) {
1417
- if (skippedItems?.has(item)) {
1418
- continue;
1419
- }
1420
- enqueueRunItemStreamEvent(result, item);
1421
- }
1422
- }
1423
- export class AgentToolUseTracker {
1424
- #agentToTools = new Map();
1425
- addToolUse(agent, toolNames) {
1426
- this.#agentToTools.set(agent, toolNames);
1427
- }
1428
- hasUsedTools(agent) {
1429
- return this.#agentToTools.has(agent);
1430
- }
1431
- toJSON() {
1432
- return Object.fromEntries(Array.from(this.#agentToTools.entries()).map(([agent, toolNames]) => {
1433
- return [agent.name, toolNames];
1434
- }));
1435
- }
1436
- }
1437
- /**
1438
- * @internal
1439
- * Convert a user-provided input into a list of input items.
1440
- */
1441
- export function toInputItemList(input) {
1442
- if (typeof input === 'string') {
1443
- return [
1444
- {
1445
- type: 'message',
1446
- role: 'user',
1447
- content: input,
1448
- },
1449
- ];
1450
- }
1451
- return [...input];
1452
- }
1453
- /**
1454
- * @internal
1455
- * Extract model output items from run items, excluding tool approval items.
1456
- */
1457
- export function extractOutputItemsFromRunItems(items) {
1458
- return items
1459
- .filter((item) => item.type !== 'tool_approval_item')
1460
- .map((item) => item.rawItem);
1461
- }
1462
- function normalizeItemsForSessionPersistence(items) {
1463
- // Persisted sessions must avoid raw binary so we convert every item into a JSON-safe shape before writing to storage.
1464
- return items.map((item) => sanitizeValueForSession(stripTransientCallIds(item)));
1465
- }
1466
- function sanitizeValueForSession(value, context = {}) {
1467
- if (value === null || value === undefined) {
1468
- return value;
1469
- }
1470
- // Convert supported binary payloads into ArrayBuffer views before serialization.
1471
- const binary = toUint8ArrayIfBinary(value);
1472
- if (binary) {
1473
- return toDataUrlFromBytes(binary, context.mediaType);
1474
- }
1475
- if (Array.isArray(value)) {
1476
- return value.map((entry) => sanitizeValueForSession(entry, context));
1477
- }
1478
- if (!isPlainObject(value)) {
1479
- return value;
1480
- }
1481
- const record = value;
1482
- const result = {};
1483
- const mediaType = typeof record.mediaType === 'string' && record.mediaType.length > 0
1484
- ? record.mediaType
1485
- : context.mediaType;
1486
- for (const [key, entry] of Object.entries(record)) {
1487
- // Propagate explicit media type only when walking into binary payload containers.
1488
- const nextContext = key === 'data' || key === 'fileData' ? { mediaType } : context;
1489
- result[key] = sanitizeValueForSession(entry, nextContext);
1490
- }
1491
- return result;
1492
- }
1493
- function toUint8ArrayIfBinary(value) {
1494
- // Normalize the diverse binary containers we may receive into a shared Uint8Array view.
1495
- if (value instanceof ArrayBuffer) {
1496
- return new Uint8Array(value);
1497
- }
1498
- if (isArrayBufferView(value)) {
1499
- const view = value;
1500
- return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
1501
- }
1502
- if (isNodeBuffer(value)) {
1503
- const view = value;
1504
- return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
1505
- }
1506
- if (isSerializedBufferSnapshot(value)) {
1507
- const snapshot = value;
1508
- return Uint8Array.from(snapshot.data);
1509
- }
1510
- return undefined;
1511
- }
1512
- function toDataUrlFromBytes(bytes, mediaType) {
1513
- // Convert binary payloads into a durable data URL so session files remain self-contained.
1514
- const base64 = encodeUint8ArrayToBase64(bytes);
1515
- // Note that OpenAI Responses API never accepts application/octet-stream as a media type,
1516
- // so we fall back to text/plain; that said, tools are supposed to return a valid media type when this utility is used.
1517
- const type = mediaType && !mediaType.startsWith('data:') ? mediaType : 'text/plain';
1518
- return `data:${type};base64,${base64}`;
1519
- }
1520
- function isPlainObject(value) {
1521
- if (typeof value !== 'object' || value === null) {
1522
- return false;
1523
- }
1524
- const proto = Object.getPrototypeOf(value);
1525
- return proto === Object.prototype || proto === null;
1526
- }
1527
- function stripTransientCallIds(value) {
1528
- if (value === null || value === undefined) {
1529
- return value;
1530
- }
1531
- if (Array.isArray(value)) {
1532
- return value.map((entry) => stripTransientCallIds(entry));
1533
- }
1534
- if (!isPlainObject(value)) {
1535
- return value;
1536
- }
1537
- const record = value;
1538
- const result = {};
1539
- const isProtocolItem = typeof record.type === 'string' && record.type.length > 0;
1540
- const shouldStripId = isProtocolItem && shouldStripIdForType(record.type);
1541
- for (const [key, entry] of Object.entries(record)) {
1542
- if (shouldStripId && key === 'id') {
1543
- continue;
1544
- }
1545
- result[key] = stripTransientCallIds(entry);
1546
- }
1547
- return result;
1548
- }
1549
- function shouldStripIdForType(type) {
1550
- switch (type) {
1551
- case 'function_call':
1552
- case 'function_call_result':
1553
- return true;
1554
- default:
1555
- return false;
1556
- }
1557
- }
1558
- async function runCompactionOnSession(session, responseId, state) {
1559
- if (!isOpenAIResponsesCompactionAwareSession(session)) {
1560
- return;
1561
- }
1562
- // Called after a completed turn is persisted so compaction can consider the latest stored state.
1563
- const compactionResult = await session.runCompaction(typeof responseId === 'undefined' ? undefined : { responseId });
1564
- if (!compactionResult) {
1565
- return;
1566
- }
1567
- const usage = compactionResult.usage;
1568
- state._context.usage.add(new Usage({
1569
- requests: 1,
1570
- inputTokens: usage.inputTokens,
1571
- outputTokens: usage.outputTokens,
1572
- totalTokens: usage.totalTokens,
1573
- inputTokensDetails: usage.inputTokensDetails,
1574
- outputTokensDetails: usage.outputTokensDetails,
1575
- requestUsageEntries: [usage],
1576
- }));
1577
- }
1578
- /**
1579
- * @internal
1580
- * Persist full turn (input + outputs) for non-streaming runs.
1581
- */
1582
- // Persists the combination of user inputs (possibly filtered) and model outputs for a completed turn.
1583
- export async function saveToSession(session, sessionInputItems, result) {
1584
- if (!session) {
1585
- return;
1586
- }
1587
- const inputItems = sessionInputItems ?? [];
1588
- const state = result.state;
1589
- const alreadyPersisted = state._currentTurnPersistedItemCount ?? 0;
1590
- // Persist only the portion of _generatedItems that has not yet been stored for this turn.
1591
- const newRunItems = result.newItems.slice(alreadyPersisted);
1592
- if (process.env.OPENAI_AGENTS__DEBUG_SAVE_SESSION) {
1593
- console.debug('saveToSession:newRunItems', newRunItems.map((item) => item.type));
1594
- }
1595
- const outputItems = extractOutputItemsFromRunItems(newRunItems);
1596
- const itemsToSave = [...inputItems, ...outputItems];
1597
- if (itemsToSave.length === 0) {
1598
- state._currentTurnPersistedItemCount =
1599
- alreadyPersisted + newRunItems.length;
1600
- await runCompactionOnSession(session, result.lastResponseId, state);
1601
- return;
1602
- }
1603
- const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
1604
- await session.addItems(sanitizedItems);
1605
- await runCompactionOnSession(session, result.lastResponseId, state);
1606
- state._currentTurnPersistedItemCount = alreadyPersisted + newRunItems.length;
1607
- }
1608
- /**
1609
- * @internal
1610
- * Persist only the user input for streaming runs at start.
1611
- */
1612
- // For streaming runs we persist user input as soon as it is sent so reconnections can resume.
1613
- export async function saveStreamInputToSession(session, sessionInputItems) {
1614
- if (!session) {
1615
- return;
1616
- }
1617
- if (!sessionInputItems || sessionInputItems.length === 0) {
1618
- return;
1619
- }
1620
- const sanitizedInput = normalizeItemsForSessionPersistence(sessionInputItems);
1621
- await session.addItems(sanitizedInput);
1622
- }
1623
- /**
1624
- * @internal
1625
- * Persist only the model outputs for streaming runs at the end of a turn.
1626
- */
1627
- // Complements saveStreamInputToSession by recording the streaming outputs at the end of the turn.
1628
- export async function saveStreamResultToSession(session, result) {
1629
- if (!session) {
1630
- return;
1631
- }
1632
- const state = result.state;
1633
- const alreadyPersisted = state._currentTurnPersistedItemCount ?? 0;
1634
- const newRunItems = result.newItems.slice(alreadyPersisted);
1635
- const itemsToSave = extractOutputItemsFromRunItems(newRunItems);
1636
- if (itemsToSave.length === 0) {
1637
- state._currentTurnPersistedItemCount =
1638
- alreadyPersisted + newRunItems.length;
1639
- await runCompactionOnSession(session, result.lastResponseId, state);
1640
- return;
1641
- }
1642
- const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
1643
- await session.addItems(sanitizedItems);
1644
- await runCompactionOnSession(session, result.lastResponseId, state);
1645
- state._currentTurnPersistedItemCount = alreadyPersisted + newRunItems.length;
1646
- }
1647
- export async function prepareInputItemsWithSession(input, session, sessionInputCallback, options) {
1648
- if (!session) {
1649
- return {
1650
- preparedInput: input,
1651
- sessionItems: undefined,
1652
- };
1653
- }
1654
- const includeHistoryInPreparedInput = options?.includeHistoryInPreparedInput ?? true;
1655
- const preserveDroppedNewItems = options?.preserveDroppedNewItems ?? false;
1656
- const history = await session.getItems();
1657
- const newInputItems = Array.isArray(input)
1658
- ? [...input]
1659
- : toInputItemList(input);
1660
- if (!sessionInputCallback) {
1661
- return {
1662
- preparedInput: includeHistoryInPreparedInput
1663
- ? [...history, ...newInputItems]
1664
- : newInputItems,
1665
- sessionItems: newInputItems,
1666
- };
1667
- }
1668
- // Capture snapshots before invoking the callback so we can reason about the original state even
1669
- // if the callback mutates the history array in-place.
1670
- const historySnapshot = history.slice();
1671
- const newInputSnapshot = newInputItems.slice();
1672
- // Delegate history reconciliation to the user-supplied callback. It must return a concrete list
1673
- // to keep downstream model requests well-typed.
1674
- const combined = await sessionInputCallback(history, newInputItems);
1675
- if (!Array.isArray(combined)) {
1676
- throw new UserError('Session input callback must return an array of AgentInputItem objects.');
1677
- }
1678
- const historyCounts = buildItemFrequencyMap(historySnapshot);
1679
- const newInputCounts = buildItemFrequencyMap(newInputSnapshot);
1680
- const historyRefs = buildItemReferenceMap(historySnapshot);
1681
- const newInputRefs = buildItemReferenceMap(newInputSnapshot);
1682
- const appended = [];
1683
- for (const item of combined) {
1684
- const key = sessionItemKey(item);
1685
- if (consumeReference(newInputRefs, key, item)) {
1686
- decrementCount(newInputCounts, key);
1687
- appended.push(item);
1688
- continue;
1689
- }
1690
- // Prioritize exact history matches before payload-based counts so callbacks that surface
1691
- // history ahead of identical new inputs keep previously persisted items out of the new queue.
1692
- if (consumeReference(historyRefs, key, item)) {
1693
- decrementCount(historyCounts, key);
1694
- continue;
1695
- }
1696
- const historyRemaining = historyCounts.get(key) ?? 0;
1697
- if (historyRemaining > 0) {
1698
- historyCounts.set(key, historyRemaining - 1);
1699
- continue;
1700
- }
1701
- const newRemaining = newInputCounts.get(key) ?? 0;
1702
- if (newRemaining > 0) {
1703
- newInputCounts.set(key, newRemaining - 1);
1704
- appended.push(item);
1705
- continue;
1706
- }
1707
- appended.push(item);
1708
- }
1709
- // Preserve redacted inputs for model delivery when requested (e.g. server-managed histories).
1710
- const preparedItems = includeHistoryInPreparedInput
1711
- ? combined
1712
- : appended.length > 0
1713
- ? appended
1714
- : preserveDroppedNewItems
1715
- ? newInputSnapshot
1716
- : [];
1717
- return {
1718
- preparedInput: preparedItems,
1719
- // Respect callbacks that intentionally drop the latest inputs (e.g. to redact sensitive
1720
- // values) by persisting only the items they kept in the combined array.
1721
- sessionItems: appended,
1722
- };
1723
- }
1724
- /**
1725
- * Accepts whatever the tool returned and attempts to coerce it into the structured protocol
1726
- * shapes we expose to downstream model adapters (input_text/input_image/input_file). Tools are
1727
- * allowed to return either a single structured object or an array of them; anything else falls
1728
- * back to the legacy string pipeline.
1729
- */
1730
- function normalizeStructuredToolOutputs(output) {
1731
- if (Array.isArray(output)) {
1732
- const structured = [];
1733
- for (const item of output) {
1734
- const normalized = normalizeStructuredToolOutput(item);
1735
- if (!normalized) {
1736
- return null;
1737
- }
1738
- structured.push(normalized);
1739
- }
1740
- return structured;
1741
- }
1742
- const normalized = normalizeStructuredToolOutput(output);
1743
- return normalized ? [normalized] : null;
1744
- }
1745
- /**
1746
- * Best-effort normalization of a single tool output item. If the object already matches the
1747
- * protocol shape we simply cast it; otherwise we copy the recognised fields into the canonical
1748
- * structure. Returning null lets the caller know we should revert to plain-string handling.
1749
- */
1750
- function normalizeStructuredToolOutput(value) {
1751
- if (!isRecord(value)) {
1752
- return null;
1753
- }
1754
- const type = value.type;
1755
- if (type === 'text' && typeof value.text === 'string') {
1756
- const output = { type: 'text', text: value.text };
1757
- if (isRecord(value.providerData)) {
1758
- output.providerData = value.providerData;
1759
- }
1760
- return output;
1761
- }
1762
- if (type === 'image') {
1763
- const output = { type: 'image' };
1764
- let imageString;
1765
- let imageFileId;
1766
- const fallbackImageMediaType = isNonEmptyString(value.mediaType)
1767
- ? value.mediaType
1768
- : undefined;
1769
- const imageField = value.image;
1770
- if (typeof imageField === 'string' && imageField.length > 0) {
1771
- imageString = imageField;
1772
- }
1773
- else if (isRecord(imageField)) {
1774
- const imageObj = imageField;
1775
- const inlineMediaType = isNonEmptyString(imageObj.mediaType)
1776
- ? imageObj.mediaType
1777
- : fallbackImageMediaType;
1778
- if (isNonEmptyString(imageObj.url)) {
1779
- imageString = imageObj.url;
1780
- }
1781
- else if (isNonEmptyString(imageObj.data)) {
1782
- imageString = toInlineImageString(imageObj.data, inlineMediaType);
1783
- }
1784
- else if (imageObj.data instanceof Uint8Array &&
1785
- imageObj.data.length > 0) {
1786
- imageString = toInlineImageString(imageObj.data, inlineMediaType);
1787
- }
1788
- if (!imageString) {
1789
- const candidateId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
1790
- (isNonEmptyString(imageObj.id) && imageObj.id) ||
1791
- undefined;
1792
- if (candidateId) {
1793
- imageFileId = candidateId;
1794
- }
1795
- }
1796
- }
1797
- if (!imageString &&
1798
- typeof value.imageUrl === 'string' &&
1799
- value.imageUrl.length > 0) {
1800
- imageString = value.imageUrl;
1801
- }
1802
- if (!imageFileId &&
1803
- typeof value.fileId === 'string' &&
1804
- value.fileId.length > 0) {
1805
- imageFileId = value.fileId;
1806
- }
1807
- if (!imageString &&
1808
- typeof value.data === 'string' &&
1809
- value.data.length > 0) {
1810
- imageString = fallbackImageMediaType
1811
- ? toInlineImageString(value.data, fallbackImageMediaType)
1812
- : value.data;
1813
- }
1814
- else if (!imageString &&
1815
- value.data instanceof Uint8Array &&
1816
- value.data.length > 0) {
1817
- imageString = toInlineImageString(value.data, fallbackImageMediaType);
1818
- }
1819
- if (typeof value.detail === 'string' && value.detail.length > 0) {
1820
- output.detail = value.detail;
1821
- }
1822
- if (imageString) {
1823
- output.image = imageString;
1824
- }
1825
- else if (imageFileId) {
1826
- output.image = { fileId: imageFileId };
1827
- }
1828
- else {
1829
- return null;
1830
- }
1831
- if (isRecord(value.providerData)) {
1832
- output.providerData = value.providerData;
1833
- }
1834
- return output;
1835
- }
1836
- if (type === 'file') {
1837
- const fileValue = normalizeFileValue(value);
1838
- if (!fileValue) {
1839
- return null;
1840
- }
1841
- const output = { type: 'file', file: fileValue };
1842
- if (isRecord(value.providerData)) {
1843
- output.providerData = value.providerData;
1844
- }
1845
- return output;
1846
- }
1847
- return null;
1848
- }
1849
- /**
1850
- * Translates the normalized tool output into the protocol `input_*` items. This is the last hop
1851
- * before we hand the data to model-specific adapters, so we generate the exact schema expected by
1852
- * the protocol definitions.
1853
- */
1854
- function convertStructuredToolOutputToInputItem(output) {
1855
- if (output.type === 'text') {
1856
- const result = {
1857
- type: 'input_text',
1858
- text: output.text,
1859
- };
1860
- if (output.providerData) {
1861
- result.providerData = output.providerData;
1862
- }
1863
- return result;
1864
- }
1865
- if (output.type === 'image') {
1866
- const result = { type: 'input_image' };
1867
- if (typeof output.detail === 'string' && output.detail.length > 0) {
1868
- result.detail = output.detail;
1869
- }
1870
- if (typeof output.image === 'string' && output.image.length > 0) {
1871
- result.image = output.image;
1872
- }
1873
- else if (isRecord(output.image)) {
1874
- const imageObj = output.image;
1875
- const inlineMediaType = isNonEmptyString(imageObj.mediaType)
1876
- ? imageObj.mediaType
1877
- : undefined;
1878
- if (isNonEmptyString(imageObj.url)) {
1879
- result.image = imageObj.url;
1880
- }
1881
- else if (isNonEmptyString(imageObj.data)) {
1882
- result.image =
1883
- inlineMediaType && !imageObj.data.startsWith('data:')
1884
- ? asDataUrl(imageObj.data, inlineMediaType)
1885
- : imageObj.data;
1886
- }
1887
- else if (imageObj.data instanceof Uint8Array &&
1888
- imageObj.data.length > 0) {
1889
- const base64 = encodeUint8ArrayToBase64(imageObj.data);
1890
- result.image = asDataUrl(base64, inlineMediaType);
1891
- }
1892
- else {
1893
- const referencedId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
1894
- (isNonEmptyString(imageObj.id) && imageObj.id) ||
1895
- undefined;
1896
- if (referencedId) {
1897
- result.image = { id: referencedId };
1898
- }
1899
- }
1900
- }
1901
- if (output.providerData) {
1902
- result.providerData = output.providerData;
1903
- }
1904
- return result;
1905
- }
1906
- if (output.type === 'file') {
1907
- const result = { type: 'input_file' };
1908
- const fileValue = output.file;
1909
- if (typeof fileValue === 'string') {
1910
- result.file = fileValue;
1911
- }
1912
- else if (fileValue && typeof fileValue === 'object') {
1913
- const record = fileValue;
1914
- if ('data' in record && record.data) {
1915
- const mediaType = record.mediaType ?? 'text/plain';
1916
- if (typeof record.data === 'string') {
1917
- result.file = asDataUrl(record.data, mediaType);
1918
- }
1919
- else {
1920
- const base64 = encodeUint8ArrayToBase64(record.data);
1921
- result.file = asDataUrl(base64, mediaType);
1922
- }
1923
- }
1924
- else if (typeof record.url === 'string' && record.url.length > 0) {
1925
- result.file = { url: record.url };
1926
- }
1927
- else {
1928
- const referencedId = (typeof record.id === 'string' &&
1929
- record.id.length > 0 &&
1930
- record.id) ||
1931
- (typeof record.fileId === 'string' && record.fileId.length > 0
1932
- ? record.fileId
1933
- : undefined);
1934
- if (referencedId) {
1935
- result.file = { id: referencedId };
1936
- }
1937
- }
1938
- if (typeof record.filename === 'string' && record.filename.length > 0) {
1939
- result.filename = record.filename;
1940
- }
1941
- }
1942
- if (output.providerData) {
1943
- result.providerData = output.providerData;
1944
- }
1945
- return result;
1946
- }
1947
- const exhaustiveCheck = output;
1948
- return exhaustiveCheck;
1949
- }
1950
- function buildItemFrequencyMap(items) {
1951
- const counts = new Map();
1952
- for (const item of items) {
1953
- const key = sessionItemKey(item);
1954
- counts.set(key, (counts.get(key) ?? 0) + 1);
1955
- }
1956
- return counts;
1957
- }
1958
- function buildItemReferenceMap(items) {
1959
- const refs = new Map();
1960
- for (const item of items) {
1961
- const key = sessionItemKey(item);
1962
- const list = refs.get(key);
1963
- if (list) {
1964
- list.push(item);
1965
- }
1966
- else {
1967
- refs.set(key, [item]);
1968
- }
1969
- }
1970
- return refs;
1971
- }
1972
- function consumeReference(refs, key, target) {
1973
- const candidates = refs.get(key);
1974
- if (!candidates || candidates.length === 0) {
1975
- return false;
1976
- }
1977
- const index = candidates.findIndex((candidate) => candidate === target);
1978
- if (index === -1) {
1979
- return false;
1980
- }
1981
- candidates.splice(index, 1);
1982
- if (candidates.length === 0) {
1983
- refs.delete(key);
1984
- }
1985
- return true;
1986
- }
1987
- function decrementCount(map, key) {
1988
- const remaining = (map.get(key) ?? 0) - 1;
1989
- if (remaining <= 0) {
1990
- map.delete(key);
1991
- }
1992
- else {
1993
- map.set(key, remaining);
1994
- }
1995
- }
1996
- function sessionItemKey(item) {
1997
- return JSON.stringify(item, sessionSerializationReplacer);
1998
- }
1999
- function sessionSerializationReplacer(_key, value) {
2000
- if (value instanceof ArrayBuffer) {
2001
- return {
2002
- __type: 'ArrayBuffer',
2003
- data: encodeUint8ArrayToBase64(new Uint8Array(value)),
2004
- };
2005
- }
2006
- if (isArrayBufferView(value)) {
2007
- const view = value;
2008
- return {
2009
- __type: view.constructor.name,
2010
- data: encodeUint8ArrayToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
2011
- };
2012
- }
2013
- if (isNodeBuffer(value)) {
2014
- const view = value;
2015
- return {
2016
- __type: 'Buffer',
2017
- data: encodeUint8ArrayToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
2018
- };
2019
- }
2020
- if (isSerializedBufferSnapshot(value)) {
2021
- return {
2022
- __type: 'Buffer',
2023
- data: encodeUint8ArrayToBase64(Uint8Array.from(value.data)),
2024
- };
2025
- }
2026
- return value;
2027
- }
2028
- //# sourceMappingURL=runImplementation.mjs.map