@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
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.AgentToolUseTracker = exports.nextStepSchema = void 0;
|
|
7
7
|
exports.processModelResponse = processModelResponse;
|
|
8
8
|
exports.maybeResetToolChoice = maybeResetToolChoice;
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
9
|
+
exports.resolveInterruptedTurn = resolveInterruptedTurn;
|
|
10
|
+
exports.resolveTurnAfterModelResponse = resolveTurnAfterModelResponse;
|
|
11
11
|
exports.getToolCallOutputItem = getToolCallOutputItem;
|
|
12
12
|
exports.executeFunctionToolCalls = executeFunctionToolCalls;
|
|
13
13
|
exports.executeComputerActions = executeComputerActions;
|
|
@@ -15,6 +15,12 @@ exports.executeHandoffCalls = executeHandoffCalls;
|
|
|
15
15
|
exports.checkForFinalOutputFromTools = checkForFinalOutputFromTools;
|
|
16
16
|
exports.streamStepItemsToRunResult = streamStepItemsToRunResult;
|
|
17
17
|
exports.addStepToRunResult = addStepToRunResult;
|
|
18
|
+
exports.toInputItemList = toInputItemList;
|
|
19
|
+
exports.extractOutputItemsFromRunItems = extractOutputItemsFromRunItems;
|
|
20
|
+
exports.saveToSession = saveToSession;
|
|
21
|
+
exports.saveStreamInputToSession = saveStreamInputToSession;
|
|
22
|
+
exports.saveStreamResultToSession = saveStreamResultToSession;
|
|
23
|
+
exports.prepareInputItemsWithSession = prepareInputItemsWithSession;
|
|
18
24
|
const agent_1 = require("./agent.js");
|
|
19
25
|
const errors_1 = require("./errors.js");
|
|
20
26
|
const handoff_1 = require("./handoff.js");
|
|
@@ -24,14 +30,59 @@ const messages_1 = require("./utils/messages.js");
|
|
|
24
30
|
const createSpans_1 = require("./tracing/createSpans.js");
|
|
25
31
|
const tools_1 = require("./utils/tools.js");
|
|
26
32
|
const base64_1 = require("./utils/base64.js");
|
|
33
|
+
const smartString_1 = require("./utils/smartString.js");
|
|
27
34
|
const safeExecute_1 = require("./utils/safeExecute.js");
|
|
28
35
|
const context_1 = require("./tracing/context.js");
|
|
29
36
|
const events_1 = require("./events.js");
|
|
30
37
|
const zod_1 = require("zod");
|
|
31
|
-
const smartString_1 = require("./utils/smartString.js");
|
|
32
38
|
const utils_1 = require("./utils/index.js");
|
|
39
|
+
function isApprovalItemLike(value) {
|
|
40
|
+
if (!value || typeof value !== 'object') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (!('rawItem' in value)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const rawItem = value.rawItem;
|
|
47
|
+
if (!rawItem || typeof rawItem !== 'object') {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const itemType = rawItem.type;
|
|
51
|
+
return itemType === 'function_call' || itemType === 'hosted_tool_call';
|
|
52
|
+
}
|
|
53
|
+
function getApprovalIdentity(approval) {
|
|
54
|
+
const rawItem = approval.rawItem;
|
|
55
|
+
if (!rawItem) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (rawItem.type === 'function_call' && rawItem.callId) {
|
|
59
|
+
return `function_call:${rawItem.callId}`;
|
|
60
|
+
}
|
|
61
|
+
if ('callId' in rawItem && rawItem.callId) {
|
|
62
|
+
return `${rawItem.type}:${rawItem.callId}`;
|
|
63
|
+
}
|
|
64
|
+
const id = 'id' in rawItem ? rawItem.id : undefined;
|
|
65
|
+
if (id) {
|
|
66
|
+
return `${rawItem.type}:${id}`;
|
|
67
|
+
}
|
|
68
|
+
const providerData = typeof rawItem.providerData === 'object' && rawItem.providerData
|
|
69
|
+
? rawItem.providerData
|
|
70
|
+
: undefined;
|
|
71
|
+
if (providerData?.id) {
|
|
72
|
+
return `${rawItem.type}:provider:${providerData.id}`;
|
|
73
|
+
}
|
|
74
|
+
const agentName = 'agent' in approval && approval.agent ? approval.agent.name : '';
|
|
75
|
+
try {
|
|
76
|
+
return `${agentName}:${rawItem.type}:${JSON.stringify(rawItem)}`;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return `${agentName}:${rawItem.type}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
33
82
|
/**
|
|
34
83
|
* @internal
|
|
84
|
+
* Walks a raw model response and classifies each item so the runner can schedule follow-up work.
|
|
85
|
+
* Returns both the serializable RunItems (for history/streaming) and the actionable tool metadata.
|
|
35
86
|
*/
|
|
36
87
|
function processModelResponse(modelResponse, agent, tools, handoffs) {
|
|
37
88
|
const items = [];
|
|
@@ -41,6 +92,7 @@ function processModelResponse(modelResponse, agent, tools, handoffs) {
|
|
|
41
92
|
const runMCPApprovalRequests = [];
|
|
42
93
|
const toolsUsed = [];
|
|
43
94
|
const handoffMap = new Map(handoffs.map((h) => [h.toolName, h]));
|
|
95
|
+
// Resolve tools upfront so we can look up the concrete handler in O(1) while iterating outputs.
|
|
44
96
|
const functionMap = new Map(tools.filter((t) => t.type === 'function').map((t) => [t.name, t]));
|
|
45
97
|
const computerTool = tools.find((t) => t.type === 'computer');
|
|
46
98
|
const mcpToolMap = new Map(tools
|
|
@@ -175,6 +227,10 @@ exports.nextStepSchema = zod_1.z.discriminatedUnion('type', [
|
|
|
175
227
|
data: zod_1.z.record(zod_1.z.string(), zod_1.z.any()),
|
|
176
228
|
}),
|
|
177
229
|
]);
|
|
230
|
+
/**
|
|
231
|
+
* Internal convenience wrapper that groups the outcome of a single agent turn. It lets the caller
|
|
232
|
+
* update the RunState in one shot and decide which step to execute next.
|
|
233
|
+
*/
|
|
178
234
|
class SingleStepResult {
|
|
179
235
|
originalInput;
|
|
180
236
|
modelResponse;
|
|
@@ -183,7 +239,7 @@ class SingleStepResult {
|
|
|
183
239
|
nextStep;
|
|
184
240
|
constructor(
|
|
185
241
|
/**
|
|
186
|
-
* The input items i.e
|
|
242
|
+
* The input items (i.e., the items before run() was called). May be mutated by handoff input filters.
|
|
187
243
|
*/
|
|
188
244
|
originalInput,
|
|
189
245
|
/**
|
|
@@ -217,6 +273,8 @@ class SingleStepResult {
|
|
|
217
273
|
}
|
|
218
274
|
/**
|
|
219
275
|
* @internal
|
|
276
|
+
* Resets the tool choice when the agent is configured to prefer a fresh tool selection after
|
|
277
|
+
* any tool usage. This prevents the provider from reusing stale tool hints across turns.
|
|
220
278
|
*/
|
|
221
279
|
function maybeResetToolChoice(agent, toolUseTracker, modelSettings) {
|
|
222
280
|
if (agent.resetToolChoice && toolUseTracker.hasUsedTools(agent)) {
|
|
@@ -226,27 +284,94 @@ function maybeResetToolChoice(agent, toolUseTracker, modelSettings) {
|
|
|
226
284
|
}
|
|
227
285
|
/**
|
|
228
286
|
* @internal
|
|
287
|
+
* Continues a turn that was previously interrupted waiting for tool approval. Executes the now
|
|
288
|
+
* approved tools and returns the resulting step transition.
|
|
229
289
|
*/
|
|
230
|
-
async function
|
|
290
|
+
async function resolveInterruptedTurn(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
|
|
231
291
|
// call_ids for function tools
|
|
232
292
|
const functionCallIds = originalPreStepItems
|
|
233
293
|
.filter((item) => item instanceof items_1.RunToolApprovalItem &&
|
|
234
294
|
'callId' in item.rawItem &&
|
|
235
295
|
item.rawItem.type === 'function_call')
|
|
236
296
|
.map((item) => item.rawItem.callId);
|
|
297
|
+
// We already persisted the turn once when the approval interrupt was raised, so the
|
|
298
|
+
// counter reflects the approval items as "flushed". When we resume the same turn we need
|
|
299
|
+
// to rewind it so the eventual tool output for this call is still written to the session.
|
|
300
|
+
const pendingApprovalItems = state
|
|
301
|
+
.getInterruptions()
|
|
302
|
+
.filter(isApprovalItemLike);
|
|
303
|
+
if (pendingApprovalItems.length > 0) {
|
|
304
|
+
const pendingApprovalIdentities = new Set();
|
|
305
|
+
for (const approval of pendingApprovalItems) {
|
|
306
|
+
const identity = getApprovalIdentity(approval);
|
|
307
|
+
if (identity) {
|
|
308
|
+
pendingApprovalIdentities.add(identity);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (pendingApprovalIdentities.size > 0) {
|
|
312
|
+
let rewindCount = 0;
|
|
313
|
+
for (let index = originalPreStepItems.length - 1; index >= 0; index--) {
|
|
314
|
+
const item = originalPreStepItems[index];
|
|
315
|
+
if (!(item instanceof items_1.RunToolApprovalItem)) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const identity = getApprovalIdentity(item);
|
|
319
|
+
if (!identity) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (!pendingApprovalIdentities.has(identity)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
rewindCount++;
|
|
326
|
+
pendingApprovalIdentities.delete(identity);
|
|
327
|
+
if (pendingApprovalIdentities.size === 0) {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Persisting the approval request already advanced the counter once, so undo the increment
|
|
332
|
+
// to make sure we write the final tool output back to the session when the turn resumes.
|
|
333
|
+
if (rewindCount > 0) {
|
|
334
|
+
state._currentTurnPersistedItemCount = Math.max(0, state._currentTurnPersistedItemCount - rewindCount);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
237
338
|
// Run function tools that require approval after they get their approval results
|
|
238
339
|
const functionToolRuns = processedResponse.functions.filter((run) => {
|
|
239
340
|
return functionCallIds.includes(run.toolCall.callId);
|
|
240
341
|
});
|
|
241
342
|
const functionResults = await executeFunctionToolCalls(agent, functionToolRuns, runner, state);
|
|
242
|
-
//
|
|
243
|
-
|
|
343
|
+
// There is no built-in HITL approval surface for computer tools today, so every pending action
|
|
344
|
+
// is executed immediately when the turn resumes.
|
|
345
|
+
const computerResults = processedResponse.computerActions.length > 0
|
|
346
|
+
? await executeComputerActions(agent, processedResponse.computerActions, runner, state._context)
|
|
347
|
+
: [];
|
|
348
|
+
// When resuming we receive the original RunItem references; suppress duplicates so history and streaming do not double-emit the same items.
|
|
349
|
+
const originalPreStepItemSet = new Set(originalPreStepItems);
|
|
350
|
+
const newItems = [];
|
|
351
|
+
const newItemsSet = new Set();
|
|
352
|
+
const appendIfNew = (item) => {
|
|
353
|
+
if (originalPreStepItemSet.has(item) || newItemsSet.has(item)) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
newItems.push(item);
|
|
357
|
+
newItemsSet.add(item);
|
|
358
|
+
};
|
|
359
|
+
for (const result of functionResults) {
|
|
360
|
+
appendIfNew(result.runItem);
|
|
361
|
+
}
|
|
362
|
+
for (const result of computerResults) {
|
|
363
|
+
appendIfNew(result);
|
|
364
|
+
}
|
|
244
365
|
// Run MCP tools that require approval after they get their approval results
|
|
245
366
|
const mcpApprovalRuns = processedResponse.mcpApprovalRequests.filter((run) => {
|
|
246
367
|
return (run.requestItem.type === 'tool_approval_item' &&
|
|
247
368
|
run.requestItem.rawItem.type === 'hosted_tool_call' &&
|
|
248
369
|
run.requestItem.rawItem.providerData?.type === 'mcp_approval_request');
|
|
249
370
|
});
|
|
371
|
+
// Hosted MCP approvals may still be waiting on a human decision when the turn resumes.
|
|
372
|
+
const pendingHostedMCPApprovals = new Set();
|
|
373
|
+
const pendingHostedMCPApprovalIds = new Set();
|
|
374
|
+
// Keep track of approvals we still need to surface next turn so HITL flows can resume cleanly.
|
|
250
375
|
for (const run of mcpApprovalRuns) {
|
|
251
376
|
// the approval_request_id "mcpr_123..."
|
|
252
377
|
const approvalRequestId = run.requestItem.rawItem.id;
|
|
@@ -262,50 +387,93 @@ async function executeInterruptedToolsAndSideEffects(agent, originalInput, origi
|
|
|
262
387
|
reason: undefined,
|
|
263
388
|
};
|
|
264
389
|
// Tell Responses API server the approval result in the next turn
|
|
265
|
-
|
|
390
|
+
const responseItem = new items_1.RunToolCallItem({
|
|
266
391
|
type: 'hosted_tool_call',
|
|
267
392
|
name: 'mcp_approval_response',
|
|
268
393
|
providerData,
|
|
269
|
-
}, agent)
|
|
394
|
+
}, agent);
|
|
395
|
+
appendIfNew(responseItem);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
pendingHostedMCPApprovals.add(run.requestItem);
|
|
399
|
+
pendingHostedMCPApprovalIds.add(approvalRequestId);
|
|
400
|
+
functionResults.push({
|
|
401
|
+
type: 'hosted_mcp_tool_approval',
|
|
402
|
+
tool: run.mcpTool,
|
|
403
|
+
runItem: run.requestItem,
|
|
404
|
+
});
|
|
405
|
+
appendIfNew(run.requestItem);
|
|
270
406
|
}
|
|
271
407
|
}
|
|
272
|
-
|
|
273
|
-
//
|
|
274
|
-
//
|
|
408
|
+
// Server-managed conversations rely on preStepItems to re-surface pending approvals.
|
|
409
|
+
// Keep unresolved hosted MCP approvals in place so HITL flows still have something to approve next turn.
|
|
410
|
+
// Drop resolved approval placeholders so they are not replayed on the next turn, but keep
|
|
411
|
+
// pending approvals in place to signal the outstanding work to the UI and session store.
|
|
275
412
|
const preStepItems = originalPreStepItems.filter((item) => {
|
|
276
|
-
|
|
413
|
+
if (!(item instanceof items_1.RunToolApprovalItem)) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
if (item.rawItem.type === 'hosted_tool_call' &&
|
|
417
|
+
item.rawItem.providerData?.type === 'mcp_approval_request') {
|
|
418
|
+
if (pendingHostedMCPApprovals.has(item)) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
const approvalRequestId = item.rawItem.id;
|
|
422
|
+
if (approvalRequestId) {
|
|
423
|
+
return pendingHostedMCPApprovalIds.has(approvalRequestId);
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
277
428
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
interruptions: checkToolOutput.interruptions,
|
|
291
|
-
},
|
|
292
|
-
});
|
|
429
|
+
const completedStep = await maybeCompleteTurnFromToolResults({
|
|
430
|
+
agent,
|
|
431
|
+
runner,
|
|
432
|
+
state,
|
|
433
|
+
functionResults,
|
|
434
|
+
originalInput,
|
|
435
|
+
newResponse,
|
|
436
|
+
preStepItems,
|
|
437
|
+
newItems,
|
|
438
|
+
});
|
|
439
|
+
if (completedStep) {
|
|
440
|
+
return completedStep;
|
|
293
441
|
}
|
|
294
442
|
// we only ran new tools and side effects. We need to run the rest of the agent
|
|
295
443
|
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
|
|
296
444
|
}
|
|
297
445
|
/**
|
|
298
446
|
* @internal
|
|
447
|
+
* Executes every follow-up action the model requested (function tools, computer actions, MCP flows),
|
|
448
|
+
* appends their outputs to the run history, and determines the next step for the agent loop.
|
|
299
449
|
*/
|
|
300
|
-
async function
|
|
450
|
+
async function resolveTurnAfterModelResponse(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state) {
|
|
451
|
+
// Reuse the same array reference so we can compare object identity when deciding whether to
|
|
452
|
+
// append new items, ensuring we never double-stream existing RunItems.
|
|
301
453
|
const preStepItems = originalPreStepItems;
|
|
302
|
-
|
|
454
|
+
const seenItems = new Set(originalPreStepItems);
|
|
455
|
+
const newItems = [];
|
|
456
|
+
const appendIfNew = (item) => {
|
|
457
|
+
if (seenItems.has(item)) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
newItems.push(item);
|
|
461
|
+
seenItems.add(item);
|
|
462
|
+
};
|
|
463
|
+
for (const item of processedResponse.newItems) {
|
|
464
|
+
appendIfNew(item);
|
|
465
|
+
}
|
|
466
|
+
// Run function tools and computer actions in parallel; neither depends on the other's side effects.
|
|
303
467
|
const [functionResults, computerResults] = await Promise.all([
|
|
304
468
|
executeFunctionToolCalls(agent, processedResponse.functions, runner, state),
|
|
305
469
|
executeComputerActions(agent, processedResponse.computerActions, runner, state._context),
|
|
306
470
|
]);
|
|
307
|
-
|
|
308
|
-
|
|
471
|
+
for (const result of functionResults) {
|
|
472
|
+
appendIfNew(result.runItem);
|
|
473
|
+
}
|
|
474
|
+
for (const item of computerResults) {
|
|
475
|
+
appendIfNew(item);
|
|
476
|
+
}
|
|
309
477
|
// run hosted MCP approval requests
|
|
310
478
|
if (processedResponse.mcpApprovalRequests.length > 0) {
|
|
311
479
|
for (const approvalRequest of processedResponse.mcpApprovalRequests) {
|
|
@@ -351,22 +519,18 @@ async function executeToolsAndSideEffects(agent, originalInput, originalPreStepI
|
|
|
351
519
|
if (processedResponse.handoffs.length > 0) {
|
|
352
520
|
return await executeHandoffCalls(agent, originalInput, preStepItems, newItems, newResponse, processedResponse.handoffs, runner, state._context);
|
|
353
521
|
}
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
runner
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
data: {
|
|
367
|
-
interruptions: checkToolOutput.interruptions,
|
|
368
|
-
},
|
|
369
|
-
});
|
|
522
|
+
const completedStep = await maybeCompleteTurnFromToolResults({
|
|
523
|
+
agent,
|
|
524
|
+
runner,
|
|
525
|
+
state,
|
|
526
|
+
functionResults,
|
|
527
|
+
originalInput,
|
|
528
|
+
newResponse,
|
|
529
|
+
preStepItems,
|
|
530
|
+
newItems,
|
|
531
|
+
});
|
|
532
|
+
if (completedStep) {
|
|
533
|
+
return completedStep;
|
|
370
534
|
}
|
|
371
535
|
// If the model issued any tool calls or handoffs in this turn,
|
|
372
536
|
// we must NOT treat any assistant message in the same turn as the final output.
|
|
@@ -388,6 +552,7 @@ async function executeToolsAndSideEffects(agent, originalInput, originalPreStepI
|
|
|
388
552
|
if (typeof potentialFinalOutput === 'undefined') {
|
|
389
553
|
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
|
|
390
554
|
}
|
|
555
|
+
// Keep looping if any tool output placeholders still require an approval follow-up.
|
|
391
556
|
const hasPendingToolsOrApprovals = functionResults.some((result) => result.runItem instanceof items_1.RunToolApprovalItem);
|
|
392
557
|
if (!hasPendingToolsOrApprovals) {
|
|
393
558
|
if (agent.outputType === 'text') {
|
|
@@ -414,8 +579,32 @@ async function executeToolsAndSideEffects(agent, originalInput, originalPreStepI
|
|
|
414
579
|
}
|
|
415
580
|
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
|
|
416
581
|
}
|
|
582
|
+
// Consolidates the logic that determines whether tool results yielded a final answer,
|
|
583
|
+
// triggered an interruption, or require the agent loop to continue running.
|
|
584
|
+
async function maybeCompleteTurnFromToolResults({ agent, runner, state, functionResults, originalInput, newResponse, preStepItems, newItems, }) {
|
|
585
|
+
const toolOutcome = await checkForFinalOutputFromTools(agent, functionResults, state);
|
|
586
|
+
if (toolOutcome.isFinalOutput) {
|
|
587
|
+
runner.emit('agent_end', state._context, agent, toolOutcome.finalOutput);
|
|
588
|
+
agent.emit('agent_end', state._context, toolOutcome.finalOutput);
|
|
589
|
+
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
|
|
590
|
+
type: 'next_step_final_output',
|
|
591
|
+
output: toolOutcome.finalOutput,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (toolOutcome.isInterrupted) {
|
|
595
|
+
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
|
|
596
|
+
type: 'next_step_interruption',
|
|
597
|
+
data: {
|
|
598
|
+
interruptions: toolOutcome.interruptions,
|
|
599
|
+
},
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
417
604
|
/**
|
|
418
605
|
* @internal
|
|
606
|
+
* Normalizes tool outputs once so downstream code works with fully structured protocol items.
|
|
607
|
+
* Doing this here keeps API surface stable even when providers add new shapes.
|
|
419
608
|
*/
|
|
420
609
|
function getToolCallOutputItem(toolCall, output) {
|
|
421
610
|
const maybeStructuredOutputs = normalizeStructuredToolOutputs(output);
|
|
@@ -440,318 +629,92 @@ function getToolCallOutputItem(toolCall, output) {
|
|
|
440
629
|
},
|
|
441
630
|
};
|
|
442
631
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
* back to the legacy string pipeline.
|
|
448
|
-
*/
|
|
449
|
-
function normalizeStructuredToolOutputs(output) {
|
|
450
|
-
if (Array.isArray(output)) {
|
|
451
|
-
const structured = [];
|
|
452
|
-
for (const item of output) {
|
|
453
|
-
const normalized = normalizeStructuredToolOutput(item);
|
|
454
|
-
if (!normalized) {
|
|
455
|
-
return null;
|
|
456
|
-
}
|
|
457
|
-
structured.push(normalized);
|
|
458
|
-
}
|
|
459
|
-
return structured;
|
|
632
|
+
function normalizeFileValue(value) {
|
|
633
|
+
const directFile = value.file;
|
|
634
|
+
if (typeof directFile === 'string' && directFile.length > 0) {
|
|
635
|
+
return directFile;
|
|
460
636
|
}
|
|
461
|
-
const
|
|
462
|
-
|
|
637
|
+
const normalizedObject = normalizeFileObjectCandidate(directFile);
|
|
638
|
+
if (normalizedObject) {
|
|
639
|
+
return normalizedObject;
|
|
640
|
+
}
|
|
641
|
+
const legacyValue = normalizeLegacyFileValue(value);
|
|
642
|
+
if (legacyValue) {
|
|
643
|
+
return legacyValue;
|
|
644
|
+
}
|
|
645
|
+
return null;
|
|
463
646
|
}
|
|
464
|
-
|
|
465
|
-
* Best-effort normalization of a single tool output item. If the object already matches the
|
|
466
|
-
* protocol shape we simply cast it; otherwise we copy the recognised fields into the canonical
|
|
467
|
-
* structure. Returning null lets the caller know we should revert to plain-string handling.
|
|
468
|
-
*/
|
|
469
|
-
function normalizeStructuredToolOutput(value) {
|
|
647
|
+
function normalizeFileObjectCandidate(value) {
|
|
470
648
|
if (!isRecord(value)) {
|
|
471
649
|
return null;
|
|
472
650
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
return output;
|
|
480
|
-
}
|
|
481
|
-
if (type === 'image') {
|
|
482
|
-
const output = { type: 'image' };
|
|
483
|
-
let imageString;
|
|
484
|
-
let imageFileId;
|
|
485
|
-
const fallbackImageMediaType = isNonEmptyString(value.mediaType)
|
|
486
|
-
? value.mediaType
|
|
487
|
-
: undefined;
|
|
488
|
-
const imageField = value.image;
|
|
489
|
-
if (typeof imageField === 'string' && imageField.length > 0) {
|
|
490
|
-
imageString = imageField;
|
|
491
|
-
}
|
|
492
|
-
else if (isRecord(imageField)) {
|
|
493
|
-
const imageObj = imageField;
|
|
494
|
-
const inlineMediaType = isNonEmptyString(imageObj.mediaType)
|
|
495
|
-
? imageObj.mediaType
|
|
496
|
-
: fallbackImageMediaType;
|
|
497
|
-
if (isNonEmptyString(imageObj.url)) {
|
|
498
|
-
imageString = imageObj.url;
|
|
499
|
-
}
|
|
500
|
-
else if (isNonEmptyString(imageObj.data)) {
|
|
501
|
-
imageString = toInlineImageString(imageObj.data, inlineMediaType);
|
|
502
|
-
}
|
|
503
|
-
else if (imageObj.data instanceof Uint8Array &&
|
|
504
|
-
imageObj.data.length > 0) {
|
|
505
|
-
imageString = toInlineImageString(imageObj.data, inlineMediaType);
|
|
506
|
-
}
|
|
507
|
-
if (!imageString) {
|
|
508
|
-
const candidateId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
|
|
509
|
-
(isNonEmptyString(imageObj.id) && imageObj.id) ||
|
|
510
|
-
undefined;
|
|
511
|
-
if (candidateId) {
|
|
512
|
-
imageFileId = candidateId;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
if (!imageString &&
|
|
517
|
-
typeof value.imageUrl === 'string' &&
|
|
518
|
-
value.imageUrl.length > 0) {
|
|
519
|
-
imageString = value.imageUrl;
|
|
520
|
-
}
|
|
521
|
-
if (!imageFileId &&
|
|
522
|
-
typeof value.fileId === 'string' &&
|
|
523
|
-
value.fileId.length > 0) {
|
|
524
|
-
imageFileId = value.fileId;
|
|
525
|
-
}
|
|
526
|
-
if (!imageString &&
|
|
527
|
-
typeof value.data === 'string' &&
|
|
528
|
-
value.data.length > 0) {
|
|
529
|
-
imageString = fallbackImageMediaType
|
|
530
|
-
? toInlineImageString(value.data, fallbackImageMediaType)
|
|
531
|
-
: value.data;
|
|
532
|
-
}
|
|
533
|
-
else if (!imageString &&
|
|
534
|
-
value.data instanceof Uint8Array &&
|
|
535
|
-
value.data.length > 0) {
|
|
536
|
-
imageString = toInlineImageString(value.data, fallbackImageMediaType);
|
|
651
|
+
if ('data' in value && value.data !== undefined) {
|
|
652
|
+
const dataValue = value.data;
|
|
653
|
+
const hasStringData = typeof dataValue === 'string' && dataValue.length > 0;
|
|
654
|
+
const hasBinaryData = dataValue instanceof Uint8Array && dataValue.length > 0;
|
|
655
|
+
if (!hasStringData && !hasBinaryData) {
|
|
656
|
+
return null;
|
|
537
657
|
}
|
|
538
|
-
if (
|
|
539
|
-
|
|
658
|
+
if (!isNonEmptyString(value.mediaType) ||
|
|
659
|
+
!isNonEmptyString(value.filename)) {
|
|
660
|
+
return null;
|
|
540
661
|
}
|
|
541
|
-
|
|
542
|
-
|
|
662
|
+
return {
|
|
663
|
+
data: typeof dataValue === 'string' ? dataValue : new Uint8Array(dataValue),
|
|
664
|
+
mediaType: value.mediaType,
|
|
665
|
+
filename: value.filename,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
if (isNonEmptyString(value.url)) {
|
|
669
|
+
const result = { url: value.url };
|
|
670
|
+
if (isNonEmptyString(value.filename)) {
|
|
671
|
+
result.filename = value.filename;
|
|
543
672
|
}
|
|
544
|
-
|
|
545
|
-
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
const referencedId = (isNonEmptyString(value.id) && value.id) ||
|
|
676
|
+
(isNonEmptyString(value.fileId) && value.fileId);
|
|
677
|
+
if (referencedId) {
|
|
678
|
+
const result = { id: referencedId };
|
|
679
|
+
if (isNonEmptyString(value.filename)) {
|
|
680
|
+
result.filename = value.filename;
|
|
546
681
|
}
|
|
547
|
-
|
|
682
|
+
return result;
|
|
683
|
+
}
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
function normalizeLegacyFileValue(value) {
|
|
687
|
+
const filename = typeof value.filename === 'string' && value.filename.length > 0
|
|
688
|
+
? value.filename
|
|
689
|
+
: undefined;
|
|
690
|
+
const mediaType = typeof value.mediaType === 'string' && value.mediaType.length > 0
|
|
691
|
+
? value.mediaType
|
|
692
|
+
: undefined;
|
|
693
|
+
if (typeof value.fileData === 'string' && value.fileData.length > 0) {
|
|
694
|
+
if (!mediaType || !filename) {
|
|
548
695
|
return null;
|
|
549
696
|
}
|
|
550
|
-
|
|
551
|
-
output.providerData = value.providerData;
|
|
552
|
-
}
|
|
553
|
-
return output;
|
|
697
|
+
return { data: value.fileData, mediaType, filename };
|
|
554
698
|
}
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
if (!fileValue) {
|
|
699
|
+
if (value.fileData instanceof Uint8Array && value.fileData.length > 0) {
|
|
700
|
+
if (!mediaType || !filename) {
|
|
558
701
|
return null;
|
|
559
702
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
703
|
+
return { data: new Uint8Array(value.fileData), mediaType, filename };
|
|
704
|
+
}
|
|
705
|
+
if (typeof value.fileUrl === 'string' && value.fileUrl.length > 0) {
|
|
706
|
+
const result = { url: value.fileUrl };
|
|
707
|
+
if (filename) {
|
|
708
|
+
result.filename = filename;
|
|
563
709
|
}
|
|
564
|
-
return
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
*/
|
|
573
|
-
function convertStructuredToolOutputToInputItem(output) {
|
|
574
|
-
if (output.type === 'text') {
|
|
575
|
-
const result = {
|
|
576
|
-
type: 'input_text',
|
|
577
|
-
text: output.text,
|
|
578
|
-
};
|
|
579
|
-
if (output.providerData) {
|
|
580
|
-
result.providerData = output.providerData;
|
|
581
|
-
}
|
|
582
|
-
return result;
|
|
583
|
-
}
|
|
584
|
-
if (output.type === 'image') {
|
|
585
|
-
const result = { type: 'input_image' };
|
|
586
|
-
if (typeof output.detail === 'string' && output.detail.length > 0) {
|
|
587
|
-
result.detail = output.detail;
|
|
588
|
-
}
|
|
589
|
-
if (typeof output.image === 'string' && output.image.length > 0) {
|
|
590
|
-
result.image = output.image;
|
|
591
|
-
}
|
|
592
|
-
else if (isRecord(output.image)) {
|
|
593
|
-
const imageObj = output.image;
|
|
594
|
-
const inlineMediaType = isNonEmptyString(imageObj.mediaType)
|
|
595
|
-
? imageObj.mediaType
|
|
596
|
-
: undefined;
|
|
597
|
-
if (isNonEmptyString(imageObj.url)) {
|
|
598
|
-
result.image = imageObj.url;
|
|
599
|
-
}
|
|
600
|
-
else if (isNonEmptyString(imageObj.data)) {
|
|
601
|
-
result.image =
|
|
602
|
-
inlineMediaType && !imageObj.data.startsWith('data:')
|
|
603
|
-
? asDataUrl(imageObj.data, inlineMediaType)
|
|
604
|
-
: imageObj.data;
|
|
605
|
-
}
|
|
606
|
-
else if (imageObj.data instanceof Uint8Array &&
|
|
607
|
-
imageObj.data.length > 0) {
|
|
608
|
-
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(imageObj.data);
|
|
609
|
-
result.image = asDataUrl(base64, inlineMediaType);
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
const referencedId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
|
|
613
|
-
(isNonEmptyString(imageObj.id) && imageObj.id) ||
|
|
614
|
-
undefined;
|
|
615
|
-
if (referencedId) {
|
|
616
|
-
result.image = { id: referencedId };
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (output.providerData) {
|
|
621
|
-
result.providerData = output.providerData;
|
|
622
|
-
}
|
|
623
|
-
return result;
|
|
624
|
-
}
|
|
625
|
-
if (output.type === 'file') {
|
|
626
|
-
const result = { type: 'input_file' };
|
|
627
|
-
const fileValue = output.file;
|
|
628
|
-
if (typeof fileValue === 'string') {
|
|
629
|
-
result.file = fileValue;
|
|
630
|
-
}
|
|
631
|
-
else if (fileValue && typeof fileValue === 'object') {
|
|
632
|
-
const record = fileValue;
|
|
633
|
-
if ('data' in record && record.data) {
|
|
634
|
-
const mediaType = record.mediaType ?? 'text/plain';
|
|
635
|
-
if (typeof record.data === 'string') {
|
|
636
|
-
result.file = asDataUrl(record.data, mediaType);
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(record.data);
|
|
640
|
-
result.file = asDataUrl(base64, mediaType);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
else if (typeof record.url === 'string' && record.url.length > 0) {
|
|
644
|
-
result.file = { url: record.url };
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
const referencedId = (typeof record.id === 'string' &&
|
|
648
|
-
record.id.length > 0 &&
|
|
649
|
-
record.id) ||
|
|
650
|
-
(typeof record.fileId === 'string' && record.fileId.length > 0
|
|
651
|
-
? record.fileId
|
|
652
|
-
: undefined);
|
|
653
|
-
if (referencedId) {
|
|
654
|
-
result.file = { id: referencedId };
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (typeof record.filename === 'string' && record.filename.length > 0) {
|
|
658
|
-
result.filename = record.filename;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
if (output.providerData) {
|
|
662
|
-
result.providerData = output.providerData;
|
|
663
|
-
}
|
|
664
|
-
return result;
|
|
665
|
-
}
|
|
666
|
-
const exhaustiveCheck = output;
|
|
667
|
-
return exhaustiveCheck;
|
|
668
|
-
}
|
|
669
|
-
function normalizeFileValue(value) {
|
|
670
|
-
const directFile = value.file;
|
|
671
|
-
if (typeof directFile === 'string' && directFile.length > 0) {
|
|
672
|
-
return directFile;
|
|
673
|
-
}
|
|
674
|
-
const normalizedObject = normalizeFileObjectCandidate(directFile);
|
|
675
|
-
if (normalizedObject) {
|
|
676
|
-
return normalizedObject;
|
|
677
|
-
}
|
|
678
|
-
const legacyValue = normalizeLegacyFileValue(value);
|
|
679
|
-
if (legacyValue) {
|
|
680
|
-
return legacyValue;
|
|
681
|
-
}
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
function normalizeFileObjectCandidate(value) {
|
|
685
|
-
if (!isRecord(value)) {
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
if ('data' in value && value.data !== undefined) {
|
|
689
|
-
const dataValue = value.data;
|
|
690
|
-
const hasStringData = typeof dataValue === 'string' && dataValue.length > 0;
|
|
691
|
-
const hasBinaryData = dataValue instanceof Uint8Array && dataValue.length > 0;
|
|
692
|
-
if (!hasStringData && !hasBinaryData) {
|
|
693
|
-
return null;
|
|
694
|
-
}
|
|
695
|
-
if (!isNonEmptyString(value.mediaType) ||
|
|
696
|
-
!isNonEmptyString(value.filename)) {
|
|
697
|
-
return null;
|
|
698
|
-
}
|
|
699
|
-
return {
|
|
700
|
-
data: typeof dataValue === 'string' ? dataValue : new Uint8Array(dataValue),
|
|
701
|
-
mediaType: value.mediaType,
|
|
702
|
-
filename: value.filename,
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
if (isNonEmptyString(value.url)) {
|
|
706
|
-
const result = { url: value.url };
|
|
707
|
-
if (isNonEmptyString(value.filename)) {
|
|
708
|
-
result.filename = value.filename;
|
|
709
|
-
}
|
|
710
|
-
return result;
|
|
711
|
-
}
|
|
712
|
-
const referencedId = (isNonEmptyString(value.id) && value.id) ||
|
|
713
|
-
(isNonEmptyString(value.fileId) && value.fileId);
|
|
714
|
-
if (referencedId) {
|
|
715
|
-
const result = { id: referencedId };
|
|
716
|
-
if (isNonEmptyString(value.filename)) {
|
|
717
|
-
result.filename = value.filename;
|
|
718
|
-
}
|
|
719
|
-
return result;
|
|
720
|
-
}
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
function normalizeLegacyFileValue(value) {
|
|
724
|
-
const filename = typeof value.filename === 'string' && value.filename.length > 0
|
|
725
|
-
? value.filename
|
|
726
|
-
: undefined;
|
|
727
|
-
const mediaType = typeof value.mediaType === 'string' && value.mediaType.length > 0
|
|
728
|
-
? value.mediaType
|
|
729
|
-
: undefined;
|
|
730
|
-
if (typeof value.fileData === 'string' && value.fileData.length > 0) {
|
|
731
|
-
if (!mediaType || !filename) {
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
return { data: value.fileData, mediaType, filename };
|
|
735
|
-
}
|
|
736
|
-
if (value.fileData instanceof Uint8Array && value.fileData.length > 0) {
|
|
737
|
-
if (!mediaType || !filename) {
|
|
738
|
-
return null;
|
|
739
|
-
}
|
|
740
|
-
return { data: new Uint8Array(value.fileData), mediaType, filename };
|
|
741
|
-
}
|
|
742
|
-
if (typeof value.fileUrl === 'string' && value.fileUrl.length > 0) {
|
|
743
|
-
const result = { url: value.fileUrl };
|
|
744
|
-
if (filename) {
|
|
745
|
-
result.filename = filename;
|
|
746
|
-
}
|
|
747
|
-
return result;
|
|
748
|
-
}
|
|
749
|
-
if (typeof value.fileId === 'string' && value.fileId.length > 0) {
|
|
750
|
-
const result = { id: value.fileId };
|
|
751
|
-
if (filename) {
|
|
752
|
-
result.filename = filename;
|
|
753
|
-
}
|
|
754
|
-
return result;
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
if (typeof value.fileId === 'string' && value.fileId.length > 0) {
|
|
713
|
+
const result = { id: value.fileId };
|
|
714
|
+
if (filename) {
|
|
715
|
+
result.filename = filename;
|
|
716
|
+
}
|
|
717
|
+
return result;
|
|
755
718
|
}
|
|
756
719
|
return null;
|
|
757
720
|
}
|
|
@@ -776,6 +739,8 @@ function asDataUrl(base64, mediaType) {
|
|
|
776
739
|
}
|
|
777
740
|
/**
|
|
778
741
|
* @internal
|
|
742
|
+
* Runs every function tool call requested by the model and returns their outputs alongside
|
|
743
|
+
* the `RunItem` instances that should be appended to history.
|
|
779
744
|
*/
|
|
780
745
|
async function executeFunctionToolCalls(agent, toolRuns, runner, state) {
|
|
781
746
|
async function runSingleTool(toolRun) {
|
|
@@ -788,6 +753,7 @@ async function executeFunctionToolCalls(agent, toolRuns, runner, state) {
|
|
|
788
753
|
parsedArgs = JSON.parse(parsedArgs);
|
|
789
754
|
}
|
|
790
755
|
}
|
|
756
|
+
// Some tools require a human or policy check before execution; defer until approval is recorded.
|
|
791
757
|
const needsApproval = await toolRun.tool.needsApproval(state._context, parsedArgs, toolRun.toolCall.callId);
|
|
792
758
|
if (needsApproval) {
|
|
793
759
|
const approval = state._context.isToolApproved({
|
|
@@ -941,6 +907,8 @@ async function _runComputerActionAndScreenshot(computer, toolCall) {
|
|
|
941
907
|
}
|
|
942
908
|
/**
|
|
943
909
|
* @internal
|
|
910
|
+
* Executes any computer-use actions emitted by the model and returns the resulting items so the
|
|
911
|
+
* run history reflects the computer session.
|
|
944
912
|
*/
|
|
945
913
|
async function executeComputerActions(agent, actions, runner, runContext, customLogger = undefined) {
|
|
946
914
|
const _logger = customLogger ?? logger_1.default;
|
|
@@ -973,7 +941,7 @@ async function executeComputerActions(agent, actions, runner, runContext, custom
|
|
|
973
941
|
toolCall,
|
|
974
942
|
});
|
|
975
943
|
}
|
|
976
|
-
//
|
|
944
|
+
// Return the screenshot as a data URL when available; fall back to an empty string on failures.
|
|
977
945
|
const imageUrl = output ? `data:image/png;base64,${output}` : '';
|
|
978
946
|
const rawItem = {
|
|
979
947
|
type: 'computer_call_result',
|
|
@@ -986,6 +954,8 @@ async function executeComputerActions(agent, actions, runner, runContext, custom
|
|
|
986
954
|
}
|
|
987
955
|
/**
|
|
988
956
|
* @internal
|
|
957
|
+
* Drives handoff calls by invoking the downstream agent and capturing any generated items so
|
|
958
|
+
* the current agent can continue with the new context.
|
|
989
959
|
*/
|
|
990
960
|
async function executeHandoffCalls(agent, originalInput, preStepItems, newStepItems, newResponse, runHandoffs, runner, runContext) {
|
|
991
961
|
newStepItems = [...newStepItems];
|
|
@@ -1054,6 +1024,8 @@ const NOT_FINAL_OUTPUT = {
|
|
|
1054
1024
|
};
|
|
1055
1025
|
/**
|
|
1056
1026
|
* @internal
|
|
1027
|
+
* Determines whether tool executions produced a final agent output, triggered an interruption,
|
|
1028
|
+
* or whether the agent loop should continue collecting more responses.
|
|
1057
1029
|
*/
|
|
1058
1030
|
async function checkForFinalOutputFromTools(agent, toolResults, state) {
|
|
1059
1031
|
if (toolResults.length === 0) {
|
|
@@ -1181,4 +1153,571 @@ class AgentToolUseTracker {
|
|
|
1181
1153
|
}
|
|
1182
1154
|
}
|
|
1183
1155
|
exports.AgentToolUseTracker = AgentToolUseTracker;
|
|
1156
|
+
/**
|
|
1157
|
+
* @internal
|
|
1158
|
+
* Convert a user-provided input into a list of input items.
|
|
1159
|
+
*/
|
|
1160
|
+
function toInputItemList(input) {
|
|
1161
|
+
if (typeof input === 'string') {
|
|
1162
|
+
return [
|
|
1163
|
+
{
|
|
1164
|
+
type: 'message',
|
|
1165
|
+
role: 'user',
|
|
1166
|
+
content: input,
|
|
1167
|
+
},
|
|
1168
|
+
];
|
|
1169
|
+
}
|
|
1170
|
+
return [...input];
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* @internal
|
|
1174
|
+
* Extract model output items from run items, excluding tool approval items.
|
|
1175
|
+
*/
|
|
1176
|
+
function extractOutputItemsFromRunItems(items) {
|
|
1177
|
+
return items
|
|
1178
|
+
.filter((item) => item.type !== 'tool_approval_item')
|
|
1179
|
+
.map((item) => item.rawItem);
|
|
1180
|
+
}
|
|
1181
|
+
function normalizeItemsForSessionPersistence(items) {
|
|
1182
|
+
// Persisted sessions must avoid raw binary so we convert every item into a JSON-safe shape before writing to storage.
|
|
1183
|
+
return items.map((item) => sanitizeValueForSession(stripTransientCallIds(item)));
|
|
1184
|
+
}
|
|
1185
|
+
function sanitizeValueForSession(value, context = {}) {
|
|
1186
|
+
if (value === null || value === undefined) {
|
|
1187
|
+
return value;
|
|
1188
|
+
}
|
|
1189
|
+
// Convert supported binary payloads into ArrayBuffer views before serialization.
|
|
1190
|
+
const binary = toUint8ArrayIfBinary(value);
|
|
1191
|
+
if (binary) {
|
|
1192
|
+
return toDataUrlFromBytes(binary, context.mediaType);
|
|
1193
|
+
}
|
|
1194
|
+
if (Array.isArray(value)) {
|
|
1195
|
+
return value.map((entry) => sanitizeValueForSession(entry, context));
|
|
1196
|
+
}
|
|
1197
|
+
if (!isPlainObject(value)) {
|
|
1198
|
+
return value;
|
|
1199
|
+
}
|
|
1200
|
+
const record = value;
|
|
1201
|
+
const result = {};
|
|
1202
|
+
const mediaType = typeof record.mediaType === 'string' && record.mediaType.length > 0
|
|
1203
|
+
? record.mediaType
|
|
1204
|
+
: context.mediaType;
|
|
1205
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
1206
|
+
// Propagate explicit media type only when walking into binary payload containers.
|
|
1207
|
+
const nextContext = key === 'data' || key === 'fileData' ? { mediaType } : context;
|
|
1208
|
+
result[key] = sanitizeValueForSession(entry, nextContext);
|
|
1209
|
+
}
|
|
1210
|
+
return result;
|
|
1211
|
+
}
|
|
1212
|
+
function toUint8ArrayIfBinary(value) {
|
|
1213
|
+
// Normalize the diverse binary containers we may receive into a shared Uint8Array view.
|
|
1214
|
+
if (value instanceof ArrayBuffer) {
|
|
1215
|
+
return new Uint8Array(value);
|
|
1216
|
+
}
|
|
1217
|
+
if ((0, smartString_1.isArrayBufferView)(value)) {
|
|
1218
|
+
const view = value;
|
|
1219
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
1220
|
+
}
|
|
1221
|
+
if ((0, smartString_1.isNodeBuffer)(value)) {
|
|
1222
|
+
const view = value;
|
|
1223
|
+
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
1224
|
+
}
|
|
1225
|
+
if ((0, smartString_1.isSerializedBufferSnapshot)(value)) {
|
|
1226
|
+
const snapshot = value;
|
|
1227
|
+
return Uint8Array.from(snapshot.data);
|
|
1228
|
+
}
|
|
1229
|
+
return undefined;
|
|
1230
|
+
}
|
|
1231
|
+
function toDataUrlFromBytes(bytes, mediaType) {
|
|
1232
|
+
// Convert binary payloads into a durable data URL so session files remain self-contained.
|
|
1233
|
+
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(bytes);
|
|
1234
|
+
// Note that OpenAI Responses API never accepts application/octet-stream as a media type,
|
|
1235
|
+
// so we fall back to text/plain; that said, tools are supposed to return a valid media type when this utility is used.
|
|
1236
|
+
const type = mediaType && !mediaType.startsWith('data:') ? mediaType : 'text/plain';
|
|
1237
|
+
return `data:${type};base64,${base64}`;
|
|
1238
|
+
}
|
|
1239
|
+
function isPlainObject(value) {
|
|
1240
|
+
if (typeof value !== 'object' || value === null) {
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
const proto = Object.getPrototypeOf(value);
|
|
1244
|
+
return proto === Object.prototype || proto === null;
|
|
1245
|
+
}
|
|
1246
|
+
function stripTransientCallIds(value) {
|
|
1247
|
+
if (value === null || value === undefined) {
|
|
1248
|
+
return value;
|
|
1249
|
+
}
|
|
1250
|
+
if (Array.isArray(value)) {
|
|
1251
|
+
return value.map((entry) => stripTransientCallIds(entry));
|
|
1252
|
+
}
|
|
1253
|
+
if (!isPlainObject(value)) {
|
|
1254
|
+
return value;
|
|
1255
|
+
}
|
|
1256
|
+
const record = value;
|
|
1257
|
+
const result = {};
|
|
1258
|
+
const isProtocolItem = typeof record.type === 'string' && record.type.length > 0;
|
|
1259
|
+
const shouldStripId = isProtocolItem && shouldStripIdForType(record.type);
|
|
1260
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
1261
|
+
if (shouldStripId && key === 'id') {
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
result[key] = stripTransientCallIds(entry);
|
|
1265
|
+
}
|
|
1266
|
+
return result;
|
|
1267
|
+
}
|
|
1268
|
+
function shouldStripIdForType(type) {
|
|
1269
|
+
switch (type) {
|
|
1270
|
+
case 'function_call':
|
|
1271
|
+
case 'function_call_result':
|
|
1272
|
+
return true;
|
|
1273
|
+
default:
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* @internal
|
|
1279
|
+
* Persist full turn (input + outputs) for non-streaming runs.
|
|
1280
|
+
*/
|
|
1281
|
+
// Persists the combination of user inputs (possibly filtered) and model outputs for a completed turn.
|
|
1282
|
+
async function saveToSession(session, sessionInputItems, result) {
|
|
1283
|
+
if (!session) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const inputItems = sessionInputItems ?? [];
|
|
1287
|
+
const state = result.state;
|
|
1288
|
+
const alreadyPersisted = state._currentTurnPersistedItemCount ?? 0;
|
|
1289
|
+
// Persist only the portion of _generatedItems that has not yet been stored for this turn.
|
|
1290
|
+
const newRunItems = result.newItems.slice(alreadyPersisted);
|
|
1291
|
+
if (process.env.OPENAI_AGENTS__DEBUG_SAVE_SESSION) {
|
|
1292
|
+
console.debug('saveToSession:newRunItems', newRunItems.map((item) => item.type));
|
|
1293
|
+
}
|
|
1294
|
+
const outputItems = extractOutputItemsFromRunItems(newRunItems);
|
|
1295
|
+
const itemsToSave = [...inputItems, ...outputItems];
|
|
1296
|
+
if (itemsToSave.length === 0) {
|
|
1297
|
+
state._currentTurnPersistedItemCount =
|
|
1298
|
+
alreadyPersisted + newRunItems.length;
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
|
|
1302
|
+
await session.addItems(sanitizedItems);
|
|
1303
|
+
state._currentTurnPersistedItemCount = alreadyPersisted + newRunItems.length;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* @internal
|
|
1307
|
+
* Persist only the user input for streaming runs at start.
|
|
1308
|
+
*/
|
|
1309
|
+
// For streaming runs we persist user input as soon as it is sent so reconnections can resume.
|
|
1310
|
+
async function saveStreamInputToSession(session, sessionInputItems) {
|
|
1311
|
+
if (!session) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
if (!sessionInputItems || sessionInputItems.length === 0) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const sanitizedInput = normalizeItemsForSessionPersistence(sessionInputItems);
|
|
1318
|
+
await session.addItems(sanitizedInput);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* @internal
|
|
1322
|
+
* Persist only the model outputs for streaming runs at the end of a turn.
|
|
1323
|
+
*/
|
|
1324
|
+
// Complements saveStreamInputToSession by recording the streaming outputs at the end of the turn.
|
|
1325
|
+
async function saveStreamResultToSession(session, result) {
|
|
1326
|
+
if (!session) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
const state = result.state;
|
|
1330
|
+
const alreadyPersisted = state._currentTurnPersistedItemCount ?? 0;
|
|
1331
|
+
const newRunItems = result.newItems.slice(alreadyPersisted);
|
|
1332
|
+
const itemsToSave = extractOutputItemsFromRunItems(newRunItems);
|
|
1333
|
+
if (itemsToSave.length === 0) {
|
|
1334
|
+
state._currentTurnPersistedItemCount =
|
|
1335
|
+
alreadyPersisted + newRunItems.length;
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
|
|
1339
|
+
await session.addItems(sanitizedItems);
|
|
1340
|
+
state._currentTurnPersistedItemCount = alreadyPersisted + newRunItems.length;
|
|
1341
|
+
}
|
|
1342
|
+
async function prepareInputItemsWithSession(input, session, sessionInputCallback, options) {
|
|
1343
|
+
if (!session) {
|
|
1344
|
+
return {
|
|
1345
|
+
preparedInput: input,
|
|
1346
|
+
sessionItems: undefined,
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
const includeHistoryInPreparedInput = options?.includeHistoryInPreparedInput ?? true;
|
|
1350
|
+
const preserveDroppedNewItems = options?.preserveDroppedNewItems ?? false;
|
|
1351
|
+
const history = await session.getItems();
|
|
1352
|
+
const newInputItems = Array.isArray(input)
|
|
1353
|
+
? [...input]
|
|
1354
|
+
: toInputItemList(input);
|
|
1355
|
+
if (!sessionInputCallback) {
|
|
1356
|
+
return {
|
|
1357
|
+
preparedInput: includeHistoryInPreparedInput
|
|
1358
|
+
? [...history, ...newInputItems]
|
|
1359
|
+
: newInputItems,
|
|
1360
|
+
sessionItems: newInputItems,
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
// Capture snapshots before invoking the callback so we can reason about the original state even
|
|
1364
|
+
// if the callback mutates the history array in-place.
|
|
1365
|
+
const historySnapshot = history.slice();
|
|
1366
|
+
const newInputSnapshot = newInputItems.slice();
|
|
1367
|
+
// Delegate history reconciliation to the user-supplied callback. It must return a concrete list
|
|
1368
|
+
// to keep downstream model requests well-typed.
|
|
1369
|
+
const combined = await sessionInputCallback(history, newInputItems);
|
|
1370
|
+
if (!Array.isArray(combined)) {
|
|
1371
|
+
throw new errors_1.UserError('Session input callback must return an array of AgentInputItem objects.');
|
|
1372
|
+
}
|
|
1373
|
+
const historyCounts = buildItemFrequencyMap(historySnapshot);
|
|
1374
|
+
const newInputCounts = buildItemFrequencyMap(newInputSnapshot);
|
|
1375
|
+
const historyRefs = buildItemReferenceMap(historySnapshot);
|
|
1376
|
+
const newInputRefs = buildItemReferenceMap(newInputSnapshot);
|
|
1377
|
+
const appended = [];
|
|
1378
|
+
for (const item of combined) {
|
|
1379
|
+
const key = sessionItemKey(item);
|
|
1380
|
+
if (consumeReference(newInputRefs, key, item)) {
|
|
1381
|
+
decrementCount(newInputCounts, key);
|
|
1382
|
+
appended.push(item);
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
// Prioritize exact history matches before payload-based counts so callbacks that surface
|
|
1386
|
+
// history ahead of identical new inputs keep previously persisted items out of the new queue.
|
|
1387
|
+
if (consumeReference(historyRefs, key, item)) {
|
|
1388
|
+
decrementCount(historyCounts, key);
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
const historyRemaining = historyCounts.get(key) ?? 0;
|
|
1392
|
+
if (historyRemaining > 0) {
|
|
1393
|
+
historyCounts.set(key, historyRemaining - 1);
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
const newRemaining = newInputCounts.get(key) ?? 0;
|
|
1397
|
+
if (newRemaining > 0) {
|
|
1398
|
+
newInputCounts.set(key, newRemaining - 1);
|
|
1399
|
+
appended.push(item);
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
appended.push(item);
|
|
1403
|
+
}
|
|
1404
|
+
// Preserve redacted inputs for model delivery when requested (e.g. server-managed histories).
|
|
1405
|
+
const preparedItems = includeHistoryInPreparedInput
|
|
1406
|
+
? combined
|
|
1407
|
+
: appended.length > 0
|
|
1408
|
+
? appended
|
|
1409
|
+
: preserveDroppedNewItems
|
|
1410
|
+
? newInputSnapshot
|
|
1411
|
+
: [];
|
|
1412
|
+
return {
|
|
1413
|
+
preparedInput: preparedItems,
|
|
1414
|
+
// Respect callbacks that intentionally drop the latest inputs (e.g. to redact sensitive
|
|
1415
|
+
// values) by persisting only the items they kept in the combined array.
|
|
1416
|
+
sessionItems: appended,
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Accepts whatever the tool returned and attempts to coerce it into the structured protocol
|
|
1421
|
+
* shapes we expose to downstream model adapters (input_text/input_image/input_file). Tools are
|
|
1422
|
+
* allowed to return either a single structured object or an array of them; anything else falls
|
|
1423
|
+
* back to the legacy string pipeline.
|
|
1424
|
+
*/
|
|
1425
|
+
function normalizeStructuredToolOutputs(output) {
|
|
1426
|
+
if (Array.isArray(output)) {
|
|
1427
|
+
const structured = [];
|
|
1428
|
+
for (const item of output) {
|
|
1429
|
+
const normalized = normalizeStructuredToolOutput(item);
|
|
1430
|
+
if (!normalized) {
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1433
|
+
structured.push(normalized);
|
|
1434
|
+
}
|
|
1435
|
+
return structured;
|
|
1436
|
+
}
|
|
1437
|
+
const normalized = normalizeStructuredToolOutput(output);
|
|
1438
|
+
return normalized ? [normalized] : null;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Best-effort normalization of a single tool output item. If the object already matches the
|
|
1442
|
+
* protocol shape we simply cast it; otherwise we copy the recognised fields into the canonical
|
|
1443
|
+
* structure. Returning null lets the caller know we should revert to plain-string handling.
|
|
1444
|
+
*/
|
|
1445
|
+
function normalizeStructuredToolOutput(value) {
|
|
1446
|
+
if (!isRecord(value)) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
const type = value.type;
|
|
1450
|
+
if (type === 'text' && typeof value.text === 'string') {
|
|
1451
|
+
const output = { type: 'text', text: value.text };
|
|
1452
|
+
if (isRecord(value.providerData)) {
|
|
1453
|
+
output.providerData = value.providerData;
|
|
1454
|
+
}
|
|
1455
|
+
return output;
|
|
1456
|
+
}
|
|
1457
|
+
if (type === 'image') {
|
|
1458
|
+
const output = { type: 'image' };
|
|
1459
|
+
let imageString;
|
|
1460
|
+
let imageFileId;
|
|
1461
|
+
const fallbackImageMediaType = isNonEmptyString(value.mediaType)
|
|
1462
|
+
? value.mediaType
|
|
1463
|
+
: undefined;
|
|
1464
|
+
const imageField = value.image;
|
|
1465
|
+
if (typeof imageField === 'string' && imageField.length > 0) {
|
|
1466
|
+
imageString = imageField;
|
|
1467
|
+
}
|
|
1468
|
+
else if (isRecord(imageField)) {
|
|
1469
|
+
const imageObj = imageField;
|
|
1470
|
+
const inlineMediaType = isNonEmptyString(imageObj.mediaType)
|
|
1471
|
+
? imageObj.mediaType
|
|
1472
|
+
: fallbackImageMediaType;
|
|
1473
|
+
if (isNonEmptyString(imageObj.url)) {
|
|
1474
|
+
imageString = imageObj.url;
|
|
1475
|
+
}
|
|
1476
|
+
else if (isNonEmptyString(imageObj.data)) {
|
|
1477
|
+
imageString = toInlineImageString(imageObj.data, inlineMediaType);
|
|
1478
|
+
}
|
|
1479
|
+
else if (imageObj.data instanceof Uint8Array &&
|
|
1480
|
+
imageObj.data.length > 0) {
|
|
1481
|
+
imageString = toInlineImageString(imageObj.data, inlineMediaType);
|
|
1482
|
+
}
|
|
1483
|
+
if (!imageString) {
|
|
1484
|
+
const candidateId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
|
|
1485
|
+
(isNonEmptyString(imageObj.id) && imageObj.id) ||
|
|
1486
|
+
undefined;
|
|
1487
|
+
if (candidateId) {
|
|
1488
|
+
imageFileId = candidateId;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (!imageString &&
|
|
1493
|
+
typeof value.imageUrl === 'string' &&
|
|
1494
|
+
value.imageUrl.length > 0) {
|
|
1495
|
+
imageString = value.imageUrl;
|
|
1496
|
+
}
|
|
1497
|
+
if (!imageFileId &&
|
|
1498
|
+
typeof value.fileId === 'string' &&
|
|
1499
|
+
value.fileId.length > 0) {
|
|
1500
|
+
imageFileId = value.fileId;
|
|
1501
|
+
}
|
|
1502
|
+
if (!imageString &&
|
|
1503
|
+
typeof value.data === 'string' &&
|
|
1504
|
+
value.data.length > 0) {
|
|
1505
|
+
imageString = fallbackImageMediaType
|
|
1506
|
+
? toInlineImageString(value.data, fallbackImageMediaType)
|
|
1507
|
+
: value.data;
|
|
1508
|
+
}
|
|
1509
|
+
else if (!imageString &&
|
|
1510
|
+
value.data instanceof Uint8Array &&
|
|
1511
|
+
value.data.length > 0) {
|
|
1512
|
+
imageString = toInlineImageString(value.data, fallbackImageMediaType);
|
|
1513
|
+
}
|
|
1514
|
+
if (typeof value.detail === 'string' && value.detail.length > 0) {
|
|
1515
|
+
output.detail = value.detail;
|
|
1516
|
+
}
|
|
1517
|
+
if (imageString) {
|
|
1518
|
+
output.image = imageString;
|
|
1519
|
+
}
|
|
1520
|
+
else if (imageFileId) {
|
|
1521
|
+
output.image = { fileId: imageFileId };
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
return null;
|
|
1525
|
+
}
|
|
1526
|
+
if (isRecord(value.providerData)) {
|
|
1527
|
+
output.providerData = value.providerData;
|
|
1528
|
+
}
|
|
1529
|
+
return output;
|
|
1530
|
+
}
|
|
1531
|
+
if (type === 'file') {
|
|
1532
|
+
const fileValue = normalizeFileValue(value);
|
|
1533
|
+
if (!fileValue) {
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
const output = { type: 'file', file: fileValue };
|
|
1537
|
+
if (isRecord(value.providerData)) {
|
|
1538
|
+
output.providerData = value.providerData;
|
|
1539
|
+
}
|
|
1540
|
+
return output;
|
|
1541
|
+
}
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Translates the normalized tool output into the protocol `input_*` items. This is the last hop
|
|
1546
|
+
* before we hand the data to model-specific adapters, so we generate the exact schema expected by
|
|
1547
|
+
* the protocol definitions.
|
|
1548
|
+
*/
|
|
1549
|
+
function convertStructuredToolOutputToInputItem(output) {
|
|
1550
|
+
if (output.type === 'text') {
|
|
1551
|
+
const result = {
|
|
1552
|
+
type: 'input_text',
|
|
1553
|
+
text: output.text,
|
|
1554
|
+
};
|
|
1555
|
+
if (output.providerData) {
|
|
1556
|
+
result.providerData = output.providerData;
|
|
1557
|
+
}
|
|
1558
|
+
return result;
|
|
1559
|
+
}
|
|
1560
|
+
if (output.type === 'image') {
|
|
1561
|
+
const result = { type: 'input_image' };
|
|
1562
|
+
if (typeof output.detail === 'string' && output.detail.length > 0) {
|
|
1563
|
+
result.detail = output.detail;
|
|
1564
|
+
}
|
|
1565
|
+
if (typeof output.image === 'string' && output.image.length > 0) {
|
|
1566
|
+
result.image = output.image;
|
|
1567
|
+
}
|
|
1568
|
+
else if (isRecord(output.image)) {
|
|
1569
|
+
const imageObj = output.image;
|
|
1570
|
+
const inlineMediaType = isNonEmptyString(imageObj.mediaType)
|
|
1571
|
+
? imageObj.mediaType
|
|
1572
|
+
: undefined;
|
|
1573
|
+
if (isNonEmptyString(imageObj.url)) {
|
|
1574
|
+
result.image = imageObj.url;
|
|
1575
|
+
}
|
|
1576
|
+
else if (isNonEmptyString(imageObj.data)) {
|
|
1577
|
+
result.image =
|
|
1578
|
+
inlineMediaType && !imageObj.data.startsWith('data:')
|
|
1579
|
+
? asDataUrl(imageObj.data, inlineMediaType)
|
|
1580
|
+
: imageObj.data;
|
|
1581
|
+
}
|
|
1582
|
+
else if (imageObj.data instanceof Uint8Array &&
|
|
1583
|
+
imageObj.data.length > 0) {
|
|
1584
|
+
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(imageObj.data);
|
|
1585
|
+
result.image = asDataUrl(base64, inlineMediaType);
|
|
1586
|
+
}
|
|
1587
|
+
else {
|
|
1588
|
+
const referencedId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
|
|
1589
|
+
(isNonEmptyString(imageObj.id) && imageObj.id) ||
|
|
1590
|
+
undefined;
|
|
1591
|
+
if (referencedId) {
|
|
1592
|
+
result.image = { id: referencedId };
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (output.providerData) {
|
|
1597
|
+
result.providerData = output.providerData;
|
|
1598
|
+
}
|
|
1599
|
+
return result;
|
|
1600
|
+
}
|
|
1601
|
+
if (output.type === 'file') {
|
|
1602
|
+
const result = { type: 'input_file' };
|
|
1603
|
+
const fileValue = output.file;
|
|
1604
|
+
if (typeof fileValue === 'string') {
|
|
1605
|
+
result.file = fileValue;
|
|
1606
|
+
}
|
|
1607
|
+
else if (fileValue && typeof fileValue === 'object') {
|
|
1608
|
+
const record = fileValue;
|
|
1609
|
+
if ('data' in record && record.data) {
|
|
1610
|
+
const mediaType = record.mediaType ?? 'text/plain';
|
|
1611
|
+
if (typeof record.data === 'string') {
|
|
1612
|
+
result.file = asDataUrl(record.data, mediaType);
|
|
1613
|
+
}
|
|
1614
|
+
else {
|
|
1615
|
+
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(record.data);
|
|
1616
|
+
result.file = asDataUrl(base64, mediaType);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
else if (typeof record.url === 'string' && record.url.length > 0) {
|
|
1620
|
+
result.file = { url: record.url };
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
const referencedId = (typeof record.id === 'string' &&
|
|
1624
|
+
record.id.length > 0 &&
|
|
1625
|
+
record.id) ||
|
|
1626
|
+
(typeof record.fileId === 'string' && record.fileId.length > 0
|
|
1627
|
+
? record.fileId
|
|
1628
|
+
: undefined);
|
|
1629
|
+
if (referencedId) {
|
|
1630
|
+
result.file = { id: referencedId };
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
if (typeof record.filename === 'string' && record.filename.length > 0) {
|
|
1634
|
+
result.filename = record.filename;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (output.providerData) {
|
|
1638
|
+
result.providerData = output.providerData;
|
|
1639
|
+
}
|
|
1640
|
+
return result;
|
|
1641
|
+
}
|
|
1642
|
+
const exhaustiveCheck = output;
|
|
1643
|
+
return exhaustiveCheck;
|
|
1644
|
+
}
|
|
1645
|
+
function buildItemFrequencyMap(items) {
|
|
1646
|
+
const counts = new Map();
|
|
1647
|
+
for (const item of items) {
|
|
1648
|
+
const key = sessionItemKey(item);
|
|
1649
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1650
|
+
}
|
|
1651
|
+
return counts;
|
|
1652
|
+
}
|
|
1653
|
+
function buildItemReferenceMap(items) {
|
|
1654
|
+
const refs = new Map();
|
|
1655
|
+
for (const item of items) {
|
|
1656
|
+
const key = sessionItemKey(item);
|
|
1657
|
+
const list = refs.get(key);
|
|
1658
|
+
if (list) {
|
|
1659
|
+
list.push(item);
|
|
1660
|
+
}
|
|
1661
|
+
else {
|
|
1662
|
+
refs.set(key, [item]);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return refs;
|
|
1666
|
+
}
|
|
1667
|
+
function consumeReference(refs, key, target) {
|
|
1668
|
+
const candidates = refs.get(key);
|
|
1669
|
+
if (!candidates || candidates.length === 0) {
|
|
1670
|
+
return false;
|
|
1671
|
+
}
|
|
1672
|
+
const index = candidates.findIndex((candidate) => candidate === target);
|
|
1673
|
+
if (index === -1) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
candidates.splice(index, 1);
|
|
1677
|
+
if (candidates.length === 0) {
|
|
1678
|
+
refs.delete(key);
|
|
1679
|
+
}
|
|
1680
|
+
return true;
|
|
1681
|
+
}
|
|
1682
|
+
function decrementCount(map, key) {
|
|
1683
|
+
const remaining = (map.get(key) ?? 0) - 1;
|
|
1684
|
+
if (remaining <= 0) {
|
|
1685
|
+
map.delete(key);
|
|
1686
|
+
}
|
|
1687
|
+
else {
|
|
1688
|
+
map.set(key, remaining);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
function sessionItemKey(item) {
|
|
1692
|
+
return JSON.stringify(item, sessionSerializationReplacer);
|
|
1693
|
+
}
|
|
1694
|
+
function sessionSerializationReplacer(_key, value) {
|
|
1695
|
+
if (value instanceof ArrayBuffer) {
|
|
1696
|
+
return {
|
|
1697
|
+
__type: 'ArrayBuffer',
|
|
1698
|
+
data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(value)),
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
if ((0, smartString_1.isArrayBufferView)(value)) {
|
|
1702
|
+
const view = value;
|
|
1703
|
+
return {
|
|
1704
|
+
__type: view.constructor.name,
|
|
1705
|
+
data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
if ((0, smartString_1.isNodeBuffer)(value)) {
|
|
1709
|
+
const view = value;
|
|
1710
|
+
return {
|
|
1711
|
+
__type: 'Buffer',
|
|
1712
|
+
data: (0, base64_1.encodeUint8ArrayToBase64)(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)),
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
if ((0, smartString_1.isSerializedBufferSnapshot)(value)) {
|
|
1716
|
+
return {
|
|
1717
|
+
__type: 'Buffer',
|
|
1718
|
+
data: (0, base64_1.encodeUint8ArrayToBase64)(Uint8Array.from(value.data)),
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
return value;
|
|
1722
|
+
}
|
|
1184
1723
|
//# sourceMappingURL=runImplementation.js.map
|