@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
468 lines • 16.6 kB
JavaScript
"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