mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 03:03:44 +00:00
Extract shared compaction/branch-summarization utils
- New utils.ts with shared functions: - FileOperations type and createFileOps() - extractFileOpsFromMessage() - computeFileLists() - formatFileOperations() - serializeConversation() - SUMMARIZATION_SYSTEM_PROMPT - branch-summarization.ts now uses: - Serialization approach (conversation as text, not LLM messages) - completeSimple with system prompt - Shared utility functions
This commit is contained in:
parent
17ce3814a8
commit
81f4cdf3e3
4 changed files with 193 additions and 199 deletions
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import type { Model } from "@mariozechner/pi-ai";
|
import type { Model } from "@mariozechner/pi-ai";
|
||||||
import { complete } from "@mariozechner/pi-ai";
|
import { completeSimple } from "@mariozechner/pi-ai";
|
||||||
import {
|
import {
|
||||||
convertToLlm,
|
convertToLlm,
|
||||||
createBranchSummaryMessage,
|
createBranchSummaryMessage,
|
||||||
|
|
@ -16,6 +16,15 @@ import {
|
||||||
} from "../messages.js";
|
} from "../messages.js";
|
||||||
import type { ReadonlySessionManager, SessionEntry } from "../session-manager.js";
|
import type { ReadonlySessionManager, SessionEntry } from "../session-manager.js";
|
||||||
import { estimateTokens } from "./compaction.js";
|
import { estimateTokens } from "./compaction.js";
|
||||||
|
import {
|
||||||
|
computeFileLists,
|
||||||
|
createFileOps,
|
||||||
|
extractFileOpsFromMessage,
|
||||||
|
type FileOperations,
|
||||||
|
formatFileOperations,
|
||||||
|
SUMMARIZATION_SYSTEM_PROMPT,
|
||||||
|
serializeConversation,
|
||||||
|
} from "./utils.js";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Types
|
||||||
|
|
@ -35,11 +44,7 @@ export interface BranchSummaryDetails {
|
||||||
modifiedFiles: string[];
|
modifiedFiles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileOperations {
|
export type { FileOperations } from "./utils.js";
|
||||||
read: Set<string>;
|
|
||||||
written: Set<string>;
|
|
||||||
edited: Set<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BranchPreparation {
|
export interface BranchPreparation {
|
||||||
/** Messages extracted for summarization, in chronological order */
|
/** Messages extracted for summarization, in chronological order */
|
||||||
|
|
@ -159,38 +164,6 @@ function getMessageFromEntry(entry: SessionEntry): AgentMessage | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract file operations from tool calls in an assistant message.
|
|
||||||
*/
|
|
||||||
function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {
|
|
||||||
if (message.role !== "assistant") return;
|
|
||||||
if (!("content" in message) || !Array.isArray(message.content)) return;
|
|
||||||
|
|
||||||
for (const block of message.content) {
|
|
||||||
if (typeof block !== "object" || block === null) continue;
|
|
||||||
if (!("type" in block) || block.type !== "toolCall") continue;
|
|
||||||
if (!("arguments" in block) || !("name" in block)) continue;
|
|
||||||
|
|
||||||
const args = block.arguments as Record<string, unknown> | undefined;
|
|
||||||
if (!args) continue;
|
|
||||||
|
|
||||||
const path = typeof args.path === "string" ? args.path : undefined;
|
|
||||||
if (!path) continue;
|
|
||||||
|
|
||||||
switch (block.name) {
|
|
||||||
case "read":
|
|
||||||
fileOps.read.add(path);
|
|
||||||
break;
|
|
||||||
case "write":
|
|
||||||
fileOps.written.add(path);
|
|
||||||
break;
|
|
||||||
case "edit":
|
|
||||||
fileOps.edited.add(path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare entries for summarization with token budget.
|
* Prepare entries for summarization with token budget.
|
||||||
*
|
*
|
||||||
|
|
@ -206,11 +179,7 @@ function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperation
|
||||||
*/
|
*/
|
||||||
export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: number = 0): BranchPreparation {
|
export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: number = 0): BranchPreparation {
|
||||||
const messages: AgentMessage[] = [];
|
const messages: AgentMessage[] = [];
|
||||||
const fileOps: FileOperations = {
|
const fileOps = createFileOps();
|
||||||
read: new Set(),
|
|
||||||
written: new Set(),
|
|
||||||
edited: new Set(),
|
|
||||||
};
|
|
||||||
let totalTokens = 0;
|
let totalTokens = 0;
|
||||||
|
|
||||||
// First pass: collect file ops from ALL entries (even if they don't fit in token budget)
|
// First pass: collect file ops from ALL entries (even if they don't fit in token budget)
|
||||||
|
|
@ -322,24 +291,29 @@ export async function generateBranchSummary(
|
||||||
return { summary: "No content to summarize" };
|
return { summary: "No content to summarize" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform to LLM-compatible messages (preserves tool calls, etc.)
|
// Transform to LLM-compatible messages, then serialize to text
|
||||||
const transformedMessages = convertToLlm(messages);
|
// Serialization prevents the model from treating it as a conversation to continue
|
||||||
|
const llmMessages = convertToLlm(messages);
|
||||||
|
const conversationText = serializeConversation(llmMessages);
|
||||||
|
|
||||||
// Build prompt
|
// Build prompt
|
||||||
const instructions = customInstructions || BRANCH_SUMMARY_PROMPT;
|
const instructions = customInstructions || BRANCH_SUMMARY_PROMPT;
|
||||||
|
const promptText = `<conversation>\n${conversationText}\n</conversation>\n\n${instructions}`;
|
||||||
|
|
||||||
// Append summarization prompt as final user message
|
|
||||||
const summarizationMessages = [
|
const summarizationMessages = [
|
||||||
...transformedMessages,
|
|
||||||
{
|
{
|
||||||
role: "user" as const,
|
role: "user" as const,
|
||||||
content: [{ type: "text" as const, text: instructions }],
|
content: [{ type: "text" as const, text: promptText }],
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Call LLM for summarization
|
// Call LLM for summarization
|
||||||
const response = await complete(model, { messages: summarizationMessages }, { apiKey, signal, maxTokens: 2048 });
|
const response = await completeSimple(
|
||||||
|
model,
|
||||||
|
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
|
||||||
|
{ apiKey, signal, maxTokens: 2048 },
|
||||||
|
);
|
||||||
|
|
||||||
// Check if aborted or errored
|
// Check if aborted or errored
|
||||||
if (response.stopReason === "aborted") {
|
if (response.stopReason === "aborted") {
|
||||||
|
|
@ -357,26 +331,13 @@ export async function generateBranchSummary(
|
||||||
// Prepend preamble to provide context about the branch summary
|
// Prepend preamble to provide context about the branch summary
|
||||||
summary = BRANCH_SUMMARY_PREAMBLE + summary;
|
summary = BRANCH_SUMMARY_PREAMBLE + summary;
|
||||||
|
|
||||||
// Compute file lists
|
// Compute file lists and append to summary
|
||||||
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
||||||
const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
summary += formatFileOperations(readFiles, modifiedFiles);
|
||||||
const modifiedFiles = [...modified].sort();
|
|
||||||
|
|
||||||
// Append file lists to summary text (for LLM context and TUI display)
|
|
||||||
const fileSections: string[] = [];
|
|
||||||
if (readOnly.length > 0) {
|
|
||||||
fileSections.push(`<read-files>\n${readOnly.join("\n")}\n</read-files>`);
|
|
||||||
}
|
|
||||||
if (modifiedFiles.length > 0) {
|
|
||||||
fileSections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
|
|
||||||
}
|
|
||||||
if (fileSections.length > 0) {
|
|
||||||
summary += `\n\n${fileSections.join("\n\n")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
summary: summary || "No summary generated",
|
summary: summary || "No summary generated",
|
||||||
readFiles: readOnly,
|
readFiles,
|
||||||
modifiedFiles,
|
modifiedFiles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import type { AssistantMessage, Message, Model, Usage } from "@mariozechner/pi-ai";
|
import type { AssistantMessage, Model, Usage } from "@mariozechner/pi-ai";
|
||||||
import { complete, completeSimple } from "@mariozechner/pi-ai";
|
import { complete, completeSimple } from "@mariozechner/pi-ai";
|
||||||
import { convertToLlm, createBranchSummaryMessage, createHookMessage } from "../messages.js";
|
import { convertToLlm, createBranchSummaryMessage, createHookMessage } from "../messages.js";
|
||||||
import type { CompactionEntry, SessionEntry } from "../session-manager.js";
|
import type { CompactionEntry, SessionEntry } from "../session-manager.js";
|
||||||
|
import {
|
||||||
|
computeFileLists,
|
||||||
|
createFileOps,
|
||||||
|
extractFileOpsFromMessage,
|
||||||
|
type FileOperations,
|
||||||
|
formatFileOperations,
|
||||||
|
SUMMARIZATION_SYSTEM_PROMPT,
|
||||||
|
serializeConversation,
|
||||||
|
} from "./utils.js";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// File Operation Tracking
|
// File Operation Tracking
|
||||||
|
|
@ -21,44 +30,6 @@ export interface CompactionDetails {
|
||||||
modifiedFiles: string[];
|
modifiedFiles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FileOperations {
|
|
||||||
read: Set<string>;
|
|
||||||
written: Set<string>;
|
|
||||||
edited: Set<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract file operations from tool calls in an assistant message.
|
|
||||||
*/
|
|
||||||
function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {
|
|
||||||
if (message.role !== "assistant") return;
|
|
||||||
if (!("content" in message) || !Array.isArray(message.content)) return;
|
|
||||||
|
|
||||||
for (const block of message.content) {
|
|
||||||
if (typeof block !== "object" || block === null) continue;
|
|
||||||
if (!("type" in block) || block.type !== "toolCall") continue;
|
|
||||||
if (!("arguments" in block) || !("name" in block)) continue;
|
|
||||||
|
|
||||||
const args = block.arguments as Record<string, unknown> | undefined;
|
|
||||||
if (!args) continue;
|
|
||||||
|
|
||||||
const path = typeof args.path === "string" ? args.path : undefined;
|
|
||||||
if (!path) continue;
|
|
||||||
|
|
||||||
switch (block.name) {
|
|
||||||
case "read":
|
|
||||||
fileOps.read.add(path);
|
|
||||||
break;
|
|
||||||
case "write":
|
|
||||||
fileOps.written.add(path);
|
|
||||||
break;
|
|
||||||
case "edit":
|
|
||||||
fileOps.edited.add(path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract file operations from messages and previous compaction entries.
|
* Extract file operations from messages and previous compaction entries.
|
||||||
*/
|
*/
|
||||||
|
|
@ -67,11 +38,7 @@ function extractFileOperations(
|
||||||
entries: SessionEntry[],
|
entries: SessionEntry[],
|
||||||
prevCompactionIndex: number,
|
prevCompactionIndex: number,
|
||||||
): FileOperations {
|
): FileOperations {
|
||||||
const fileOps: FileOperations = {
|
const fileOps = createFileOps();
|
||||||
read: new Set(),
|
|
||||||
written: new Set(),
|
|
||||||
edited: new Set(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Collect from previous compaction's details (if pi-generated)
|
// Collect from previous compaction's details (if pi-generated)
|
||||||
if (prevCompactionIndex >= 0) {
|
if (prevCompactionIndex >= 0) {
|
||||||
|
|
@ -95,91 +62,6 @@ function extractFileOperations(
|
||||||
return fileOps;
|
return fileOps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute final file lists from file operations.
|
|
||||||
*/
|
|
||||||
function computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {
|
|
||||||
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
|
||||||
const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
|
||||||
const modifiedFiles = [...modified].sort();
|
|
||||||
return { readFiles: readOnly, modifiedFiles };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format file operations as XML tags for summary.
|
|
||||||
*/
|
|
||||||
function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {
|
|
||||||
const sections: string[] = [];
|
|
||||||
if (readFiles.length > 0) {
|
|
||||||
sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
|
|
||||||
}
|
|
||||||
if (modifiedFiles.length > 0) {
|
|
||||||
sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
|
|
||||||
}
|
|
||||||
if (sections.length === 0) return "";
|
|
||||||
return `\n\n${sections.join("\n\n")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize LLM messages to text for summarization.
|
|
||||||
* This prevents the model from treating it as a conversation to continue.
|
|
||||||
* Call convertToLlm() first to handle custom message types.
|
|
||||||
*/
|
|
||||||
function serializeConversation(messages: Message[]): string {
|
|
||||||
const parts: string[] = [];
|
|
||||||
|
|
||||||
for (const msg of messages) {
|
|
||||||
if (msg.role === "user") {
|
|
||||||
const content =
|
|
||||||
typeof msg.content === "string"
|
|
||||||
? msg.content
|
|
||||||
: msg.content
|
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
if (content) parts.push(`[User]: ${content}`);
|
|
||||||
} else if (msg.role === "assistant") {
|
|
||||||
const textParts: string[] = [];
|
|
||||||
const thinkingParts: string[] = [];
|
|
||||||
const toolCalls: string[] = [];
|
|
||||||
|
|
||||||
for (const block of msg.content) {
|
|
||||||
if (block.type === "text") {
|
|
||||||
textParts.push(block.text);
|
|
||||||
} else if (block.type === "thinking") {
|
|
||||||
thinkingParts.push(block.thinking);
|
|
||||||
} else if (block.type === "toolCall") {
|
|
||||||
const args = block.arguments as Record<string, unknown>;
|
|
||||||
const argsStr = Object.entries(args)
|
|
||||||
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
||||||
.join(", ");
|
|
||||||
toolCalls.push(`${block.name}(${argsStr})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thinkingParts.length > 0) {
|
|
||||||
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
|
||||||
}
|
|
||||||
if (textParts.length > 0) {
|
|
||||||
parts.push(`[Assistant]: ${textParts.join("\n")}`);
|
|
||||||
}
|
|
||||||
if (toolCalls.length > 0) {
|
|
||||||
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
|
||||||
}
|
|
||||||
} else if (msg.role === "toolResult") {
|
|
||||||
const content = msg.content
|
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
if (content) {
|
|
||||||
parts.push(`[Tool result]: ${content}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Message Extraction
|
// Message Extraction
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -501,10 +383,6 @@ export function findCutPoint(
|
||||||
// Summarization
|
// Summarization
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.
|
|
||||||
|
|
||||||
Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;
|
|
||||||
|
|
||||||
const SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.
|
const SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.
|
||||||
|
|
||||||
Use this EXACT format:
|
Use this EXACT format:
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
|
|
||||||
export * from "./branch-summarization.js";
|
export * from "./branch-summarization.js";
|
||||||
export * from "./compaction.js";
|
export * from "./compaction.js";
|
||||||
|
export * from "./utils.js";
|
||||||
|
|
|
||||||
154
packages/coding-agent/src/core/compaction/utils.ts
Normal file
154
packages/coding-agent/src/core/compaction/utils.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
/**
|
||||||
|
* Shared utilities for compaction and branch summarization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
|
import type { Message } from "@mariozechner/pi-ai";
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// File Operation Tracking
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface FileOperations {
|
||||||
|
read: Set<string>;
|
||||||
|
written: Set<string>;
|
||||||
|
edited: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFileOps(): FileOperations {
|
||||||
|
return {
|
||||||
|
read: new Set(),
|
||||||
|
written: new Set(),
|
||||||
|
edited: new Set(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract file operations from tool calls in an assistant message.
|
||||||
|
*/
|
||||||
|
export function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void {
|
||||||
|
if (message.role !== "assistant") return;
|
||||||
|
if (!("content" in message) || !Array.isArray(message.content)) return;
|
||||||
|
|
||||||
|
for (const block of message.content) {
|
||||||
|
if (typeof block !== "object" || block === null) continue;
|
||||||
|
if (!("type" in block) || block.type !== "toolCall") continue;
|
||||||
|
if (!("arguments" in block) || !("name" in block)) continue;
|
||||||
|
|
||||||
|
const args = block.arguments as Record<string, unknown> | undefined;
|
||||||
|
if (!args) continue;
|
||||||
|
|
||||||
|
const path = typeof args.path === "string" ? args.path : undefined;
|
||||||
|
if (!path) continue;
|
||||||
|
|
||||||
|
switch (block.name) {
|
||||||
|
case "read":
|
||||||
|
fileOps.read.add(path);
|
||||||
|
break;
|
||||||
|
case "write":
|
||||||
|
fileOps.written.add(path);
|
||||||
|
break;
|
||||||
|
case "edit":
|
||||||
|
fileOps.edited.add(path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute final file lists from file operations.
|
||||||
|
* Returns readFiles (files only read, not modified) and modifiedFiles.
|
||||||
|
*/
|
||||||
|
export function computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {
|
||||||
|
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
||||||
|
const readOnly = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
||||||
|
const modifiedFiles = [...modified].sort();
|
||||||
|
return { readFiles: readOnly, modifiedFiles };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format file operations as XML tags for summary.
|
||||||
|
*/
|
||||||
|
export function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {
|
||||||
|
const sections: string[] = [];
|
||||||
|
if (readFiles.length > 0) {
|
||||||
|
sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
|
||||||
|
}
|
||||||
|
if (modifiedFiles.length > 0) {
|
||||||
|
sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
|
||||||
|
}
|
||||||
|
if (sections.length === 0) return "";
|
||||||
|
return `\n\n${sections.join("\n\n")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Message Serialization
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize LLM messages to text for summarization.
|
||||||
|
* This prevents the model from treating it as a conversation to continue.
|
||||||
|
* Call convertToLlm() first to handle custom message types.
|
||||||
|
*/
|
||||||
|
export function serializeConversation(messages: Message[]): string {
|
||||||
|
const parts: string[] = [];
|
||||||
|
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (msg.role === "user") {
|
||||||
|
const content =
|
||||||
|
typeof msg.content === "string"
|
||||||
|
? msg.content
|
||||||
|
: msg.content
|
||||||
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
||||||
|
.map((c) => c.text)
|
||||||
|
.join("");
|
||||||
|
if (content) parts.push(`[User]: ${content}`);
|
||||||
|
} else if (msg.role === "assistant") {
|
||||||
|
const textParts: string[] = [];
|
||||||
|
const thinkingParts: string[] = [];
|
||||||
|
const toolCalls: string[] = [];
|
||||||
|
|
||||||
|
for (const block of msg.content) {
|
||||||
|
if (block.type === "text") {
|
||||||
|
textParts.push(block.text);
|
||||||
|
} else if (block.type === "thinking") {
|
||||||
|
thinkingParts.push(block.thinking);
|
||||||
|
} else if (block.type === "toolCall") {
|
||||||
|
const args = block.arguments as Record<string, unknown>;
|
||||||
|
const argsStr = Object.entries(args)
|
||||||
|
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
||||||
|
.join(", ");
|
||||||
|
toolCalls.push(`${block.name}(${argsStr})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thinkingParts.length > 0) {
|
||||||
|
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
||||||
|
}
|
||||||
|
if (textParts.length > 0) {
|
||||||
|
parts.push(`[Assistant]: ${textParts.join("\n")}`);
|
||||||
|
}
|
||||||
|
if (toolCalls.length > 0) {
|
||||||
|
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
||||||
|
}
|
||||||
|
} else if (msg.role === "toolResult") {
|
||||||
|
const content = msg.content
|
||||||
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
||||||
|
.map((c) => c.text)
|
||||||
|
.join("");
|
||||||
|
if (content) {
|
||||||
|
parts.push(`[Tool result]: ${content}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Summarization System Prompt
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.
|
||||||
|
|
||||||
|
Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue