@openai/agents-core 0.3.7 → 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 +51 -6
  119. package/dist/shims/mcp-server/node.js.map +1 -1
  120. package/dist/shims/mcp-server/node.mjs +51 -6
  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
package/dist/run.mjs CHANGED
@@ -1,24 +1,32 @@
1
1
  import { Agent } from "./agent.mjs";
2
+ import { RunAgentUpdatedStreamEvent, RunRawModelStreamEvent } from "./events.mjs";
3
+ import { ModelBehaviorError, UserError } from "./errors.mjs";
2
4
  import { defineInputGuardrail, defineOutputGuardrail, } from "./guardrail.mjs";
5
+ import { RunHooks } from "./lifecycle.mjs";
6
+ import logger from "./logger.mjs";
3
7
  import { getDefaultModelProvider } from "./providers.mjs";
4
8
  import { RunContext } from "./runContext.mjs";
5
9
  import { RunResult, StreamedRunResult } from "./result.mjs";
6
- import { RunHooks } from "./lifecycle.mjs";
7
- import logger from "./logger.mjs";
8
- import { serializeTool, serializeHandoff } from "./utils/serialize.mjs";
9
- import { GuardrailExecutionError, InputGuardrailTripwireTriggered, MaxTurnsExceededError, ModelBehaviorError, OutputGuardrailTripwireTriggered, UserError, } from "./errors.mjs";
10
- import { addStepToRunResult, resolveInterruptedTurn, resolveTurnAfterModelResponse, maybeResetToolChoice, processModelResponse, streamStepItemsToRunResult, saveStreamInputToSession, saveStreamResultToSession, saveToSession, prepareInputItemsWithSession, } from "./runImplementation.mjs";
11
- import { resolveComputer, disposeResolvedComputers, } from "./tool.mjs";
12
- import { getOrCreateTrace, addErrorToCurrentSpan, resetCurrentSpan, setCurrentSpan, withNewSpanContext, withTrace, } from "./tracing/context.mjs";
13
- import { createAgentSpan, withGuardrailSpan } from "./tracing/index.mjs";
14
- import { Usage } from "./usage.mjs";
15
- import { RunAgentUpdatedStreamEvent, RunRawModelStreamEvent } from "./events.mjs";
16
10
  import { RunState } from "./runState.mjs";
17
- import { StreamEventResponseCompleted } from "./types/protocol.mjs";
11
+ import { disposeResolvedComputers } from "./tool.mjs";
12
+ import { getOrCreateTrace, resetCurrentSpan, setCurrentSpan, withNewSpanContext, withTrace, } from "./tracing/context.mjs";
13
+ import { Usage } from "./usage.mjs";
18
14
  import { convertAgentOutputTypeToSerializable } from "./utils/tools.mjs";
19
- import { gpt5ReasoningSettingsRequired, isGpt5Default } from "./defaultModel.mjs";
20
- import { encodeUint8ArrayToBase64 } from "./utils/base64.mjs";
21
- import { isArrayBufferView, isNodeBuffer, isSerializedBufferSnapshot, } from "./utils/smartString.mjs";
15
+ import { DEFAULT_MAX_TURNS } from "./runner/constants.mjs";
16
+ import { StreamEventResponseCompleted } from "./types/protocol.mjs";
17
+ import { ServerConversationTracker, applyCallModelInputFilter, } from "./runner/conversation.mjs";
18
+ import { createGuardrailTracker, runOutputGuardrails, } from "./runner/guardrails.mjs";
19
+ import { adjustModelSettingsForNonGPT5RunnerModel, maybeResetToolChoice, selectModel, } from "./runner/modelSettings.mjs";
20
+ import { processModelResponse } from "./runner/modelOutputs.mjs";
21
+ import { addStepToRunResult, streamStepItemsToRunResult, isAbortError, } from "./runner/streaming.mjs";
22
+ import { createSessionPersistenceTracker, prepareInputItemsWithSession, saveStreamInputToSession, saveStreamResultToSession, saveToSession, } from "./runner/sessionPersistence.mjs";
23
+ import { resolveTurnAfterModelResponse } from "./runner/turnResolution.mjs";
24
+ import { prepareTurn } from "./runner/turnPreparation.mjs";
25
+ import { applyTurnResult, handleInterruptedOutcome, resumeInterruptedTurn, } from "./runner/runLoop.mjs";
26
+ import { getTracing } from "./runner/tracing.mjs";
27
+ export { getTracing } from "./runner/tracing.mjs";
28
+ export { selectModel } from "./runner/modelSettings.mjs";
29
+ export { getTurnInput } from "./runner/items.mjs";
22
30
  export async function run(agent, input, options) {
23
31
  const runner = getDefaultRunner();
24
32
  if (options?.stream) {
@@ -54,6 +62,7 @@ export class Runner extends RunHooks {
54
62
  traceId: config.traceId,
55
63
  groupId: config.groupId,
56
64
  traceMetadata: config.traceMetadata,
65
+ tracing: config.tracing,
57
66
  sessionInputCallback: config.sessionInputCallback,
58
67
  callModelInputFilter: config.callModelInputFilter,
59
68
  };
@@ -70,147 +79,34 @@ export class Runner extends RunHooks {
70
79
  // Likewise allow callers to override callModelInputFilter on individual runs.
71
80
  const callModelInputFilter = resolvedOptions.callModelInputFilter ?? this.config.callModelInputFilter;
72
81
  const hasCallModelInputFilter = Boolean(callModelInputFilter);
82
+ const tracingConfig = resolvedOptions.tracing ?? this.config.tracing;
73
83
  const effectiveOptions = {
74
84
  ...resolvedOptions,
75
85
  sessionInputCallback,
76
86
  callModelInputFilter,
77
87
  };
78
- const serverManagesConversation = Boolean(effectiveOptions.conversationId) ||
79
- Boolean(effectiveOptions.previousResponseId);
88
+ const resumingFromState = input instanceof RunState;
89
+ const preserveTurnPersistenceOnResume = resumingFromState &&
90
+ input._currentTurnInProgress === true;
91
+ const resumedConversationId = resumingFromState
92
+ ? input._conversationId
93
+ : undefined;
94
+ const resumedPreviousResponseId = resumingFromState
95
+ ? input._previousResponseId
96
+ : undefined;
97
+ const serverManagesConversation = Boolean(effectiveOptions.conversationId ?? resumedConversationId) ||
98
+ Boolean(effectiveOptions.previousResponseId ?? resumedPreviousResponseId);
80
99
  // When the server tracks conversation history we defer to it for previous turns so local session
81
100
  // persistence can focus solely on the new delta being generated in this process.
82
101
  const session = effectiveOptions.session;
83
- const resumingFromState = input instanceof RunState;
84
- let sessionInputOriginalSnapshot = session && resumingFromState ? [] : undefined;
85
- let sessionInputFilteredSnapshot = undefined;
86
- // Tracks remaining persistence slots per AgentInputItem key so resumed sessions only write each original occurrence once.
87
- let sessionInputPendingWriteCounts = session && resumingFromState ? new Map() : undefined;
88
- // Keeps track of which inputs should be written back to session memory. `sourceItems` reflects
89
- // the original objects (so we can respect resume counts) while `filteredItems`, when present,
90
- // contains the filtered/redacted clones that must be persisted for history.
91
- // The helper reconciles the filtered copies produced by callModelInputFilter with their original
92
- // counterparts so resume-from-state bookkeeping stays consistent and duplicate references only
93
- // consume a single persistence slot.
94
- const recordSessionItemsForPersistence = (sourceItems, filteredItems) => {
95
- const pendingWriteCounts = sessionInputPendingWriteCounts;
96
- if (filteredItems !== undefined) {
97
- if (!pendingWriteCounts) {
98
- sessionInputFilteredSnapshot = filteredItems.map((item) => structuredClone(item));
99
- return;
100
- }
101
- const persistableItems = [];
102
- const sourceOccurrenceCounts = new WeakMap();
103
- // Track how many times each original object appears so duplicate references only consume one persistence slot.
104
- for (const source of sourceItems) {
105
- if (!source || typeof source !== 'object') {
106
- continue;
107
- }
108
- const nextCount = (sourceOccurrenceCounts.get(source) ?? 0) + 1;
109
- sourceOccurrenceCounts.set(source, nextCount);
110
- }
111
- // Let filtered items without a one-to-one source match claim any remaining persistence count.
112
- const consumeAnyPendingWriteSlot = () => {
113
- for (const [key, remaining] of pendingWriteCounts) {
114
- if (remaining > 0) {
115
- pendingWriteCounts.set(key, remaining - 1);
116
- return true;
117
- }
118
- }
119
- return false;
120
- };
121
- for (let i = 0; i < filteredItems.length; i++) {
122
- const filteredItem = filteredItems[i];
123
- if (!filteredItem) {
124
- continue;
125
- }
126
- let allocated = false;
127
- const source = sourceItems[i];
128
- if (source && typeof source === 'object') {
129
- const pendingOccurrences = (sourceOccurrenceCounts.get(source) ?? 0) - 1;
130
- sourceOccurrenceCounts.set(source, pendingOccurrences);
131
- if (pendingOccurrences > 0) {
132
- continue;
133
- }
134
- const sourceKey = getAgentInputItemKey(source);
135
- const remaining = pendingWriteCounts.get(sourceKey) ?? 0;
136
- if (remaining > 0) {
137
- pendingWriteCounts.set(sourceKey, remaining - 1);
138
- persistableItems.push(structuredClone(filteredItem));
139
- allocated = true;
140
- continue;
141
- }
142
- }
143
- const filteredKey = getAgentInputItemKey(filteredItem);
144
- const filteredRemaining = pendingWriteCounts.get(filteredKey) ?? 0;
145
- if (filteredRemaining > 0) {
146
- pendingWriteCounts.set(filteredKey, filteredRemaining - 1);
147
- persistableItems.push(structuredClone(filteredItem));
148
- allocated = true;
149
- continue;
150
- }
151
- if (!source && consumeAnyPendingWriteSlot()) {
152
- persistableItems.push(structuredClone(filteredItem));
153
- allocated = true;
154
- }
155
- if (!allocated &&
156
- !source &&
157
- sessionInputFilteredSnapshot === undefined) {
158
- // Preserve at least one copy so later persistence resolves even when no counters remain.
159
- persistableItems.push(structuredClone(filteredItem));
160
- }
161
- }
162
- if (persistableItems.length > 0 ||
163
- sessionInputFilteredSnapshot === undefined) {
164
- sessionInputFilteredSnapshot = persistableItems;
165
- }
166
- return;
167
- }
168
- const filtered = [];
169
- if (!pendingWriteCounts) {
170
- for (const item of sourceItems) {
171
- if (!item) {
172
- continue;
173
- }
174
- filtered.push(structuredClone(item));
175
- }
176
- }
177
- else {
178
- for (const item of sourceItems) {
179
- if (!item) {
180
- continue;
181
- }
182
- const key = getAgentInputItemKey(item);
183
- const remaining = pendingWriteCounts.get(key) ?? 0;
184
- if (remaining <= 0) {
185
- continue;
186
- }
187
- pendingWriteCounts.set(key, remaining - 1);
188
- filtered.push(structuredClone(item));
189
- }
190
- }
191
- if (filtered.length > 0) {
192
- sessionInputFilteredSnapshot = filtered;
193
- }
194
- else if (sessionInputFilteredSnapshot === undefined) {
195
- sessionInputFilteredSnapshot = [];
196
- }
197
- };
198
- // Determine which items should be committed to session memory for this turn.
199
- // Filters take precedence because they reflect the exact payload delivered to the model.
200
- const resolveSessionItemsForPersistence = () => {
201
- if (sessionInputFilteredSnapshot !== undefined) {
202
- return sessionInputFilteredSnapshot;
203
- }
204
- if (hasCallModelInputFilter) {
205
- return undefined;
206
- }
207
- return sessionInputOriginalSnapshot;
208
- };
102
+ const sessionPersistence = createSessionPersistenceTracker({
103
+ session,
104
+ hasCallModelInputFilter,
105
+ persistInput: saveStreamInputToSession,
106
+ resumingFromState,
107
+ });
209
108
  let preparedInput = input;
210
109
  if (!(preparedInput instanceof RunState)) {
211
- if (session && Array.isArray(preparedInput) && !sessionInputCallback) {
212
- throw new UserError('RunConfig.sessionInputCallback must be provided when using session history with list inputs.');
213
- }
214
110
  const prepared = await prepareInputItemsWithSession(preparedInput, session, sessionInputCallback, {
215
111
  // When the server tracks conversation state we only send the new turn inputs;
216
112
  // previous messages are recovered via conversationId/previousResponseId.
@@ -231,48 +127,21 @@ export class Runner extends RunHooks {
231
127
  else {
232
128
  preparedInput = prepared.preparedInput;
233
129
  }
234
- if (session) {
235
- const items = prepared.sessionItems ?? [];
236
- // Clone the items that will be persisted so later mutations (filters, hooks) cannot desync history.
237
- sessionInputOriginalSnapshot = items.map((item) => structuredClone(item));
238
- // Reset pending counts so each prepared item reserves exactly one write slot until filters resolve matches.
239
- sessionInputPendingWriteCounts = new Map();
240
- for (const item of items) {
241
- const key = getAgentInputItemKey(item);
242
- sessionInputPendingWriteCounts.set(key, (sessionInputPendingWriteCounts.get(key) ?? 0) + 1);
243
- }
244
- }
130
+ sessionPersistence?.setPreparedItems(prepared.sessionItems);
245
131
  }
246
132
  // Streaming runs persist the input asynchronously, so track a one-shot helper
247
133
  // that can be awaited from multiple branches without double-writing.
248
- let ensureStreamInputPersisted;
249
- // Sessions remain usable alongside server-managed conversations (e.g., OpenAIConversationsSession)
250
- // so callers can reuse callbacks, resume-from-state logic, and other helpers without duplicating
251
- // remote history, so persistence is gated on serverManagesConversation.
252
- if (session && !serverManagesConversation) {
253
- let persisted = false;
254
- ensureStreamInputPersisted = async () => {
255
- if (persisted) {
256
- return;
257
- }
258
- const itemsToPersist = resolveSessionItemsForPersistence();
259
- if (!itemsToPersist || itemsToPersist.length === 0) {
260
- return;
261
- }
262
- persisted = true;
263
- await saveStreamInputToSession(session, itemsToPersist);
264
- };
265
- }
134
+ const ensureStreamInputPersisted = sessionPersistence?.buildPersistInputOnce(serverManagesConversation);
266
135
  const executeRun = async () => {
267
136
  if (effectiveOptions.stream) {
268
- const streamResult = await this.#runIndividualStream(agent, preparedInput, effectiveOptions, ensureStreamInputPersisted, recordSessionItemsForPersistence);
137
+ const streamResult = await this.#runIndividualStream(agent, preparedInput, effectiveOptions, ensureStreamInputPersisted, sessionPersistence?.recordTurnItems, preserveTurnPersistenceOnResume);
269
138
  return streamResult;
270
139
  }
271
- const runResult = await this.#runIndividualNonStream(agent, preparedInput, effectiveOptions, recordSessionItemsForPersistence);
140
+ const runResult = await this.#runIndividualNonStream(agent, preparedInput, effectiveOptions, sessionPersistence?.recordTurnItems, preserveTurnPersistenceOnResume);
272
141
  // See note above: allow sessions to run for callbacks/state but skip writes when the server
273
142
  // is the source of truth for transcript history.
274
- if (session && !serverManagesConversation) {
275
- await saveToSession(session, resolveSessionItemsForPersistence(), runResult);
143
+ if (sessionPersistence && !serverManagesConversation) {
144
+ await saveToSession(session, sessionPersistence.getItemsForPersistence(), runResult);
276
145
  }
277
146
  return runResult;
278
147
  };
@@ -289,6 +158,8 @@ export class Runner extends RunHooks {
289
158
  name: this.config.workflowName,
290
159
  groupId: this.config.groupId,
291
160
  metadata: this.config.traceMetadata,
161
+ // Per-run tracing config overrides exporter defaults such as environment API key.
162
+ tracingApiKey: tracingConfig?.apiKey,
292
163
  });
293
164
  }
294
165
  // --------------------------------------------------------------
@@ -296,23 +167,6 @@ export class Runner extends RunHooks {
296
167
  // --------------------------------------------------------------
297
168
  inputGuardrailDefs;
298
169
  outputGuardrailDefs;
299
- #getInputGuardrailDefinitions(state) {
300
- return this.inputGuardrailDefs.concat(state._currentAgent.inputGuardrails.map(defineInputGuardrail));
301
- }
302
- #splitInputGuardrails(state) {
303
- const guardrails = this.#getInputGuardrailDefinitions(state);
304
- const blocking = [];
305
- const parallel = [];
306
- for (const guardrail of guardrails) {
307
- if (guardrail.runInParallel === false) {
308
- blocking.push(guardrail);
309
- }
310
- else {
311
- parallel.push(guardrail);
312
- }
313
- }
314
- return { blocking, parallel };
315
- }
316
170
  /**
317
171
  * @internal
318
172
  * Resolves the effective model once so both run loops obey the same precedence rules.
@@ -322,11 +176,12 @@ export class Runner extends RunHooks {
322
176
  agent.model !== Agent.DEFAULT_MODEL_PLACEHOLDER) ||
323
177
  (this.config.model !== undefined &&
324
178
  this.config.model !== Agent.DEFAULT_MODEL_PLACEHOLDER);
325
- let resolvedModel = selectModel(agent.model, this.config.model);
326
- if (typeof resolvedModel === 'string') {
327
- resolvedModel = await this.config.modelProvider.getModel(resolvedModel);
328
- }
329
- return { model: resolvedModel, explictlyModelSet };
179
+ const selectedModel = selectModel(agent.model, this.config.model);
180
+ const resolvedModelName = typeof selectedModel === 'string' ? selectedModel : undefined;
181
+ const resolvedModel = typeof selectedModel === 'string'
182
+ ? await this.config.modelProvider.getModel(selectedModel)
183
+ : selectedModel;
184
+ return { model: resolvedModel, explictlyModelSet, resolvedModelName };
330
185
  }
331
186
  /**
332
187
  * @internal
@@ -334,7 +189,7 @@ export class Runner extends RunHooks {
334
189
  async #runIndividualNonStream(startingAgent, input, options,
335
190
  // sessionInputUpdate lets the caller adjust queued session items after filters run so we
336
191
  // persist exactly what we send to the model (e.g., after redactions or truncation).
337
- sessionInputUpdate) {
192
+ sessionInputUpdate, preserveTurnPersistenceOnResume) {
338
193
  return withNewSpanContext(async () => {
339
194
  // if we have a saved state we use that one, otherwise we create a new one
340
195
  const isResumedState = input instanceof RunState;
@@ -343,10 +198,17 @@ export class Runner extends RunHooks {
343
198
  : new RunState(options.context instanceof RunContext
344
199
  ? options.context
345
200
  : new RunContext(options.context), input, startingAgent, options.maxTurns ?? DEFAULT_MAX_TURNS);
346
- const serverConversationTracker = options.conversationId || options.previousResponseId
201
+ const resolvedConversationId = options.conversationId ??
202
+ (isResumedState ? state._conversationId : undefined);
203
+ const resolvedPreviousResponseId = options.previousResponseId ??
204
+ (isResumedState ? state._previousResponseId : undefined);
205
+ if (!isResumedState) {
206
+ state.setConversationContext(resolvedConversationId, resolvedPreviousResponseId);
207
+ }
208
+ const serverConversationTracker = resolvedConversationId || resolvedPreviousResponseId
347
209
  ? new ServerConversationTracker({
348
- conversationId: options.conversationId,
349
- previousResponseId: options.previousResponseId,
210
+ conversationId: resolvedConversationId,
211
+ previousResponseId: resolvedPreviousResponseId,
350
212
  })
351
213
  : undefined;
352
214
  if (serverConversationTracker && isResumedState) {
@@ -355,7 +217,9 @@ export class Runner extends RunHooks {
355
217
  generatedItems: state._generatedItems,
356
218
  modelResponses: state._modelResponses,
357
219
  });
220
+ state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
358
221
  }
222
+ // Tracks when we resume an approval interruption so the next run-again step stays in the same turn.
359
223
  let continuingInterruptedTurn = false;
360
224
  try {
361
225
  while (true) {
@@ -368,66 +232,60 @@ export class Runner extends RunHooks {
368
232
  if (!state._lastTurnResponse || !state._lastProcessedResponse) {
369
233
  throw new UserError('No model response found in previous state', state);
370
234
  }
371
- const turnResult = await resolveInterruptedTurn(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state);
372
- state._toolUseTracker.addToolUse(state._currentAgent, state._lastProcessedResponse.toolsUsed);
373
- state._originalInput = turnResult.originalInput;
374
- state._generatedItems = turnResult.generatedItems;
235
+ const interruptedOutcome = await resumeInterruptedTurn({
236
+ state,
237
+ runner: this,
238
+ });
375
239
  // Don't reset counter here - resolveInterruptedTurn already adjusted it via rewind logic
376
240
  // The counter will be reset when _currentTurn is incremented (starting a new turn)
377
- if (turnResult.nextStep.type === 'next_step_interruption') {
241
+ const { shouldReturn, shouldContinue } = handleInterruptedOutcome({
242
+ state,
243
+ outcome: interruptedOutcome,
244
+ setContinuingInterruptedTurn: (value) => {
245
+ continuingInterruptedTurn = value;
246
+ },
247
+ });
248
+ if (shouldReturn) {
378
249
  // we are still in an interruption, so we need to avoid an infinite loop
379
- state._currentStep = turnResult.nextStep;
380
250
  return new RunResult(state);
381
251
  }
382
- // If continuing from interruption with next_step_run_again, set step to undefined
383
- // so the loop treats it as a new step without incrementing the turn.
384
- // The counter has already been adjusted by resolveInterruptedTurn's rewind logic.
385
- if (turnResult.nextStep.type === 'next_step_run_again') {
386
- continuingInterruptedTurn = true;
387
- state._currentStep = undefined;
252
+ if (shouldContinue) {
388
253
  continue;
389
254
  }
390
- continuingInterruptedTurn = false;
391
- state._currentStep = turnResult.nextStep;
392
255
  }
393
256
  if (state._currentStep.type === 'next_step_run_again') {
394
- const artifacts = await prepareAgentArtifacts(state);
395
- const isResumingFromInterruption = isResumedState && continuingInterruptedTurn;
257
+ const wasContinuingInterruptedTurn = continuingInterruptedTurn;
396
258
  continuingInterruptedTurn = false;
397
- // Do not advance the turn when resuming from an interruption; the next model call is
398
- // still part of the same logical turn.
399
- if (!isResumingFromInterruption) {
400
- state._currentTurn++;
401
- state._currentTurnPersistedItemCount = 0;
402
- }
403
- if (state._currentTurn > state._maxTurns) {
404
- state._currentAgentSpan?.setError({
405
- message: 'Max turns exceeded',
406
- data: { max_turns: state._maxTurns },
407
- });
408
- throw new MaxTurnsExceededError(`Max turns (${state._maxTurns}) exceeded`, state);
409
- }
410
- logger.debug(`Running agent ${state._currentAgent.name} (turn ${state._currentTurn})`);
411
- let parallelGuardrailPromise;
412
- // Only run input guardrails on the first turn of a new run.
413
- if (state._currentTurn === 1 && !isResumingFromInterruption) {
414
- const guardrails = this.#splitInputGuardrails(state);
415
- if (guardrails.blocking.length > 0) {
416
- await this.#runInputGuardrails(state, guardrails.blocking);
417
- }
418
- if (guardrails.parallel.length > 0) {
419
- parallelGuardrailPromise = this.#runInputGuardrails(state, guardrails.parallel);
420
- parallelGuardrailPromise.catch(() => { });
421
- }
422
- }
423
- const turnInput = serverConversationTracker
424
- ? serverConversationTracker.prepareInput(state._originalInput, state._generatedItems)
425
- : getTurnInput(state._originalInput, state._generatedItems);
426
- if (state._noActiveAgentRun) {
427
- state._currentAgent.emit('agent_start', state._context, state._currentAgent, turnInput);
428
- this.emit('agent_start', state._context, state._currentAgent, turnInput);
259
+ const guardrailTracker = createGuardrailTracker();
260
+ const previousTurn = state._currentTurn;
261
+ const previousPersistedCount = state._currentTurnPersistedItemCount;
262
+ const previousGeneratedCount = state._generatedItems.length;
263
+ const { artifacts, turnInput, parallelGuardrailPromise } = await prepareTurn({
264
+ state,
265
+ input: state._originalInput,
266
+ generatedItems: state._generatedItems,
267
+ isResumedState,
268
+ preserveTurnPersistenceOnResume,
269
+ continuingInterruptedTurn: wasContinuingInterruptedTurn,
270
+ serverConversationTracker,
271
+ inputGuardrailDefs: this.inputGuardrailDefs,
272
+ guardrailHandlers: {
273
+ onParallelStart: guardrailTracker.markPending,
274
+ onParallelError: guardrailTracker.setError,
275
+ },
276
+ emitAgentStart: (context, agent, inputItems) => {
277
+ this.emit('agent_start', context, agent, inputItems);
278
+ },
279
+ });
280
+ if (preserveTurnPersistenceOnResume &&
281
+ state._currentTurn > previousTurn &&
282
+ previousPersistedCount <= previousGeneratedCount) {
283
+ // Preserve persisted offsets from a resumed run to avoid re-saving prior items.
284
+ state._currentTurnPersistedItemCount = previousPersistedCount;
429
285
  }
286
+ guardrailTracker.setPromise(parallelGuardrailPromise);
430
287
  const preparedCall = await this.#prepareModelCall(state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate);
288
+ guardrailTracker.throwIfError();
431
289
  state._lastTurnResponse = await preparedCall.model.getResponse({
432
290
  systemInstructions: preparedCall.modelInput.instructions,
433
291
  prompt: preparedCall.prompt,
@@ -446,56 +304,71 @@ export class Runner extends RunHooks {
446
304
  tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
447
305
  signal: options.signal,
448
306
  });
307
+ if (serverConversationTracker) {
308
+ serverConversationTracker.markInputAsSent(preparedCall.sourceItems, {
309
+ filterApplied: preparedCall.filterApplied,
310
+ allTurnItems: preparedCall.turnInput,
311
+ });
312
+ }
449
313
  state._modelResponses.push(state._lastTurnResponse);
450
314
  state._context.usage.add(state._lastTurnResponse.usage);
451
315
  state._noActiveAgentRun = false;
452
316
  // After each turn record the items echoed by the server so future requests only
453
317
  // include the incremental inputs that have not yet been acknowledged.
454
318
  serverConversationTracker?.trackServerItems(state._lastTurnResponse);
319
+ if (serverConversationTracker) {
320
+ state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
321
+ }
455
322
  const processedResponse = processModelResponse(state._lastTurnResponse, state._currentAgent, preparedCall.tools, preparedCall.handoffs);
456
323
  state._lastProcessedResponse = processedResponse;
457
324
  const turnResult = await resolveTurnAfterModelResponse(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state);
458
- state._toolUseTracker.addToolUse(state._currentAgent, state._lastProcessedResponse.toolsUsed);
459
- state._originalInput = turnResult.originalInput;
460
- state._generatedItems = turnResult.generatedItems;
461
- if (turnResult.nextStep.type === 'next_step_run_again') {
462
- state._currentTurnPersistedItemCount = 0;
463
- }
464
- state._currentStep = turnResult.nextStep;
465
- if (parallelGuardrailPromise) {
466
- await parallelGuardrailPromise;
467
- }
468
- }
469
- if (state._currentStep &&
470
- state._currentStep.type === 'next_step_final_output') {
471
- await this.#runOutputGuardrails(state, state._currentStep.output);
472
- this.emit('agent_end', state._context, state._currentAgent, state._currentStep.output);
473
- state._currentAgent.emit('agent_end', state._context, state._currentStep.output);
474
- return new RunResult(state);
475
- }
476
- else if (state._currentStep &&
477
- state._currentStep.type === 'next_step_handoff') {
478
- state._currentAgent = state._currentStep.newAgent;
479
- if (state._currentAgentSpan) {
480
- state._currentAgentSpan.end();
481
- resetCurrentSpan();
482
- state._currentAgentSpan = undefined;
483
- }
484
- state._noActiveAgentRun = true;
485
- // we've processed the handoff, so we need to run the loop again
486
- state._currentStep = { type: 'next_step_run_again' };
487
- }
488
- else if (state._currentStep &&
489
- state._currentStep.type === 'next_step_interruption') {
490
- // interrupted. Don't run any guardrails
491
- return new RunResult(state);
325
+ applyTurnResult({
326
+ state,
327
+ turnResult,
328
+ agent: state._currentAgent,
329
+ toolsUsed: state._lastProcessedResponse?.toolsUsed ?? [],
330
+ resetTurnPersistence: !isResumedState,
331
+ });
332
+ await guardrailTracker.awaitCompletion();
492
333
  }
493
- else {
334
+ const currentStep = state._currentStep;
335
+ if (!currentStep) {
494
336
  logger.debug('Running next loop');
337
+ continue;
338
+ }
339
+ switch (currentStep.type) {
340
+ case 'next_step_final_output':
341
+ await runOutputGuardrails(state, this.outputGuardrailDefs, currentStep.output);
342
+ state._currentTurnInProgress = false;
343
+ this.emit('agent_end', state._context, state._currentAgent, currentStep.output);
344
+ state._currentAgent.emit('agent_end', state._context, currentStep.output);
345
+ return new RunResult(state);
346
+ case 'next_step_handoff':
347
+ state.setCurrentAgent(currentStep.newAgent);
348
+ if (state._currentAgentSpan) {
349
+ state._currentAgentSpan.end();
350
+ resetCurrentSpan();
351
+ state.setCurrentAgentSpan(undefined);
352
+ }
353
+ state._noActiveAgentRun = true;
354
+ state._currentTurnInProgress = false;
355
+ // We've processed the handoff, so we need to run the loop again.
356
+ state._currentStep = { type: 'next_step_run_again' };
357
+ break;
358
+ case 'next_step_interruption':
359
+ // Interrupted. Don't run any guardrails.
360
+ return new RunResult(state);
361
+ case 'next_step_run_again':
362
+ state._currentTurnInProgress = false;
363
+ logger.debug('Running next loop');
364
+ break;
365
+ default:
366
+ logger.debug('Running next loop');
495
367
  }
496
368
  }
497
369
  }
498
370
  catch (err) {
371
+ state._currentTurnInProgress = false;
499
372
  if (state._currentAgentSpan) {
500
373
  state._currentAgentSpan.setError({
501
374
  message: 'Error in agent run',
@@ -526,16 +399,22 @@ export class Runner extends RunHooks {
526
399
  /**
527
400
  * @internal
528
401
  */
529
- async #runStreamLoop(result, options, isResumedState, ensureStreamInputPersisted, sessionInputUpdate) {
530
- const serverManagesConversation = Boolean(options.conversationId) || Boolean(options.previousResponseId);
402
+ async #runStreamLoop(result, options, isResumedState, ensureStreamInputPersisted, sessionInputUpdate, preserveTurnPersistenceOnResume) {
403
+ const resolvedConversationId = options.conversationId ?? result.state._conversationId;
404
+ const resolvedPreviousResponseId = options.previousResponseId ?? result.state._previousResponseId;
405
+ const serverManagesConversation = Boolean(resolvedConversationId) || Boolean(resolvedPreviousResponseId);
531
406
  const serverConversationTracker = serverManagesConversation
532
407
  ? new ServerConversationTracker({
533
- conversationId: options.conversationId,
534
- previousResponseId: options.previousResponseId,
408
+ conversationId: resolvedConversationId,
409
+ previousResponseId: resolvedPreviousResponseId,
535
410
  })
536
411
  : undefined;
537
- let handedInputToModel = false;
412
+ if (serverConversationTracker) {
413
+ result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
414
+ }
415
+ let sentInputToModel = false;
538
416
  let streamInputPersisted = false;
417
+ let guardrailTracker = createGuardrailTracker();
539
418
  const persistStreamInputIfNeeded = async () => {
540
419
  if (streamInputPersisted || !ensureStreamInputPersisted) {
541
420
  return;
@@ -544,13 +423,27 @@ export class Runner extends RunHooks {
544
423
  await ensureStreamInputPersisted();
545
424
  streamInputPersisted = true;
546
425
  };
426
+ let parallelGuardrailPromise;
427
+ const awaitGuardrailsAndPersistInput = async () => {
428
+ await guardrailTracker.awaitCompletion();
429
+ if (guardrailTracker.failed) {
430
+ throw guardrailTracker.error;
431
+ }
432
+ if (sentInputToModel &&
433
+ !streamInputPersisted &&
434
+ !guardrailTracker.failed) {
435
+ await persistStreamInputIfNeeded();
436
+ }
437
+ };
547
438
  if (serverConversationTracker && isResumedState) {
548
439
  serverConversationTracker.primeFromState({
549
440
  originalInput: result.state._originalInput,
550
441
  generatedItems: result.state._generatedItems,
551
442
  modelResponses: result.state._modelResponses,
552
443
  });
444
+ result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
553
445
  }
446
+ // Tracks when we resume an approval interruption so the next run-again step stays in the same turn.
554
447
  let continuingInterruptedTurn = false;
555
448
  try {
556
449
  while (true) {
@@ -564,119 +457,149 @@ export class Runner extends RunHooks {
564
457
  !result.state._lastProcessedResponse) {
565
458
  throw new UserError('No model response found in previous state', result.state);
566
459
  }
567
- const turnResult = await resolveInterruptedTurn(result.state._currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state);
568
- addStepToRunResult(result, turnResult);
569
- result.state._toolUseTracker.addToolUse(result.state._currentAgent, result.state._lastProcessedResponse.toolsUsed);
570
- result.state._originalInput = turnResult.originalInput;
571
- result.state._generatedItems = turnResult.generatedItems;
460
+ const interruptedOutcome = await resumeInterruptedTurn({
461
+ state: result.state,
462
+ runner: this,
463
+ onStepItems: (turnResult) => {
464
+ addStepToRunResult(result, turnResult);
465
+ },
466
+ });
572
467
  // Don't reset counter here - resolveInterruptedTurn already adjusted it via rewind logic
573
468
  // The counter will be reset when _currentTurn is incremented (starting a new turn)
574
- if (turnResult.nextStep.type === 'next_step_interruption') {
469
+ const { shouldReturn, shouldContinue } = handleInterruptedOutcome({
470
+ state: result.state,
471
+ outcome: interruptedOutcome,
472
+ setContinuingInterruptedTurn: (value) => {
473
+ continuingInterruptedTurn = value;
474
+ },
475
+ });
476
+ if (shouldReturn) {
575
477
  // we are still in an interruption, so we need to avoid an infinite loop
576
- result.state._currentStep = turnResult.nextStep;
577
478
  return;
578
479
  }
579
- // If continuing from interruption with next_step_run_again, set step to undefined
580
- // so the loop treats it as a new step without incrementing the turn.
581
- // The counter has already been adjusted by resolveInterruptedTurn's rewind logic.
582
- if (turnResult.nextStep.type === 'next_step_run_again') {
583
- continuingInterruptedTurn = true;
584
- result.state._currentStep = undefined;
480
+ if (shouldContinue) {
585
481
  continue;
586
482
  }
587
- continuingInterruptedTurn = false;
588
- result.state._currentStep = turnResult.nextStep;
589
483
  }
590
484
  if (result.state._currentStep.type === 'next_step_run_again') {
591
- const artifacts = await prepareAgentArtifacts(result.state);
592
- const isResumingFromInterruption = isResumedState && continuingInterruptedTurn;
485
+ parallelGuardrailPromise = undefined;
486
+ guardrailTracker = createGuardrailTracker();
487
+ const wasContinuingInterruptedTurn = continuingInterruptedTurn;
593
488
  continuingInterruptedTurn = false;
594
- // Do not advance the turn when resuming from an interruption; the next model call is
595
- // still part of the same logical turn.
596
- if (!isResumingFromInterruption) {
597
- result.state._currentTurn++;
598
- result.state._currentTurnPersistedItemCount = 0;
599
- }
600
- if (result.state._currentTurn > result.state._maxTurns) {
601
- result.state._currentAgentSpan?.setError({
602
- message: 'Max turns exceeded',
603
- data: { max_turns: result.state._maxTurns },
489
+ const previousTurn = result.state._currentTurn;
490
+ const previousPersistedCount = result.state._currentTurnPersistedItemCount;
491
+ const previousGeneratedCount = result.state._generatedItems.length;
492
+ const preparedTurn = await prepareTurn({
493
+ state: result.state,
494
+ input: result.input,
495
+ generatedItems: result.newItems,
496
+ isResumedState,
497
+ preserveTurnPersistenceOnResume,
498
+ continuingInterruptedTurn: wasContinuingInterruptedTurn,
499
+ serverConversationTracker,
500
+ inputGuardrailDefs: this.inputGuardrailDefs,
501
+ guardrailHandlers: {
502
+ onParallelStart: () => {
503
+ guardrailTracker.markPending();
504
+ },
505
+ onParallelError: (err) => {
506
+ guardrailTracker.setError(err);
507
+ },
508
+ },
509
+ emitAgentStart: (context, agent, inputItems) => {
510
+ this.emit('agent_start', context, agent, inputItems);
511
+ },
512
+ });
513
+ if (preserveTurnPersistenceOnResume &&
514
+ result.state._currentTurn > previousTurn &&
515
+ previousPersistedCount <= previousGeneratedCount) {
516
+ // Preserve persisted offsets from a resumed run to avoid re-saving prior items.
517
+ result.state._currentTurnPersistedItemCount =
518
+ previousPersistedCount;
519
+ }
520
+ const { artifacts, turnInput } = preparedTurn;
521
+ parallelGuardrailPromise = preparedTurn.parallelGuardrailPromise;
522
+ guardrailTracker.setPromise(parallelGuardrailPromise);
523
+ // If guardrails are still running, defer input persistence until they finish.
524
+ const delayStreamInputPersistence = guardrailTracker.pending;
525
+ const preparedCall = await this.#prepareModelCall(result.state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate);
526
+ guardrailTracker.throwIfError();
527
+ let finalResponse = undefined;
528
+ let inputMarked = false;
529
+ const markInputOnce = () => {
530
+ if (inputMarked || !serverConversationTracker) {
531
+ return;
532
+ }
533
+ // We only mark inputs as sent after receiving the first stream event,
534
+ // which is the earliest reliable confirmation that the server accepted
535
+ // the request. If the stream fails before any events, leave inputs
536
+ // unmarked so a retry can resend safely.
537
+ // Record the exact input that was sent so the server tracker can advance safely.
538
+ serverConversationTracker.markInputAsSent(preparedCall.sourceItems, {
539
+ filterApplied: preparedCall.filterApplied,
540
+ allTurnItems: preparedCall.turnInput,
604
541
  });
605
- throw new MaxTurnsExceededError(`Max turns (${result.state._maxTurns}) exceeded`, result.state);
542
+ inputMarked = true;
543
+ };
544
+ sentInputToModel = true;
545
+ if (!delayStreamInputPersistence) {
546
+ await persistStreamInputIfNeeded();
606
547
  }
607
- logger.debug(`Running agent ${currentAgent.name} (turn ${result.state._currentTurn})`);
608
- let guardrailError;
609
- let parallelGuardrailPromise;
610
- // Only run input guardrails on the first turn of a new run.
611
- if (result.state._currentTurn === 1 && !isResumingFromInterruption) {
612
- const guardrails = this.#splitInputGuardrails(result.state);
613
- if (guardrails.blocking.length > 0) {
614
- await this.#runInputGuardrails(result.state, guardrails.blocking);
615
- }
616
- if (guardrails.parallel.length > 0) {
617
- const promise = this.#runInputGuardrails(result.state, guardrails.parallel);
618
- parallelGuardrailPromise = promise.catch((err) => {
619
- guardrailError = err;
620
- return [];
621
- });
548
+ try {
549
+ for await (const event of preparedCall.model.getStreamedResponse({
550
+ systemInstructions: preparedCall.modelInput.instructions,
551
+ prompt: preparedCall.prompt,
552
+ // Streaming requests should also honor explicitly chosen models.
553
+ ...(preparedCall.explictlyModelSet
554
+ ? { overridePromptModel: true }
555
+ : {}),
556
+ input: preparedCall.modelInput.input,
557
+ previousResponseId: preparedCall.previousResponseId,
558
+ conversationId: preparedCall.conversationId,
559
+ modelSettings: preparedCall.modelSettings,
560
+ tools: preparedCall.serializedTools,
561
+ toolsExplicitlyProvided: preparedCall.toolsExplicitlyProvided,
562
+ handoffs: preparedCall.serializedHandoffs,
563
+ outputType: convertAgentOutputTypeToSerializable(currentAgent.outputType),
564
+ tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
565
+ signal: options.signal,
566
+ })) {
567
+ guardrailTracker.throwIfError();
568
+ markInputOnce();
569
+ if (event.type === 'response_done') {
570
+ const parsed = StreamEventResponseCompleted.parse(event);
571
+ finalResponse = {
572
+ usage: new Usage(parsed.response.usage),
573
+ output: parsed.response.output,
574
+ responseId: parsed.response.id,
575
+ };
576
+ result.state._context.usage.add(finalResponse.usage);
577
+ }
578
+ if (result.cancelled) {
579
+ // When the user's code exits a loop to consume the stream, we need to break
580
+ // this loop to prevent internal false errors and unnecessary processing
581
+ await awaitGuardrailsAndPersistInput();
582
+ return;
583
+ }
584
+ result._addItem(new RunRawModelStreamEvent(event));
622
585
  }
623
586
  }
624
- const turnInput = serverConversationTracker
625
- ? serverConversationTracker.prepareInput(result.input, result.newItems)
626
- : getTurnInput(result.input, result.newItems);
627
- if (result.state._noActiveAgentRun) {
628
- currentAgent.emit('agent_start', result.state._context, currentAgent, turnInput);
629
- this.emit('agent_start', result.state._context, currentAgent, turnInput);
630
- }
631
- let finalResponse = undefined;
632
- const preparedCall = await this.#prepareModelCall(result.state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate);
633
- if (guardrailError) {
634
- throw guardrailError;
635
- }
636
- handedInputToModel = true;
637
- await persistStreamInputIfNeeded();
638
- for await (const event of preparedCall.model.getStreamedResponse({
639
- systemInstructions: preparedCall.modelInput.instructions,
640
- prompt: preparedCall.prompt,
641
- // Streaming requests should also honor explicitly chosen models.
642
- ...(preparedCall.explictlyModelSet
643
- ? { overridePromptModel: true }
644
- : {}),
645
- input: preparedCall.modelInput.input,
646
- previousResponseId: preparedCall.previousResponseId,
647
- conversationId: preparedCall.conversationId,
648
- modelSettings: preparedCall.modelSettings,
649
- tools: preparedCall.serializedTools,
650
- toolsExplicitlyProvided: preparedCall.toolsExplicitlyProvided,
651
- handoffs: preparedCall.serializedHandoffs,
652
- outputType: convertAgentOutputTypeToSerializable(currentAgent.outputType),
653
- tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
654
- signal: options.signal,
655
- })) {
656
- if (guardrailError) {
657
- throw guardrailError;
658
- }
659
- if (event.type === 'response_done') {
660
- const parsed = StreamEventResponseCompleted.parse(event);
661
- finalResponse = {
662
- usage: new Usage(parsed.response.usage),
663
- output: parsed.response.output,
664
- responseId: parsed.response.id,
665
- };
666
- result.state._context.usage.add(finalResponse.usage);
667
- }
668
- if (result.cancelled) {
669
- // When the user's code exits a loop to consume the stream, we need to break
670
- // this loop to prevent internal false errors and unnecessary processing
587
+ catch (error) {
588
+ if (isAbortError(error)) {
589
+ if (sentInputToModel) {
590
+ markInputOnce();
591
+ }
592
+ await awaitGuardrailsAndPersistInput();
671
593
  return;
672
594
  }
673
- result._addItem(new RunRawModelStreamEvent(event));
595
+ throw error;
674
596
  }
675
- if (parallelGuardrailPromise) {
676
- await parallelGuardrailPromise;
677
- if (guardrailError) {
678
- throw guardrailError;
679
- }
597
+ if (finalResponse) {
598
+ markInputOnce();
599
+ }
600
+ await awaitGuardrailsAndPersistInput();
601
+ if (result.cancelled) {
602
+ return;
680
603
  }
681
604
  result.state._noActiveAgentRun = false;
682
605
  if (!finalResponse) {
@@ -685,6 +608,9 @@ export class Runner extends RunHooks {
685
608
  result.state._lastTurnResponse = finalResponse;
686
609
  // Keep the tracker in sync with the streamed response so reconnections remain accurate.
687
610
  serverConversationTracker?.trackServerItems(finalResponse);
611
+ if (serverConversationTracker) {
612
+ result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
613
+ }
688
614
  result.state._modelResponses.push(result.state._lastTurnResponse);
689
615
  const processedResponse = processModelResponse(result.state._lastTurnResponse, currentAgent, preparedCall.tools, preparedCall.handoffs);
690
616
  result.state._lastProcessedResponse = processedResponse;
@@ -695,58 +621,69 @@ export class Runner extends RunHooks {
695
621
  streamStepItemsToRunResult(result, processedResponse.newItems);
696
622
  }
697
623
  const turnResult = await resolveTurnAfterModelResponse(currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state);
698
- addStepToRunResult(result, turnResult, {
699
- skipItems: preToolItems,
624
+ applyTurnResult({
625
+ state: result.state,
626
+ turnResult,
627
+ agent: currentAgent,
628
+ toolsUsed: processedResponse.toolsUsed,
629
+ resetTurnPersistence: !isResumedState,
630
+ onStepItems: (step) => {
631
+ addStepToRunResult(result, step, { skipItems: preToolItems });
632
+ },
700
633
  });
701
- result.state._toolUseTracker.addToolUse(currentAgent, processedResponse.toolsUsed);
702
- result.state._originalInput = turnResult.originalInput;
703
- result.state._generatedItems = turnResult.generatedItems;
704
- if (turnResult.nextStep.type === 'next_step_run_again') {
705
- result.state._currentTurnPersistedItemCount = 0;
706
- }
707
- result.state._currentStep = turnResult.nextStep;
708
634
  }
709
- if (result.state._currentStep.type === 'next_step_final_output') {
710
- await this.#runOutputGuardrails(result.state, result.state._currentStep.output);
711
- await persistStreamInputIfNeeded();
712
- // Guardrails must succeed before persisting session memory to avoid storing blocked outputs.
713
- if (!serverManagesConversation) {
714
- await saveStreamResultToSession(options.session, result);
715
- }
716
- this.emit('agent_end', result.state._context, currentAgent, result.state._currentStep.output);
717
- currentAgent.emit('agent_end', result.state._context, result.state._currentStep.output);
718
- return;
719
- }
720
- else if (result.state._currentStep.type === 'next_step_interruption') {
721
- // we are done for now. Don't run any output guardrails
722
- await persistStreamInputIfNeeded();
723
- if (!serverManagesConversation) {
724
- await saveStreamResultToSession(options.session, result);
725
- }
726
- return;
727
- }
728
- else if (result.state._currentStep.type === 'next_step_handoff') {
729
- result.state._currentAgent = result.state._currentStep
730
- ?.newAgent;
731
- if (result.state._currentAgentSpan) {
732
- result.state._currentAgentSpan.end();
733
- resetCurrentSpan();
734
- }
735
- result.state._currentAgentSpan = undefined;
736
- result._addItem(new RunAgentUpdatedStreamEvent(result.state._currentAgent));
737
- result.state._noActiveAgentRun = true;
738
- // we've processed the handoff, so we need to run the loop again
739
- result.state._currentStep = {
740
- type: 'next_step_run_again',
741
- };
742
- }
743
- else {
744
- logger.debug('Running next loop');
635
+ const currentStep = result.state._currentStep;
636
+ switch (currentStep.type) {
637
+ case 'next_step_final_output':
638
+ await runOutputGuardrails(result.state, this.outputGuardrailDefs, currentStep.output);
639
+ result.state._currentTurnInProgress = false;
640
+ await persistStreamInputIfNeeded();
641
+ // Guardrails must succeed before persisting session memory to avoid storing blocked outputs.
642
+ if (!serverManagesConversation) {
643
+ await saveStreamResultToSession(options.session, result);
644
+ }
645
+ this.emit('agent_end', result.state._context, currentAgent, currentStep.output);
646
+ currentAgent.emit('agent_end', result.state._context, currentStep.output);
647
+ return;
648
+ case 'next_step_interruption':
649
+ // We are done for now. Don't run any output guardrails.
650
+ await persistStreamInputIfNeeded();
651
+ if (!serverManagesConversation) {
652
+ await saveStreamResultToSession(options.session, result);
653
+ }
654
+ return;
655
+ case 'next_step_handoff':
656
+ result.state.setCurrentAgent(currentStep.newAgent);
657
+ if (result.state._currentAgentSpan) {
658
+ result.state._currentAgentSpan.end();
659
+ resetCurrentSpan();
660
+ }
661
+ result.state.setCurrentAgentSpan(undefined);
662
+ result._addItem(new RunAgentUpdatedStreamEvent(result.state._currentAgent));
663
+ result.state._noActiveAgentRun = true;
664
+ result.state._currentTurnInProgress = false;
665
+ // We've processed the handoff, so we need to run the loop again.
666
+ result.state._currentStep = {
667
+ type: 'next_step_run_again',
668
+ };
669
+ break;
670
+ case 'next_step_run_again':
671
+ result.state._currentTurnInProgress = false;
672
+ logger.debug('Running next loop');
673
+ break;
674
+ default:
675
+ logger.debug('Running next loop');
745
676
  }
746
677
  }
747
678
  }
748
679
  catch (error) {
749
- if (handedInputToModel && !streamInputPersisted) {
680
+ result.state._currentTurnInProgress = false;
681
+ if (guardrailTracker.pending) {
682
+ await guardrailTracker.awaitCompletion({ suppressErrors: true });
683
+ }
684
+ if (sentInputToModel &&
685
+ !streamInputPersisted &&
686
+ !guardrailTracker.failed) {
750
687
  await persistStreamInputIfNeeded();
751
688
  }
752
689
  if (result.state._currentAgentSpan) {
@@ -758,6 +695,14 @@ export class Runner extends RunHooks {
758
695
  throw error;
759
696
  }
760
697
  finally {
698
+ if (guardrailTracker.pending) {
699
+ await guardrailTracker.awaitCompletion({ suppressErrors: true });
700
+ }
701
+ if (sentInputToModel &&
702
+ !streamInputPersisted &&
703
+ !guardrailTracker.failed) {
704
+ await persistStreamInputIfNeeded();
705
+ }
761
706
  if (result.state._currentStep?.type !== 'next_step_interruption') {
762
707
  try {
763
708
  await disposeResolvedComputers({ runContext: result.state._context });
@@ -777,7 +722,7 @@ export class Runner extends RunHooks {
777
722
  /**
778
723
  * @internal
779
724
  */
780
- async #runIndividualStream(agent, input, options, ensureStreamInputPersisted, sessionInputUpdate) {
725
+ async #runIndividualStream(agent, input, options, ensureStreamInputPersisted, sessionInputUpdate, preserveTurnPersistenceOnResume) {
781
726
  options = options ?? {};
782
727
  return withNewSpanContext(async () => {
783
728
  // Initialize or reuse existing state
@@ -787,15 +732,26 @@ export class Runner extends RunHooks {
787
732
  : new RunState(options.context instanceof RunContext
788
733
  ? options.context
789
734
  : new RunContext(options.context), input, agent, options.maxTurns ?? DEFAULT_MAX_TURNS);
735
+ const resolvedConversationId = options.conversationId ??
736
+ (isResumedState ? state._conversationId : undefined);
737
+ const resolvedPreviousResponseId = options.previousResponseId ??
738
+ (isResumedState ? state._previousResponseId : undefined);
739
+ if (!isResumedState) {
740
+ state.setConversationContext(resolvedConversationId, resolvedPreviousResponseId);
741
+ }
790
742
  // Initialize the streamed result with existing state
791
743
  const result = new StreamedRunResult({
792
744
  signal: options.signal,
793
745
  state,
794
746
  });
747
+ const streamOptions = {
748
+ ...options,
749
+ signal: result._getAbortSignal(),
750
+ };
795
751
  // Setup defaults
796
- result.maxTurns = options.maxTurns ?? state._maxTurns;
752
+ result.maxTurns = streamOptions.maxTurns ?? state._maxTurns;
797
753
  // Continue the stream loop without blocking
798
- const streamLoopPromise = this.#runStreamLoop(result, options, isResumedState, ensureStreamInputPersisted, sessionInputUpdate).then(() => {
754
+ const streamLoopPromise = this.#runStreamLoop(result, streamOptions, isResumedState, ensureStreamInputPersisted, sessionInputUpdate, preserveTurnPersistenceOnResume).then(() => {
799
755
  result._done();
800
756
  }, (err) => {
801
757
  result._raiseError(err);
@@ -805,108 +761,22 @@ export class Runner extends RunHooks {
805
761
  return result;
806
762
  });
807
763
  }
808
- async #runInputGuardrails(state, guardrailsOverride) {
809
- const guardrails = guardrailsOverride ?? this.#getInputGuardrailDefinitions(state);
810
- if (guardrails.length > 0) {
811
- const guardrailArgs = {
812
- agent: state._currentAgent,
813
- input: state._originalInput,
814
- context: state._context,
815
- };
816
- try {
817
- const results = await Promise.all(guardrails.map(async (guardrail) => {
818
- return withGuardrailSpan(async (span) => {
819
- const result = await guardrail.run(guardrailArgs);
820
- span.spanData.triggered = result.output.tripwireTriggered;
821
- return result;
822
- }, { data: { name: guardrail.name } }, state._currentAgentSpan);
823
- }));
824
- state._inputGuardrailResults.push(...results);
825
- for (const result of results) {
826
- if (result.output.tripwireTriggered) {
827
- if (state._currentAgentSpan) {
828
- state._currentAgentSpan.setError({
829
- message: 'Guardrail tripwire triggered',
830
- data: { guardrail: result.guardrail.name },
831
- });
832
- }
833
- throw new InputGuardrailTripwireTriggered(`Input guardrail triggered: ${JSON.stringify(result.output.outputInfo)}`, result, state);
834
- }
835
- }
836
- return results;
837
- }
838
- catch (e) {
839
- if (e instanceof InputGuardrailTripwireTriggered) {
840
- throw e;
841
- }
842
- // roll back the current turn to enable reruns
843
- state._currentTurn--;
844
- throw new GuardrailExecutionError(`Input guardrail failed to complete: ${e}`, e, state);
845
- }
846
- }
847
- return [];
848
- }
849
- async #runOutputGuardrails(state, output) {
850
- const guardrails = this.outputGuardrailDefs.concat(state._currentAgent.outputGuardrails.map(defineOutputGuardrail));
851
- if (guardrails.length > 0) {
852
- const agentOutput = state._currentAgent.processFinalOutput(output);
853
- const runOutput = getTurnInput([], state._generatedItems);
854
- const guardrailArgs = {
855
- agent: state._currentAgent,
856
- agentOutput,
857
- context: state._context,
858
- details: {
859
- modelResponse: state._lastTurnResponse,
860
- output: runOutput,
861
- },
862
- };
863
- try {
864
- const results = await Promise.all(guardrails.map(async (guardrail) => {
865
- return withGuardrailSpan(async (span) => {
866
- const result = await guardrail.run(guardrailArgs);
867
- span.spanData.triggered = result.output.tripwireTriggered;
868
- return result;
869
- }, { data: { name: guardrail.name } }, state._currentAgentSpan);
870
- }));
871
- for (const result of results) {
872
- if (result.output.tripwireTriggered) {
873
- if (state._currentAgentSpan) {
874
- state._currentAgentSpan.setError({
875
- message: 'Guardrail tripwire triggered',
876
- data: { guardrail: result.guardrail.name },
877
- });
878
- }
879
- throw new OutputGuardrailTripwireTriggered(`Output guardrail triggered: ${JSON.stringify(result.output.outputInfo)}`, result, state);
880
- }
881
- }
882
- }
883
- catch (e) {
884
- if (e instanceof OutputGuardrailTripwireTriggered) {
885
- throw e;
886
- }
887
- throw new GuardrailExecutionError(`Output guardrail failed to complete: ${e}`, e, state);
888
- }
889
- }
890
- }
891
764
  /**
892
765
  * @internal
893
766
  * Applies call-level filters and merges session updates so the model request mirrors exactly
894
767
  * what we persisted for history.
895
768
  */
896
769
  async #prepareModelCall(state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate) {
897
- const { model, explictlyModelSet } = await this.#resolveModelForAgent(state._currentAgent);
770
+ const { model, explictlyModelSet, resolvedModelName } = await this.#resolveModelForAgent(state._currentAgent);
898
771
  let modelSettings = {
899
772
  ...this.config.modelSettings,
900
773
  ...state._currentAgent.modelSettings,
901
774
  };
902
- modelSettings = adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, state._currentAgent.modelSettings, model, modelSettings);
775
+ modelSettings = adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, state._currentAgent.modelSettings, model, modelSettings, resolvedModelName);
903
776
  modelSettings = maybeResetToolChoice(state._currentAgent, state._toolUseTracker, modelSettings);
904
777
  const systemInstructions = await state._currentAgent.getSystemPrompt(state._context);
905
778
  const prompt = await state._currentAgent.getPrompt(state._context);
906
779
  const { modelInput, sourceItems, persistedItems, filterApplied } = await applyCallModelInputFilter(state._currentAgent, options.callModelInputFilter, state._context, turnInput, systemInstructions);
907
- // Inform the tracker which exact original objects made it to the provider so future turns
908
- // only send the delta that has not yet been acknowledged by the server.
909
- serverConversationTracker?.markInputAsSent(sourceItems);
910
780
  // Provide filtered clones whenever filters run so session history mirrors the model payload.
911
781
  // Returning an empty array is intentional: it tells the session layer to persist "nothing"
912
782
  // instead of falling back to the unfiltered originals when the filter redacts everything.
@@ -923,461 +793,18 @@ export class Runner extends RunHooks {
923
793
  prompt,
924
794
  previousResponseId,
925
795
  conversationId,
926
- };
927
- }
928
- }
929
- /**
930
- * Constructs the model input array for the current turn by combining the original turn input with
931
- * any new run items (excluding tool approval placeholders). This helps ensure that repeated calls
932
- * to the Responses API only send newly generated content.
933
- *
934
- * See: https://platform.openai.com/docs/guides/conversation-state?api-mode=responses.
935
- */
936
- export function getTurnInput(originalInput, generatedItems) {
937
- const rawItems = generatedItems
938
- .filter((item) => item.type !== 'tool_approval_item') // don't include approval items to avoid double function calls
939
- .map((item) => item.rawItem);
940
- return [...toAgentInputList(originalInput), ...rawItems];
941
- }
942
- // --------------------------------------------------------------
943
- // Internal helpers
944
- // --------------------------------------------------------------
945
- const DEFAULT_MAX_TURNS = 10;
946
- let _defaultRunner = undefined;
947
- function getDefaultRunner() {
948
- if (_defaultRunner) {
949
- return _defaultRunner;
950
- }
951
- _defaultRunner = new Runner();
952
- return _defaultRunner;
953
- }
954
- /**
955
- * Resolves the effective model for the next turn by giving precedence to the agent-specific
956
- * configuration when present, otherwise falling back to the runner-level default.
957
- */
958
- export function selectModel(agentModel, runConfigModel) {
959
- // When initializing an agent without model name, the model property is set to an empty string. So,
960
- // * agentModel === Agent.DEFAULT_MODEL_PLACEHOLDER & runConfigModel exists, runConfigModel will be used
961
- // * agentModel is set, the agentModel will be used over runConfigModel
962
- if ((typeof agentModel === 'string' &&
963
- agentModel !== Agent.DEFAULT_MODEL_PLACEHOLDER) ||
964
- agentModel // any truthy value
965
- ) {
966
- return agentModel;
967
- }
968
- return runConfigModel ?? agentModel ?? Agent.DEFAULT_MODEL_PLACEHOLDER;
969
- }
970
- /**
971
- * Normalizes tracing configuration into the format expected by model providers.
972
- * Returns `false` to disable tracing, `true` to include full payload data, or
973
- * `'enabled_without_data'` to omit sensitive content while still emitting spans.
974
- */
975
- export function getTracing(tracingDisabled, traceIncludeSensitiveData) {
976
- if (tracingDisabled) {
977
- return false;
978
- }
979
- if (traceIncludeSensitiveData) {
980
- return true;
981
- }
982
- return 'enabled_without_data';
983
- }
984
- /**
985
- * @internal
986
- */
987
- async function applyCallModelInputFilter(agent, callModelInputFilter, context, inputItems, systemInstructions) {
988
- const cloneInputItems = (items, map) => items.map((item) => {
989
- const cloned = structuredClone(item);
990
- if (map && cloned && typeof cloned === 'object') {
991
- map.set(cloned, item);
992
- }
993
- return cloned;
994
- });
995
- // Record the relationship between the cloned array passed to filters and the original inputs.
996
- const cloneMap = new WeakMap();
997
- const originalPool = buildAgentInputPool(inputItems);
998
- const fallbackOriginals = [];
999
- // Track any original object inputs so filtered replacements can still mark them as delivered.
1000
- for (const item of inputItems) {
1001
- if (item && typeof item === 'object') {
1002
- fallbackOriginals.push(item);
1003
- }
1004
- }
1005
- const removeFromFallback = (candidate) => {
1006
- if (!candidate || typeof candidate !== 'object') {
1007
- return;
1008
- }
1009
- const index = fallbackOriginals.findIndex((original) => original === candidate);
1010
- if (index !== -1) {
1011
- fallbackOriginals.splice(index, 1);
1012
- }
1013
- };
1014
- const takeFallbackOriginal = () => {
1015
- const next = fallbackOriginals.shift();
1016
- if (next) {
1017
- removeAgentInputFromPool(originalPool, next);
1018
- }
1019
- return next;
1020
- };
1021
- // Always create a deep copy so downstream mutations inside filters cannot affect
1022
- // the cached turn state.
1023
- const clonedBaseInput = cloneInputItems(inputItems, cloneMap);
1024
- const base = {
1025
- input: clonedBaseInput,
1026
- instructions: systemInstructions,
1027
- };
1028
- if (!callModelInputFilter) {
1029
- return {
1030
- modelInput: base,
1031
- sourceItems: [...inputItems],
1032
- persistedItems: [],
1033
- filterApplied: false,
1034
- };
1035
- }
1036
- try {
1037
- const result = await callModelInputFilter({
1038
- modelData: base,
1039
- agent,
1040
- context: context.context,
1041
- });
1042
- if (!result || !Array.isArray(result.input)) {
1043
- throw new UserError('callModelInputFilter must return a ModelInputData object with an input array.');
1044
- }
1045
- // Preserve a pointer to the original object backing each filtered clone so downstream
1046
- // trackers can keep their bookkeeping consistent even after redaction.
1047
- const sourceItems = result.input.map((item) => {
1048
- if (!item || typeof item !== 'object') {
1049
- return undefined;
1050
- }
1051
- const original = cloneMap.get(item);
1052
- if (original) {
1053
- removeFromFallback(original);
1054
- removeAgentInputFromPool(originalPool, original);
1055
- return original;
1056
- }
1057
- const key = getAgentInputItemKey(item);
1058
- const matchedByContent = takeAgentInputFromPool(originalPool, key);
1059
- if (matchedByContent) {
1060
- removeFromFallback(matchedByContent);
1061
- return matchedByContent;
1062
- }
1063
- const fallback = takeFallbackOriginal();
1064
- if (fallback) {
1065
- return fallback;
1066
- }
1067
- return undefined;
1068
- });
1069
- const clonedFilteredInput = cloneInputItems(result.input);
1070
- return {
1071
- modelInput: {
1072
- input: clonedFilteredInput,
1073
- instructions: typeof result.instructions === 'undefined'
1074
- ? systemInstructions
1075
- : result.instructions,
1076
- },
1077
796
  sourceItems,
1078
- persistedItems: clonedFilteredInput.map((item) => structuredClone(item)),
1079
- filterApplied: true,
797
+ filterApplied,
798
+ turnInput,
1080
799
  };
1081
800
  }
1082
- catch (error) {
1083
- addErrorToCurrentSpan({
1084
- message: 'Error in callModelInputFilter',
1085
- data: { error: String(error) },
1086
- });
1087
- throw error;
1088
- }
1089
- }
1090
- // Tracks which items have already been sent to or received from the Responses API when the caller
1091
- // supplies `conversationId`/`previousResponseId`. This ensures we only send the delta each turn.
1092
- class ServerConversationTracker {
1093
- // Conversation ID:
1094
- // - https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#using-the-conversations-api
1095
- // - https://platform.openai.com/docs/api-reference/conversations/create
1096
- conversationId;
1097
- // Previous Response ID:
1098
- // https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#passing-context-from-the-previous-response
1099
- previousResponseId;
1100
- // Using this flag because WeakSet does not provide a way to check its size
1101
- sentInitialInput = false;
1102
- // The items already sent to the model; using WeakSet for memory efficiency
1103
- sentItems = new WeakSet();
1104
- // The items received from the server; using WeakSet for memory efficiency
1105
- serverItems = new WeakSet();
1106
- // Track initial input items that have not yet been sent so they can be retried on later turns.
1107
- remainingInitialInput = null;
1108
- constructor({ conversationId, previousResponseId, }) {
1109
- this.conversationId = conversationId ?? undefined;
1110
- this.previousResponseId = previousResponseId ?? undefined;
1111
- }
1112
- /**
1113
- * Pre-populates tracker caches from an existing RunState when resuming server-managed runs.
1114
- */
1115
- primeFromState({ originalInput, generatedItems, modelResponses, }) {
1116
- if (this.sentInitialInput) {
1117
- return;
1118
- }
1119
- for (const item of toAgentInputList(originalInput)) {
1120
- if (item && typeof item === 'object') {
1121
- this.sentItems.add(item);
1122
- }
1123
- }
1124
- this.sentInitialInput = true;
1125
- this.remainingInitialInput = null;
1126
- const latestResponse = modelResponses[modelResponses.length - 1];
1127
- for (const response of modelResponses) {
1128
- for (const item of response.output) {
1129
- if (item && typeof item === 'object') {
1130
- this.serverItems.add(item);
1131
- }
1132
- }
1133
- }
1134
- if (!this.conversationId && latestResponse?.responseId) {
1135
- this.previousResponseId = latestResponse.responseId;
1136
- }
1137
- for (const item of generatedItems) {
1138
- const rawItem = item.rawItem;
1139
- if (!rawItem || typeof rawItem !== 'object') {
1140
- continue;
1141
- }
1142
- if (this.serverItems.has(rawItem)) {
1143
- this.sentItems.add(rawItem);
1144
- }
1145
- }
1146
- }
1147
- /**
1148
- * Records the raw items returned by the server so future delta calculations skip them.
1149
- * Also captures the latest response identifier to chain follow-up calls when possible.
1150
- */
1151
- trackServerItems(modelResponse) {
1152
- if (!modelResponse) {
1153
- return;
1154
- }
1155
- for (const item of modelResponse.output) {
1156
- if (item && typeof item === 'object') {
1157
- this.serverItems.add(item);
1158
- }
1159
- }
1160
- if (!this.conversationId && modelResponse.responseId) {
1161
- this.previousResponseId = modelResponse.responseId;
1162
- }
1163
- }
1164
- /**
1165
- * Returns the minimum set of items that still need to be delivered to the server for the
1166
- * current turn. This includes the original turn inputs (until acknowledged) plus any
1167
- * newly generated items that have not yet been echoed back by the API.
1168
- */
1169
- prepareInput(originalInput, generatedItems) {
1170
- const inputItems = [];
1171
- if (!this.sentInitialInput) {
1172
- const initialItems = toAgentInputList(originalInput);
1173
- // Preserve the full initial payload so a filter can drop items without losing their originals.
1174
- inputItems.push(...initialItems);
1175
- this.remainingInitialInput = initialItems.filter((item) => Boolean(item) && typeof item === 'object');
1176
- this.sentInitialInput = true;
1177
- }
1178
- else if (this.remainingInitialInput &&
1179
- this.remainingInitialInput.length > 0) {
1180
- // Re-queue prior initial items until the tracker confirms they were delivered to the API.
1181
- inputItems.push(...this.remainingInitialInput);
1182
- }
1183
- for (const item of generatedItems) {
1184
- if (item.type === 'tool_approval_item') {
1185
- continue;
1186
- }
1187
- const rawItem = item.rawItem;
1188
- if (!rawItem || typeof rawItem !== 'object') {
1189
- continue;
1190
- }
1191
- if (this.sentItems.has(rawItem) || this.serverItems.has(rawItem)) {
1192
- continue;
1193
- }
1194
- inputItems.push(rawItem);
1195
- }
1196
- return inputItems;
1197
- }
1198
- /**
1199
- * Marks the provided originals as delivered so future turns do not resend them and any
1200
- * pending initial inputs can be dropped once the server acknowledges receipt.
1201
- */
1202
- markInputAsSent(items) {
1203
- if (!items.length) {
1204
- return;
1205
- }
1206
- const delivered = new Set();
1207
- for (const item of items) {
1208
- if (!item || typeof item !== 'object' || delivered.has(item)) {
1209
- continue;
1210
- }
1211
- // Some inputs may be repeated in the filtered list; only mark unique originals once.
1212
- delivered.add(item);
1213
- this.sentItems.add(item);
1214
- }
1215
- if (!this.remainingInitialInput ||
1216
- this.remainingInitialInput.length === 0) {
1217
- return;
1218
- }
1219
- this.remainingInitialInput = this.remainingInitialInput.filter((item) => !delivered.has(item));
1220
- if (this.remainingInitialInput.length === 0) {
1221
- this.remainingInitialInput = null;
1222
- }
1223
- }
1224
- }
1225
- /**
1226
- * When the default model is a GPT-5 variant, agents may carry GPT-5-specific providerData
1227
- * (e.g., reasoning effort, text verbosity). If a run resolves to a non-GPT-5 model and the
1228
- * agent relied on the default model (i.e., no explicit model set), these GPT-5-only settings
1229
- * are incompatible and should be stripped to avoid runtime errors.
1230
- */
1231
- function adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, agentModelSettings, runnerModel, modelSettings) {
1232
- if (
1233
- // gpt-5 is enabled for the default model for agents
1234
- isGpt5Default() &&
1235
- // explicitly set model for the agent
1236
- explictlyModelSet &&
1237
- // this runner uses a non-gpt-5 model
1238
- (typeof runnerModel !== 'string' ||
1239
- !gpt5ReasoningSettingsRequired(runnerModel)) &&
1240
- (agentModelSettings.providerData?.reasoning ||
1241
- agentModelSettings.providerData?.text?.verbosity ||
1242
- agentModelSettings.providerData?.reasoning_effort)) {
1243
- const copiedModelSettings = { ...modelSettings };
1244
- // the incompatible parameters should be removed to avoid runtime errors
1245
- delete copiedModelSettings.providerData?.reasoning;
1246
- delete copiedModelSettings.providerData?.text?.verbosity;
1247
- delete copiedModelSettings.providerData?.reasoning_effort;
1248
- if (copiedModelSettings.reasoning) {
1249
- delete copiedModelSettings.reasoning.effort;
1250
- delete copiedModelSettings.reasoning.summary;
1251
- }
1252
- if (copiedModelSettings.text) {
1253
- delete copiedModelSettings.text.verbosity;
1254
- }
1255
- return copiedModelSettings;
1256
- }
1257
- return modelSettings;
1258
- }
1259
- /**
1260
- * @internal
1261
- * Collects tools/handoffs early so we can annotate spans before model execution begins.
1262
- */
1263
- async function prepareAgentArtifacts(state) {
1264
- const handoffs = await state._currentAgent.getEnabledHandoffs(state._context);
1265
- const tools = await state._currentAgent.getAllTools(state._context);
1266
- const computerTools = tools.filter((tool) => tool.type === 'computer');
1267
- if (computerTools.length > 0) {
1268
- await Promise.all(computerTools.map(async (tool) => {
1269
- await resolveComputer({ tool, runContext: state._context });
1270
- }));
1271
- }
1272
- if (!state._currentAgentSpan) {
1273
- const handoffNames = handoffs.map((h) => h.agentName);
1274
- state._currentAgentSpan = createAgentSpan({
1275
- data: {
1276
- name: state._currentAgent.name,
1277
- handoffs: handoffNames,
1278
- tools: tools.map((t) => t.name),
1279
- output_type: state._currentAgent.outputSchemaName,
1280
- },
1281
- });
1282
- state._currentAgentSpan.start();
1283
- setCurrentSpan(state._currentAgentSpan);
1284
- }
1285
- else {
1286
- state._currentAgentSpan.spanData.tools = tools.map((t) => t.name);
1287
- }
1288
- return {
1289
- handoffs,
1290
- tools,
1291
- serializedHandoffs: handoffs.map((handoff) => serializeHandoff(handoff)),
1292
- serializedTools: tools.map((tool) => serializeTool(tool)),
1293
- toolsExplicitlyProvided: state._currentAgent.hasExplicitToolConfig(),
1294
- };
1295
- }
1296
- function getAgentInputItemKey(item) {
1297
- // Deep serialization keeps binary inputs comparable after filters clone them.
1298
- return JSON.stringify(item, agentInputSerializationReplacer);
1299
- }
1300
- function buildAgentInputPool(items) {
1301
- // Track every original object so filters can safely return cloned copies.
1302
- const pool = new Map();
1303
- for (const item of items) {
1304
- const key = getAgentInputItemKey(item);
1305
- const existing = pool.get(key);
1306
- if (existing) {
1307
- existing.push(item);
1308
- }
1309
- else {
1310
- pool.set(key, [item]);
1311
- }
1312
- }
1313
- return pool;
1314
- }
1315
- function takeAgentInputFromPool(pool, key) {
1316
- // Prefer reusing the earliest untouched original to keep ordering stable.
1317
- const candidates = pool.get(key);
1318
- if (!candidates || candidates.length === 0) {
1319
- return undefined;
1320
- }
1321
- const [first] = candidates;
1322
- candidates.shift();
1323
- if (candidates.length === 0) {
1324
- pool.delete(key);
1325
- }
1326
- return first;
1327
- }
1328
- function removeAgentInputFromPool(pool, item) {
1329
- // Remove exactly the matched instance so duplicate payloads remain available.
1330
- const key = getAgentInputItemKey(item);
1331
- const candidates = pool.get(key);
1332
- if (!candidates || candidates.length === 0) {
1333
- return;
1334
- }
1335
- const index = candidates.findIndex((candidate) => candidate === item);
1336
- if (index === -1) {
1337
- return;
1338
- }
1339
- candidates.splice(index, 1);
1340
- if (candidates.length === 0) {
1341
- pool.delete(key);
1342
- }
1343
- }
1344
- function agentInputSerializationReplacer(_key, value) {
1345
- // Mirror runImplementation serialization so buffer snapshots round-trip.
1346
- if (value instanceof ArrayBuffer) {
1347
- return {
1348
- __type: 'ArrayBuffer',
1349
- data: encodeUint8ArrayToBase64(new Uint8Array(value)),
1350
- };
1351
- }
1352
- if (isArrayBufferView(value)) {
1353
- const view = value;
1354
- return {
1355
- __type: view.constructor.name,
1356
- data: encodeUint8ArrayToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
1357
- };
1358
- }
1359
- if (isNodeBuffer(value)) {
1360
- const view = value;
1361
- return {
1362
- __type: 'Buffer',
1363
- data: encodeUint8ArrayToBase64(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
1364
- };
1365
- }
1366
- if (isSerializedBufferSnapshot(value)) {
1367
- return {
1368
- __type: 'Buffer',
1369
- data: encodeUint8ArrayToBase64(Uint8Array.from(value.data)),
1370
- };
1371
- }
1372
- return value;
1373
- }
1374
- // Normalizes user-provided input into the structure the model expects. Strings become user messages,
1375
- // arrays are kept as-is so downstream loops can treat both scenarios uniformly.
1376
- function toAgentInputList(originalInput) {
1377
- // Allow callers to pass plain strings while preserving original item order.
1378
- if (typeof originalInput === 'string') {
1379
- return [{ type: 'message', role: 'user', content: originalInput }];
1380
- }
1381
- return [...originalInput];
1382
801
  }
802
+ // internal helpers and constants
803
+ let defaultRunner;
804
+ const getDefaultRunner = () => {
805
+ if (!defaultRunner) {
806
+ defaultRunner = new Runner();
807
+ }
808
+ return defaultRunner;
809
+ };
1383
810
  //# sourceMappingURL=run.mjs.map