ae051a6a33
# Conflicts: # packages/coding-agent/src/hindsight/transcript.ts
72 lines
2.6 KiB
TypeScript
72 lines
2.6 KiB
TypeScript
/**
|
|
* Pull plain-text user/assistant messages out of a session manager.
|
|
*
|
|
* The Hindsight retain/recall API only takes flat `{role, content}` records,
|
|
* so we drop tool calls, tool results, bash execution wrappers, custom
|
|
* messages, and anything else that isn't a primary conversation turn. Each
|
|
* surviving message's `TextContent` parts are joined with newlines.
|
|
*/
|
|
|
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
import type { SessionEntry } from "../session/session-entries";
|
|
import { type HindsightMessage, hasSubstantiveContent } from "./content";
|
|
|
|
export interface ReadonlySessionManagerLike {
|
|
getEntries(): SessionEntry[];
|
|
}
|
|
|
|
/**
|
|
* Walk session entries top-to-bottom, returning a flat user/assistant list.
|
|
*
|
|
* Implementation choices:
|
|
* - Skip entries whose type isn't `"message"` (compaction, branch_summary,
|
|
* custom_message, tool exec records, ...). Those don't represent a
|
|
* conversational turn, only the LLM's plain-text utterances do.
|
|
* - Skip messages whose role isn't `"user"` or `"assistant"`. We deliberately
|
|
* ignore `toolResult`, `bashExecution`, `hookMessage`, etc. — they're noise
|
|
* for memory purposes.
|
|
* - For assistant messages, only `text` blocks contribute. Thinking and
|
|
* toolCall blocks are intentionally dropped: the user never saw them, so
|
|
* retaining them would prime recall on internal monologue.
|
|
*/
|
|
export function extractMessages(sessionManager: ReadonlySessionManagerLike): HindsightMessage[] {
|
|
const messages: HindsightMessage[] = [];
|
|
|
|
for (const entry of sessionManager.getEntries()) {
|
|
if (entry.type !== "message") continue;
|
|
const msg = entry.message;
|
|
const role = msg.role;
|
|
if (role !== "user" && role !== "assistant") continue;
|
|
|
|
const text = role === "user" ? extractUserText(msg) : extractAssistantText(msg as AssistantMessage);
|
|
if (!hasSubstantiveContent(text)) continue;
|
|
messages.push({ role, content: text });
|
|
}
|
|
|
|
return messages;
|
|
}
|
|
|
|
function extractUserText(msg: { content: unknown }): string {
|
|
const content = msg.content;
|
|
if (typeof content === "string") return content;
|
|
if (!Array.isArray(content)) return "";
|
|
|
|
const parts: string[] = [];
|
|
for (const block of content) {
|
|
if (!block || typeof block !== "object") continue;
|
|
const maybeText = block as { type?: unknown; text?: unknown };
|
|
if (maybeText.type === "text" && typeof maybeText.text === "string") {
|
|
parts.push(maybeText.text);
|
|
}
|
|
}
|
|
return parts.join("\n");
|
|
}
|
|
|
|
function extractAssistantText(msg: AssistantMessage): string {
|
|
const parts: string[] = [];
|
|
for (const block of msg.content) {
|
|
if (block.type === "text" && block.text) parts.push(block.text);
|
|
}
|
|
return parts.join("\n");
|
|
}
|