src/history-normalize.js
Rama master · réplica de lectura del repositorio original.
// History-tail normalization for the internal OpenAI-shaped message list.
//
// The upstream only needs the tail it can still pair. Three operations:
// 1. strip a leading tool result (and the assistant turn it belonged to);
// 2. fold orphan tool results into text instead of dropping their content;
// 3. optionally synthesize interrupted results for a non-trailing call.
// A truncation placeholder is prepended only when something was stripped.
//
// Request-scoped: no global mutable state.
import { foldOrphanToolResults, synthesizeMissingResults } from './tool-ids.js';
export const TRUNCATION_PLACEHOLDER = '[Earlier conversation history was truncated to fit the model\'s input limit. Older messages and tool activity have been omitted.]';
export function normalizeHistoryTail(messages, {
placeholder = '',
fold = true,
synthesizeMissing = false,
} = {}) {
if (!Array.isArray(messages) || messages.length === 0) return messages;
const head = [];
let start = 0;
while (start < messages.length && messages[start]?.role === 'system') {
head.push(messages[start]);
start += 1;
}
let stripped = false;
while (start < messages.length && messages[start]?.role === 'tool') {
start += 1;
stripped = true;
}
// Do NOT strip the assistant after a leading tool result: in OpenAI order the
// assistant that issued those calls is BEFORE them (already truncated away),
// so a following assistant is a legitimate new turn.
let out = start > 0 ? messages.slice(start) : messages;
if (fold) out = foldOrphanToolResults(out);
if (synthesizeMissing) out = synthesizeMissingResults(out);
if (stripped && placeholder) {
out = [...head, { role: 'user', content: placeholder }, { role: 'assistant', content: 'Understood.' }, ...out];
} else if (head.length) {
out = [...head, ...out];
}
return out === messages ? messages : out;
}