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