import type { MessagePage, SessionUiEvent } from "../shared/apiTypes.js"; /** * Remove provider-only thinking data at the browser transport boundary. The * runtime message remains unchanged because only affected messages and content * blocks are copied. */ export function projectBrowserMessage(message: unknown): unknown { if (!isRecord(message)) return message; const originalContent = message["content"]; if (!isUnknownArray(originalContent)) return message; const content = mapChanged(originalContent, (part) => { if (!isRecord(part) || part["type"] !== "thinking" || !Object.hasOwn(part, "thinkingSignature")) return part; const projected = { ...part }; delete projected["thinkingSignature"]; return projected; }); return content === originalContent ? message : { ...message, content }; } export function projectBrowserMessageResponse(response: unknown[] | MessagePage): unknown[] | MessagePage { if (Array.isArray(response)) return mapChanged(response, projectBrowserMessage); const messages = mapChanged(response.messages, projectBrowserMessage); return messages === response.messages ? response : { ...response, messages }; } export function projectBrowserSessionEvent(event: SessionUiEvent): SessionUiEvent { if (event.type !== "message.end" || event.message === undefined) return event; const message = projectBrowserMessage(event.message); return message === event.message ? event : { ...event, message }; } function mapChanged(values: T[], project: (value: T) => T): T[] { let projectedValues: T[] | undefined; let index = 0; for (const value of values) { const projected = project(value); if (projectedValues === undefined) { if (projected === value) { index += 1; continue; } projectedValues = values.slice(0, index); } projectedValues.push(projected); index += 1; } return projectedValues ?? values; } function isUnknownArray(value: unknown): value is unknown[] { return Array.isArray(value); } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); }