@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
291 lines • 11 kB
JavaScript
import { UserError } from "../../errors.mjs";
import { dir } from "../entries/index.mjs";
import { normalizeRelativePath } from "../manifest.mjs";
import { withSandboxSpan } from "../runtime/spans.mjs";
import { shellQuote } from "../shared/shell.mjs";
import { renderMemoryReadPrompt } from "../memory/prompts.mjs";
import { joinRelativePaths, SandboxMemoryStorage, } from "../memory/storage.mjs";
import { Capability, requireBoundSession } from "./base.mjs";
const MEMORY_SUMMARY_FILE = 'memory_summary.md';
const MEMORY_SUMMARY_MAX_BYTES = 1_000_000;
const DEFAULT_PHASE_ONE_MODEL = 'gpt-5.4-mini';
const DEFAULT_PHASE_TWO_MODEL = 'gpt-5.4';
const DEFAULT_MEMORY_MODEL_SETTINGS = {
reasoning: { effort: 'medium' },
};
const MEMORY_BEGIN_MARKER = '__OPENAI_AGENTS_MEMORY_SUMMARY_BEGIN__';
const MEMORY_END_MARKER = '__OPENAI_AGENTS_MEMORY_SUMMARY_END__';
class MemoryCapability extends Capability {
type = 'memory';
read;
generate;
layout;
store;
constructor(args = {}) {
super();
this.read = normalizeReadConfig(args.read);
this.generate = normalizeGenerateConfig(args.generate);
this.layout = normalizeLayoutConfig(args.layout);
this.store = args.store;
if (this.read === null && this.generate === null) {
throw new UserError('Memory requires at least one of `read` or `generate`.');
}
}
requiredCapabilityTypes() {
if (this.read === null) {
return new Set();
}
if (this.read.liveUpdate) {
return new Set(['filesystem', 'shell']);
}
return new Set(['shell']);
}
processManifest(manifest) {
if (this.read?.liveUpdate || this.generate !== null) {
ensureDirectoryEntry(manifest, this.layout.memoriesDir);
}
if (this.generate !== null) {
ensureDirectoryEntry(manifest, this.layout.sessionsDir);
}
return manifest;
}
async instructions(manifest) {
if (this.read === null) {
return null;
}
const session = requireBoundSession(this.type, this._session);
if (this.store) {
await new SandboxMemoryStorage({
session,
runAs: this._runAs,
store: this.store,
tracingParent: this._tracingParent,
}).hydrateFromStore(this.layout.memoriesDir);
}
const memorySummary = await this.readMemorySummary(session);
if (!memorySummary) {
return null;
}
return renderMemoryReadPrompt({
memoryDir: this.modelResourcePath(manifest, this.layout.memoriesDir),
memorySummary,
liveUpdate: Boolean(this.read.liveUpdate),
});
}
async readMemorySummary(session) {
const path = joinRelativePaths(this.layout.memoriesDir, this.layout.summaryFile);
if (session.pathExists && !(await session.pathExists(path, this._runAs))) {
return await this.readStoredText(path);
}
if (session.readFile) {
try {
const payload = await withSandboxSpan('sandbox.memory.read_summary', {
path,
run_as: this._runAs,
}, async () => await session.readFile({
path,
runAs: this._runAs,
maxBytes: MEMORY_SUMMARY_MAX_BYTES,
}), this._tracingParent);
return normalizeMemorySummaryPayload(payload);
}
catch (error) {
if (isMissingFileError(error)) {
return await this.readStoredText(path);
}
throw error;
}
}
const stored = await this.readStoredText(path);
if (stored !== null) {
return stored;
}
if (session.execCommand) {
return await readMemorySummaryWithShell({
session,
path,
runAs: this._runAs,
tracingParent: this._tracingParent,
});
}
throw new UserError('Memory sandbox sessions must provide readFile() or execCommand().');
}
async readStoredText(path) {
const stored = await this.store?.read(path);
return stored ? normalizeMemorySummaryPayload(stored) : null;
}
}
export function memory(args = {}) {
return new MemoryCapability(args);
}
export function isMemoryCapability(capability) {
return capability.type === 'memory' && capability instanceof MemoryCapability;
}
function normalizeReadConfig(config) {
if (config === null || config === false) {
return null;
}
if (config === undefined || config === true) {
return { enabled: true, liveUpdate: true };
}
rejectKnownSnakeCaseConfigKeys(config, ['live_update'], 'memory read config');
if (config.enabled === false) {
return null;
}
return {
enabled: true,
liveUpdate: config.liveUpdate ?? true,
};
}
function normalizeGenerateConfig(config) {
if (config === null || config === false) {
return null;
}
if (config === undefined || config === true) {
return {
enabled: true,
maxRawMemoriesForConsolidation: 256,
phaseOneModel: DEFAULT_PHASE_ONE_MODEL,
phaseOneModelSettings: DEFAULT_MEMORY_MODEL_SETTINGS,
phaseTwoModel: DEFAULT_PHASE_TWO_MODEL,
phaseTwoModelSettings: DEFAULT_MEMORY_MODEL_SETTINGS,
phaseTwoMaxTurns: 500,
};
}
rejectKnownSnakeCaseConfigKeys(config, [
'max_raw_memories_for_consolidation',
'phase_one_model',
'phase_one_model_settings',
'phase_two_model',
'phase_two_model_settings',
'phase_two_max_turns',
'extra_prompt',
], 'memory generate config');
if (config.enabled === false) {
return null;
}
const maxRawMemoriesForConsolidation = config.maxRawMemoriesForConsolidation ?? 256;
if (!Number.isInteger(maxRawMemoriesForConsolidation) ||
maxRawMemoriesForConsolidation <= 0 ||
maxRawMemoriesForConsolidation > 4096) {
throw new UserError('MemoryGenerateConfig.maxRawMemoriesForConsolidation must be an integer between 1 and 4096.');
}
const sharedModel = normalizeMemoryModel(config.model);
const phaseOneModel = config.phaseOneModel ?? sharedModel;
const phaseTwoModel = config.phaseTwoModel ?? sharedModel;
const extraPrompt = config.extraPrompt ?? config.instructions;
const phaseOneModelSettings = config.phaseOneModelSettings ?? DEFAULT_MEMORY_MODEL_SETTINGS;
const phaseTwoModelSettings = config.phaseTwoModelSettings ?? DEFAULT_MEMORY_MODEL_SETTINGS;
return {
enabled: true,
maxRawMemoriesForConsolidation,
phaseOneModel: phaseOneModel ?? DEFAULT_PHASE_ONE_MODEL,
phaseTwoModel: phaseTwoModel ?? DEFAULT_PHASE_TWO_MODEL,
phaseOneModelSettings,
phaseTwoModelSettings,
phaseTwoMaxTurns: config.phaseTwoMaxTurns ?? 500,
...(extraPrompt ? { extraPrompt } : {}),
};
}
function normalizeMemoryModel(model) {
if (typeof model !== 'string') {
return model;
}
const trimmed = model.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
function normalizeLayoutConfig(config) {
if (config !== undefined) {
rejectKnownSnakeCaseConfigKeys(config, ['memories_dir', 'sessions_dir', 'summary_file'], 'memory layout config');
}
const memoriesDir = normalizeRequiredRelativePath('layout.memoriesDir', config?.memoriesDir ?? config?.directory ?? 'memories');
const sessionsDir = normalizeRequiredRelativePath('layout.sessionsDir', config?.sessionsDir ?? 'sessions');
const summaryFile = normalizeRequiredRelativePath('layout.summaryFile', config?.summaryFile ?? MEMORY_SUMMARY_FILE);
if (summaryFile.includes('/')) {
throw new UserError('Memory layout.summaryFile must be a file name.');
}
return {
memoriesDir,
sessionsDir,
summaryFile,
};
}
function normalizeRequiredRelativePath(name, value) {
let normalized;
try {
normalized = normalizeRelativePath(value);
}
catch {
throw new UserError(`${name} must be relative to the sandbox workspace root and must not escape root, got: ${value}`);
}
if (!normalized) {
throw new UserError(`${name} must be non-empty.`);
}
return normalized;
}
function ensureDirectoryEntry(manifest, path) {
const normalizedPath = normalizeRelativePath(path);
if (!normalizedPath ||
manifestHasOverlappingEntry(manifest, normalizedPath)) {
return;
}
manifest.entries[normalizedPath] = dir();
}
function manifestHasOverlappingEntry(manifest, path) {
const prefix = `${path}/`;
for (const entryPath of Object.keys(manifest.entries)) {
const normalizedEntryPath = normalizeRelativePath(entryPath);
if (normalizedEntryPath === path ||
normalizedEntryPath.startsWith(prefix) ||
path.startsWith(`${normalizedEntryPath}/`)) {
return true;
}
}
return false;
}
async function readMemorySummaryWithShell(args) {
const command = [
`if [ -f ${shellQuote(args.path)} ]; then`,
`printf '%s\n' ${shellQuote(MEMORY_BEGIN_MARKER)};`,
`head -c ${MEMORY_SUMMARY_MAX_BYTES} ${shellQuote(args.path)};`,
`printf '\n%s\n' ${shellQuote(MEMORY_END_MARKER)};`,
'fi',
].join(' ');
const output = await withSandboxSpan('sandbox.memory.read_summary', {
path: args.path,
run_as: args.runAs,
}, async () => await args.session.execCommand({
cmd: command,
login: false,
yieldTimeMs: 10_000,
maxOutputTokens: 20_000,
runAs: args.runAs,
}), args.tracingParent);
const begin = output.indexOf(MEMORY_BEGIN_MARKER);
const end = output.indexOf(MEMORY_END_MARKER, begin + MEMORY_BEGIN_MARKER.length);
if (begin < 0 || end < 0) {
return null;
}
return normalizeMemorySummaryPayload(output.slice(begin + MEMORY_BEGIN_MARKER.length, end));
}
function normalizeMemorySummaryPayload(payload) {
const text = typeof payload === 'string'
? payload
: new TextDecoder('utf-8', { fatal: false }).decode(payload);
const normalized = text.trim();
return normalized.length > 0 ? normalized : null;
}
function isMissingFileError(error) {
return (typeof error === 'object' &&
error !== null &&
'code' in error &&
error.code === 'ENOENT');
}
function rejectKnownSnakeCaseConfigKeys(value, keys, source) {
const found = keys.find((key) => key in value);
if (!found) {
return;
}
throw new UserError(`Use camelCase config keys in ${source}; snake_case key "${found}" is not supported.`);
}
//# sourceMappingURL=memory.mjs.map