feat(agent): rendered conversation logs with preferred tool syntax

- Updated compaction, branch summarization, and session dump formatting to pass preferred model tool syntax into conversation serialization.
- Enhanced shared serializers to render assistant tool calls and tool results through grammar envelopes when syntax is available, with the prior compact format as fallback.
- Aligned prompt, preview, and test fixtures to the new transcript tags: `[Think]`, `[Tool Call]`, and `[Tool Result]`.
This commit is contained in:
can1357
2026-06-15 12:32:47 +02:00
parent fc912904e1
commit 2a7cb56abd
11 changed files with 68 additions and 46 deletions
@@ -1,8 +1,8 @@
[User]: Fix the settings overlay crash. Wheeling past the last row throws.
[Assistant tool calls]: read(path="src/select-list.ts:140-180")
[Tool Call]: read(path="src/select-list.ts:140-180")
[Tool result]: 162: const index = Math.floor(line / rowHeight); index is never checked against bounds.
[Tool Result]: 162: const index = Math.floor(line / rowHeight); index is never checked against bounds.
[Assistant]: Found it. The hit test indexes past the filtered list; clamping to the last row fixes the crash.
@@ -38,10 +38,10 @@ const ZOOM_SCALE = 4;
const MAX_IMAGE_COLS = 28;
const MAX_IMAGE_ROWS = 14;
/** Sample transcript with `[Tool result]:` bodies wrapped in dim-ink toggles. */
/** Sample transcript with `[Tool Result]:` bodies wrapped in dim-ink toggles. */
const PREVIEW_TEXT = sampleDoc
.trim()
.replace(/\[Tool result\]: ([^[]*)/g, (_match, body: string) => `[Tool result]: ${DIM_ON}${body}${DIM_OFF}`);
.replace(/\[Tool Result\]: ([^[]*)/g, (_match, body: string) => `[Tool Result]: ${DIM_ON}${body}${DIM_OFF}`);
type PreviewEntry =
| { state: "rendering" }
@@ -4,7 +4,8 @@
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
import type { AssistantMessage, Model, ToolExample, TSchema } from "@oh-my-pi/pi-ai";
import { renderToolInventory } from "@oh-my-pi/pi-ai/grammar";
import { getInbandGrammar, renderToolInventory } from "@oh-my-pi/pi-ai/grammar";
import { preferredToolSyntax } from "@oh-my-pi/pi-catalog/identity";
import { getVisibleThinkingText } from "../utils/thinking-display";
import {
type BashExecutionMessage,
@@ -34,22 +35,12 @@ export interface FormatSessionDumpTextOptions {
tools?: readonly SessionDumpToolInfo[];
}
/** Serialize an object as XML parameter elements, one per key. */
function formatArgsAsXml(args: Record<string, unknown>, indent = "\t"): string {
const parts: string[] = [];
for (const [key, value] of Object.entries(args)) {
if (key === INTENT_FIELD) continue;
const text = typeof value === "string" ? value : JSON.stringify(value);
parts.push(`${indent}<parameter name="${key}">${text}</parameter>`);
}
return parts.join("\n");
}
/**
* Format messages and session metadata as markdown/plain text (same as AgentSession.formatSessionAsText / /dump).
*/
export function formatSessionDumpText(options: FormatSessionDumpTextOptions): string {
const lines: string[] = [];
const grammar = getInbandGrammar(preferredToolSyntax(options.model?.id ?? ""));
const systemPrompt = options.systemPrompt?.filter(prompt => prompt.length > 0) ?? [];
if (systemPrompt.length > 0) {
@@ -112,11 +103,9 @@ export function formatSessionDumpText(options: FormatSessionDumpTextOptions): st
lines.push(thinking);
lines.push("</thinking>\n");
} else if (c.type === "toolCall") {
lines.push(`<invoke name="${c.name}">`);
if (c.arguments && typeof c.arguments === "object") {
lines.push(formatArgsAsXml(c.arguments as Record<string, unknown>));
}
lines.push("<" + "/invoke>\n");
const args = { ...(c.arguments as Record<string, unknown>) };
delete args[INTENT_FIELD];
lines.push(grammar.renderToolCall({ ...c, arguments: args }));
}
}
lines.push("");
@@ -18,7 +18,7 @@ describe("serializeConversation", () => {
const result = serializeConversation(messages);
expect(result).toContain("[Tool result]:");
expect(result).toContain("[Tool Result]:");
expect(result).toContain("[... 3000 more characters truncated]");
expect(result).toContain("x".repeat(2000));
expect(result).not.toContain("x".repeat(3000));
@@ -39,7 +39,7 @@ describe("serializeConversation", () => {
const result = serializeConversation(messages);
expect(result).toBe(`[Tool result]: ${shortContent}`);
expect(result).toBe(`[Tool Result]: ${shortContent}`);
expect(result).not.toContain("truncated");
});