Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f55dac8
feat: fix #272 add memory feature
seratch Sep 2, 2025
91736b1
pnpm i
seratch Oct 29, 2025
808156b
align with python
seratch Oct 29, 2025
366cc0a
add more tests and prisma example
seratch Oct 29, 2025
0fd03a5
build error
seratch Oct 29, 2025
edf0782
Add changeset - minor release
seratch Oct 29, 2025
b2958fd
fix
seratch Oct 29, 2025
a44370a
fix review comment
seratch Oct 29, 2025
276eb29
HITL support
seratch Oct 29, 2025
a4fed4f
fix review comment
seratch Oct 29, 2025
23d2727
oai store limit issue
seratch Oct 29, 2025
60f7598
fix review comment
seratch Oct 29, 2025
8bfeeda
fix review comment
seratch Oct 29, 2025
ce85e41
fix local codex review
seratch Oct 29, 2025
7892cdc
fix local codex review
seratch Oct 29, 2025
9052aff
fix local codex review comment
seratch Oct 29, 2025
479ee58
fix local codex review comment
seratch Oct 29, 2025
252c919
fix local codex review comment
seratch Oct 29, 2025
c767837
fix local codex review comment
seratch Oct 29, 2025
e594dfe
fix local codex review comment
seratch Oct 29, 2025
12fe46c
fix local codex review comment
seratch Oct 29, 2025
e0fc814
refactor
seratch Oct 29, 2025
176f784
refactor
seratch Oct 29, 2025
fbedc61
refactor
seratch Oct 29, 2025
fe0e53d
fix local codex review comment
seratch Oct 29, 2025
ff827be
refactor
seratch Oct 29, 2025
78e1c59
fix review comment
seratch Oct 29, 2025
6eaa6e4
improve comments
seratch Oct 29, 2025
a58c9e8
fix local codex review comment
seratch Oct 29, 2025
6c584b4
fix local codex review comment
seratch Oct 29, 2025
4466847
refactor
seratch Oct 29, 2025
4d7cdbe
fix local codex review comment
seratch Oct 29, 2025
23ea5bd
fix local codex review comment
seratch Oct 29, 2025
fbad0d2
fix local codex review comment
seratch Oct 29, 2025
c2a4e1d
fix local codex review comment
seratch Oct 29, 2025
0c5ba85
fix local codex review comment
seratch Oct 29, 2025
6c57d2a
fix local codex review comment
seratch Oct 29, 2025
98d56c0
fix local codex review comment
seratch Oct 29, 2025
da895f4
fix local codex review comment
seratch Oct 29, 2025
c14e0a0
fix local codex review comment
seratch Oct 29, 2025
d687d37
fix local codex review comment:
seratch Oct 29, 2025
c49cd7f
fix local codex review comment:
seratch Oct 29, 2025
7a489f4
fix local codex review comment:
seratch Oct 29, 2025
c95f197
fix local codex review comment:
seratch Oct 29, 2025
60e2ab7
fix local codex review comment:
seratch Oct 29, 2025
683abfd
refactor
seratch Oct 29, 2025
edaad7c
fix local codex review comment:
seratch Oct 29, 2025
6eb1a57
fix local codex review comment:
seratch Oct 29, 2025
6346f3b
fix local codex review comment:
seratch Oct 29, 2025
9fb9868
revert unsupport use case changes
seratch Oct 30, 2025
42b23c6
fix oai store bugs
seratch Oct 30, 2025
911717e
tool output support and bug fixes
seratch Oct 30, 2025
72ac1dc
remove unnecessary inut validation; add more comments, refactor varia…
seratch Oct 30, 2025
46989c1
fix local codex review comment:
seratch Oct 30, 2025
9ceeb97
fix local codex review comment:
seratch Oct 30, 2025
e8c0bde
fix local codex review comment:
seratch Oct 30, 2025
c8c67f1
add memory store
seratch Oct 30, 2025
6fa4a4f
fix fc id bug
seratch Oct 30, 2025
27e74c4
Run all examples
seratch Oct 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/memory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tmp/
*.db
217 changes: 217 additions & 0 deletions examples/memory/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import type { AgentInputItem, Session } from '@openai/agents';
import { protocol } from '@openai/agents';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { randomUUID } from 'node:crypto';

export type FileSessionOptions = {
/**
* Directory where session files are stored. Defaults to `./.agents-sessions`.
*/
dir?: string;
/**
* Optional pre-existing session id to bind to.
*/
sessionId?: string;
};

/**
* A simple filesystem-backed Session implementation that stores history as a JSON array.
*/
export class FileSession implements Session {
#dir: string;
#sessionId?: string;

constructor(options: FileSessionOptions = {}) {
this.#dir = options.dir ?? path.resolve(process.cwd(), '.agents-sessions');
this.#sessionId = options.sessionId;
}

/**
* Get the current session id, creating one if necessary.
*/
async getSessionId(): Promise<string> {
if (!this.#sessionId) {
// Compact, URL-safe-ish id without dashes.
this.#sessionId = randomUUID().replace(/-/g, '').slice(0, 24);
}
await this.#ensureDir();
// Ensure the file exists.
const file = this.#filePath(this.#sessionId);
try {
await fs.access(file);
} catch {
await fs.writeFile(file, '[]', 'utf8');
}
return this.#sessionId;
}

/**
* Retrieve items from the conversation history.
*/
async getItems(limit?: number): Promise<AgentInputItem[]> {
const sessionId = await this.getSessionId();
const items = await this.#readItems(sessionId);
if (typeof limit === 'number' && limit >= 0) {
return items.slice(-limit);
}
return items;
}

/**
* Append new items to the conversation history.
*/
async addItems(items: AgentInputItem[]): Promise<void> {
if (!items.length) return;
const sessionId = await this.getSessionId();
const current = await this.#readItems(sessionId);
const next = current.concat(items);
await this.#writeItems(sessionId, next);
}

/**
* Remove and return the most recent item, if any.
*/
async popItem(): Promise<AgentInputItem | undefined> {
const sessionId = await this.getSessionId();
const items = await this.#readItems(sessionId);
if (items.length === 0) return undefined;
const popped = items.pop();
await this.#writeItems(sessionId, items);
return popped;
}

/**
* Delete all stored items and reset the session state.
*/
async clearSession(): Promise<void> {
if (!this.#sessionId) return; // Nothing to clear.
const file = this.#filePath(this.#sessionId);
try {
await fs.unlink(file);
} catch {
// Ignore if already removed or inaccessible.
}
this.#sessionId = undefined;
}

// Internal helpers
async #ensureDir(): Promise<void> {
await fs.mkdir(this.#dir, { recursive: true });
}

#filePath(sessionId: string): string {
return path.join(this.#dir, `${sessionId}.json`);
}

async #readItems(sessionId: string): Promise<AgentInputItem[]> {
const file = this.#filePath(sessionId);
try {
const data = await fs.readFile(file, 'utf8');
const parsed = JSON.parse(data);
if (!Array.isArray(parsed)) return [];
// Validate and coerce items to the protocol shape where possible.
const result: AgentInputItem[] = [];
for (const raw of parsed) {
const check = protocol.ModelItem.safeParse(raw);
if (check.success) {
result.push(check.data as AgentInputItem);
}
// Silently skip invalid entries.
}
return result;
} catch (err: any) {
// On missing file, return empty list.
if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) return [];
// For other errors, rethrow.
throw err;
}
}

async #writeItems(sessionId: string, items: AgentInputItem[]): Promise<void> {
await this.#ensureDir();
const file = this.#filePath(sessionId);
// Keep JSON compact but deterministic.
await fs.writeFile(file, JSON.stringify(items, null, 2), 'utf8');
}
}

import { Agent, run } from '@openai/agents';

async function main() {
const agent = new Agent({
name: 'Assistant',
instructions: 'You are a helpful assistant. be VERY concise.',
});

const session = new FileSession({ dir: './tmp/' });
let result = await run(
agent,
'What is the largest country in South America?',
{ session },
);
console.log(result.finalOutput); // e.g., Brazil

result = await run(agent, 'What is the capital of that country?', {
session,
});
console.log(result.finalOutput); // e.g., Brasilia
}

async function mainStream() {
const agent = new Agent({
name: 'Assistant',
instructions: 'You are a helpful assistant. be VERY concise.',
});

const session = new FileSession({ dir: './tmp/' });
let result = await run(
agent,
'What is the largest country in South America?',
{
stream: true,
session,
},
);

for await (const event of result) {
if (
event.type === 'raw_model_stream_event' &&
event.data.type === 'output_text_delta'
)
process.stdout.write(event.data.delta);
}
console.log();

result = await run(agent, 'What is the capital of that country?', {
stream: true,
session,
});

// toTextStream() automatically returns a readable stream of strings intended to be displayed
// to the user
for await (const event of result.toTextStream()) {
process.stdout.write(event);
}
console.log();
}

async function promptAndRun() {
const readline = await import('node:readline/promises');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const isStream = await rl.question('Run in stream mode? (y/n): ');
rl.close();
if (isStream.trim().toLowerCase() === 'y') {
await mainStream();
} else {
await main();
}
}

promptAndRun().catch((error) => {
console.error(error);
process.exit(1);
});
79 changes: 79 additions & 0 deletions examples/memory/oai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Agent, OpenAIConversationsSession, run } from '@openai/agents';

async function main() {
const agent = new Agent({
name: 'Assistant',
instructions: 'You are a helpful assistant. be VERY concise.',
});

const session = new OpenAIConversationsSession();
let result = await run(
agent,
'What is the largest country in South America?',
{ session },
);
console.log(result.finalOutput); // e.g., Brazil

result = await run(agent, 'What is the capital of that country?', {
session,
});
console.log(result.finalOutput); // e.g., Brasilia
}

async function mainStream() {
const agent = new Agent({
name: 'Assistant',
instructions: 'You are a helpful assistant. be VERY concise.',
});

const session = new OpenAIConversationsSession();
let result = await run(
agent,
'What is the largest country in South America?',
{
stream: true,
session,
},
);

for await (const event of result) {
if (
event.type === 'raw_model_stream_event' &&
event.data.type === 'output_text_delta'
)
process.stdout.write(event.data.delta);
}
console.log();

result = await run(agent, 'What is the capital of that country?', {
stream: true,
session,
});

// toTextStream() automatically returns a readable stream of strings intended to be displayed
// to the user
for await (const event of result.toTextStream()) {
process.stdout.write(event);
}
console.log();
}

async function promptAndRun() {
const readline = await import('node:readline/promises');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const isStream = await rl.question('Run in stream mode? (y/n): ');
rl.close();
if (isStream.trim().toLowerCase() === 'y') {
await mainStream();
} else {
await main();
}
}

promptAndRun().catch((error) => {
console.error(error);
process.exit(1);
});
17 changes: 17 additions & 0 deletions examples/memory/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"private": true,
"name": "memory",
"dependencies": {
"@openai/agents": "workspace:*",
"@prisma/client": "^6.18.0"
},
"scripts": {
"build-check": "tsc --noEmit",
"start:oai": "tsx oai.ts",
"start:file": "tsx file.ts",
"start:prisma": "pnpm prisma db push --schema ./prisma/schema.prisma && pnpm prisma generate --schema ./prisma/schema.prisma && tsx prisma.ts"
},
"devDependencies": {
"prisma": "^6.18.0"
}
}
Loading