UNPKG

@openai/agents-core

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

468 lines • 16.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.filesystem = filesystem; const zod_1 = require("zod"); const errors_1 = require("../../errors.js"); const tool_1 = require("../../tool.js"); const protocol_1 = require("../../types/protocol.js"); const base64_1 = require("../../utils/base64.js"); const spans_1 = require("../runtime/spans.js"); const typeGuards_1 = require("../shared/typeGuards.js"); const base_1 = require("./base.js"); const transport_1 = require("./transport.js"); const applyPatchFunctionParameters = { type: 'object', properties: { patch: { type: 'string', description: 'A freeform apply_patch payload wrapped in *** Begin Patch and *** End Patch.', }, operation: { type: 'object', description: 'A single structured apply_patch operation.', }, operations: { type: 'array', items: { type: 'object' }, description: 'Structured apply_patch operations to apply in order.', }, command: { type: 'array', description: 'Optional ["apply_patch", patch] command tuple form.', }, type: { type: 'string', enum: ['create_file', 'update_file', 'delete_file'], description: 'The patch operation to apply.', }, path: { type: 'string', description: 'The sandbox file path to patch.', }, diff: { type: 'string', description: 'The V4A patch diff for create_file and update_file operations.', }, moveTo: { type: 'string', description: 'Optional destination path for update_file move operations.', }, }, required: [], additionalProperties: true, }; const BEGIN_PATCH = '*** Begin Patch'; const END_PATCH = '*** End Patch'; const ADD_FILE = '*** Add File: '; const DELETE_FILE = '*** Delete File: '; const UPDATE_FILE = '*** Update File: '; const MOVE_TO = '*** Move to: '; function toErrorMessage(error) { if (error instanceof Error) { return error.message || error.toString(); } try { return JSON.stringify(error); } catch { return String(error); } } function renderImageForTextTransport(output) { if (typeof output === 'string') { return output; } const image = output.image; if (typeof image === 'string') { return image; } if (!(0, typeGuards_1.isRecord)(image)) { return 'No image data was returned by the sandbox session.'; } const imageRecord = image; if (typeof imageRecord.url === 'string') { return imageRecord.url; } if (typeof imageRecord.fileId === 'string') { return `OpenAI file reference: ${imageRecord.fileId}`; } if (typeof imageRecord.data === 'string') { const mediaType = typeof imageRecord.mediaType === 'string' ? imageRecord.mediaType : 'application/octet-stream'; return `data:${mediaType};base64,${imageRecord.data}`; } if (imageRecord.data instanceof Uint8Array) { const mediaType = typeof imageRecord.mediaType === 'string' ? imageRecord.mediaType : 'application/octet-stream'; return `data:${mediaType};base64,${(0, base64_1.encodeUint8ArrayToBase64)(imageRecord.data)}`; } return 'No image data was returned by the sandbox session.'; } function renderViewImageError(path, error) { const message = toErrorMessage(error); if (/not found/iu.test(message)) { return `image path \`${path}\` was not found`; } if (/not a file/iu.test(message)) { return `image path \`${path}\` is not a file`; } if (/exceeds the 10 MB limit|exceeded the allowed size/iu.test(message)) { return `image path \`${path}\` exceeded the allowed size of 10MB; resize or compress the image and try again`; } if (/unsupported image format|not a supported image/iu.test(message)) { return `image path \`${path}\` is not a supported image file`; } const name = error instanceof Error ? error.name : typeof error; return `unable to read image at \`${path}\`: ${name}`; } function parseApplyPatchInput(rawInput) { const trimmedInput = rawInput.trimStart(); if (trimmedInput.startsWith(BEGIN_PATCH)) { return parseFreeformPatch(trimmedInput); } let payload; try { payload = JSON.parse(rawInput); } catch (error) { return { ok: false, error: `Invalid apply_patch JSON: ${toErrorMessage(error)}`, }; } return parseApplyPatchPayload(payload); } function parseApplyPatchPayload(payload) { if (typeof payload === 'string') { return parseFreeformPatch(payload); } if (Array.isArray(payload)) { return parseStructuredOperations(payload); } if (!(0, typeGuards_1.isRecord)(payload)) { return { ok: false, error: 'apply_patch input must be an object or array.', }; } if (typeof payload.patch === 'string') { return parseFreeformPatch(payload.patch); } if (Array.isArray(payload.command)) { const [commandName, patch] = payload.command; if (commandName === 'apply_patch' && typeof patch === 'string') { return parseFreeformPatch(patch); } } if (Array.isArray(payload.operations)) { return parseStructuredOperations(payload.operations); } if (payload.operation !== undefined) { return parseStructuredOperations([payload.operation]); } return parseStructuredOperations([payload]); } function parseStructuredOperations(payloads) { const operations = []; for (const payload of payloads) { const parsed = protocol_1.ApplyPatchOperation.safeParse(payload); if (!parsed.success) { return { ok: false, error: `Invalid apply_patch operation: ${parsed.error.message}`, }; } operations.push(parsed.data); } if (operations.length === 0) { return { ok: false, error: 'apply_patch input must include an operation.' }; } return { ok: true, operations }; } function parseFreeformPatch(rawPatch) { const lines = rawPatch.split(/\r?\n/); if (lines.at(-1) === '') { lines.pop(); } if (lines[0] !== BEGIN_PATCH) { return { ok: false, error: `apply_patch input must start with "${BEGIN_PATCH}".`, }; } if (lines.length < 2 || lines.at(-1) !== END_PATCH) { return { ok: false, error: `apply_patch input must end with "${END_PATCH}".`, }; } const operations = []; let index = 1; while (index < lines.length - 1) { const line = lines[index]; let parsed; if (line.startsWith(ADD_FILE)) { parsed = parseAddFilePatch(lines, index); } else if (line.startsWith(DELETE_FILE)) { parsed = parseDeleteFilePatch(lines, index); } else if (line.startsWith(UPDATE_FILE)) { parsed = parseUpdateFilePatch(lines, index); } else { return { ok: false, error: `Invalid apply_patch file operation header: ${line}`, }; } if ('error' in parsed) { return { ok: false, error: parsed.error }; } operations.push(parsed.operation); index = parsed.nextIndex; } if (operations.length === 0) { return { ok: false, error: 'apply_patch input must include at least one file operation.', }; } return { ok: true, operations }; } function parseAddFilePatch(lines, index) { const path = parsePatchHeader(lines[index], ADD_FILE); if (!path) { return { error: `Missing path in apply_patch header: ${lines[index]}` }; } index += 1; const diffLines = []; while (index < lines.length - 1 && !isFileOperationHeader(lines[index])) { const line = lines[index]; if (!line.startsWith('+')) { return { error: `Invalid Add File line: ${line}` }; } diffLines.push(line); index += 1; } if (diffLines.length === 0) { return { error: `Add File patch for ${path} must include at least one + line.`, }; } return { operation: { type: 'create_file', path, diff: joinDiff(diffLines), }, nextIndex: index, }; } function parseDeleteFilePatch(lines, index) { const path = parsePatchHeader(lines[index], DELETE_FILE); if (!path) { return { error: `Missing path in apply_patch header: ${lines[index]}` }; } index += 1; if (index < lines.length - 1 && !isFileOperationHeader(lines[index])) { return { error: `Delete File patch for ${path} must not include a diff.` }; } return { operation: { type: 'delete_file', path, }, nextIndex: index, }; } function parseUpdateFilePatch(lines, index) { const path = parsePatchHeader(lines[index], UPDATE_FILE); if (!path) { return { error: `Missing path in apply_patch header: ${lines[index]}` }; } index += 1; let moveTo; if (index < lines.length - 1 && lines[index].startsWith(MOVE_TO)) { const parsedMoveTo = parsePatchHeader(lines[index], MOVE_TO); if (!parsedMoveTo) { return { error: `Missing path in apply_patch header: ${lines[index]}` }; } moveTo = parsedMoveTo; index += 1; } const diffLines = []; while (index < lines.length - 1 && !isFileOperationHeader(lines[index])) { diffLines.push(lines[index]); index += 1; } if (diffLines.length === 0 && !moveTo) { return { error: `Update File patch for ${path} must include a hunk.` }; } return { operation: { type: 'update_file', path, diff: diffLines.length > 0 ? joinDiff(diffLines) : '', ...(moveTo ? { moveTo } : {}), }, nextIndex: index, }; } function parsePatchHeader(line, prefix) { const path = line.slice(prefix.length).trim(); return path ? path : null; } function isFileOperationHeader(line) { return (line.startsWith(ADD_FILE) || line.startsWith(DELETE_FILE) || line.startsWith(UPDATE_FILE)); } function joinDiff(lines) { return `${lines.join('\n')}\n`; } async function applyPatchOperation(editor, operation, runContext) { let result; try { switch (operation.type) { case 'create_file': result = await editor.createFile(operation, { runContext }); break; case 'update_file': result = await editor.updateFile(operation, { runContext }); break; case 'delete_file': result = await editor.deleteFile(operation, { runContext }); break; default: return 'Unsupported apply_patch operation.'; } } catch (error) { return `Failed to apply patch: ${toErrorMessage(error)}`; } if (result?.status === 'failed') { return result.output ? `Patch failed: ${result.output}` : 'Patch failed.'; } return result?.output ?? 'Patch applied.'; } async function applyPatchOperations(editor, operations, runContext) { const outputs = []; for (const operation of operations) { const output = await applyPatchOperation(editor, operation, runContext); if (output) { outputs.push(output); } } return outputs.join('\n') || 'Patch applied.'; } function applyPatchFunctionTool(editor) { const fallbackTool = { type: 'function', name: 'apply_patch', description: 'Applies a create, update, move, or delete file patch in the sandbox workspace. Accepts a freeform patch string or structured apply_patch operations.', parameters: applyPatchFunctionParameters, strict: false, deferLoading: false, needsApproval: async () => false, isEnabled: async () => true, invoke: async (runContext, input) => { const parsed = parseApplyPatchInput(input); if (!parsed.ok) { return parsed.error; } return await applyPatchOperations(editor, parsed.operations, runContext); }, }; return fallbackTool; } class FilesystemCapability extends base_1.Capability { type = 'filesystem'; configureTools; constructor(args = {}) { super(); this.configureTools = args.configureTools; } tools() { const session = (0, base_1.requireBoundSession)(this.type, this._session); const sessionEditor = session.createEditor?.(this._runAs); if (!sessionEditor) { throw new errors_1.UserError('Filesystem sandbox sessions must provide createEditor().'); } const editor = this._workspaceScope?.cwd ? scopedEditor(sessionEditor, this._workspaceScope) : sessionEditor; const tools = []; const viewImage = async (path, details) => { if (!session.viewImage) { throw new errors_1.UserError('Filesystem sandbox sessions must provide viewImage().'); } const effectivePath = this._workspaceScope?.anchor(path) ?? path; try { return await (0, spans_1.withSandboxSpan)('sandbox.view_image', { path: effectivePath, run_as: this._runAs, }, async () => await session.viewImage({ path: effectivePath, runAs: this._runAs, }), this.tracingParent(details)); } catch (error) { return renderViewImageError(path, error); } }; if ((0, transport_1.supportsStructuredToolOutputTransport)(this._modelInstance)) { tools.push((0, tool_1.tool)({ name: 'view_image', description: 'Returns an image output from a path in the sandbox workspace or an explicitly granted sandbox path.', parameters: zod_1.z.object({ path: zod_1.z .string() .describe('Path to an image file in the sandbox workspace or an explicitly granted sandbox path'), }), execute: async ({ path }, _context, details) => await viewImage(path, details), })); } else { tools.push((0, tool_1.tool)({ name: 'view_image', description: 'Returns an image from a path in the sandbox workspace or an explicitly granted sandbox path as a data URL or reference string.', parameters: zod_1.z.object({ path: zod_1.z .string() .describe('Path to an image file in the sandbox workspace or an explicitly granted sandbox path'), }), execute: async ({ path }, _context, details) => renderImageForTextTransport(await viewImage(path, details)), })); } if ((0, transport_1.supportsApplyPatchTransport)(this._modelInstance)) { tools.push((0, tool_1.applyPatchTool)({ name: 'apply_patch', editor, })); } else { tools.push(applyPatchFunctionTool(editor)); } return this.configureTools ? this.configureTools([...tools]) : tools; } } function scopedEditor(editor, scope) { return { createFile: async (operation, context) => await editor.createFile({ ...operation, path: scope.anchor(operation.path) }, context), updateFile: async (operation, context) => await editor.updateFile({ ...operation, path: scope.anchor(operation.path), ...(operation.moveTo ? { moveTo: scope.anchor(operation.moveTo) } : {}), }, context), deleteFile: async (operation, context) => await editor.deleteFile({ ...operation, path: scope.anchor(operation.path) }, context), }; } function filesystem(args = {}) { return new FilesystemCapability(args); } //# sourceMappingURL=filesystem.js.map