Skip to content

Commit 6fa4a4f

Browse files
committed
fix fc id bug
1 parent c8c67f1 commit 6fa4a4f

File tree

7 files changed

+341
-18
lines changed

7 files changed

+341
-18
lines changed

examples/memory/file.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ async function main() {
3737
session,
3838
});
3939
console.log(result.finalOutput); // e.g., Brasilia
40-
41-
result = await run(
42-
agent,
43-
'Please provide the sample image so I can confirm bytes storage.',
44-
{ session },
45-
);
46-
console.log(result.finalOutput);
4740
});
4841
}
4942

examples/memory/memory-hitl.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import readline from 'node:readline/promises';
2+
import { stdin as input, stdout as output } from 'node:process';
3+
import {
4+
Agent,
5+
MemorySession,
6+
RunResult,
7+
RunToolApprovalItem,
8+
run,
9+
withTrace,
10+
} from '@openai/agents';
11+
12+
import type { Interface as ReadlineInterface } from 'node:readline/promises';
13+
import { createLookupCustomerProfileTool, fetchImageData } from './tools';
14+
15+
const customerDirectory: Record<string, string> = {
16+
'101':
17+
'Customer Kaz S. (tier gold) can be reached at +1-415-555-AAAA. Notes: Prefers SMS follow ups and values concise summaries.',
18+
'104':
19+
'Customer Yu S. (tier platinum) can be reached at +1-415-555-BBBB. Notes: Recently reported sync issues. Flagged for a proactive onboarding call.',
20+
'205':
21+
'Customer Ken S. (tier standard) can be reached at +1-415-555-CCCC. Notes: Interested in automation tutorials sent last week.',
22+
};
23+
24+
const lookupCustomerProfile = createLookupCustomerProfileTool({
25+
directory: customerDirectory,
26+
transientErrorMessage:
27+
'Simulated CRM outage for the first lookup. Please retry the tool call.',
28+
});
29+
lookupCustomerProfile.needsApproval = async () => true;
30+
31+
const instructions =
32+
'You assist support agents. For every user turn you must call lookup_customer_profile and fetch_image_data before responding so replies include stored notes and the sample image. If a tool reports a transient failure, request approval and retry the same call once before responding. Keep responses under three sentences.';
33+
34+
function formatToolArguments(interruption: RunToolApprovalItem): string {
35+
const args = interruption.rawItem.arguments;
36+
if (!args) {
37+
return '';
38+
}
39+
if (typeof args === 'string') {
40+
return args;
41+
}
42+
try {
43+
return JSON.stringify(args);
44+
} catch {
45+
return String(args);
46+
}
47+
}
48+
49+
async function promptYesNo(
50+
rl: ReadlineInterface,
51+
question: string,
52+
): Promise<boolean> {
53+
const answer = await rl.question(`${question} (y/n): `);
54+
const normalized = answer.trim().toLowerCase();
55+
return normalized === 'y' || normalized === 'yes';
56+
}
57+
58+
async function resolveInterruptions<TContext, TAgent extends Agent<any, any>>(
59+
rl: ReadlineInterface,
60+
agent: TAgent,
61+
initialResult: RunResult<TContext, TAgent>,
62+
session: MemorySession,
63+
): Promise<RunResult<TContext, TAgent>> {
64+
let result = initialResult;
65+
while (result.interruptions?.length) {
66+
for (const interruption of result.interruptions) {
67+
const args = formatToolArguments(interruption);
68+
const approved = await promptYesNo(
69+
rl,
70+
`Agent ${interruption.agent.name} wants to call ${interruption.rawItem.name} with ${args || 'no arguments'}`,
71+
);
72+
if (approved) {
73+
result.state.approve(interruption);
74+
console.log('Approved tool call.');
75+
} else {
76+
result.state.reject(interruption);
77+
console.log('Rejected tool call.');
78+
}
79+
}
80+
81+
result = await run(agent, result.state, { session });
82+
}
83+
84+
return result;
85+
}
86+
87+
async function main() {
88+
await withTrace('memory:memory-hitl:main', async () => {
89+
const agent = new Agent({
90+
name: 'Memory HITL assistant',
91+
instructions,
92+
modelSettings: { toolChoice: 'required' },
93+
tools: [lookupCustomerProfile, fetchImageData],
94+
});
95+
96+
const session = new MemorySession();
97+
const sessionId = await session.getSessionId();
98+
const rl = readline.createInterface({ input, output });
99+
100+
console.log(`Session id: ${sessionId}`);
101+
console.log(
102+
'Enter a message to chat with the agent. Submit an empty line to exit.',
103+
);
104+
105+
while (true) {
106+
const userMessage = await rl.question('You: ');
107+
if (!userMessage.trim()) {
108+
break;
109+
}
110+
111+
let result = await run(agent, userMessage, { session });
112+
result = await resolveInterruptions(rl, agent, result, session);
113+
114+
const reply = result.finalOutput ?? '[No final output produced]';
115+
console.log(`Assistant: ${reply}`);
116+
console.log();
117+
}
118+
119+
rl.close();
120+
});
121+
}
122+
123+
main().catch((error) => {
124+
console.error(error);
125+
process.exit(1);
126+
});

examples/memory/memory.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {
2+
Agent,
3+
getLogger,
4+
MemorySession,
5+
run,
6+
withTrace,
7+
} from '@openai/agents';
8+
import { createLookupCustomerProfileTool, fetchImageData } from './tools';
9+
10+
const directory: Record<string, string> = {
11+
'1': 'Customer 1 (tier gold). Notes: Prefers concise replies.',
12+
'2': 'Customer 2 (tier standard). Notes: Interested in tutorials.',
13+
};
14+
15+
const instructions =
16+
'You are a helpful assistant. For every user turn you must call lookup_customer_profile and fetch_image_data before responding.';
17+
18+
const lookupCustomerProfile = createLookupCustomerProfileTool({
19+
directory,
20+
transientErrorMessage:
21+
'Simulated transient CRM outage. Please retry the tool call.',
22+
});
23+
24+
async function main() {
25+
await withTrace('memory:memorySession:main', async () => {
26+
const agent = new Agent({
27+
name: 'Assistant',
28+
instructions,
29+
modelSettings: { toolChoice: 'required' },
30+
tools: [lookupCustomerProfile, fetchImageData],
31+
});
32+
33+
const session = new MemorySession({
34+
logger: getLogger('memory:memory'),
35+
});
36+
let result = await run(
37+
agent,
38+
'What is the largest country in South America?',
39+
{ session },
40+
);
41+
console.log(result.finalOutput); // e.g., Brazil.
42+
43+
result = await run(agent, 'What is the capital of that country?', {
44+
session,
45+
});
46+
console.log(result.finalOutput); // e.g., Brasilia.
47+
});
48+
}
49+
50+
async function mainStream() {
51+
await withTrace('memory:memorySession:mainStream', async () => {
52+
const agent = new Agent({
53+
name: 'Assistant',
54+
instructions,
55+
modelSettings: { toolChoice: 'required' },
56+
tools: [lookupCustomerProfile, fetchImageData],
57+
});
58+
59+
const session = new MemorySession();
60+
let result = await run(
61+
agent,
62+
'What is the largest country in South America?',
63+
{
64+
stream: true,
65+
session,
66+
},
67+
);
68+
69+
for await (const event of result) {
70+
if (
71+
event.type === 'raw_model_stream_event' &&
72+
event.data.type === 'output_text_delta'
73+
) {
74+
process.stdout.write(event.data.delta);
75+
}
76+
}
77+
console.log();
78+
79+
result = await run(agent, 'What is the capital of that country?', {
80+
stream: true,
81+
session,
82+
});
83+
84+
for await (const event of result.toTextStream()) {
85+
process.stdout.write(event);
86+
}
87+
console.log();
88+
});
89+
}
90+
91+
async function promptAndRun() {
92+
const readline = await import('node:readline/promises');
93+
const rl = readline.createInterface({
94+
input: process.stdin,
95+
output: process.stdout,
96+
});
97+
const isStream = await rl.question('Run in stream mode? (y/n): ');
98+
rl.close();
99+
if (isStream.trim().toLowerCase() === 'y') {
100+
await mainStream();
101+
} else {
102+
await main();
103+
}
104+
}
105+
106+
promptAndRun().catch((error) => {
107+
console.error(error);
108+
process.exit(1);
109+
});

examples/memory/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
},
88
"scripts": {
99
"build-check": "tsc --noEmit",
10+
"start:memory": "tsx memory.ts",
11+
"start:memory-hitl": "tsx memory-hitl.ts",
1012
"start:oai": "tsx oai.ts",
1113
"start:oai-hitl": "tsx oai-hitl.ts",
1214
"start:file": "tsx file.ts",

packages/agents-core/src/memory/memorySession.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@ import { randomUUID } from '@openai/agents-core/_shims';
22

33
import type { AgentInputItem } from '../types';
44
import type { Session } from './session';
5+
import { logger, Logger } from '../logger';
56

67
export type MemorySessionOptions = {
78
sessionId?: string;
89
initialItems?: AgentInputItem[];
10+
logger?: Logger;
911
};
1012

1113
/**
1214
* Simple in-memory session store intended for demos or tests. Not recommended for production use.
1315
*/
1416
export class MemorySession implements Session {
1517
private readonly sessionId: string;
18+
private readonly logger: Logger;
1619

1720
private items: AgentInputItem[];
1821

1922
constructor(options: MemorySessionOptions = {}) {
2023
this.sessionId = options.sessionId ?? randomUUID();
21-
this.items = options.initialItems ? [...options.initialItems] : [];
24+
this.items = options.initialItems
25+
? options.initialItems.map(cloneAgentItem)
26+
: [];
27+
this.logger = options.logger ?? logger;
2228
}
2329

2430
async getSessionId(): Promise<string> {
@@ -27,32 +33,53 @@ export class MemorySession implements Session {
2733

2834
async getItems(limit?: number): Promise<AgentInputItem[]> {
2935
if (limit === undefined) {
30-
return [...this.items];
36+
const cloned = this.items.map(cloneAgentItem);
37+
this.logger.debug(
38+
`Getting items from memory session (${this.sessionId}): ${JSON.stringify(cloned)}`,
39+
);
40+
return cloned;
3141
}
3242
if (limit <= 0) {
3343
return [];
3444
}
3545
const start = Math.max(this.items.length - limit, 0);
36-
return this.items.slice(start);
46+
const items = this.items.slice(start).map(cloneAgentItem);
47+
this.logger.debug(
48+
`Getting items from memory session (${this.sessionId}): ${JSON.stringify(items)}`,
49+
);
50+
return items;
3751
}
3852

3953
async addItems(items: AgentInputItem[]): Promise<void> {
4054
if (items.length === 0) {
4155
return;
4256
}
43-
this.items = [...this.items, ...items];
57+
const cloned = items.map(cloneAgentItem);
58+
this.logger.debug(
59+
`Adding items to memory session (${this.sessionId}): ${JSON.stringify(cloned)}`,
60+
);
61+
this.items = [...this.items, ...cloned];
4462
}
4563

4664
async popItem(): Promise<AgentInputItem | undefined> {
4765
if (this.items.length === 0) {
4866
return undefined;
4967
}
5068
const item = this.items[this.items.length - 1];
69+
const cloned = cloneAgentItem(item);
70+
this.logger.debug(
71+
`Popping item from memory session (${this.sessionId}): ${JSON.stringify(cloned)}`,
72+
);
5173
this.items = this.items.slice(0, -1);
52-
return item;
74+
return cloned;
5375
}
5476

5577
async clearSession(): Promise<void> {
78+
this.logger.debug(`Clearing memory session (${this.sessionId})`);
5679
this.items = [];
5780
}
5881
}
82+
83+
function cloneAgentItem<T extends AgentInputItem>(item: T): T {
84+
return structuredClone(item);
85+
}

0 commit comments

Comments
 (0)