Skip to content

Commit 014b926

Browse files
committed
revisit the design:
- removed compactionThreshold, countCompactionItems - instead, added general callback named shouldTriggerCompaction - added compactionCandidateItems, sessionItems, and responseId for custom shouldTriggerCompaction logic
1 parent 48d3c5f commit 014b926

File tree

7 files changed

+353
-67
lines changed

7 files changed

+353
-67
lines changed

examples/memory/oai-compact.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@ import { FileSession } from './sessions';
99

1010
async function main() {
1111
const session = new OpenAIResponsesCompactionSession({
12+
model: 'gpt-5.2',
1213
// This compaction decorator handles only compaction logic.
1314
// The underlying session is responsible for storing the history.
1415
underlyingSession: new FileSession(),
15-
// Set a low threshold to observe compaction in action.
16-
compactionThreshold: 3,
17-
model: 'gpt-4.1',
16+
// (optional customization) This example demonstrates the simplest compaction logic,
17+
// but you can also estimate the context window size using sessionItems (all items)
18+
// and trigger compaction at the optimal time.
19+
shouldTriggerCompaction: ({ compactionCandidateItems }) => {
20+
// Set a low threshold to observe compaction in action.
21+
return compactionCandidateItems.length >= 4;
22+
},
1823
});
1924

2025
const agent = new Agent({
2126
name: 'Assistant',
22-
model: 'gpt-4.1',
27+
model: 'gpt-5.2',
2328
instructions:
2429
'Keep answers short. This example demonstrates responses.compact with a custom session. For every user turn, call fetch_image_data with the provided label. Do not include raw image bytes or data URLs in your final answer.',
2530
modelSettings: { toolChoice: 'required' },
@@ -66,10 +71,19 @@ async function main() {
6671
}
6772

6873
const compactedHistory = await session.getItems();
69-
console.log('\nStored history after compaction:');
74+
console.log('\nHitory including both compaction and newer items:');
7075
for (const item of compactedHistory) {
7176
console.log(`- ${item.type}`);
7277
}
78+
79+
// You can manually run compaction this way:
80+
await session.runCompaction({ force: true });
81+
82+
const finalHistory = await session.getItems();
83+
console.log('\nStored history after final compaction:');
84+
for (const item of finalHistory) {
85+
console.log(`- ${item.type}`);
86+
}
7387
});
7488
}
7589

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ export type OpenAIResponsesCompactionArgs = {
5151
/**
5252
* The `response.id` from a completed OpenAI Responses API turn, if available.
5353
*
54-
* When undefined, compaction should be skipped.
54+
* When omitted, implementations may fall back to a cached value or throw.
5555
*/
56-
responseId: string | undefined;
56+
responseId?: string | undefined;
57+
/**
58+
* When true, compaction should run regardless of any internal thresholds or hooks.
59+
*/
60+
force?: boolean;
5761
};
5862

5963
export interface OpenAIResponsesCompactionAwareSession extends Session {
@@ -66,7 +70,7 @@ export interface OpenAIResponsesCompactionAwareSession extends Session {
6670
* This hook is best-effort. Implementations should consider handling transient failures and
6771
* deciding whether to retry or skip compaction for the current turn.
6872
*/
69-
runCompaction(args: OpenAIResponsesCompactionArgs): Promise<void> | void;
73+
runCompaction(args?: OpenAIResponsesCompactionArgs): Promise<void> | void;
7074
}
7175

7276
export function isOpenAIResponsesCompactionAwareSession(

packages/agents-core/src/runImplementation.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,10 +2291,13 @@ async function runCompactionOnSession(
22912291
session: Session | undefined,
22922292
responseId: string | undefined,
22932293
): Promise<void> {
2294-
if (isOpenAIResponsesCompactionAwareSession(session)) {
2295-
// Called after a completed turn is persisted so compaction can consider the latest stored state.
2296-
await session.runCompaction({ responseId });
2294+
if (!isOpenAIResponsesCompactionAwareSession(session)) {
2295+
return;
22972296
}
2297+
// Called after a completed turn is persisted so compaction can consider the latest stored state.
2298+
await session.runCompaction(
2299+
typeof responseId === 'undefined' ? undefined : { responseId },
2300+
);
22982301
}
22992302

23002303
/**

packages/agents-core/test/runImplementation.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,84 @@ describe('saveToSession', () => {
634634
expect(session.events).toEqual(['addItems:2', 'runCompaction:resp_123']);
635635
expect(session.items).toHaveLength(2);
636636
});
637+
638+
it('invokes runCompaction when responseId is undefined', async () => {
639+
class TrackingSession implements Session {
640+
items: AgentInputItem[] = [];
641+
events: string[] = [];
642+
643+
async getSessionId(): Promise<string> {
644+
return 'session';
645+
}
646+
647+
async getItems(): Promise<AgentInputItem[]> {
648+
return [...this.items];
649+
}
650+
651+
async addItems(items: AgentInputItem[]): Promise<void> {
652+
this.events.push(`addItems:${items.length}`);
653+
this.items.push(...items);
654+
}
655+
656+
async popItem(): Promise<AgentInputItem | undefined> {
657+
return undefined;
658+
}
659+
660+
async clearSession(): Promise<void> {
661+
this.items = [];
662+
}
663+
664+
async runCompaction(args?: { responseId?: string }): Promise<void> {
665+
this.events.push(`runCompaction:${String(args?.responseId)}`);
666+
}
667+
}
668+
669+
const textAgent = new Agent<UnknownContext, 'text'>({
670+
name: 'Recorder',
671+
outputType: 'text',
672+
instructions: 'capture',
673+
});
674+
const agent = textAgent as unknown as Agent<
675+
UnknownContext,
676+
AgentOutputType
677+
>;
678+
const session = new TrackingSession();
679+
const context = new RunContext<UnknownContext>(undefined as UnknownContext);
680+
const state = new RunState<
681+
UnknownContext,
682+
Agent<UnknownContext, AgentOutputType>
683+
>(context, 'hello', agent, 10);
684+
685+
state._modelResponses = [];
686+
state._generatedItems = [
687+
new MessageOutputItem(
688+
{
689+
type: 'message',
690+
role: 'assistant',
691+
id: 'msg_123',
692+
status: 'completed',
693+
content: [
694+
{
695+
type: 'output_text',
696+
text: 'here is the reply',
697+
},
698+
],
699+
providerData: {},
700+
},
701+
textAgent,
702+
),
703+
];
704+
state._currentStep = {
705+
type: 'next_step_final_output',
706+
output: 'here is the reply',
707+
};
708+
709+
const result = new RunResult(state);
710+
await saveToSession(session, toInputItemList(state._originalInput), result);
711+
712+
expect(session.events).toEqual(['addItems:2', 'runCompaction:undefined']);
713+
expect(session.items).toHaveLength(2);
714+
});
637715
});
638716

639717
describe('prepareInputItemsWithSession', () => {

packages/agents-openai/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export {
2626
export {
2727
OpenAIResponsesCompactionSession,
2828
type OpenAIResponsesCompactionSessionOptions,
29+
type OpenAIResponsesCompactionDecisionContext,
2930
} from './memory/openaiResponsesCompactionSession';

0 commit comments

Comments
 (0)