Skip to content
Closed
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
10 changes: 7 additions & 3 deletions packages/core/src/core/geminiChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,9 +691,13 @@ export class GeminiChat {
const history = curated
? extractCuratedHistory(this.history)
: this.history;
// Deep copy the history to avoid mutating the history outside of the
// chat session.
return structuredClone(history);
// Return a shallow copy of the array to prevent callers from mutating
// the internal history array (push/pop/splice). Content objects are
// shared references — callers MUST NOT mutate them in place.
// This replaces a prior structuredClone() which deep-copied the entire
// conversation on every call, causing O(n) memory pressure per turn
// that compounded into OOM crashes in long-running sessions.
return [...history];
Comment on lines +694 to +700
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The move to a shallow copy is a great performance optimization that resolves the OOM issue. However, relying solely on a comment to prevent callers from mutating the shared Content objects is fragile and could lead to hard-to-debug state corruption down the line.

To enforce this contract at compile time without any runtime performance penalty, you can leverage TypeScript's Readonly types. This provides a much stronger guarantee against accidental mutations.

I recommend updating the function signature to return a ReadonlyArray<Content>:

getHistory(curated: boolean = false): ReadonlyArray<Content> {

This change will prevent methods like .push() or direct index assignments on the returned array, making the implementation significantly safer.

getHistory(curated: boolean = false): ReadonlyArray<Content> {

}

/**
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/core/turn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ export class Turn {
readonly pendingToolCalls: ToolCallRequestInfo[] = [];
private debugResponses: GenerateContentResponse[] = [];
private pendingCitations = new Set<string>();
private cachedResponseText: string | undefined = undefined;
finishReason: FinishReason | undefined = undefined;

constructor(
Expand Down Expand Up @@ -432,11 +433,15 @@ export class Turn {
/**
* Get the concatenated response text from all responses in this turn.
* This extracts and joins all text content from the model's responses.
* The result is cached since this is called multiple times per turn.
*/
getResponseText(): string {
return this.debugResponses
.map((response) => getResponseText(response))
.filter((text): text is string => text !== null)
.join(' ');
if (this.cachedResponseText === undefined) {
this.cachedResponseText = this.debugResponses
.map((response) => getResponseText(response))
.filter((text): text is string => text !== null)
.join(' ');
}
return this.cachedResponseText;
}
}
Loading