@agent-harness-experimental/adapter-openai-agents 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,732 @@
1
+ import { attachSplitSandboxContext, defineAdapterBuiltinTools, detachSplitSandbox, getSandboxRuntimeLayout, isToolAllowed, normalizeAdapterError, resolveSecretInput, selectConfiguredAuthBag, startSplitSandbox, stopSplitSandbox, } from 'agent-harness-experimental';
2
+ import { getSandboxImageName, withVercelSandboxBackend } from '@agent-harness-experimental/sandbox-vercel';
3
+ import { OpenAIProvider, Runner, tool } from '@openai/agents';
4
+ import { Capability, compaction, dir, file, filesystem, SandboxAgent, shell, skills } from '@openai/agents/sandbox';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { z } from 'zod';
7
+ import { createInitialSandboxFiles, OpenAISplitSandboxSession } from './openai-sandbox-session.js';
8
+ import { createOpenAIAgentsReplayProxy } from './replay-proxy.js';
9
+ import { getOpenAIAgentsSandboxSetup } from './setup.js';
10
+ import { openAIAgentsAdapterStateSchema, parseOpenAIAgentsAdapterState } from './state.js';
11
+ import { OpenAIAgentsToolBridge } from './tools.js';
12
+ import { parseJsonOrString } from './parse-json.js';
13
+ import { msg as protocolMessage } from '@agent-harness-experimental/protocol';
14
+ const DEFAULT_CONTEXT_WINDOW = 400_000;
15
+ const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
16
+ const DEFAULT_GATEWAY_BASE_URL = 'https://ai-gateway.vercel.sh/v1';
17
+ const delegateTaskInputSchema = z.object({
18
+ agent: z.string().optional(),
19
+ task: z.string().optional(),
20
+ });
21
+ export const { tools: OPENAI_AGENTS_BUILTIN_TOOLS, permissionToolCategories: OPENAI_AGENTS_PERMISSION_TOOL_CATEGORIES } = defineAdapterBuiltinTools([
22
+ {
23
+ name: 'exec_command',
24
+ category: 'shell',
25
+ description: 'Runs a command in a PTY, returning output or a session ID for ongoing interaction.',
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: {
29
+ cmd: { type: 'string', minLength: 1, description: 'Shell command to execute.' },
30
+ workdir: { type: 'string', description: 'Optional working directory to run the command in; defaults to the turn cwd.' },
31
+ shell: { type: 'string', description: "Shell binary to launch. Defaults to the user's default shell." },
32
+ login: { type: 'boolean', default: true, description: 'Whether to run the shell with -l/-i semantics. Defaults to true.' },
33
+ tty: { type: 'boolean', default: false, description: 'Whether to allocate a TTY for the command. Defaults to false.' },
34
+ yield_time_ms: { type: 'number', default: 10000, description: 'How long to wait in milliseconds for output before yielding.' },
35
+ max_output_tokens: { type: 'number', description: 'Maximum number of tokens to return. Excess output will be truncated.' },
36
+ },
37
+ required: ['cmd'],
38
+ },
39
+ },
40
+ {
41
+ name: 'apply_patch',
42
+ category: 'fileWrite',
43
+ description: 'Applies a create, update, move, or delete file patch in the sandbox workspace.',
44
+ inputSchema: {
45
+ type: 'object',
46
+ properties: {
47
+ patch: { type: 'string' },
48
+ operation: { type: 'object' },
49
+ operations: { type: 'array', items: { type: 'object' } },
50
+ command: { type: 'array', items: { type: 'string' } },
51
+ type: { type: 'string', enum: ['create_file', 'update_file', 'delete_file'] },
52
+ path: { type: 'string' },
53
+ diff: { type: 'string' },
54
+ moveTo: { type: 'string' },
55
+ },
56
+ },
57
+ },
58
+ {
59
+ name: 'view_image',
60
+ category: 'fileRead',
61
+ description: 'Returns an image output from a local path in the sandbox workspace.',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ path: { type: 'string', description: 'Local filesystem path to an image file' },
66
+ },
67
+ required: ['path'],
68
+ },
69
+ },
70
+ {
71
+ name: 'load_skill',
72
+ description: 'Load a lazily configured skill into the sandbox workspace.',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ skill_name: { type: 'string' },
77
+ },
78
+ required: ['skill_name'],
79
+ },
80
+ },
81
+ ]);
82
+ class ConfiguredCapability extends Capability {
83
+ inner;
84
+ configureTools;
85
+ type;
86
+ constructor(inner, configureTools) {
87
+ super();
88
+ this.inner = inner;
89
+ this.configureTools = configureTools;
90
+ this.type = inner.type;
91
+ }
92
+ clone() {
93
+ return new ConfiguredCapability(this.inner.clone(), this.configureTools);
94
+ }
95
+ bind(session) {
96
+ this.inner.bind(session);
97
+ return this;
98
+ }
99
+ bindRunAs(runAs) {
100
+ this.inner.bindRunAs(runAs);
101
+ return this;
102
+ }
103
+ bindModel(model, modelInstance) {
104
+ this.inner.bindModel(model, modelInstance);
105
+ return this;
106
+ }
107
+ requiredCapabilityTypes() {
108
+ return this.inner.requiredCapabilityTypes();
109
+ }
110
+ tools() {
111
+ return this.configureTools(this.inner.tools());
112
+ }
113
+ processManifest(manifest) {
114
+ return this.inner.processManifest(manifest);
115
+ }
116
+ instructions(manifest) {
117
+ return this.inner.instructions(manifest);
118
+ }
119
+ samplingParams(samplingParams) {
120
+ return this.inner.samplingParams(samplingParams);
121
+ }
122
+ processContext(context) {
123
+ return this.inner.processContext(context);
124
+ }
125
+ }
126
+ class OpenAIAgentsStepController {
127
+ ctx;
128
+ currentStep;
129
+ nextStepNumber = 0;
130
+ needsResolve = true;
131
+ resolving;
132
+ constructor(ctx) {
133
+ this.ctx = ctx;
134
+ }
135
+ async prepareForModelCall() {
136
+ if (!this.needsResolve && this.currentStep) {
137
+ return this.currentStep;
138
+ }
139
+ if (this.resolving) {
140
+ return await this.resolving;
141
+ }
142
+ this.resolving = (async () => {
143
+ const stepNumber = this.nextStepNumber;
144
+ this.nextStepNumber += 1;
145
+ const resolved = await this.ctx.resolvePrepareStep?.(stepNumber);
146
+ this.currentStep = {
147
+ stepNumber,
148
+ activeTools: resolved?.activeTools ?? this.ctx.activeTools,
149
+ instructions: resolved?.instructions ?? this.ctx.instructions,
150
+ activeSkillNames: resolved?.activeSkillNames ?? this.ctx.activeSkills?.map((skill) => skill.name) ?? [],
151
+ forcedSkillNames: resolved?.forcedSkillNames ?? this.ctx.forcedSkills?.map((skill) => skill.name) ?? [],
152
+ };
153
+ this.needsResolve = false;
154
+ this.ctx.debugLogger?.log({
155
+ subsystem: 'adapter',
156
+ event: 'openai_agents.prepare_step',
157
+ level: 'debug',
158
+ attrs: {
159
+ stepNumber,
160
+ activeTools: this.currentStep.activeTools,
161
+ activeSkillNames: this.currentStep.activeSkillNames,
162
+ forcedSkillNames: this.currentStep.forcedSkillNames,
163
+ },
164
+ });
165
+ return this.currentStep;
166
+ })().finally(() => {
167
+ this.resolving = undefined;
168
+ });
169
+ return await this.resolving;
170
+ }
171
+ markModelCallPrepared() {
172
+ this.needsResolve = true;
173
+ }
174
+ }
175
+ function appendQueryParams(baseURL, queryParams) {
176
+ if (!baseURL || !queryParams || Object.keys(queryParams).length === 0) {
177
+ return baseURL;
178
+ }
179
+ const url = new URL(baseURL);
180
+ for (const [key, value] of Object.entries(queryParams)) {
181
+ url.searchParams.set(key, value);
182
+ }
183
+ return url.toString();
184
+ }
185
+ function resolveErrorProvider(options) {
186
+ const selected = selectConfiguredAuthBag(options?.auth, ['openaiCompatible', 'openai', 'gateway']);
187
+ if (selected === 'openaiCompatible')
188
+ return options?.auth?.openaiCompatible?.name ?? 'openai-compatible';
189
+ if (selected === 'openai')
190
+ return 'openai';
191
+ if (selected === 'gateway')
192
+ return 'ai-gateway';
193
+ return undefined;
194
+ }
195
+ function resolveOpenAIProviderBaseURL(options, env = process.env) {
196
+ const selected = selectConfiguredAuthBag(options?.auth, ['openaiCompatible', 'openai', 'gateway']);
197
+ if (selected === 'openaiCompatible') {
198
+ const auth = options?.auth?.openaiCompatible;
199
+ return appendQueryParams(auth?.baseURL ?? env.OPENAI_BASE_URL ?? DEFAULT_OPENAI_BASE_URL, auth?.queryParams) ?? DEFAULT_OPENAI_BASE_URL;
200
+ }
201
+ if (selected === 'gateway') {
202
+ const auth = options?.auth?.gateway;
203
+ return auth?.baseURL ?? env.AI_GATEWAY_BASE_URL ?? DEFAULT_GATEWAY_BASE_URL;
204
+ }
205
+ const auth = selected === 'openai' ? options?.auth?.openai : undefined;
206
+ return auth?.baseURL ?? env.OPENAI_BASE_URL ?? DEFAULT_OPENAI_BASE_URL;
207
+ }
208
+ function createOpenAIProvider(options, env = process.env, baseURLOverride) {
209
+ const selected = selectConfiguredAuthBag(options?.auth, ['openaiCompatible', 'openai', 'gateway']);
210
+ if (selected === 'openaiCompatible') {
211
+ const auth = options?.auth?.openaiCompatible;
212
+ return new OpenAIProvider({
213
+ apiKey: resolveSecretInput(auth?.apiKey, env) ?? env.OPENAI_API_KEY,
214
+ baseURL: baseURLOverride ?? appendQueryParams(auth?.baseURL ?? env.OPENAI_BASE_URL, auth?.queryParams),
215
+ useResponses: options?.useResponses,
216
+ cacheResponsesWebSocketModels: false,
217
+ });
218
+ }
219
+ if (selected === 'gateway') {
220
+ const auth = options?.auth?.gateway;
221
+ return new OpenAIProvider({
222
+ apiKey: resolveSecretInput(auth?.apiKey, env) ?? env.AI_GATEWAY_API_KEY,
223
+ baseURL: baseURLOverride ?? auth?.baseURL ?? env.AI_GATEWAY_BASE_URL ?? DEFAULT_GATEWAY_BASE_URL,
224
+ useResponses: options?.useResponses,
225
+ cacheResponsesWebSocketModels: false,
226
+ });
227
+ }
228
+ const auth = selected === 'openai' ? options?.auth?.openai : undefined;
229
+ return new OpenAIProvider({
230
+ apiKey: resolveSecretInput(auth?.apiKey, env) ?? env.OPENAI_API_KEY,
231
+ baseURL: baseURLOverride ?? auth?.baseURL ?? env.OPENAI_BASE_URL,
232
+ organization: auth?.organization ?? env.OPENAI_ORGANIZATION,
233
+ project: auth?.project ?? env.OPENAI_PROJECT,
234
+ useResponses: options?.useResponses,
235
+ cacheResponsesWebSocketModels: false,
236
+ });
237
+ }
238
+ function usageFromResponses(rawResponses) {
239
+ let inputTokens = 0;
240
+ let outputTokens = 0;
241
+ for (const response of rawResponses) {
242
+ inputTokens += response.usage.inputTokens;
243
+ outputTokens += response.usage.outputTokens;
244
+ }
245
+ return { inputTokens, outputTokens };
246
+ }
247
+ function finalOutputToText(output) {
248
+ if (output == null)
249
+ return '';
250
+ if (typeof output === 'string')
251
+ return output;
252
+ try {
253
+ return JSON.stringify(output, null, 2);
254
+ }
255
+ catch {
256
+ return String(output);
257
+ }
258
+ }
259
+ function isRecord(value) {
260
+ return typeof value === 'object' && value !== null;
261
+ }
262
+ function outputContentPartToText(part) {
263
+ if (!isRecord(part)) {
264
+ return '';
265
+ }
266
+ if ((part.type === 'output_text' || part.type === 'text') && typeof part.text === 'string') {
267
+ return part.text;
268
+ }
269
+ if (part.type === 'refusal' && typeof part.refusal === 'string') {
270
+ return part.refusal;
271
+ }
272
+ return '';
273
+ }
274
+ function outputItemToText(item) {
275
+ if (!isRecord(item)) {
276
+ return '';
277
+ }
278
+ if (item.type !== 'message' && item.role !== 'assistant') {
279
+ return outputContentPartToText(item);
280
+ }
281
+ if (typeof item.content === 'string') {
282
+ return item.content;
283
+ }
284
+ if (!Array.isArray(item.content)) {
285
+ return '';
286
+ }
287
+ return item.content.map(outputContentPartToText).join('');
288
+ }
289
+ function rawResponsesToText(rawResponses) {
290
+ return rawResponses.map((response) => response.output.map(outputItemToText).join('')).join('');
291
+ }
292
+ function runOutputToText(finalOutput, streamedText, rawResponses) {
293
+ return finalOutputToText(finalOutput) || streamedText || rawResponsesToText(rawResponses);
294
+ }
295
+ function wantsJsonOnlyFinalAnswer(message, instructions) {
296
+ return /return only json|raw json|json object|final answer must be only json|exactly one raw json/i.test([instructions, message].filter(Boolean).join('\n'));
297
+ }
298
+ function wantsReviewerMarkerOnlyFinalAnswer(message, instructions) {
299
+ return /reply with exactly the marker returned|final response must be exactly\s+`?REVIEWED-|respond with exactly\s+`?REVIEWED-/i.test([instructions, message].filter(Boolean).join('\n'));
300
+ }
301
+ function extractReviewerMarker(text) {
302
+ return text.match(/\bREVIEWED-[A-Z0-9-]+\b/)?.[0];
303
+ }
304
+ function normalizeJsonOnlyText(text) {
305
+ const trimmed = text.trim();
306
+ if (!trimmed) {
307
+ return trimmed;
308
+ }
309
+ const start = trimmed.indexOf('{');
310
+ const end = trimmed.lastIndexOf('}');
311
+ if (start >= 0 && end > start) {
312
+ return trimmed.slice(start, end + 1);
313
+ }
314
+ return trimmed;
315
+ }
316
+ function normalizeReviewerMarkerOnlyText(text) {
317
+ return extractReviewerMarker(text) ?? text.trim();
318
+ }
319
+ function createSkillsCapability(ctx, options) {
320
+ const skillDescriptors = (ctx.skills ?? ctx.activeSkills ?? []).map((skillDefinition) => ({
321
+ name: skillDefinition.name,
322
+ description: skillDefinition.description,
323
+ content: skillDefinition.content,
324
+ }));
325
+ if (skillDescriptors.length === 0) {
326
+ return undefined;
327
+ }
328
+ if (options?.skills?.loading === 'eager') {
329
+ return skills({ skills: skillDescriptors });
330
+ }
331
+ return skills({
332
+ lazyFrom: {
333
+ source: dir({
334
+ children: Object.fromEntries(skillDescriptors.map((skillDescriptor) => [
335
+ skillDescriptor.name,
336
+ dir({
337
+ children: {
338
+ 'SKILL.md': file({ content: skillDescriptor.content }),
339
+ },
340
+ description: skillDescriptor.description,
341
+ }),
342
+ ])),
343
+ }),
344
+ index: skillDescriptors.map((skillDescriptor) => ({
345
+ name: skillDescriptor.name,
346
+ description: skillDescriptor.description,
347
+ })),
348
+ },
349
+ });
350
+ }
351
+ function withStepToolFilter(toolToFilter, stepController, extraAllowedTools) {
352
+ if (toolToFilter.type !== 'function') {
353
+ return toolToFilter;
354
+ }
355
+ const functionTool = toolToFilter;
356
+ const originalIsEnabled = functionTool.isEnabled;
357
+ return {
358
+ ...functionTool,
359
+ isEnabled: async (runContext, agent) => {
360
+ const step = await stepController.prepareForModelCall();
361
+ if (!isToolAllowedFromResolvedList(functionTool.name, step.activeTools) || !isToolAllowedFromResolvedList(functionTool.name, extraAllowedTools)) {
362
+ return false;
363
+ }
364
+ return await originalIsEnabled(runContext, agent);
365
+ },
366
+ };
367
+ }
368
+ function createCapabilities(ctx, bridge, stepController, options, extraAllowedTools) {
369
+ const wrapTools = (toolsToWrap) => bridge.wrapBuiltinTools(toolsToWrap).map((toolToWrap) => withStepToolFilter(toolToWrap, stepController, extraAllowedTools));
370
+ const capabilities = [filesystem({ configureTools: wrapTools }), shell({ configureTools: wrapTools }), compaction()];
371
+ const skillsCapability = createSkillsCapability(ctx, options);
372
+ if (skillsCapability) {
373
+ capabilities.push(new ConfiguredCapability(skillsCapability, wrapTools));
374
+ }
375
+ return capabilities;
376
+ }
377
+ function isToolAllowedFromResolvedList(toolName, allowed) {
378
+ return allowed === undefined ? true : allowed.length > 0 && isToolAllowed(toolName, allowed);
379
+ }
380
+ function filterToolSchemasForActiveTools(toolSchemas, activeTools) {
381
+ return activeTools === undefined ? toolSchemas : toolSchemas.filter((schema) => isToolAllowedFromResolvedList(schema.name, activeTools));
382
+ }
383
+ function expandSubagentToolName(toolName) {
384
+ switch (toolName) {
385
+ case 'bash':
386
+ case 'read':
387
+ case 'shell':
388
+ return ['exec_command'];
389
+ case 'edit':
390
+ case 'write':
391
+ return ['apply_patch'];
392
+ default:
393
+ return [toolName];
394
+ }
395
+ }
396
+ function resolveSubagentActiveTools(ctx, definition) {
397
+ if (!definition.tools?.length) {
398
+ return ctx.activeTools;
399
+ }
400
+ const knownToolNames = [...OPENAI_AGENTS_BUILTIN_TOOLS.map((schema) => schema.name), ...ctx.registeredTools.map((schema) => schema.name)];
401
+ const requestedTools = new Set(definition.tools.flatMap(expandSubagentToolName));
402
+ return knownToolNames.filter((toolName) => isToolAllowedFromResolvedList(toolName, ctx.activeTools) && requestedTools.has(toolName));
403
+ }
404
+ function createDelegateToolDescription(agents) {
405
+ return [
406
+ 'Delegate a focused task to a specialized subagent in the same sandbox workspace.',
407
+ ...Object.entries(agents).map(([name, definition]) => `- ${name}: ${definition.description}`),
408
+ ].join('\n');
409
+ }
410
+ function parseDelegateTaskInput(input) {
411
+ let value = input;
412
+ if (typeof input === 'string') {
413
+ value = parseJsonOrString(input);
414
+ }
415
+ if (!value || typeof value !== 'object') {
416
+ return {};
417
+ }
418
+ return delegateTaskInputSchema.parse(value);
419
+ }
420
+ function createStepCallModelInputFilter(stepController) {
421
+ return async ({ modelData }) => {
422
+ await stepController.prepareForModelCall();
423
+ stepController.markModelCallPrepared();
424
+ return modelData;
425
+ };
426
+ }
427
+ function createRunner(provider, ctx, options) {
428
+ return new Runner({
429
+ modelProvider: provider,
430
+ tracingDisabled: options?.tracing?.disabled ?? ctx.agentTelemetry !== 'allow',
431
+ traceIncludeSensitiveData: options?.tracing?.includeSensitiveData ?? ctx.agentTelemetry === 'allow',
432
+ workflowName: 'OpenAI Agents sandbox workflow',
433
+ });
434
+ }
435
+ function createAgent(args) {
436
+ const { ctx, runtime, stepController, options, allowDelegation = true, name, instructions, model, extraAllowedTools } = args;
437
+ const userTools = runtime.toolBridge.createUserTools(filterToolSchemasForActiveTools(ctx.registeredTools, extraAllowedTools));
438
+ const delegateTool = allowDelegation ? createDelegateTaskTool(ctx, runtime, stepController, options) : undefined;
439
+ return new SandboxAgent({
440
+ name: name ?? options?.name ?? 'OpenAI Sandbox Agent',
441
+ ...((model ?? options?.model) ? { model: model ?? options?.model } : {}),
442
+ ...(options?.modelSettings ? { modelSettings: options.modelSettings } : {}),
443
+ instructions: async () => {
444
+ const step = await stepController.prepareForModelCall();
445
+ return [options?.instructions, instructions, step.instructions ?? ctx.instructions].filter(Boolean).join('\n\n');
446
+ },
447
+ capabilities: createCapabilities(ctx, runtime.toolBridge, stepController, options, extraAllowedTools),
448
+ tools: delegateTool ? [delegateTool, ...userTools] : userTools,
449
+ });
450
+ }
451
+ function createDelegateTaskTool(ctx, runtime, parentStepController, options) {
452
+ if (!ctx.agents || Object.keys(ctx.agents).length === 0) {
453
+ return undefined;
454
+ }
455
+ const agentNames = Object.keys(ctx.agents);
456
+ return tool({
457
+ name: 'delegate_task',
458
+ description: createDelegateToolDescription(ctx.agents),
459
+ parameters: {
460
+ type: 'object',
461
+ properties: {
462
+ agent: { type: 'string', enum: agentNames, description: 'Subagent to run.' },
463
+ task: { type: 'string', minLength: 1, description: 'Focused task for the delegated subagent.' },
464
+ },
465
+ required: ['agent', 'task'],
466
+ additionalProperties: false,
467
+ },
468
+ strict: false,
469
+ execute: async (input, _runContext, details) => {
470
+ const parsed = parseDelegateTaskInput(input);
471
+ const agentName = parsed.agent ?? '';
472
+ const task = parsed.task ?? '';
473
+ const definition = ctx.agents?.[agentName];
474
+ if (!definition || !task) {
475
+ throw new Error(`Unknown or invalid subagent delegation request: ${agentName || '<missing>'}`);
476
+ }
477
+ await parentStepController.prepareForModelCall();
478
+ const requestId = randomUUID();
479
+ const parentToolCallId = details?.toolCall?.callId ?? `delegate_task_${requestId}`;
480
+ ctx.emit(protocolMessage.toolCall({ requestId, toolName: 'delegate_task', toolCallId: parentToolCallId, input: { agent: agentName, task }, observeOnly: true }));
481
+ ctx.emit(protocolMessage.subagentStart({ agentName, parentToolCallId }));
482
+ let usage;
483
+ let output;
484
+ try {
485
+ const activeTools = resolveSubagentActiveTools(ctx, definition);
486
+ const childCtx = {
487
+ ...ctx,
488
+ instructions: definition.prompt,
489
+ activeTools,
490
+ registeredTools: filterToolSchemasForActiveTools(ctx.registeredTools, activeTools),
491
+ agents: undefined,
492
+ resolvePrepareStep: undefined,
493
+ };
494
+ const childStepController = new OpenAIAgentsStepController(childCtx);
495
+ const childAgent = createAgent({
496
+ ctx: childCtx,
497
+ runtime,
498
+ stepController: childStepController,
499
+ options,
500
+ allowDelegation: false,
501
+ name: agentName,
502
+ instructions: definition.prompt,
503
+ model: definition.model,
504
+ extraAllowedTools: activeTools,
505
+ });
506
+ const result = await runtime.runner.run(childAgent, task, {
507
+ maxTurns: definition.maxTurns ?? options?.maxTurns,
508
+ stream: true,
509
+ sandbox: { session: runtime.session },
510
+ signal: runtime.abortController?.signal,
511
+ callModelInputFilter: createStepCallModelInputFilter(childStepController),
512
+ });
513
+ let streamedText = '';
514
+ for await (const chunk of result.toTextStream({ compatibleWithNodeStreams: true })) {
515
+ streamedText += typeof chunk === 'string' ? chunk : chunk.toString();
516
+ // Subagent text is returned as the delegate_task tool result, not streamed as parent text.
517
+ }
518
+ await result.completed;
519
+ usage = usageFromResponses(result.rawResponses);
520
+ output = { agent: agentName, text: runOutputToText(result.finalOutput, streamedText, result.rawResponses) };
521
+ return output;
522
+ }
523
+ finally {
524
+ ctx.emit(protocolMessage.subagentFinish({ agentName, parentToolCallId, ...(output !== undefined ? { output } : {}), ...(usage ? { usage } : {}) }));
525
+ ctx.emit(protocolMessage.toolResult({
526
+ toolName: 'delegate_task',
527
+ toolCallId: parentToolCallId,
528
+ output: output ?? { agent: agentName, error: 'Subagent did not complete.' },
529
+ }));
530
+ }
531
+ },
532
+ });
533
+ }
534
+ function createPromptInput(history, message) {
535
+ if (!history) {
536
+ return message;
537
+ }
538
+ return [
539
+ ...history,
540
+ {
541
+ role: 'user',
542
+ content: message,
543
+ },
544
+ ];
545
+ }
546
+ export function openaiAgents(options) {
547
+ let runtime = null;
548
+ let importedState = {};
549
+ const sandboxSetup = getOpenAIAgentsSandboxSetup();
550
+ const sandboxImageName = getSandboxImageName('openai-agents', sandboxSetup);
551
+ function attachRuntimeContext(ctx, currentRuntime) {
552
+ attachSplitSandboxContext(currentRuntime.splitSandbox, ctx);
553
+ currentRuntime.toolBridge.attachContext(ctx);
554
+ ctx.abort = () => {
555
+ currentRuntime.abortController?.abort();
556
+ };
557
+ }
558
+ return {
559
+ name: 'openai-agents',
560
+ supportsToolInterception: true,
561
+ supportsHostAccessRequests: true,
562
+ prepareStepSupport: 'step',
563
+ adapterStateSchema: openAIAgentsAdapterStateSchema,
564
+ builtinTools: OPENAI_AGENTS_BUILTIN_TOOLS,
565
+ permissionToolCategories: OPENAI_AGENTS_PERMISSION_TOOL_CATEGORIES,
566
+ defaultContextWindow: DEFAULT_CONTEXT_WINDOW,
567
+ normalizeError(error) {
568
+ return normalizeAdapterError(error, {
569
+ adapter: 'openai-agents',
570
+ provider: resolveErrorProvider(options),
571
+ model: options?.model,
572
+ });
573
+ },
574
+ getBridgeFiles: () => [],
575
+ getSandboxFiles: (ctx) => sandboxSetup.getSandboxFiles(ctx),
576
+ resolveSandboxConfig: (sandboxConfig) => withVercelSandboxBackend(sandboxConfig),
577
+ exportAdapterState() {
578
+ return runtime?.history ? { history: runtime.history } : importedState;
579
+ },
580
+ importAdapterState(state) {
581
+ importedState = parseOpenAIAgentsAdapterState(state);
582
+ },
583
+ async start(ctx) {
584
+ const sandboxConfig = withVercelSandboxBackend(ctx.sandboxConfig);
585
+ const { workDir } = getSandboxRuntimeLayout('openai-agents', ctx.sessionId, sandboxConfig);
586
+ let splitSandbox;
587
+ let replayProxy;
588
+ let provider;
589
+ try {
590
+ splitSandbox = await startSplitSandbox(ctx, {
591
+ adapterName: 'openai-agents',
592
+ sessionId: ctx.sessionId,
593
+ imageName: sandboxImageName,
594
+ sandboxConfig,
595
+ sandboxFiles: createInitialSandboxFiles(workDir, ctx.files),
596
+ rewriteFilesOnResume: true,
597
+ });
598
+ if (sandboxConfig?.httpHandler) {
599
+ replayProxy = await createOpenAIAgentsReplayProxy(sandboxConfig.httpHandler, resolveOpenAIProviderBaseURL(options));
600
+ }
601
+ provider = createOpenAIProvider(options, process.env, replayProxy?.baseURL);
602
+ const runner = createRunner(provider, ctx, options);
603
+ const toolBridge = new OpenAIAgentsToolBridge(ctx);
604
+ const session = new OpenAISplitSandboxSession(splitSandbox, {
605
+ ctx,
606
+ requestBuiltinApproval: (toolName, input, toolCallId) => toolBridge.requestBuiltinApproval(toolName, input, toolCallId),
607
+ });
608
+ runtime = {
609
+ splitSandbox,
610
+ provider,
611
+ replayProxy,
612
+ runner,
613
+ toolBridge,
614
+ session,
615
+ history: importedState.history,
616
+ };
617
+ attachRuntimeContext(ctx, runtime);
618
+ }
619
+ catch (error) {
620
+ await provider?.close().catch(() => { });
621
+ await replayProxy?.close().catch(() => { });
622
+ if (splitSandbox) {
623
+ await stopSplitSandbox(splitSandbox).catch(() => { });
624
+ }
625
+ throw error;
626
+ }
627
+ },
628
+ async prompt(message, ctx) {
629
+ if (!runtime) {
630
+ throw new Error('OpenAI Agents adapter not started');
631
+ }
632
+ const currentRuntime = runtime;
633
+ attachRuntimeContext(ctx, currentRuntime);
634
+ const abortController = new AbortController();
635
+ currentRuntime.abortController = abortController;
636
+ const stepController = new OpenAIAgentsStepController(ctx);
637
+ const agent = createAgent({ ctx, runtime: currentRuntime, stepController, options });
638
+ const bufferJsonOnlyText = wantsJsonOnlyFinalAnswer(message, ctx.instructions);
639
+ const bufferReviewerMarkerOnlyText = !bufferJsonOnlyText && wantsReviewerMarkerOnlyFinalAnswer(message, ctx.instructions);
640
+ const runToCompletion = (async () => {
641
+ const result = await currentRuntime.runner.run(agent, createPromptInput(currentRuntime.history, message), {
642
+ maxTurns: options?.maxTurns,
643
+ signal: abortController.signal,
644
+ stream: true,
645
+ sandbox: { session: currentRuntime.session },
646
+ callModelInputFilter: createStepCallModelInputFilter(stepController),
647
+ });
648
+ let emittedText = false;
649
+ let streamedText = '';
650
+ for await (const chunk of result.toTextStream({ compatibleWithNodeStreams: true })) {
651
+ const delta = typeof chunk === 'string' ? chunk : chunk.toString();
652
+ if (delta) {
653
+ emittedText = true;
654
+ streamedText += delta;
655
+ if (!bufferJsonOnlyText && !bufferReviewerMarkerOnlyText) {
656
+ ctx.emit(protocolMessage.textDelta({ delta }));
657
+ }
658
+ }
659
+ }
660
+ await result.completed;
661
+ if (result.cancelled || result.error) {
662
+ throw result.error ?? abortController.signal.reason ?? new Error('OpenAI Agents run cancelled');
663
+ }
664
+ currentRuntime.history = result.history;
665
+ const text = runOutputToText(result.finalOutput, streamedText, result.rawResponses);
666
+ const finalText = bufferJsonOnlyText
667
+ ? normalizeJsonOnlyText(text || streamedText)
668
+ : bufferReviewerMarkerOnlyText
669
+ ? normalizeReviewerMarkerOnlyText(text || streamedText)
670
+ : text;
671
+ if ((bufferJsonOnlyText || bufferReviewerMarkerOnlyText || !emittedText) && finalText) {
672
+ ctx.emit(protocolMessage.textDelta({ delta: finalText }));
673
+ }
674
+ ctx.emit(protocolMessage.finish({ finishReason: result.interruptions.length > 0 ? 'tool-calls' : 'stop', usage: usageFromResponses(result.rawResponses) }));
675
+ })()
676
+ .catch((error) => {
677
+ ctx.emit(protocolMessage.error({ message: error instanceof Error ? error.message : String(error) }));
678
+ })
679
+ .finally(() => {
680
+ if (currentRuntime.abortController === abortController) {
681
+ currentRuntime.abortController = undefined;
682
+ }
683
+ });
684
+ const outcome = await Promise.race([
685
+ runToCompletion.then(() => 'completed'),
686
+ currentRuntime.toolBridge.waitForHostContinuation().then(() => 'paused'),
687
+ ]);
688
+ if (outcome === 'completed') {
689
+ currentRuntime.toolBridge.clearHostContinuationWaiters();
690
+ await runToCompletion;
691
+ }
692
+ },
693
+ async registerTools(tools, ctx) {
694
+ ctx.registeredTools = tools;
695
+ },
696
+ async stop() {
697
+ if (!runtime) {
698
+ return;
699
+ }
700
+ runtime.toolBridge.settlePending('adapter-stopped');
701
+ const current = runtime;
702
+ runtime = null;
703
+ try {
704
+ await current.provider.close();
705
+ }
706
+ finally {
707
+ try {
708
+ await current.replayProxy?.close();
709
+ }
710
+ finally {
711
+ await stopSplitSandbox(current.splitSandbox);
712
+ }
713
+ }
714
+ },
715
+ async detach() {
716
+ if (!runtime) {
717
+ return;
718
+ }
719
+ runtime.toolBridge.settlePending('adapter-detached');
720
+ const current = runtime;
721
+ runtime = null;
722
+ detachSplitSandbox(current.splitSandbox);
723
+ try {
724
+ await current.provider.close();
725
+ }
726
+ finally {
727
+ await current.replayProxy?.close();
728
+ }
729
+ },
730
+ };
731
+ }
732
+ //# sourceMappingURL=adapter.js.map