UNPKG

@openai/agents-core

Version:

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

299 lines • 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LOCAL_SNAPSHOT_FINGERPRINT_VERSION = void 0; exports.createLocalSnapshot = createLocalSnapshot; exports.persistLocalSnapshot = persistLocalSnapshot; exports.serializeLocalSnapshotSpec = serializeLocalSnapshotSpec; exports.rehydrateLocalSnapshotSpec = rehydrateLocalSnapshotSpec; exports.replaceLocalSnapshot = replaceLocalSnapshot; exports.canReuseLocalSnapshotWorkspace = canReuseLocalSnapshotWorkspace; exports.localSnapshotIsRestorable = localSnapshotIsRestorable; exports.restoreLocalSnapshotToWorkspace = restoreLocalSnapshotToWorkspace; exports.computeLocalSnapshotFingerprint = computeLocalSnapshotFingerprint; exports.copyDirectory = copyDirectory; exports.isRemoteSnapshotSpec = isRemoteSnapshotSpec; const errors_1 = require("../../../errors.js"); const snapshot_1 = require("../../snapshot.js"); const promises_1 = require("node:fs/promises"); const node_crypto_1 = require("node:crypto"); const node_path_1 = require("node:path"); const stableJson_1 = require("../../shared/stableJson.js"); const typeGuards_1 = require("../../shared/typeGuards.js"); const snapshotFilesystem_1 = require("./snapshotFilesystem.js"); const workspaceArchive_1 = require("./workspaceArchive.js"); const localSnapshotPaths_1 = require("./localSnapshotPaths.js"); const manifestPersistence_1 = require("./manifestPersistence.js"); exports.LOCAL_SNAPSHOT_FINGERPRINT_VERSION = 'workspace_tree_sha256_v1'; async function createLocalSnapshot(state, spec) { const snapshotPath = await mkdtempForSnapshot(spec); await copyDirectory(state.workspaceRootPath, snapshotPath, localSnapshotExcludedPaths(state)); return { id: (0, node_crypto_1.randomUUID)(), type: 'local', path: snapshotPath, }; } async function persistLocalSnapshot(providerName, state, spec, capture = (operation) => operation()) { if ((0, snapshot_1.isNoopSnapshotSpec)(spec)) { await replaceLocalSnapshot(state, null); replaceLocalSnapshotFingerprint(state, null); return null; } if (isRemoteSnapshotSpec(spec)) { const { fingerprint, data } = await capture(async () => ({ fingerprint: await computeLocalSnapshotFingerprint(state), data: await (0, workspaceArchive_1.createWorkspaceArchive)(state.workspaceRootPath, localSnapshotExcludedPaths(state)), })); const saved = await spec.store.save({ id: spec.id, data, metadata: spec.metadata, }); const snapshot = { id: saved.id, type: 'remote', ...(saved.metadata ? { metadata: saved.metadata } : {}), }; await replaceLocalSnapshot(state, snapshot); replaceLocalSnapshotFingerprint(state, fingerprint); return snapshot; } if (spec && spec.type !== 'local') { throw new errors_1.UserError(`${providerName} does not support snapshot specs of type "${spec.type}".`); } const { fingerprint, snapshot } = await capture(async () => ({ fingerprint: await computeLocalSnapshotFingerprint(state), snapshot: await createLocalSnapshot(state, spec ?? undefined), })); await replaceLocalSnapshot(state, snapshot); replaceLocalSnapshotFingerprint(state, fingerprint); return snapshot; } function serializeLocalSnapshotSpec(spec) { if (!spec) { return null; } if (spec.type === 'remote') { return { type: 'remote', ...(typeof spec.id === 'string' ? { id: spec.id } : {}), ...(spec.metadata ? { metadata: structuredClone(spec.metadata) } : {}), }; } return structuredClone(spec); } function rehydrateLocalSnapshotSpec(serialized, configured) { // Remote SnapshotStore implementations can carry functions or credentials, so // serialized state stores only lookup fields and reattaches the live configured store. if (!(0, typeGuards_1.isRecord)(serialized)) { return null; } if (serialized.type !== 'remote') { return structuredClone(serialized); } if (isRemoteSnapshotSpec(serialized)) { return serialized; } if (!isRemoteSnapshotSpec(configured)) { return null; } const id = typeof serialized.id === 'string' ? serialized.id : configured.id; const metadata = (0, typeGuards_1.isRecord)(serialized.metadata) ? structuredClone(serialized.metadata) : configured.metadata; return { type: 'remote', store: configured.store, ...(id ? { id } : {}), ...(metadata ? { metadata } : {}), }; } async function replaceLocalSnapshot(state, snapshot) { const previousSnapshot = state.snapshot; state.snapshot = snapshot; if (previousSnapshot?.type === 'local' && previousSnapshot.path !== snapshot?.path && previousSnapshot.path !== state.workspaceRootPath) { await (0, promises_1.rm)(previousSnapshot.path, { recursive: true, force: true }); } if (previousSnapshot?.type === 'remote' && previousSnapshot.id !== snapshot?.id) { const store = remoteSnapshotStoreFromSpec(state); await store?.delete?.({ id: previousSnapshot.id }).catch(() => undefined); } } async function canReuseLocalSnapshotWorkspace(state) { if (!(await localSnapshotIsRestorable(state))) { return true; } const storedFingerprint = state.snapshotFingerprint; const storedVersion = state.snapshotFingerprintVersion; if (!storedFingerprint || storedVersion !== exports.LOCAL_SNAPSHOT_FINGERPRINT_VERSION) { return false; } const current = await computeLocalSnapshotFingerprint(state).catch(() => null); // The fingerprint covers the persisted manifest plus durable workspace tree; ephemeral // paths are intentionally ignored because they are rebuilt after resume. return (current?.fingerprint === storedFingerprint && current.version === storedVersion); } async function localSnapshotIsRestorable(state) { if (state.snapshot?.type === 'remote') { const store = remoteSnapshotStoreFromSpec(state); if (!store) { return false; } if (store.exists) { return await store.exists({ id: state.snapshot.id }); } return true; } return Boolean(state.snapshot?.type === 'local' && (await pathExists(state.snapshot.path))); } async function restoreLocalSnapshotToWorkspace(state, workspaceRootPath, options = {}) { if (!(await localSnapshotIsRestorable(state))) { throw new errors_1.UserError('No local snapshot is available to restore.'); } if (state.snapshot?.type === 'remote') { const store = remoteSnapshotStoreFromSpec(state); if (!store) { throw new errors_1.UserError('No remote snapshot store is available to restore.'); } const restored = await store.load({ id: state.snapshot.id }); await (0, workspaceArchive_1.restoreWorkspaceArchive)(restored.data, workspaceRootPath, options); } else { await replaceDirectoryContents(state.snapshot.path, workspaceRootPath); } state.workspaceRootPath = workspaceRootPath; const fingerprint = await computeLocalSnapshotFingerprint(state).catch(() => null); replaceLocalSnapshotFingerprint(state, fingerprint); return state; } async function computeLocalSnapshotFingerprint(state) { const hash = (0, node_crypto_1.createHash)('sha256'); appendHashFrame(hash, 'version', exports.LOCAL_SNAPSHOT_FINGERPRINT_VERSION); appendHashFrame(hash, 'manifest', (0, stableJson_1.stableJsonStringify)((0, manifestPersistence_1.serializeManifest)(state.manifest), { encodeBytes: (value) => ({ type: 'Uint8Array', data: Buffer.from(value).toString('base64'), }), })); await hashDirectory(hash, state.workspaceRootPath, localSnapshotExcludedPaths(state)); return { fingerprint: hash.digest('hex'), version: exports.LOCAL_SNAPSHOT_FINGERPRINT_VERSION, }; } function localSnapshotExcludedPaths(state) { const paths = state.manifest.ephemeralPersistencePaths(); for (const path of state.snapshotExcludedPaths ?? []) { paths.add(path); } return paths; } async function copyDirectory(sourceRoot, destinationRoot, excludedLogicalPaths = new Set(), currentRelativePath = '', expectedSourceStats, destinationRootPath = destinationRoot) { const entries = await (0, snapshotFilesystem_1.readStableSnapshotDirectoryEntries)(sourceRoot, expectedSourceStats); await (0, snapshotFilesystem_1.assertSafeSnapshotDestinationPath)(destinationRootPath, destinationRoot); await (0, promises_1.mkdir)(destinationRoot, { recursive: true }); await (0, snapshotFilesystem_1.assertSafeSnapshotDestinationPath)(destinationRootPath, destinationRoot); for (const entry of entries) { const relativePath = currentRelativePath ? `${currentRelativePath}/${entry.name}` : entry.name; if ((0, snapshotFilesystem_1.shouldSkipSnapshotPath)(relativePath, excludedLogicalPaths)) { continue; } const sourcePath = (0, node_path_1.join)(sourceRoot, entry.name); const destinationPath = (0, node_path_1.join)(destinationRoot, entry.name); const sourceStats = await (0, promises_1.lstat)(sourcePath); if (sourceStats.isDirectory()) { await copyDirectory(sourcePath, destinationPath, excludedLogicalPaths, relativePath, sourceStats, destinationRootPath); continue; } if (!sourceStats.isFile()) { continue; } await (0, snapshotFilesystem_1.assertSafeSnapshotDestinationPath)(destinationRootPath, (0, node_path_1.dirname)(destinationPath)); await (0, promises_1.mkdir)((0, node_path_1.dirname)(destinationPath), { recursive: true }); await (0, snapshotFilesystem_1.assertSafeSnapshotDestinationPath)(destinationRootPath, (0, node_path_1.dirname)(destinationPath)); await (0, snapshotFilesystem_1.writeStableSnapshotFile)(destinationRootPath, destinationPath, await (0, snapshotFilesystem_1.readStableSnapshotFile)(sourcePath, sourceStats), sourceStats.mode & 0o777); const destinationStats = await (0, promises_1.lstat)(destinationPath); if (!destinationStats.isFile()) { await (0, promises_1.rm)(destinationPath, { recursive: true, force: true }); } } } async function replaceDirectoryContents(sourceRoot, destinationRoot) { await (0, snapshotFilesystem_1.assertSafeSnapshotDestinationPath)(destinationRoot, destinationRoot); await (0, promises_1.mkdir)(destinationRoot, { recursive: true }); await (0, snapshotFilesystem_1.assertSafeSnapshotDestinationPath)(destinationRoot, destinationRoot); await (0, snapshotFilesystem_1.clearDirectory)(destinationRoot); await copyDirectory(sourceRoot, destinationRoot); } function replaceLocalSnapshotFingerprint(state, fingerprint) { state.snapshotFingerprint = fingerprint?.fingerprint ?? null; state.snapshotFingerprintVersion = fingerprint?.version ?? null; } async function mkdtempForSnapshot(spec) { const baseDir = spec?.baseDir ?? (0, localSnapshotPaths_1.defaultLocalSnapshotBaseDir)(); await (0, promises_1.mkdir)(baseDir, { recursive: true }); return await (0, promises_1.mkdtemp)((0, node_path_1.join)(baseDir, 'openai-agents-sandbox-snapshot-')); } function isRemoteSnapshotSpec(spec) { const store = spec?.store; return (spec?.type === 'remote' && typeof store === 'object' && store !== null && typeof store.save === 'function' && typeof store.load === 'function'); } function remoteSnapshotStoreFromSpec(state) { const spec = state.snapshotSpec; return isRemoteSnapshotSpec(spec) ? spec.store : undefined; } async function pathExists(path) { try { await (0, promises_1.stat)(path); return true; } catch { return false; } } async function hashDirectory(hash, rootPath, excludedLogicalPaths, currentRelativePath = '', expectedSourceStats) { const entries = await (0, snapshotFilesystem_1.readStableSnapshotDirectoryEntries)(rootPath, expectedSourceStats); for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) { const relativePath = currentRelativePath ? `${currentRelativePath}/${entry.name}` : entry.name; if ((0, snapshotFilesystem_1.shouldSkipSnapshotPath)(relativePath, excludedLogicalPaths)) { continue; } const entryPath = (0, node_path_1.join)(rootPath, entry.name); const entryStats = await (0, promises_1.lstat)(entryPath); if (entryStats.isDirectory()) { appendHashFrame(hash, 'dir', relativePath); await hashDirectory(hash, entryPath, excludedLogicalPaths, relativePath, entryStats); continue; } if (!entryStats.isFile()) { continue; } const content = await (0, snapshotFilesystem_1.readStableSnapshotFile)(entryPath, entryStats); appendHashFrame(hash, 'file', relativePath); appendHashFrame(hash, 'bytes', String(content.byteLength)); hash.update(content); } } function appendHashFrame(hash, label, value) { // Length-delimited frames avoid collisions such as ["ab", "c"] vs ["a", "bc"]. hash.update(label); hash.update('\0'); hash.update(String(value.length)); hash.update('\0'); hash.update(value); hash.update('\0'); } //# sourceMappingURL=localSnapshots.js.map