in packages/core/src/core/geminiChat.ts [511:595]
private recordHistory(
userInput: Content,
modelOutput: Content[],
automaticFunctionCallingHistory?: Content[],
) {
const nonThoughtModelOutput = modelOutput.filter(
(content) => !this.isThoughtContent(content),
);
let outputContents: Content[] = [];
if (
nonThoughtModelOutput.length > 0 &&
nonThoughtModelOutput.every((content) => content.role !== undefined)
) {
outputContents = nonThoughtModelOutput;
} else if (nonThoughtModelOutput.length === 0 && modelOutput.length > 0) {
// This case handles when the model returns only a thought.
// We don't want to add an empty model response in this case.
} else {
// When not a function response appends an empty content when model returns empty response, so that the
// history is always alternating between user and model.
// Workaround for: https://b.corp.google.com/issues/420354090
if (!isFunctionResponse(userInput)) {
outputContents.push({
role: 'model',
parts: [],
} as Content);
}
}
if (
automaticFunctionCallingHistory &&
automaticFunctionCallingHistory.length > 0
) {
this.history.push(
...extractCuratedHistory(automaticFunctionCallingHistory),
);
} else {
this.history.push(userInput);
}
// Consolidate adjacent model roles in outputContents
const consolidatedOutputContents: Content[] = [];
for (const content of outputContents) {
if (this.isThoughtContent(content)) {
continue;
}
const lastContent =
consolidatedOutputContents[consolidatedOutputContents.length - 1];
if (this.isTextContent(lastContent) && this.isTextContent(content)) {
// If both current and last are text, combine their text into the lastContent's first part
// and append any other parts from the current content.
lastContent.parts[0].text += content.parts[0].text || '';
if (content.parts.length > 1) {
lastContent.parts.push(...content.parts.slice(1));
}
} else {
consolidatedOutputContents.push(content);
}
}
if (consolidatedOutputContents.length > 0) {
const lastHistoryEntry = this.history[this.history.length - 1];
const canMergeWithLastHistory =
!automaticFunctionCallingHistory ||
automaticFunctionCallingHistory.length === 0;
if (
canMergeWithLastHistory &&
this.isTextContent(lastHistoryEntry) &&
this.isTextContent(consolidatedOutputContents[0])
) {
// If both current and last are text, combine their text into the lastHistoryEntry's first part
// and append any other parts from the current content.
lastHistoryEntry.parts[0].text +=
consolidatedOutputContents[0].parts[0].text || '';
if (consolidatedOutputContents[0].parts.length > 1) {
lastHistoryEntry.parts.push(
...consolidatedOutputContents[0].parts.slice(1),
);
}
consolidatedOutputContents.shift(); // Remove the first element as it's merged
}
this.history.push(...consolidatedOutputContents);
}
}