Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/quiet-lamps-restore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@openai/agents-openai': patch
---

fix: restore session history when responses compaction replacement fails
107 changes: 103 additions & 4 deletions packages/agents-openai/src/memory/openaiResponsesCompactionSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,11 @@ export class OpenAIResponsesCompactionSession
const compacted = await this.client.responses.compact(compactRequest);

const outputItems = normalizeCompactionOutputItems(compacted.output ?? []);
await this.underlyingSession.clearSession();
if (outputItems.length > 0) {
await this.underlyingSession.addItems(outputItems);
}
const previousItems = await this.getAllUnderlyingSessionItems();
await this.replaceUnderlyingSessionItems({
outputItems,
previousItems,
});
this.compactionCandidateItems = selectCompactionCandidateItems(outputItems);
this.sessionItems = outputItems;

Expand Down Expand Up @@ -287,6 +288,97 @@ export class OpenAIResponsesCompactionSession
this.sessionItems = [];
}

private async getAllUnderlyingSessionItems(): Promise<AgentInputItem[]> {
return this.underlyingSession.getItems();
}

private async replaceUnderlyingSessionItems({
outputItems,
previousItems,
}: {
outputItems: AgentInputItem[];
previousItems: AgentInputItem[];
}): Promise<void> {
try {
await this.underlyingSession.clearSession();
} catch (error) {
await this.restoreUnderlyingSessionItemsAfterFailedClear(
previousItems,
error,
);
throw error;
}

try {
if (outputItems.length > 0) {
await this.underlyingSession.addItems(outputItems);
}
} catch (error) {
await this.restoreUnderlyingSessionItems(previousItems, error);
throw error;
}
}

private async restoreUnderlyingSessionItemsAfterFailedClear(
previousItems: AgentInputItem[],
error: unknown,
): Promise<void> {
let currentItems: AgentInputItem[];
try {
currentItems = await this.getAllUnderlyingSessionItems();
} catch (inspectionError) {
logger.warn(
'Failed to inspect session history after compaction replacement clear failed.',
inspectionError,
);
return;
}

if (areAgentItemsEqual(currentItems, previousItems)) {
return;
}

await this.restoreUnderlyingSessionItems(previousItems, error, {
popExistingItemCount: currentItems.length,
});
Comment thread
seratch marked this conversation as resolved.
}

private async restoreUnderlyingSessionItems(
previousItems: AgentInputItem[],
error: unknown,
options: {
clearExistingItems?: boolean;
popExistingItemCount?: number;
} = {},
): Promise<void> {
try {
if (options.popExistingItemCount !== undefined) {
for (let i = 0; i < options.popExistingItemCount; i += 1) {
const popped = await this.underlyingSession.popItem();
if (!popped) {
break;
}
}
} else if (options.clearExistingItems !== false) {
await this.underlyingSession.clearSession();
}
if (previousItems.length > 0) {
await this.underlyingSession.addItems(previousItems);
}
} catch (restoreError) {
logger.warn(
'Failed to restore session history after compaction replacement failed.',
restoreError,
);
return;
}

logger.warn(
'Restored previous session history after compaction replacement failed.',
error,
);
}

private async ensureCompactionCandidates(): Promise<{
compactionCandidateItems: AgentInputItem[];
sessionItems: AgentInputItem[];
Expand Down Expand Up @@ -525,3 +617,10 @@ function isOpenAIConversationsSessionDelegate(
] === 'conversations'
);
}

function areAgentItemsEqual(
left: AgentInputItem[],
right: AgentInputItem[],
): boolean {
return JSON.stringify(left) === JSON.stringify(right);
}
Loading
Loading