@openai/agents-core 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/editor.d.ts +38 -0
  2. package/dist/editor.js +3 -0
  3. package/dist/editor.js.map +1 -0
  4. package/dist/editor.mjs +2 -0
  5. package/dist/editor.mjs.map +1 -0
  6. package/dist/extensions/handoffFilters.js +4 -0
  7. package/dist/extensions/handoffFilters.js.map +1 -1
  8. package/dist/extensions/handoffFilters.mjs +4 -0
  9. package/dist/extensions/handoffFilters.mjs.map +1 -1
  10. package/dist/index.d.ts +8 -2
  11. package/dist/index.js +8 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +3 -1
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/items.d.ts +518 -4
  16. package/dist/items.js +22 -1
  17. package/dist/items.js.map +1 -1
  18. package/dist/items.mjs +22 -1
  19. package/dist/items.mjs.map +1 -1
  20. package/dist/memory/memorySession.d.ts +22 -0
  21. package/dist/memory/memorySession.js +64 -0
  22. package/dist/memory/memorySession.js.map +1 -0
  23. package/dist/memory/memorySession.mjs +60 -0
  24. package/dist/memory/memorySession.mjs.map +1 -0
  25. package/dist/memory/session.d.ts +36 -0
  26. package/dist/memory/session.js +3 -0
  27. package/dist/memory/session.js.map +1 -0
  28. package/dist/memory/session.mjs +2 -0
  29. package/dist/memory/session.mjs.map +1 -0
  30. package/dist/metadata.js +2 -2
  31. package/dist/metadata.mjs +2 -2
  32. package/dist/model.d.ts +10 -2
  33. package/dist/run.d.ts +88 -8
  34. package/dist/run.js +859 -347
  35. package/dist/run.js.map +1 -1
  36. package/dist/run.mjs +859 -347
  37. package/dist/run.mjs.map +1 -1
  38. package/dist/runContext.js +2 -2
  39. package/dist/runContext.js.map +1 -1
  40. package/dist/runContext.mjs +2 -2
  41. package/dist/runContext.mjs.map +1 -1
  42. package/dist/runImplementation.d.ts +37 -3
  43. package/dist/runImplementation.js +1116 -319
  44. package/dist/runImplementation.js.map +1 -1
  45. package/dist/runImplementation.mjs +1106 -317
  46. package/dist/runImplementation.mjs.map +1 -1
  47. package/dist/runState.d.ts +4453 -785
  48. package/dist/runState.js +62 -3
  49. package/dist/runState.js.map +1 -1
  50. package/dist/runState.mjs +62 -3
  51. package/dist/runState.mjs.map +1 -1
  52. package/dist/shell.d.ts +36 -0
  53. package/dist/shell.js +3 -0
  54. package/dist/shell.js.map +1 -0
  55. package/dist/shell.mjs +2 -0
  56. package/dist/shell.mjs.map +1 -0
  57. package/dist/tool.d.ts +62 -1
  58. package/dist/tool.js +30 -0
  59. package/dist/tool.js.map +1 -1
  60. package/dist/tool.mjs +28 -0
  61. package/dist/tool.mjs.map +1 -1
  62. package/dist/types/aliases.d.ts +3 -3
  63. package/dist/types/protocol.d.ts +5470 -1519
  64. package/dist/types/protocol.js +74 -1
  65. package/dist/types/protocol.js.map +1 -1
  66. package/dist/types/protocol.mjs +73 -0
  67. package/dist/types/protocol.mjs.map +1 -1
  68. package/dist/utils/applyDiff.d.ts +9 -0
  69. package/dist/utils/applyDiff.js +275 -0
  70. package/dist/utils/applyDiff.js.map +1 -0
  71. package/dist/utils/applyDiff.mjs +272 -0
  72. package/dist/utils/applyDiff.mjs.map +1 -0
  73. package/dist/utils/index.d.ts +1 -0
  74. package/dist/utils/index.js +3 -1
  75. package/dist/utils/index.js.map +1 -1
  76. package/dist/utils/index.mjs +1 -0
  77. package/dist/utils/index.mjs.map +1 -1
  78. package/dist/utils/serialize.js +12 -0
  79. package/dist/utils/serialize.js.map +1 -1
  80. package/dist/utils/serialize.mjs +12 -0
  81. package/dist/utils/serialize.mjs.map +1 -1
  82. package/dist/utils/smartString.d.ts +9 -0
  83. package/dist/utils/smartString.js +15 -0
  84. package/dist/utils/smartString.js.map +1 -1
  85. package/dist/utils/smartString.mjs +14 -3
  86. package/dist/utils/smartString.mjs.map +1 -1
  87. package/package.json +3 -3
package/dist/run.js CHANGED
@@ -4,10 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Runner = void 0;
7
- exports.getTracing = getTracing;
7
+ exports.run = run;
8
8
  exports.getTurnInput = getTurnInput;
9
9
  exports.selectModel = selectModel;
10
- exports.run = run;
10
+ exports.getTracing = getTracing;
11
11
  const agent_1 = require("./agent.js");
12
12
  const guardrail_1 = require("./guardrail.js");
13
13
  const providers_1 = require("./providers.js");
@@ -26,142 +26,28 @@ const runState_1 = require("./runState.js");
26
26
  const protocol_1 = require("./types/protocol.js");
27
27
  const tools_1 = require("./utils/tools.js");
28
28
  const defaultModel_1 = require("./defaultModel.js");
29
- const DEFAULT_MAX_TURNS = 10;
30
- /**
31
- * @internal
32
- */
33
- function getTracing(tracingDisabled, traceIncludeSensitiveData) {
34
- if (tracingDisabled) {
35
- return false;
36
- }
37
- if (traceIncludeSensitiveData) {
38
- return true;
39
- }
40
- return 'enabled_without_data';
41
- }
42
- function toAgentInputList(originalInput) {
43
- if (typeof originalInput === 'string') {
44
- return [{ type: 'message', role: 'user', content: originalInput }];
45
- }
46
- return [...originalInput];
47
- }
48
- /**
49
- * Internal module for tracking the items in turns and ensuring that we don't send duplicate items.
50
- * This logic is vital for properly handling the items to send during multiple turns
51
- * when you use either `conversationId` or `previousResponseId`.
52
- * Both scenarios expect an agent loop to send only new items for each Responses API call.
53
- *
54
- * see also: https://platform.openai.com/docs/guides/conversation-state?api-mode=responses
55
- */
56
- class ServerConversationTracker {
57
- // Conversation ID:
58
- // - https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#using-the-conversations-api
59
- // - https://platform.openai.com/docs/api-reference/conversations/create
60
- conversationId;
61
- // Previous Response ID:
62
- // https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#passing-context-from-the-previous-response
63
- previousResponseId;
64
- // Using this flag because WeakSet does not provide a way to check its size
65
- sentInitialInput = false;
66
- // The items already sent to the model; using WeakSet for memory efficiency
67
- sentItems = new WeakSet();
68
- // The items received from the server; using WeakSet for memory efficiency
69
- serverItems = new WeakSet();
70
- constructor({ conversationId, previousResponseId, }) {
71
- this.conversationId = conversationId ?? undefined;
72
- this.previousResponseId = previousResponseId ?? undefined;
73
- }
74
- /**
75
- * Pre-populates tracker caches from an existing RunState when resuming server-managed runs.
76
- */
77
- primeFromState({ originalInput, generatedItems, modelResponses, }) {
78
- if (this.sentInitialInput) {
79
- return;
80
- }
81
- for (const item of toAgentInputList(originalInput)) {
82
- if (item && typeof item === 'object') {
83
- this.sentItems.add(item);
84
- }
85
- }
86
- this.sentInitialInput = true;
87
- const latestResponse = modelResponses[modelResponses.length - 1];
88
- for (const response of modelResponses) {
89
- for (const item of response.output) {
90
- if (item && typeof item === 'object') {
91
- this.serverItems.add(item);
92
- }
93
- }
94
- }
95
- if (!this.conversationId && latestResponse?.responseId) {
96
- this.previousResponseId = latestResponse.responseId;
97
- }
98
- for (const item of generatedItems) {
99
- const rawItem = item.rawItem;
100
- if (!rawItem || typeof rawItem !== 'object') {
101
- continue;
102
- }
103
- if (this.serverItems.has(rawItem)) {
104
- this.sentItems.add(rawItem);
105
- }
106
- }
107
- }
108
- trackServerItems(modelResponse) {
109
- if (!modelResponse) {
110
- return;
111
- }
112
- for (const item of modelResponse.output) {
113
- if (item && typeof item === 'object') {
114
- this.serverItems.add(item);
115
- }
116
- }
117
- if (!this.conversationId &&
118
- this.previousResponseId !== undefined &&
119
- modelResponse.responseId) {
120
- this.previousResponseId = modelResponse.responseId;
121
- }
29
+ const base64_1 = require("./utils/base64.js");
30
+ const smartString_1 = require("./utils/smartString.js");
31
+ async function run(agent, input, options) {
32
+ const runner = getDefaultRunner();
33
+ if (options?.stream) {
34
+ return await runner.run(agent, input, options);
122
35
  }
123
- prepareInput(originalInput, generatedItems) {
124
- const inputItems = [];
125
- if (!this.sentInitialInput) {
126
- const initialItems = toAgentInputList(originalInput);
127
- for (const item of initialItems) {
128
- inputItems.push(item);
129
- if (item && typeof item === 'object') {
130
- this.sentItems.add(item);
131
- }
132
- }
133
- this.sentInitialInput = true;
134
- }
135
- for (const item of generatedItems) {
136
- if (item.type === 'tool_approval_item') {
137
- continue;
138
- }
139
- const rawItem = item.rawItem;
140
- if (!rawItem || typeof rawItem !== 'object') {
141
- continue;
142
- }
143
- if (this.sentItems.has(rawItem) || this.serverItems.has(rawItem)) {
144
- continue;
145
- }
146
- inputItems.push(rawItem);
147
- this.sentItems.add(rawItem);
148
- }
149
- return inputItems;
36
+ else {
37
+ return await runner.run(agent, input, options);
150
38
  }
151
39
  }
152
- function getTurnInput(originalInput, generatedItems) {
153
- const rawItems = generatedItems
154
- .filter((item) => item.type !== 'tool_approval_item') // don't include approval items to avoid double function calls
155
- .map((item) => item.rawItem);
156
- return [...toAgentInputList(originalInput), ...rawItems];
157
- }
158
40
  /**
159
- * A Runner is responsible for running an agent workflow.
41
+ * Orchestrates agent execution, including guardrails, tool calls, session persistence, and
42
+ * tracing. Reuse a `Runner` instance when you want consistent configuration across multiple runs.
160
43
  */
161
44
  class Runner extends lifecycle_1.RunHooks {
162
45
  config;
163
- inputGuardrailDefs;
164
- outputGuardrailDefs;
46
+ /**
47
+ * Creates a runner with optional defaults that apply to every subsequent run invocation.
48
+ *
49
+ * @param config - Overrides for models, guardrails, tracing, or session behavior.
50
+ */
165
51
  constructor(config = {}) {
166
52
  super();
167
53
  this.config = {
@@ -177,14 +63,270 @@ class Runner extends lifecycle_1.RunHooks {
177
63
  traceId: config.traceId,
178
64
  groupId: config.groupId,
179
65
  traceMetadata: config.traceMetadata,
66
+ sessionInputCallback: config.sessionInputCallback,
67
+ callModelInputFilter: config.callModelInputFilter,
180
68
  };
181
69
  this.inputGuardrailDefs = (config.inputGuardrails ?? []).map(guardrail_1.defineInputGuardrail);
182
70
  this.outputGuardrailDefs = (config.outputGuardrails ?? []).map(guardrail_1.defineOutputGuardrail);
183
71
  }
72
+ async run(agent, input, options = {
73
+ stream: false,
74
+ context: undefined,
75
+ }) {
76
+ const resolvedOptions = options ?? { stream: false, context: undefined };
77
+ // Per-run options take precedence over runner defaults for session memory behavior.
78
+ const sessionInputCallback = resolvedOptions.sessionInputCallback ?? this.config.sessionInputCallback;
79
+ // Likewise allow callers to override callModelInputFilter on individual runs.
80
+ const callModelInputFilter = resolvedOptions.callModelInputFilter ?? this.config.callModelInputFilter;
81
+ const hasCallModelInputFilter = Boolean(callModelInputFilter);
82
+ const effectiveOptions = {
83
+ ...resolvedOptions,
84
+ sessionInputCallback,
85
+ callModelInputFilter,
86
+ };
87
+ const serverManagesConversation = Boolean(effectiveOptions.conversationId) ||
88
+ Boolean(effectiveOptions.previousResponseId);
89
+ // When the server tracks conversation history we defer to it for previous turns so local session
90
+ // persistence can focus solely on the new delta being generated in this process.
91
+ const session = effectiveOptions.session;
92
+ const resumingFromState = input instanceof runState_1.RunState;
93
+ let sessionInputOriginalSnapshot = session && resumingFromState ? [] : undefined;
94
+ let sessionInputFilteredSnapshot = undefined;
95
+ // Tracks remaining persistence slots per AgentInputItem key so resumed sessions only write each original occurrence once.
96
+ let sessionInputPendingWriteCounts = session && resumingFromState ? new Map() : undefined;
97
+ // Keeps track of which inputs should be written back to session memory. `sourceItems` reflects
98
+ // the original objects (so we can respect resume counts) while `filteredItems`, when present,
99
+ // contains the filtered/redacted clones that must be persisted for history.
100
+ // The helper reconciles the filtered copies produced by callModelInputFilter with their original
101
+ // counterparts so resume-from-state bookkeeping stays consistent and duplicate references only
102
+ // consume a single persistence slot.
103
+ const recordSessionItemsForPersistence = (sourceItems, filteredItems) => {
104
+ const pendingWriteCounts = sessionInputPendingWriteCounts;
105
+ if (filteredItems !== undefined) {
106
+ if (!pendingWriteCounts) {
107
+ sessionInputFilteredSnapshot = filteredItems.map((item) => structuredClone(item));
108
+ return;
109
+ }
110
+ const persistableItems = [];
111
+ const sourceOccurrenceCounts = new WeakMap();
112
+ // Track how many times each original object appears so duplicate references only consume one persistence slot.
113
+ for (const source of sourceItems) {
114
+ if (!source || typeof source !== 'object') {
115
+ continue;
116
+ }
117
+ const nextCount = (sourceOccurrenceCounts.get(source) ?? 0) + 1;
118
+ sourceOccurrenceCounts.set(source, nextCount);
119
+ }
120
+ // Let filtered items without a one-to-one source match claim any remaining persistence count.
121
+ const consumeAnyPendingWriteSlot = () => {
122
+ for (const [key, remaining] of pendingWriteCounts) {
123
+ if (remaining > 0) {
124
+ pendingWriteCounts.set(key, remaining - 1);
125
+ return true;
126
+ }
127
+ }
128
+ return false;
129
+ };
130
+ for (let i = 0; i < filteredItems.length; i++) {
131
+ const filteredItem = filteredItems[i];
132
+ if (!filteredItem) {
133
+ continue;
134
+ }
135
+ let allocated = false;
136
+ const source = sourceItems[i];
137
+ if (source && typeof source === 'object') {
138
+ const pendingOccurrences = (sourceOccurrenceCounts.get(source) ?? 0) - 1;
139
+ sourceOccurrenceCounts.set(source, pendingOccurrences);
140
+ if (pendingOccurrences > 0) {
141
+ continue;
142
+ }
143
+ const sourceKey = getAgentInputItemKey(source);
144
+ const remaining = pendingWriteCounts.get(sourceKey) ?? 0;
145
+ if (remaining > 0) {
146
+ pendingWriteCounts.set(sourceKey, remaining - 1);
147
+ persistableItems.push(structuredClone(filteredItem));
148
+ allocated = true;
149
+ continue;
150
+ }
151
+ }
152
+ const filteredKey = getAgentInputItemKey(filteredItem);
153
+ const filteredRemaining = pendingWriteCounts.get(filteredKey) ?? 0;
154
+ if (filteredRemaining > 0) {
155
+ pendingWriteCounts.set(filteredKey, filteredRemaining - 1);
156
+ persistableItems.push(structuredClone(filteredItem));
157
+ allocated = true;
158
+ continue;
159
+ }
160
+ if (!source && consumeAnyPendingWriteSlot()) {
161
+ persistableItems.push(structuredClone(filteredItem));
162
+ allocated = true;
163
+ }
164
+ if (!allocated &&
165
+ !source &&
166
+ sessionInputFilteredSnapshot === undefined) {
167
+ // Preserve at least one copy so later persistence resolves even when no counters remain.
168
+ persistableItems.push(structuredClone(filteredItem));
169
+ }
170
+ }
171
+ if (persistableItems.length > 0 ||
172
+ sessionInputFilteredSnapshot === undefined) {
173
+ sessionInputFilteredSnapshot = persistableItems;
174
+ }
175
+ return;
176
+ }
177
+ const filtered = [];
178
+ if (!pendingWriteCounts) {
179
+ for (const item of sourceItems) {
180
+ if (!item) {
181
+ continue;
182
+ }
183
+ filtered.push(structuredClone(item));
184
+ }
185
+ }
186
+ else {
187
+ for (const item of sourceItems) {
188
+ if (!item) {
189
+ continue;
190
+ }
191
+ const key = getAgentInputItemKey(item);
192
+ const remaining = pendingWriteCounts.get(key) ?? 0;
193
+ if (remaining <= 0) {
194
+ continue;
195
+ }
196
+ pendingWriteCounts.set(key, remaining - 1);
197
+ filtered.push(structuredClone(item));
198
+ }
199
+ }
200
+ if (filtered.length > 0) {
201
+ sessionInputFilteredSnapshot = filtered;
202
+ }
203
+ else if (sessionInputFilteredSnapshot === undefined) {
204
+ sessionInputFilteredSnapshot = [];
205
+ }
206
+ };
207
+ // Determine which items should be committed to session memory for this turn.
208
+ // Filters take precedence because they reflect the exact payload delivered to the model.
209
+ const resolveSessionItemsForPersistence = () => {
210
+ if (sessionInputFilteredSnapshot !== undefined) {
211
+ return sessionInputFilteredSnapshot;
212
+ }
213
+ if (hasCallModelInputFilter) {
214
+ return undefined;
215
+ }
216
+ return sessionInputOriginalSnapshot;
217
+ };
218
+ let preparedInput = input;
219
+ if (!(preparedInput instanceof runState_1.RunState)) {
220
+ if (session && Array.isArray(preparedInput) && !sessionInputCallback) {
221
+ throw new errors_1.UserError('RunConfig.sessionInputCallback must be provided when using session history with list inputs.');
222
+ }
223
+ const prepared = await (0, runImplementation_1.prepareInputItemsWithSession)(preparedInput, session, sessionInputCallback, {
224
+ // When the server tracks conversation state we only send the new turn inputs;
225
+ // previous messages are recovered via conversationId/previousResponseId.
226
+ includeHistoryInPreparedInput: !serverManagesConversation,
227
+ preserveDroppedNewItems: serverManagesConversation,
228
+ });
229
+ if (serverManagesConversation && session) {
230
+ // When the server manages memory we only persist the new turn inputs locally so the
231
+ // conversation service stays the single source of truth for prior exchanges.
232
+ const sessionItems = prepared.sessionItems;
233
+ if (sessionItems && sessionItems.length > 0) {
234
+ preparedInput = sessionItems;
235
+ }
236
+ else {
237
+ preparedInput = prepared.preparedInput;
238
+ }
239
+ }
240
+ else {
241
+ preparedInput = prepared.preparedInput;
242
+ }
243
+ if (session) {
244
+ const items = prepared.sessionItems ?? [];
245
+ // Clone the items that will be persisted so later mutations (filters, hooks) cannot desync history.
246
+ sessionInputOriginalSnapshot = items.map((item) => structuredClone(item));
247
+ // Reset pending counts so each prepared item reserves exactly one write slot until filters resolve matches.
248
+ sessionInputPendingWriteCounts = new Map();
249
+ for (const item of items) {
250
+ const key = getAgentInputItemKey(item);
251
+ sessionInputPendingWriteCounts.set(key, (sessionInputPendingWriteCounts.get(key) ?? 0) + 1);
252
+ }
253
+ }
254
+ }
255
+ // Streaming runs persist the input asynchronously, so track a one-shot helper
256
+ // that can be awaited from multiple branches without double-writing.
257
+ let ensureStreamInputPersisted;
258
+ // Sessions remain usable alongside server-managed conversations (e.g., OpenAIConversationsSession)
259
+ // so callers can reuse callbacks, resume-from-state logic, and other helpers without duplicating
260
+ // remote history, so persistence is gated on serverManagesConversation.
261
+ if (session && !serverManagesConversation) {
262
+ let persisted = false;
263
+ ensureStreamInputPersisted = async () => {
264
+ if (persisted) {
265
+ return;
266
+ }
267
+ const itemsToPersist = resolveSessionItemsForPersistence();
268
+ if (!itemsToPersist || itemsToPersist.length === 0) {
269
+ return;
270
+ }
271
+ persisted = true;
272
+ await (0, runImplementation_1.saveStreamInputToSession)(session, itemsToPersist);
273
+ };
274
+ }
275
+ const executeRun = async () => {
276
+ if (effectiveOptions.stream) {
277
+ const streamResult = await this.#runIndividualStream(agent, preparedInput, effectiveOptions, ensureStreamInputPersisted, recordSessionItemsForPersistence);
278
+ return streamResult;
279
+ }
280
+ const runResult = await this.#runIndividualNonStream(agent, preparedInput, effectiveOptions, recordSessionItemsForPersistence);
281
+ // See note above: allow sessions to run for callbacks/state but skip writes when the server
282
+ // is the source of truth for transcript history.
283
+ if (session && !serverManagesConversation) {
284
+ await (0, runImplementation_1.saveToSession)(session, resolveSessionItemsForPersistence(), runResult);
285
+ }
286
+ return runResult;
287
+ };
288
+ if (preparedInput instanceof runState_1.RunState && preparedInput._trace) {
289
+ return (0, context_1.withTrace)(preparedInput._trace, async () => {
290
+ if (preparedInput._currentAgentSpan) {
291
+ (0, context_1.setCurrentSpan)(preparedInput._currentAgentSpan);
292
+ }
293
+ return executeRun();
294
+ });
295
+ }
296
+ return (0, context_1.getOrCreateTrace)(async () => executeRun(), {
297
+ traceId: this.config.traceId,
298
+ name: this.config.workflowName,
299
+ groupId: this.config.groupId,
300
+ metadata: this.config.traceMetadata,
301
+ });
302
+ }
303
+ // --------------------------------------------------------------
304
+ // Internals
305
+ // --------------------------------------------------------------
306
+ inputGuardrailDefs;
307
+ outputGuardrailDefs;
184
308
  /**
185
309
  * @internal
310
+ * Resolves the effective model once so both run loops obey the same precedence rules.
186
311
  */
187
- async #runIndividualNonStream(startingAgent, input, options) {
312
+ async #resolveModelForAgent(agent) {
313
+ const explictlyModelSet = (agent.model !== undefined &&
314
+ agent.model !== agent_1.Agent.DEFAULT_MODEL_PLACEHOLDER) ||
315
+ (this.config.model !== undefined &&
316
+ this.config.model !== agent_1.Agent.DEFAULT_MODEL_PLACEHOLDER);
317
+ let resolvedModel = selectModel(agent.model, this.config.model);
318
+ if (typeof resolvedModel === 'string') {
319
+ resolvedModel = await this.config.modelProvider.getModel(resolvedModel);
320
+ }
321
+ return { model: resolvedModel, explictlyModelSet };
322
+ }
323
+ /**
324
+ * @internal
325
+ */
326
+ async #runIndividualNonStream(startingAgent, input, options,
327
+ // sessionInputUpdate lets the caller adjust queued session items after filters run so we
328
+ // persist exactly what we send to the model (e.g., after redactions or truncation).
329
+ sessionInputUpdate) {
188
330
  return (0, context_1.withNewSpanContext)(async () => {
189
331
  // if we have a saved state we use that one, otherwise we create a new one
190
332
  const isResumedState = input instanceof runState_1.RunState;
@@ -208,13 +350,6 @@ class Runner extends lifecycle_1.RunHooks {
208
350
  }
209
351
  try {
210
352
  while (true) {
211
- const explictlyModelSet = (state._currentAgent.model !== undefined &&
212
- state._currentAgent.model !== '') ||
213
- (this.config.model !== undefined && this.config.model !== '');
214
- let model = selectModel(state._currentAgent.model, this.config.model);
215
- if (typeof model === 'string') {
216
- model = await this.config.modelProvider.getModel(model);
217
- }
218
353
  // if we don't have a current step, we treat this as a new run
219
354
  state._currentStep = state._currentStep ?? {
220
355
  type: 'next_step_run_again',
@@ -224,10 +359,13 @@ class Runner extends lifecycle_1.RunHooks {
224
359
  if (!state._lastTurnResponse || !state._lastProcessedResponse) {
225
360
  throw new errors_1.UserError('No model response found in previous state', state);
226
361
  }
227
- const turnResult = await (0, runImplementation_1.executeInterruptedToolsAndSideEffects)(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state);
362
+ const turnResult = await (0, runImplementation_1.resolveInterruptedTurn)(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state);
228
363
  state._toolUseTracker.addToolUse(state._currentAgent, state._lastProcessedResponse.toolsUsed);
229
364
  state._originalInput = turnResult.originalInput;
230
365
  state._generatedItems = turnResult.generatedItems;
366
+ if (turnResult.nextStep.type === 'next_step_run_again') {
367
+ state._currentTurnPersistedItemCount = 0;
368
+ }
231
369
  state._currentStep = turnResult.nextStep;
232
370
  if (turnResult.nextStep.type === 'next_step_interruption') {
233
371
  // we are still in an interruption, so we need to avoid an infinite loop
@@ -236,26 +374,9 @@ class Runner extends lifecycle_1.RunHooks {
236
374
  continue;
237
375
  }
238
376
  if (state._currentStep.type === 'next_step_run_again') {
239
- const handoffs = await state._currentAgent.getEnabledHandoffs(state._context);
240
- if (!state._currentAgentSpan) {
241
- const handoffNames = handoffs.map((h) => h.agentName);
242
- state._currentAgentSpan = (0, tracing_1.createAgentSpan)({
243
- data: {
244
- name: state._currentAgent.name,
245
- handoffs: handoffNames,
246
- output_type: state._currentAgent.outputSchemaName,
247
- },
248
- });
249
- state._currentAgentSpan.start();
250
- (0, context_1.setCurrentSpan)(state._currentAgentSpan);
251
- }
252
- const tools = await state._currentAgent.getAllTools(state._context);
253
- const serializedTools = tools.map((t) => (0, serialize_1.serializeTool)(t));
254
- const serializedHandoffs = handoffs.map((h) => (0, serialize_1.serializeHandoff)(h));
255
- if (state._currentAgentSpan) {
256
- state._currentAgentSpan.spanData.tools = tools.map((t) => t.name);
257
- }
377
+ const artifacts = await prepareAgentArtifacts(state);
258
378
  state._currentTurn++;
379
+ state._currentTurnPersistedItemCount = 0;
259
380
  if (state._currentTurn > state._maxTurns) {
260
381
  state._currentAgentSpan?.setError({
261
382
  message: 'Max turns exceeded',
@@ -274,42 +395,39 @@ class Runner extends lifecycle_1.RunHooks {
274
395
  state._currentAgent.emit('agent_start', state._context, state._currentAgent);
275
396
  this.emit('agent_start', state._context, state._currentAgent);
276
397
  }
277
- let modelSettings = {
278
- ...this.config.modelSettings,
279
- ...state._currentAgent.modelSettings,
280
- };
281
- const agentModelSettings = state._currentAgent.modelSettings;
282
- modelSettings = adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, agentModelSettings, model, modelSettings);
283
- modelSettings = (0, runImplementation_1.maybeResetToolChoice)(state._currentAgent, state._toolUseTracker, modelSettings);
284
- const previousResponseId = serverConversationTracker?.previousResponseId ??
285
- options.previousResponseId;
286
- const conversationId = serverConversationTracker?.conversationId ??
287
- options.conversationId;
288
- state._lastTurnResponse = await model.getResponse({
289
- systemInstructions: await state._currentAgent.getSystemPrompt(state._context),
290
- prompt: await state._currentAgent.getPrompt(state._context),
398
+ const preparedCall = await this.#prepareModelCall(state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate);
399
+ state._lastTurnResponse = await preparedCall.model.getResponse({
400
+ systemInstructions: preparedCall.modelInput.instructions,
401
+ prompt: preparedCall.prompt,
291
402
  // Explicit agent/run config models should take precedence over prompt defaults.
292
- ...(explictlyModelSet ? { overridePromptModel: true } : {}),
293
- input: turnInput,
294
- previousResponseId,
295
- conversationId,
296
- modelSettings,
297
- tools: serializedTools,
403
+ ...(preparedCall.explictlyModelSet
404
+ ? { overridePromptModel: true }
405
+ : {}),
406
+ input: preparedCall.modelInput.input,
407
+ previousResponseId: preparedCall.previousResponseId,
408
+ conversationId: preparedCall.conversationId,
409
+ modelSettings: preparedCall.modelSettings,
410
+ tools: preparedCall.serializedTools,
298
411
  outputType: (0, tools_1.convertAgentOutputTypeToSerializable)(state._currentAgent.outputType),
299
- handoffs: serializedHandoffs,
412
+ handoffs: preparedCall.serializedHandoffs,
300
413
  tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
301
414
  signal: options.signal,
302
415
  });
303
416
  state._modelResponses.push(state._lastTurnResponse);
304
417
  state._context.usage.add(state._lastTurnResponse.usage);
305
418
  state._noActiveAgentRun = false;
419
+ // After each turn record the items echoed by the server so future requests only
420
+ // include the incremental inputs that have not yet been acknowledged.
306
421
  serverConversationTracker?.trackServerItems(state._lastTurnResponse);
307
- const processedResponse = (0, runImplementation_1.processModelResponse)(state._lastTurnResponse, state._currentAgent, tools, handoffs);
422
+ const processedResponse = (0, runImplementation_1.processModelResponse)(state._lastTurnResponse, state._currentAgent, preparedCall.tools, preparedCall.handoffs);
308
423
  state._lastProcessedResponse = processedResponse;
309
- const turnResult = await (0, runImplementation_1.executeToolsAndSideEffects)(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state);
424
+ const turnResult = await (0, runImplementation_1.resolveTurnAfterModelResponse)(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state);
310
425
  state._toolUseTracker.addToolUse(state._currentAgent, state._lastProcessedResponse.toolsUsed);
311
426
  state._originalInput = turnResult.originalInput;
312
427
  state._generatedItems = turnResult.generatedItems;
428
+ if (turnResult.nextStep.type === 'next_step_run_again') {
429
+ state._currentTurnPersistedItemCount = 0;
430
+ }
313
431
  state._currentStep = turnResult.nextStep;
314
432
  }
315
433
  if (state._currentStep &&
@@ -361,92 +479,27 @@ class Runner extends lifecycle_1.RunHooks {
361
479
  }
362
480
  });
363
481
  }
364
- async #runInputGuardrails(state) {
365
- const guardrails = this.inputGuardrailDefs.concat(state._currentAgent.inputGuardrails.map(guardrail_1.defineInputGuardrail));
366
- if (guardrails.length > 0) {
367
- const guardrailArgs = {
368
- agent: state._currentAgent,
369
- input: state._originalInput,
370
- context: state._context,
371
- };
372
- try {
373
- const results = await Promise.all(guardrails.map(async (guardrail) => {
374
- return (0, tracing_1.withGuardrailSpan)(async (span) => {
375
- const result = await guardrail.run(guardrailArgs);
376
- span.spanData.triggered = result.output.tripwireTriggered;
377
- return result;
378
- }, { data: { name: guardrail.name } }, state._currentAgentSpan);
379
- }));
380
- for (const result of results) {
381
- if (result.output.tripwireTriggered) {
382
- if (state._currentAgentSpan) {
383
- state._currentAgentSpan.setError({
384
- message: 'Guardrail tripwire triggered',
385
- data: { guardrail: result.guardrail.name },
386
- });
387
- }
388
- throw new errors_1.InputGuardrailTripwireTriggered(`Input guardrail triggered: ${JSON.stringify(result.output.outputInfo)}`, result, state);
389
- }
390
- }
391
- }
392
- catch (e) {
393
- if (e instanceof errors_1.InputGuardrailTripwireTriggered) {
394
- throw e;
395
- }
396
- // roll back the current turn to enable reruns
397
- state._currentTurn--;
398
- throw new errors_1.GuardrailExecutionError(`Input guardrail failed to complete: ${e}`, e, state);
482
+ /**
483
+ * @internal
484
+ */
485
+ async #runStreamLoop(result, options, isResumedState, ensureStreamInputPersisted, sessionInputUpdate) {
486
+ const serverManagesConversation = Boolean(options.conversationId) || Boolean(options.previousResponseId);
487
+ const serverConversationTracker = serverManagesConversation
488
+ ? new ServerConversationTracker({
489
+ conversationId: options.conversationId,
490
+ previousResponseId: options.previousResponseId,
491
+ })
492
+ : undefined;
493
+ let handedInputToModel = false;
494
+ let streamInputPersisted = false;
495
+ const persistStreamInputIfNeeded = async () => {
496
+ if (streamInputPersisted || !ensureStreamInputPersisted) {
497
+ return;
399
498
  }
400
- }
401
- }
402
- async #runOutputGuardrails(state, output) {
403
- const guardrails = this.outputGuardrailDefs.concat(state._currentAgent.outputGuardrails.map(guardrail_1.defineOutputGuardrail));
404
- if (guardrails.length > 0) {
405
- const agentOutput = state._currentAgent.processFinalOutput(output);
406
- const guardrailArgs = {
407
- agent: state._currentAgent,
408
- agentOutput,
409
- context: state._context,
410
- details: { modelResponse: state._lastTurnResponse },
411
- };
412
- try {
413
- const results = await Promise.all(guardrails.map(async (guardrail) => {
414
- return (0, tracing_1.withGuardrailSpan)(async (span) => {
415
- const result = await guardrail.run(guardrailArgs);
416
- span.spanData.triggered = result.output.tripwireTriggered;
417
- return result;
418
- }, { data: { name: guardrail.name } }, state._currentAgentSpan);
419
- }));
420
- for (const result of results) {
421
- if (result.output.tripwireTriggered) {
422
- if (state._currentAgentSpan) {
423
- state._currentAgentSpan.setError({
424
- message: 'Guardrail tripwire triggered',
425
- data: { guardrail: result.guardrail.name },
426
- });
427
- }
428
- throw new errors_1.OutputGuardrailTripwireTriggered(`Output guardrail triggered: ${JSON.stringify(result.output.outputInfo)}`, result, state);
429
- }
430
- }
431
- }
432
- catch (e) {
433
- if (e instanceof errors_1.OutputGuardrailTripwireTriggered) {
434
- throw e;
435
- }
436
- throw new errors_1.GuardrailExecutionError(`Output guardrail failed to complete: ${e}`, e, state);
437
- }
438
- }
439
- }
440
- /**
441
- * @internal
442
- */
443
- async #runStreamLoop(result, options, isResumedState) {
444
- const serverConversationTracker = options.conversationId || options.previousResponseId
445
- ? new ServerConversationTracker({
446
- conversationId: options.conversationId,
447
- previousResponseId: options.previousResponseId,
448
- })
449
- : undefined;
499
+ // Both success and error paths call this helper, so guard against multiple writes.
500
+ await ensureStreamInputPersisted();
501
+ streamInputPersisted = true;
502
+ };
450
503
  if (serverConversationTracker && isResumedState) {
451
504
  serverConversationTracker.primeFromState({
452
505
  originalInput: result.state._originalInput,
@@ -457,10 +510,6 @@ class Runner extends lifecycle_1.RunHooks {
457
510
  try {
458
511
  while (true) {
459
512
  const currentAgent = result.state._currentAgent;
460
- const handoffs = await currentAgent.getEnabledHandoffs(result.state._context);
461
- const tools = await currentAgent.getAllTools(result.state._context);
462
- const serializedTools = tools.map((t) => (0, serialize_1.serializeTool)(t));
463
- const serializedHandoffs = handoffs.map((h) => (0, serialize_1.serializeHandoff)(h));
464
513
  result.state._currentStep = result.state._currentStep ?? {
465
514
  type: 'next_step_run_again',
466
515
  };
@@ -470,11 +519,14 @@ class Runner extends lifecycle_1.RunHooks {
470
519
  !result.state._lastProcessedResponse) {
471
520
  throw new errors_1.UserError('No model response found in previous state', result.state);
472
521
  }
473
- const turnResult = await (0, runImplementation_1.executeInterruptedToolsAndSideEffects)(result.state._currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state);
522
+ const turnResult = await (0, runImplementation_1.resolveInterruptedTurn)(result.state._currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state);
474
523
  (0, runImplementation_1.addStepToRunResult)(result, turnResult);
475
524
  result.state._toolUseTracker.addToolUse(result.state._currentAgent, result.state._lastProcessedResponse.toolsUsed);
476
525
  result.state._originalInput = turnResult.originalInput;
477
526
  result.state._generatedItems = turnResult.generatedItems;
527
+ if (turnResult.nextStep.type === 'next_step_run_again') {
528
+ result.state._currentTurnPersistedItemCount = 0;
529
+ }
478
530
  result.state._currentStep = turnResult.nextStep;
479
531
  if (turnResult.nextStep.type === 'next_step_interruption') {
480
532
  // we are still in an interruption, so we need to avoid an infinite loop
@@ -483,20 +535,9 @@ class Runner extends lifecycle_1.RunHooks {
483
535
  continue;
484
536
  }
485
537
  if (result.state._currentStep.type === 'next_step_run_again') {
486
- if (!result.state._currentAgentSpan) {
487
- const handoffNames = handoffs.map((h) => h.agentName);
488
- result.state._currentAgentSpan = (0, tracing_1.createAgentSpan)({
489
- data: {
490
- name: currentAgent.name,
491
- handoffs: handoffNames,
492
- tools: tools.map((t) => t.name),
493
- output_type: currentAgent.outputSchemaName,
494
- },
495
- });
496
- result.state._currentAgentSpan.start();
497
- (0, context_1.setCurrentSpan)(result.state._currentAgentSpan);
498
- }
538
+ const artifacts = await prepareAgentArtifacts(result.state);
499
539
  result.state._currentTurn++;
540
+ result.state._currentTurnPersistedItemCount = 0;
500
541
  if (result.state._currentTurn > result.state._maxTurns) {
501
542
  result.state._currentAgentSpan?.setError({
502
543
  message: 'Max turns exceeded',
@@ -505,22 +546,9 @@ class Runner extends lifecycle_1.RunHooks {
505
546
  throw new errors_1.MaxTurnsExceededError(`Max turns (${result.state._maxTurns}) exceeded`, result.state);
506
547
  }
507
548
  logger_1.default.debug(`Running agent ${currentAgent.name} (turn ${result.state._currentTurn})`);
508
- const explictlyModelSet = (currentAgent.model !== undefined && currentAgent.model !== '') ||
509
- (this.config.model !== undefined && this.config.model !== '');
510
- let model = selectModel(currentAgent.model, this.config.model);
511
- if (typeof model === 'string') {
512
- model = await this.config.modelProvider.getModel(model);
513
- }
514
549
  if (result.state._currentTurn === 1) {
515
550
  await this.#runInputGuardrails(result.state);
516
551
  }
517
- let modelSettings = {
518
- ...this.config.modelSettings,
519
- ...currentAgent.modelSettings,
520
- };
521
- const agentModelSettings = currentAgent.modelSettings;
522
- modelSettings = adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, agentModelSettings, model, modelSettings);
523
- modelSettings = (0, runImplementation_1.maybeResetToolChoice)(currentAgent, result.state._toolUseTracker, modelSettings);
524
552
  const turnInput = serverConversationTracker
525
553
  ? serverConversationTracker.prepareInput(result.input, result.newItems)
526
554
  : getTurnInput(result.input, result.newItems);
@@ -529,20 +557,22 @@ class Runner extends lifecycle_1.RunHooks {
529
557
  this.emit('agent_start', result.state._context, currentAgent);
530
558
  }
531
559
  let finalResponse = undefined;
532
- const previousResponseId = serverConversationTracker?.previousResponseId ??
533
- options.previousResponseId;
534
- const conversationId = serverConversationTracker?.conversationId ?? options.conversationId;
535
- for await (const event of model.getStreamedResponse({
536
- systemInstructions: await currentAgent.getSystemPrompt(result.state._context),
537
- prompt: await currentAgent.getPrompt(result.state._context),
560
+ const preparedCall = await this.#prepareModelCall(result.state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate);
561
+ handedInputToModel = true;
562
+ await persistStreamInputIfNeeded();
563
+ for await (const event of preparedCall.model.getStreamedResponse({
564
+ systemInstructions: preparedCall.modelInput.instructions,
565
+ prompt: preparedCall.prompt,
538
566
  // Streaming requests should also honor explicitly chosen models.
539
- ...(explictlyModelSet ? { overridePromptModel: true } : {}),
540
- input: turnInput,
541
- previousResponseId,
542
- conversationId,
543
- modelSettings,
544
- tools: serializedTools,
545
- handoffs: serializedHandoffs,
567
+ ...(preparedCall.explictlyModelSet
568
+ ? { overridePromptModel: true }
569
+ : {}),
570
+ input: preparedCall.modelInput.input,
571
+ previousResponseId: preparedCall.previousResponseId,
572
+ conversationId: preparedCall.conversationId,
573
+ modelSettings: preparedCall.modelSettings,
574
+ tools: preparedCall.serializedTools,
575
+ handoffs: preparedCall.serializedHandoffs,
546
576
  outputType: (0, tools_1.convertAgentOutputTypeToSerializable)(currentAgent.outputType),
547
577
  tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
548
578
  signal: options.signal,
@@ -567,9 +597,10 @@ class Runner extends lifecycle_1.RunHooks {
567
597
  throw new errors_1.ModelBehaviorError('Model did not produce a final response!', result.state);
568
598
  }
569
599
  result.state._lastTurnResponse = finalResponse;
600
+ // Keep the tracker in sync with the streamed response so reconnections remain accurate.
570
601
  serverConversationTracker?.trackServerItems(finalResponse);
571
602
  result.state._modelResponses.push(result.state._lastTurnResponse);
572
- const processedResponse = (0, runImplementation_1.processModelResponse)(result.state._lastTurnResponse, currentAgent, tools, handoffs);
603
+ const processedResponse = (0, runImplementation_1.processModelResponse)(result.state._lastTurnResponse, currentAgent, preparedCall.tools, preparedCall.handoffs);
573
604
  result.state._lastProcessedResponse = processedResponse;
574
605
  // Record the items emitted directly from the model response so we do not
575
606
  // stream them again after tools and other side effects finish.
@@ -577,23 +608,35 @@ class Runner extends lifecycle_1.RunHooks {
577
608
  if (preToolItems.size > 0) {
578
609
  (0, runImplementation_1.streamStepItemsToRunResult)(result, processedResponse.newItems);
579
610
  }
580
- const turnResult = await (0, runImplementation_1.executeToolsAndSideEffects)(currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state);
611
+ const turnResult = await (0, runImplementation_1.resolveTurnAfterModelResponse)(currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state);
581
612
  (0, runImplementation_1.addStepToRunResult)(result, turnResult, {
582
613
  skipItems: preToolItems,
583
614
  });
584
615
  result.state._toolUseTracker.addToolUse(currentAgent, processedResponse.toolsUsed);
585
616
  result.state._originalInput = turnResult.originalInput;
586
617
  result.state._generatedItems = turnResult.generatedItems;
618
+ if (turnResult.nextStep.type === 'next_step_run_again') {
619
+ result.state._currentTurnPersistedItemCount = 0;
620
+ }
587
621
  result.state._currentStep = turnResult.nextStep;
588
622
  }
589
623
  if (result.state._currentStep.type === 'next_step_final_output') {
590
624
  await this.#runOutputGuardrails(result.state, result.state._currentStep.output);
625
+ await persistStreamInputIfNeeded();
626
+ // Guardrails must succeed before persisting session memory to avoid storing blocked outputs.
627
+ if (!serverManagesConversation) {
628
+ await (0, runImplementation_1.saveStreamResultToSession)(options.session, result);
629
+ }
591
630
  this.emit('agent_end', result.state._context, currentAgent, result.state._currentStep.output);
592
631
  currentAgent.emit('agent_end', result.state._context, result.state._currentStep.output);
593
632
  return;
594
633
  }
595
634
  else if (result.state._currentStep.type === 'next_step_interruption') {
596
635
  // we are done for now. Don't run any output guardrails
636
+ await persistStreamInputIfNeeded();
637
+ if (!serverManagesConversation) {
638
+ await (0, runImplementation_1.saveStreamResultToSession)(options.session, result);
639
+ }
597
640
  return;
598
641
  }
599
642
  else if (result.state._currentStep.type === 'next_step_handoff') {
@@ -617,6 +660,9 @@ class Runner extends lifecycle_1.RunHooks {
617
660
  }
618
661
  }
619
662
  catch (error) {
663
+ if (handedInputToModel && !streamInputPersisted) {
664
+ await persistStreamInputIfNeeded();
665
+ }
620
666
  if (result.state._currentAgentSpan) {
621
667
  result.state._currentAgentSpan.setError({
622
668
  message: 'Error in agent run',
@@ -637,7 +683,7 @@ class Runner extends lifecycle_1.RunHooks {
637
683
  /**
638
684
  * @internal
639
685
  */
640
- async #runIndividualStream(agent, input, options) {
686
+ async #runIndividualStream(agent, input, options, ensureStreamInputPersisted, sessionInputUpdate) {
641
687
  options = options ?? {};
642
688
  return (0, context_1.withNewSpanContext)(async () => {
643
689
  // Initialize or reuse existing state
@@ -655,7 +701,7 @@ class Runner extends lifecycle_1.RunHooks {
655
701
  // Setup defaults
656
702
  result.maxTurns = options.maxTurns ?? state._maxTurns;
657
703
  // Continue the stream loop without blocking
658
- const streamLoopPromise = this.#runStreamLoop(result, options, isResumedState).then(() => {
704
+ const streamLoopPromise = this.#runStreamLoop(result, options, isResumedState, ensureStreamInputPersisted, sessionInputUpdate).then(() => {
659
705
  result._done();
660
706
  }, (err) => {
661
707
  result._raiseError(err);
@@ -665,39 +711,138 @@ class Runner extends lifecycle_1.RunHooks {
665
711
  return result;
666
712
  });
667
713
  }
668
- run(agent, input, options = {
669
- stream: false,
670
- context: undefined,
671
- }) {
672
- if (input instanceof runState_1.RunState && input._trace) {
673
- return (0, context_1.withTrace)(input._trace, async () => {
674
- if (input._currentAgentSpan) {
675
- (0, context_1.setCurrentSpan)(input._currentAgentSpan);
676
- }
677
- if (options?.stream) {
678
- return this.#runIndividualStream(agent, input, options);
714
+ async #runInputGuardrails(state) {
715
+ const guardrails = this.inputGuardrailDefs.concat(state._currentAgent.inputGuardrails.map(guardrail_1.defineInputGuardrail));
716
+ if (guardrails.length > 0) {
717
+ const guardrailArgs = {
718
+ agent: state._currentAgent,
719
+ input: state._originalInput,
720
+ context: state._context,
721
+ };
722
+ try {
723
+ const results = await Promise.all(guardrails.map(async (guardrail) => {
724
+ return (0, tracing_1.withGuardrailSpan)(async (span) => {
725
+ const result = await guardrail.run(guardrailArgs);
726
+ span.spanData.triggered = result.output.tripwireTriggered;
727
+ return result;
728
+ }, { data: { name: guardrail.name } }, state._currentAgentSpan);
729
+ }));
730
+ for (const result of results) {
731
+ if (result.output.tripwireTriggered) {
732
+ if (state._currentAgentSpan) {
733
+ state._currentAgentSpan.setError({
734
+ message: 'Guardrail tripwire triggered',
735
+ data: { guardrail: result.guardrail.name },
736
+ });
737
+ }
738
+ throw new errors_1.InputGuardrailTripwireTriggered(`Input guardrail triggered: ${JSON.stringify(result.output.outputInfo)}`, result, state);
739
+ }
679
740
  }
680
- else {
681
- return this.#runIndividualNonStream(agent, input, options);
741
+ }
742
+ catch (e) {
743
+ if (e instanceof errors_1.InputGuardrailTripwireTriggered) {
744
+ throw e;
682
745
  }
683
- });
746
+ // roll back the current turn to enable reruns
747
+ state._currentTurn--;
748
+ throw new errors_1.GuardrailExecutionError(`Input guardrail failed to complete: ${e}`, e, state);
749
+ }
684
750
  }
685
- return (0, context_1.getOrCreateTrace)(async () => {
686
- if (options?.stream) {
687
- return this.#runIndividualStream(agent, input, options);
751
+ }
752
+ async #runOutputGuardrails(state, output) {
753
+ const guardrails = this.outputGuardrailDefs.concat(state._currentAgent.outputGuardrails.map(guardrail_1.defineOutputGuardrail));
754
+ if (guardrails.length > 0) {
755
+ const agentOutput = state._currentAgent.processFinalOutput(output);
756
+ const guardrailArgs = {
757
+ agent: state._currentAgent,
758
+ agentOutput,
759
+ context: state._context,
760
+ details: { modelResponse: state._lastTurnResponse },
761
+ };
762
+ try {
763
+ const results = await Promise.all(guardrails.map(async (guardrail) => {
764
+ return (0, tracing_1.withGuardrailSpan)(async (span) => {
765
+ const result = await guardrail.run(guardrailArgs);
766
+ span.spanData.triggered = result.output.tripwireTriggered;
767
+ return result;
768
+ }, { data: { name: guardrail.name } }, state._currentAgentSpan);
769
+ }));
770
+ for (const result of results) {
771
+ if (result.output.tripwireTriggered) {
772
+ if (state._currentAgentSpan) {
773
+ state._currentAgentSpan.setError({
774
+ message: 'Guardrail tripwire triggered',
775
+ data: { guardrail: result.guardrail.name },
776
+ });
777
+ }
778
+ throw new errors_1.OutputGuardrailTripwireTriggered(`Output guardrail triggered: ${JSON.stringify(result.output.outputInfo)}`, result, state);
779
+ }
780
+ }
688
781
  }
689
- else {
690
- return this.#runIndividualNonStream(agent, input, options);
782
+ catch (e) {
783
+ if (e instanceof errors_1.OutputGuardrailTripwireTriggered) {
784
+ throw e;
785
+ }
786
+ throw new errors_1.GuardrailExecutionError(`Output guardrail failed to complete: ${e}`, e, state);
691
787
  }
692
- }, {
693
- traceId: this.config.traceId,
694
- name: this.config.workflowName,
695
- groupId: this.config.groupId,
696
- metadata: this.config.traceMetadata,
697
- });
788
+ }
789
+ }
790
+ /**
791
+ * @internal
792
+ * Applies call-level filters and merges session updates so the model request mirrors exactly
793
+ * what we persisted for history.
794
+ */
795
+ async #prepareModelCall(state, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate) {
796
+ const { model, explictlyModelSet } = await this.#resolveModelForAgent(state._currentAgent);
797
+ let modelSettings = {
798
+ ...this.config.modelSettings,
799
+ ...state._currentAgent.modelSettings,
800
+ };
801
+ modelSettings = adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, state._currentAgent.modelSettings, model, modelSettings);
802
+ modelSettings = (0, runImplementation_1.maybeResetToolChoice)(state._currentAgent, state._toolUseTracker, modelSettings);
803
+ const systemInstructions = await state._currentAgent.getSystemPrompt(state._context);
804
+ const prompt = await state._currentAgent.getPrompt(state._context);
805
+ const { modelInput, sourceItems, persistedItems, filterApplied } = await applyCallModelInputFilter(state._currentAgent, options.callModelInputFilter, state._context, turnInput, systemInstructions);
806
+ // Inform the tracker which exact original objects made it to the provider so future turns
807
+ // only send the delta that has not yet been acknowledged by the server.
808
+ serverConversationTracker?.markInputAsSent(sourceItems);
809
+ // Provide filtered clones whenever filters run so session history mirrors the model payload.
810
+ // Returning an empty array is intentional: it tells the session layer to persist "nothing"
811
+ // instead of falling back to the unfiltered originals when the filter redacts everything.
812
+ sessionInputUpdate?.(sourceItems, filterApplied ? persistedItems : undefined);
813
+ const previousResponseId = serverConversationTracker?.previousResponseId ??
814
+ options.previousResponseId;
815
+ const conversationId = serverConversationTracker?.conversationId ?? options.conversationId;
816
+ return {
817
+ ...artifacts,
818
+ model,
819
+ explictlyModelSet,
820
+ modelSettings,
821
+ modelInput,
822
+ prompt,
823
+ previousResponseId,
824
+ conversationId,
825
+ };
698
826
  }
699
827
  }
700
828
  exports.Runner = Runner;
829
+ /**
830
+ * Constructs the model input array for the current turn by combining the original turn input with
831
+ * any new run items (excluding tool approval placeholders). This helps ensure that repeated calls
832
+ * to the Responses API only send newly generated content.
833
+ *
834
+ * See: https://platform.openai.com/docs/guides/conversation-state?api-mode=responses.
835
+ */
836
+ function getTurnInput(originalInput, generatedItems) {
837
+ const rawItems = generatedItems
838
+ .filter((item) => item.type !== 'tool_approval_item') // don't include approval items to avoid double function calls
839
+ .map((item) => item.rawItem);
840
+ return [...toAgentInputList(originalInput), ...rawItems];
841
+ }
842
+ // --------------------------------------------------------------
843
+ // Internal helpers
844
+ // --------------------------------------------------------------
845
+ const DEFAULT_MAX_TURNS = 10;
701
846
  let _defaultRunner = undefined;
702
847
  function getDefaultRunner() {
703
848
  if (_defaultRunner) {
@@ -706,9 +851,13 @@ function getDefaultRunner() {
706
851
  _defaultRunner = new Runner();
707
852
  return _defaultRunner;
708
853
  }
854
+ /**
855
+ * Resolves the effective model for the next turn by giving precedence to the agent-specific
856
+ * configuration when present, otherwise falling back to the runner-level default.
857
+ */
709
858
  function selectModel(agentModel, runConfigModel) {
710
859
  // When initializing an agent without model name, the model property is set to an empty string. So,
711
- // * agentModel === '' & runConfigModel exists, runConfigModel will be used
860
+ // * agentModel === Agent.DEFAULT_MODEL_PLACEHOLDER & runConfigModel exists, runConfigModel will be used
712
861
  // * agentModel is set, the agentModel will be used over runConfigModel
713
862
  if ((typeof agentModel === 'string' &&
714
863
  agentModel !== agent_1.Agent.DEFAULT_MODEL_PLACEHOLDER) ||
@@ -718,13 +867,259 @@ function selectModel(agentModel, runConfigModel) {
718
867
  }
719
868
  return runConfigModel ?? agentModel ?? agent_1.Agent.DEFAULT_MODEL_PLACEHOLDER;
720
869
  }
721
- async function run(agent, input, options) {
722
- const runner = getDefaultRunner();
723
- if (options?.stream) {
724
- return await runner.run(agent, input, options);
870
+ /**
871
+ * Normalizes tracing configuration into the format expected by model providers.
872
+ * Returns `false` to disable tracing, `true` to include full payload data, or
873
+ * `'enabled_without_data'` to omit sensitive content while still emitting spans.
874
+ */
875
+ function getTracing(tracingDisabled, traceIncludeSensitiveData) {
876
+ if (tracingDisabled) {
877
+ return false;
725
878
  }
726
- else {
727
- return await runner.run(agent, input, options);
879
+ if (traceIncludeSensitiveData) {
880
+ return true;
881
+ }
882
+ return 'enabled_without_data';
883
+ }
884
+ /**
885
+ * @internal
886
+ */
887
+ async function applyCallModelInputFilter(agent, callModelInputFilter, context, inputItems, systemInstructions) {
888
+ const cloneInputItems = (items, map) => items.map((item) => {
889
+ const cloned = structuredClone(item);
890
+ if (map && cloned && typeof cloned === 'object') {
891
+ map.set(cloned, item);
892
+ }
893
+ return cloned;
894
+ });
895
+ // Record the relationship between the cloned array passed to filters and the original inputs.
896
+ const cloneMap = new WeakMap();
897
+ const originalPool = buildAgentInputPool(inputItems);
898
+ const fallbackOriginals = [];
899
+ // Track any original object inputs so filtered replacements can still mark them as delivered.
900
+ for (const item of inputItems) {
901
+ if (item && typeof item === 'object') {
902
+ fallbackOriginals.push(item);
903
+ }
904
+ }
905
+ const removeFromFallback = (candidate) => {
906
+ if (!candidate || typeof candidate !== 'object') {
907
+ return;
908
+ }
909
+ const index = fallbackOriginals.findIndex((original) => original === candidate);
910
+ if (index !== -1) {
911
+ fallbackOriginals.splice(index, 1);
912
+ }
913
+ };
914
+ const takeFallbackOriginal = () => {
915
+ const next = fallbackOriginals.shift();
916
+ if (next) {
917
+ removeAgentInputFromPool(originalPool, next);
918
+ }
919
+ return next;
920
+ };
921
+ // Always create a deep copy so downstream mutations inside filters cannot affect
922
+ // the cached turn state.
923
+ const clonedBaseInput = cloneInputItems(inputItems, cloneMap);
924
+ const base = {
925
+ input: clonedBaseInput,
926
+ instructions: systemInstructions,
927
+ };
928
+ if (!callModelInputFilter) {
929
+ return {
930
+ modelInput: base,
931
+ sourceItems: [...inputItems],
932
+ persistedItems: [],
933
+ filterApplied: false,
934
+ };
935
+ }
936
+ try {
937
+ const result = await callModelInputFilter({
938
+ modelData: base,
939
+ agent,
940
+ context: context.context,
941
+ });
942
+ if (!result || !Array.isArray(result.input)) {
943
+ throw new errors_1.UserError('callModelInputFilter must return a ModelInputData object with an input array.');
944
+ }
945
+ // Preserve a pointer to the original object backing each filtered clone so downstream
946
+ // trackers can keep their bookkeeping consistent even after redaction.
947
+ const sourceItems = result.input.map((item) => {
948
+ if (!item || typeof item !== 'object') {
949
+ return undefined;
950
+ }
951
+ const original = cloneMap.get(item);
952
+ if (original) {
953
+ removeFromFallback(original);
954
+ removeAgentInputFromPool(originalPool, original);
955
+ return original;
956
+ }
957
+ const key = getAgentInputItemKey(item);
958
+ const matchedByContent = takeAgentInputFromPool(originalPool, key);
959
+ if (matchedByContent) {
960
+ removeFromFallback(matchedByContent);
961
+ return matchedByContent;
962
+ }
963
+ const fallback = takeFallbackOriginal();
964
+ if (fallback) {
965
+ return fallback;
966
+ }
967
+ return undefined;
968
+ });
969
+ const clonedFilteredInput = cloneInputItems(result.input);
970
+ return {
971
+ modelInput: {
972
+ input: clonedFilteredInput,
973
+ instructions: typeof result.instructions === 'undefined'
974
+ ? systemInstructions
975
+ : result.instructions,
976
+ },
977
+ sourceItems,
978
+ persistedItems: clonedFilteredInput.map((item) => structuredClone(item)),
979
+ filterApplied: true,
980
+ };
981
+ }
982
+ catch (error) {
983
+ (0, context_1.addErrorToCurrentSpan)({
984
+ message: 'Error in callModelInputFilter',
985
+ data: { error: String(error) },
986
+ });
987
+ throw error;
988
+ }
989
+ }
990
+ // Tracks which items have already been sent to or received from the Responses API when the caller
991
+ // supplies `conversationId`/`previousResponseId`. This ensures we only send the delta each turn.
992
+ class ServerConversationTracker {
993
+ // Conversation ID:
994
+ // - https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#using-the-conversations-api
995
+ // - https://platform.openai.com/docs/api-reference/conversations/create
996
+ conversationId;
997
+ // Previous Response ID:
998
+ // https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#passing-context-from-the-previous-response
999
+ previousResponseId;
1000
+ // Using this flag because WeakSet does not provide a way to check its size
1001
+ sentInitialInput = false;
1002
+ // The items already sent to the model; using WeakSet for memory efficiency
1003
+ sentItems = new WeakSet();
1004
+ // The items received from the server; using WeakSet for memory efficiency
1005
+ serverItems = new WeakSet();
1006
+ // Track initial input items that have not yet been sent so they can be retried on later turns.
1007
+ remainingInitialInput = null;
1008
+ constructor({ conversationId, previousResponseId, }) {
1009
+ this.conversationId = conversationId ?? undefined;
1010
+ this.previousResponseId = previousResponseId ?? undefined;
1011
+ }
1012
+ /**
1013
+ * Pre-populates tracker caches from an existing RunState when resuming server-managed runs.
1014
+ */
1015
+ primeFromState({ originalInput, generatedItems, modelResponses, }) {
1016
+ if (this.sentInitialInput) {
1017
+ return;
1018
+ }
1019
+ for (const item of toAgentInputList(originalInput)) {
1020
+ if (item && typeof item === 'object') {
1021
+ this.sentItems.add(item);
1022
+ }
1023
+ }
1024
+ this.sentInitialInput = true;
1025
+ this.remainingInitialInput = null;
1026
+ const latestResponse = modelResponses[modelResponses.length - 1];
1027
+ for (const response of modelResponses) {
1028
+ for (const item of response.output) {
1029
+ if (item && typeof item === 'object') {
1030
+ this.serverItems.add(item);
1031
+ }
1032
+ }
1033
+ }
1034
+ if (!this.conversationId && latestResponse?.responseId) {
1035
+ this.previousResponseId = latestResponse.responseId;
1036
+ }
1037
+ for (const item of generatedItems) {
1038
+ const rawItem = item.rawItem;
1039
+ if (!rawItem || typeof rawItem !== 'object') {
1040
+ continue;
1041
+ }
1042
+ if (this.serverItems.has(rawItem)) {
1043
+ this.sentItems.add(rawItem);
1044
+ }
1045
+ }
1046
+ }
1047
+ /**
1048
+ * Records the raw items returned by the server so future delta calculations skip them.
1049
+ * Also captures the latest response identifier to chain follow-up calls when possible.
1050
+ */
1051
+ trackServerItems(modelResponse) {
1052
+ if (!modelResponse) {
1053
+ return;
1054
+ }
1055
+ for (const item of modelResponse.output) {
1056
+ if (item && typeof item === 'object') {
1057
+ this.serverItems.add(item);
1058
+ }
1059
+ }
1060
+ if (!this.conversationId && modelResponse.responseId) {
1061
+ this.previousResponseId = modelResponse.responseId;
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Returns the minimum set of items that still need to be delivered to the server for the
1066
+ * current turn. This includes the original turn inputs (until acknowledged) plus any
1067
+ * newly generated items that have not yet been echoed back by the API.
1068
+ */
1069
+ prepareInput(originalInput, generatedItems) {
1070
+ const inputItems = [];
1071
+ if (!this.sentInitialInput) {
1072
+ const initialItems = toAgentInputList(originalInput);
1073
+ // Preserve the full initial payload so a filter can drop items without losing their originals.
1074
+ inputItems.push(...initialItems);
1075
+ this.remainingInitialInput = initialItems.filter((item) => Boolean(item) && typeof item === 'object');
1076
+ this.sentInitialInput = true;
1077
+ }
1078
+ else if (this.remainingInitialInput &&
1079
+ this.remainingInitialInput.length > 0) {
1080
+ // Re-queue prior initial items until the tracker confirms they were delivered to the API.
1081
+ inputItems.push(...this.remainingInitialInput);
1082
+ }
1083
+ for (const item of generatedItems) {
1084
+ if (item.type === 'tool_approval_item') {
1085
+ continue;
1086
+ }
1087
+ const rawItem = item.rawItem;
1088
+ if (!rawItem || typeof rawItem !== 'object') {
1089
+ continue;
1090
+ }
1091
+ if (this.sentItems.has(rawItem) || this.serverItems.has(rawItem)) {
1092
+ continue;
1093
+ }
1094
+ inputItems.push(rawItem);
1095
+ }
1096
+ return inputItems;
1097
+ }
1098
+ /**
1099
+ * Marks the provided originals as delivered so future turns do not resend them and any
1100
+ * pending initial inputs can be dropped once the server acknowledges receipt.
1101
+ */
1102
+ markInputAsSent(items) {
1103
+ if (!items.length) {
1104
+ return;
1105
+ }
1106
+ const delivered = new Set();
1107
+ for (const item of items) {
1108
+ if (!item || typeof item !== 'object' || delivered.has(item)) {
1109
+ continue;
1110
+ }
1111
+ // Some inputs may be repeated in the filtered list; only mark unique originals once.
1112
+ delivered.add(item);
1113
+ this.sentItems.add(item);
1114
+ }
1115
+ if (!this.remainingInitialInput ||
1116
+ this.remainingInitialInput.length === 0) {
1117
+ return;
1118
+ }
1119
+ this.remainingInitialInput = this.remainingInitialInput.filter((item) => !delivered.has(item));
1120
+ if (this.remainingInitialInput.length === 0) {
1121
+ this.remainingInitialInput = null;
1122
+ }
728
1123
  }
729
1124
  }
730
1125
  /**
@@ -761,4 +1156,121 @@ function adjustModelSettingsForNonGPT5RunnerModel(explictlyModelSet, agentModelS
761
1156
  }
762
1157
  return modelSettings;
763
1158
  }
1159
+ /**
1160
+ * @internal
1161
+ * Collects tools/handoffs early so we can annotate spans before model execution begins.
1162
+ */
1163
+ async function prepareAgentArtifacts(state) {
1164
+ const handoffs = await state._currentAgent.getEnabledHandoffs(state._context);
1165
+ const tools = await state._currentAgent.getAllTools(state._context);
1166
+ if (!state._currentAgentSpan) {
1167
+ const handoffNames = handoffs.map((h) => h.agentName);
1168
+ state._currentAgentSpan = (0, tracing_1.createAgentSpan)({
1169
+ data: {
1170
+ name: state._currentAgent.name,
1171
+ handoffs: handoffNames,
1172
+ tools: tools.map((t) => t.name),
1173
+ output_type: state._currentAgent.outputSchemaName,
1174
+ },
1175
+ });
1176
+ state._currentAgentSpan.start();
1177
+ (0, context_1.setCurrentSpan)(state._currentAgentSpan);
1178
+ }
1179
+ else {
1180
+ state._currentAgentSpan.spanData.tools = tools.map((t) => t.name);
1181
+ }
1182
+ return {
1183
+ handoffs,
1184
+ tools,
1185
+ serializedHandoffs: handoffs.map((handoff) => (0, serialize_1.serializeHandoff)(handoff)),
1186
+ serializedTools: tools.map((tool) => (0, serialize_1.serializeTool)(tool)),
1187
+ };
1188
+ }
1189
+ function getAgentInputItemKey(item) {
1190
+ // Deep serialization keeps binary inputs comparable after filters clone them.
1191
+ return JSON.stringify(item, agentInputSerializationReplacer);
1192
+ }
1193
+ function buildAgentInputPool(items) {
1194
+ // Track every original object so filters can safely return cloned copies.
1195
+ const pool = new Map();
1196
+ for (const item of items) {
1197
+ const key = getAgentInputItemKey(item);
1198
+ const existing = pool.get(key);
1199
+ if (existing) {
1200
+ existing.push(item);
1201
+ }
1202
+ else {
1203
+ pool.set(key, [item]);
1204
+ }
1205
+ }
1206
+ return pool;
1207
+ }
1208
+ function takeAgentInputFromPool(pool, key) {
1209
+ // Prefer reusing the earliest untouched original to keep ordering stable.
1210
+ const candidates = pool.get(key);
1211
+ if (!candidates || candidates.length === 0) {
1212
+ return undefined;
1213
+ }
1214
+ const [first] = candidates;
1215
+ candidates.shift();
1216
+ if (candidates.length === 0) {
1217
+ pool.delete(key);
1218
+ }
1219
+ return first;
1220
+ }
1221
+ function removeAgentInputFromPool(pool, item) {
1222
+ // Remove exactly the matched instance so duplicate payloads remain available.
1223
+ const key = getAgentInputItemKey(item);
1224
+ const candidates = pool.get(key);
1225
+ if (!candidates || candidates.length === 0) {
1226
+ return;
1227
+ }
1228
+ const index = candidates.findIndex((candidate) => candidate === item);
1229
+ if (index === -1) {
1230
+ return;
1231
+ }
1232
+ candidates.splice(index, 1);
1233
+ if (candidates.length === 0) {
1234
+ pool.delete(key);
1235
+ }
1236
+ }
1237
+ function agentInputSerializationReplacer(_key, value) {
1238
+ // Mirror runImplementation serialization so buffer snapshots round-trip.
1239
+ if (value instanceof ArrayBuffer) {
1240
+ return {
1241
+ __type: 'ArrayBuffer',
1242
+ data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(value)),
1243
+ };
1244
+ }
1245
+ if ((0, smartString_1.isArrayBufferView)(value)) {
1246
+ const view = value;
1247
+ return {
1248
+ __type: view.constructor.name,
1249
+ data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
1250
+ };
1251
+ }
1252
+ if ((0, smartString_1.isNodeBuffer)(value)) {
1253
+ const view = value;
1254
+ return {
1255
+ __type: 'Buffer',
1256
+ data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
1257
+ };
1258
+ }
1259
+ if ((0, smartString_1.isSerializedBufferSnapshot)(value)) {
1260
+ return {
1261
+ __type: 'Buffer',
1262
+ data: (0, base64_1.encodeUint8ArrayToBase64)(Uint8Array.from(value.data)),
1263
+ };
1264
+ }
1265
+ return value;
1266
+ }
1267
+ // Normalizes user-provided input into the structure the model expects. Strings become user messages,
1268
+ // arrays are kept as-is so downstream loops can treat both scenarios uniformly.
1269
+ function toAgentInputList(originalInput) {
1270
+ // Allow callers to pass plain strings while preserving original item order.
1271
+ if (typeof originalInput === 'string') {
1272
+ return [{ type: 'message', role: 'user', content: originalInput }];
1273
+ }
1274
+ return [...originalInput];
1275
+ }
764
1276
  //# sourceMappingURL=run.js.map