@openai/agents-core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/memory/memorySession.d.ts +22 -0
- package/dist/memory/memorySession.js +64 -0
- package/dist/memory/memorySession.js.map +1 -0
- package/dist/memory/memorySession.mjs +60 -0
- package/dist/memory/memorySession.mjs.map +1 -0
- package/dist/memory/session.d.ts +36 -0
- package/dist/memory/session.js +3 -0
- package/dist/memory/session.js.map +1 -0
- package/dist/memory/session.mjs +2 -0
- package/dist/memory/session.mjs.map +1 -0
- package/dist/metadata.js +3 -3
- package/dist/metadata.js.map +1 -1
- package/dist/metadata.mjs +3 -3
- package/dist/metadata.mjs.map +1 -1
- package/dist/run.d.ts +88 -8
- package/dist/run.js +859 -347
- package/dist/run.js.map +1 -1
- package/dist/run.mjs +859 -347
- package/dist/run.mjs.map +1 -1
- package/dist/runImplementation.d.ts +21 -2
- package/dist/runImplementation.js +885 -346
- package/dist/runImplementation.js.map +1 -1
- package/dist/runImplementation.mjs +877 -344
- package/dist/runImplementation.mjs.map +1 -1
- package/dist/runState.d.ts +1360 -226
- package/dist/runState.js +16 -0
- package/dist/runState.js.map +1 -1
- package/dist/runState.mjs +16 -0
- package/dist/runState.mjs.map +1 -1
- package/dist/types/protocol.d.ts +1869 -193
- package/dist/types/protocol.js +1 -0
- package/dist/types/protocol.js.map +1 -1
- package/dist/types/protocol.mjs +1 -0
- package/dist/types/protocol.mjs.map +1 -1
- package/dist/utils/smartString.d.ts +9 -0
- package/dist/utils/smartString.js +15 -0
- package/dist/utils/smartString.js.map +1 -1
- package/dist/utils/smartString.mjs +14 -3
- package/dist/utils/smartString.mjs.map +1 -1
- package/dist/utils/tools.js +59 -20
- package/dist/utils/tools.js.map +1 -1
- package/dist/utils/tools.mjs +59 -20
- package/dist/utils/tools.mjs.map +1 -1
- package/package.json +2 -2
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.
|
|
7
|
+
exports.run = run;
|
|
8
8
|
exports.getTurnInput = getTurnInput;
|
|
9
9
|
exports.selectModel = selectModel;
|
|
10
|
-
exports.
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
164
|
-
|
|
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 #
|
|
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.
|
|
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
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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.
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
403
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
for await (const event of model.getStreamedResponse({
|
|
536
|
-
systemInstructions:
|
|
537
|
-
prompt:
|
|
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
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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.
|
|
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
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
return
|
|
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
|
-
|
|
681
|
-
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
690
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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 ===
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
727
|
-
return
|
|
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
|