feat(ai): add shared contentText utility (#6840)
Co-authored-by: Armin Ronacher <armin.ronacher@active-4.com>
This commit is contained in:
@@ -1,4 +1,11 @@
|
|||||||
import type { AssistantMessage, ImageContent, Model, Models, UserMessage } from "@earendil-works/pi-ai";
|
import {
|
||||||
|
type AssistantMessage,
|
||||||
|
contentText,
|
||||||
|
type ImageContent,
|
||||||
|
type Model,
|
||||||
|
type Models,
|
||||||
|
type UserMessage,
|
||||||
|
} from "@earendil-works/pi-ai";
|
||||||
import { runAgentLoop } from "../agent-loop.ts";
|
import { runAgentLoop } from "../agent-loop.ts";
|
||||||
import type {
|
import type {
|
||||||
AgentContext,
|
AgentContext,
|
||||||
@@ -781,23 +788,10 @@ export class AgentHarness<
|
|||||||
let newLeafId: string | null;
|
let newLeafId: string | null;
|
||||||
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
||||||
newLeafId = targetEntry.parentId;
|
newLeafId = targetEntry.parentId;
|
||||||
const content = targetEntry.message.content;
|
editorText = contentText(targetEntry.message.content, "");
|
||||||
editorText =
|
|
||||||
typeof content === "string"
|
|
||||||
? content
|
|
||||||
: content
|
|
||||||
.filter((c): c is { readonly type: "text"; readonly text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
} else if (targetEntry.type === "custom_message") {
|
} else if (targetEntry.type === "custom_message") {
|
||||||
newLeafId = targetEntry.parentId;
|
newLeafId = targetEntry.parentId;
|
||||||
editorText =
|
editorText = contentText(targetEntry.content, "");
|
||||||
typeof targetEntry.content === "string"
|
|
||||||
? targetEntry.content
|
|
||||||
: targetEntry.content
|
|
||||||
.filter((c): c is { readonly type: "text"; readonly text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
} else {
|
} else {
|
||||||
newLeafId = targetId;
|
newLeafId = targetId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Model, Models } from "@earendil-works/pi-ai";
|
import { contentText, type Model, type Models } from "@earendil-works/pi-ai";
|
||||||
|
|
||||||
import type { AgentMessage } from "../../types.ts";
|
import type { AgentMessage } from "../../types.ts";
|
||||||
import {
|
import {
|
||||||
@@ -245,10 +245,7 @@ export async function generateBranchSummary(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = response.content
|
let summary = contentText(response.content);
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("\n");
|
|
||||||
summary = BRANCH_SUMMARY_PREAMBLE + summary;
|
summary = BRANCH_SUMMARY_PREAMBLE + summary;
|
||||||
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
const { readFiles, modifiedFiles } = computeFileLists(fileOps);
|
||||||
summary += formatFileOperations(readFiles, modifiedFiles);
|
summary += formatFileOperations(readFiles, modifiedFiles);
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
import type { AssistantMessage, ImageContent, Model, Models, TextContent, Usage } from "@earendil-works/pi-ai";
|
import {
|
||||||
|
type AssistantMessage,
|
||||||
|
contentText,
|
||||||
|
type ImageContent,
|
||||||
|
type Model,
|
||||||
|
type Models,
|
||||||
|
type TextContent,
|
||||||
|
type Usage,
|
||||||
|
} from "@earendil-works/pi-ai";
|
||||||
import type { AgentMessage, ThinkingLevel } from "../../types.ts";
|
import type { AgentMessage, ThinkingLevel } from "../../types.ts";
|
||||||
import {
|
import {
|
||||||
convertToLlm,
|
convertToLlm,
|
||||||
@@ -513,10 +521,7 @@ export async function generateSummary(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const textContent = response.content
|
const textContent = contentText(response.content);
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
return ok(textContent);
|
return ok(textContent);
|
||||||
}
|
}
|
||||||
@@ -744,10 +749,5 @@ async function generateTurnPrefixSummary(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok(
|
return ok(contentText(response.content));
|
||||||
response.content
|
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("\n"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Message } from "@earendil-works/pi-ai";
|
import { contentText, type Message } from "@earendil-works/pi-ai";
|
||||||
import type { AgentMessage } from "../../types.ts";
|
import type { AgentMessage } from "../../types.ts";
|
||||||
|
|
||||||
/** File paths touched by a session branch or compaction range. */
|
/** File paths touched by a session branch or compaction range. */
|
||||||
@@ -93,23 +93,14 @@ export function serializeConversation(messages: Message[]): string {
|
|||||||
|
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
if (msg.role === "user") {
|
if (msg.role === "user") {
|
||||||
const content =
|
const content = contentText(msg.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}`);
|
if (content) parts.push(`[User]: ${content}`);
|
||||||
} else if (msg.role === "assistant") {
|
} else if (msg.role === "assistant") {
|
||||||
const textParts: string[] = [];
|
|
||||||
const thinkingParts: string[] = [];
|
const thinkingParts: string[] = [];
|
||||||
const toolCalls: string[] = [];
|
const toolCalls: string[] = [];
|
||||||
|
|
||||||
for (const block of msg.content) {
|
for (const block of msg.content) {
|
||||||
if (block.type === "text") {
|
if (block.type === "thinking") {
|
||||||
textParts.push(block.text);
|
|
||||||
} else if (block.type === "thinking") {
|
|
||||||
thinkingParts.push(block.thinking);
|
thinkingParts.push(block.thinking);
|
||||||
} else if (block.type === "toolCall") {
|
} else if (block.type === "toolCall") {
|
||||||
const args = block.arguments as Record<string, unknown>;
|
const args = block.arguments as Record<string, unknown>;
|
||||||
@@ -123,17 +114,14 @@ export function serializeConversation(messages: Message[]): string {
|
|||||||
if (thinkingParts.length > 0) {
|
if (thinkingParts.length > 0) {
|
||||||
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
||||||
}
|
}
|
||||||
if (textParts.length > 0) {
|
if (msg.content.some((block) => block.type === "text")) {
|
||||||
parts.push(`[Assistant]: ${textParts.join("\n")}`);
|
parts.push(`[Assistant]: ${contentText(msg.content)}`);
|
||||||
}
|
}
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
||||||
}
|
}
|
||||||
} else if (msg.role === "toolResult") {
|
} else if (msg.role === "toolResult") {
|
||||||
const content = msg.content
|
const content = contentText(msg.content, "");
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
if (content) {
|
if (content) {
|
||||||
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added `contentText` for extracting joined text from message content.
|
||||||
- Added a shared `uuidv7` utility for time-ordered identifiers.
|
- Added a shared `uuidv7` utility for time-ordered identifiers.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export * from "./utils/event-stream.ts";
|
|||||||
export * from "./utils/json-parse.ts";
|
export * from "./utils/json-parse.ts";
|
||||||
export * from "./utils/overflow.ts";
|
export * from "./utils/overflow.ts";
|
||||||
export * from "./utils/retry.ts";
|
export * from "./utils/retry.ts";
|
||||||
|
export { contentText } from "./utils/text.ts";
|
||||||
export * from "./utils/typebox-helpers.ts";
|
export * from "./utils/typebox-helpers.ts";
|
||||||
export { uuidv7 } from "./utils/uuid.ts";
|
export { uuidv7 } from "./utils/uuid.ts";
|
||||||
export * from "./utils/validation.ts";
|
export * from "./utils/validation.ts";
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { ImageContent, TextContent, ThinkingContent, ToolCall } from "../types.ts";
|
||||||
|
|
||||||
|
type Content = TextContent | ImageContent | ThinkingContent | ToolCall;
|
||||||
|
|
||||||
|
/** Extract and join text from message content. */
|
||||||
|
export function contentText(content: string | readonly Content[], separator = "\n"): string {
|
||||||
|
if (typeof content === "string") return content;
|
||||||
|
return content
|
||||||
|
.filter((block) => block.type === "text")
|
||||||
|
.map((block) => block.text)
|
||||||
|
.join(separator);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { type AssistantMessage, contentText, type ToolResultMessage } from "../src/index.ts";
|
||||||
|
|
||||||
|
const content: AssistantMessage["content"] = [
|
||||||
|
{ type: "thinking", thinking: "reasoning" },
|
||||||
|
{ type: "text", text: "first" },
|
||||||
|
{ type: "toolCall", id: "1", name: "read", arguments: {} },
|
||||||
|
{ type: "text", text: "second" },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("contentText", () => {
|
||||||
|
it("extracts assistant text blocks", () => {
|
||||||
|
expect(contentText(content)).toBe("first\nsecond");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports custom separators", () => {
|
||||||
|
expect(contentText(content, "")).toBe("firstsecond");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes string content through", () => {
|
||||||
|
expect(contentText("hello")).toBe("hello");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("extracts text from tool-result content", () => {
|
||||||
|
const toolResultContent: ToolResultMessage["content"] = [
|
||||||
|
{ type: "text", text: "first" },
|
||||||
|
{ type: "image", data: "...", mimeType: "image/png" },
|
||||||
|
{ type: "text", text: "second" },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(contentText(toolResultContent, "")).toBe("firstsecond");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -24,11 +24,11 @@ import type {
|
|||||||
PrepareNextTurnContext,
|
PrepareNextTurnContext,
|
||||||
ThinkingLevel,
|
ThinkingLevel,
|
||||||
} from "@earendil-works/pi-agent-core";
|
} from "@earendil-works/pi-agent-core";
|
||||||
|
import { contentText } from "@earendil-works/pi-ai";
|
||||||
import type {
|
import type {
|
||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
AuthResult,
|
AuthResult,
|
||||||
ImageContent,
|
ImageContent,
|
||||||
Message,
|
|
||||||
Model,
|
Model,
|
||||||
ProviderHeaders,
|
ProviderHeaders,
|
||||||
TextContent,
|
TextContent,
|
||||||
@@ -576,7 +576,7 @@ export class AgentSession {
|
|||||||
// This ensures the UI sees the updated queue state
|
// This ensures the UI sees the updated queue state
|
||||||
if (event.type === "message_start" && event.message.role === "user") {
|
if (event.type === "message_start" && event.message.role === "user") {
|
||||||
this._overflowRecoveryAttempted = false;
|
this._overflowRecoveryAttempted = false;
|
||||||
const messageText = this._getUserMessageText(event.message);
|
const messageText = contentText(event.message.content, "");
|
||||||
if (messageText) {
|
if (messageText) {
|
||||||
// Check steering queue first
|
// Check steering queue first
|
||||||
const steeringIndex = this._steeringMessages.indexOf(messageText);
|
const steeringIndex = this._steeringMessages.indexOf(messageText);
|
||||||
@@ -659,15 +659,6 @@ export class AgentSession {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Extract text content from a message */
|
|
||||||
private _getUserMessageText(message: Message): string {
|
|
||||||
if (message.role !== "user") return "";
|
|
||||||
const content = message.content;
|
|
||||||
if (typeof content === "string") return content;
|
|
||||||
const textBlocks = content.filter((c) => c.type === "text");
|
|
||||||
return textBlocks.map((c) => (c as TextContent).text).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Find the last assistant message in agent state (including aborted ones) */
|
/** Find the last assistant message in agent state (including aborted ones) */
|
||||||
private _findLastAssistantMessage(): AssistantMessage | undefined {
|
private _findLastAssistantMessage(): AssistantMessage | undefined {
|
||||||
const messages = this.agent.state.messages;
|
const messages = this.agent.state.messages;
|
||||||
@@ -2954,17 +2945,11 @@ export class AgentSession {
|
|||||||
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
||||||
// User message: leaf = parent (null if root), text goes to editor
|
// User message: leaf = parent (null if root), text goes to editor
|
||||||
newLeafId = targetEntry.parentId;
|
newLeafId = targetEntry.parentId;
|
||||||
editorText = this._extractUserMessageText(targetEntry.message.content);
|
editorText = contentText(targetEntry.message.content, "");
|
||||||
} else if (targetEntry.type === "custom_message") {
|
} else if (targetEntry.type === "custom_message") {
|
||||||
// Custom message: leaf = parent (null if root), text goes to editor
|
// Custom message: leaf = parent (null if root), text goes to editor
|
||||||
newLeafId = targetEntry.parentId;
|
newLeafId = targetEntry.parentId;
|
||||||
editorText =
|
editorText = contentText(targetEntry.content, "");
|
||||||
typeof targetEntry.content === "string"
|
|
||||||
? targetEntry.content
|
|
||||||
: targetEntry.content
|
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
} else {
|
} else {
|
||||||
// Non-user message: leaf = selected node
|
// Non-user message: leaf = selected node
|
||||||
newLeafId = targetId;
|
newLeafId = targetId;
|
||||||
@@ -3032,7 +3017,7 @@ export class AgentSession {
|
|||||||
if (entry.type !== "message") continue;
|
if (entry.type !== "message") continue;
|
||||||
if (entry.message.role !== "user") continue;
|
if (entry.message.role !== "user") continue;
|
||||||
|
|
||||||
const text = this._extractUserMessageText(entry.message.content);
|
const text = contentText(entry.message.content, "");
|
||||||
if (text) {
|
if (text) {
|
||||||
result.push({ entryId: entry.id, text });
|
result.push({ entryId: entry.id, text });
|
||||||
}
|
}
|
||||||
@@ -3041,17 +3026,6 @@ export class AgentSession {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _extractUserMessageText(content: string | Array<{ type: string; text?: string }>): string {
|
|
||||||
if (typeof content === "string") return content;
|
|
||||||
if (Array.isArray(content)) {
|
|
||||||
return content
|
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get session statistics. Aggregates over ALL session entries (including
|
* Get session statistics. Aggregates over ALL session entries (including
|
||||||
* history that was compacted away), so token/cost totals reflect what was
|
* history that was compacted away), so token/cost totals reflect what was
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AgentMessage, StreamFn } from "@earendil-works/pi-agent-core";
|
import type { AgentMessage, StreamFn } from "@earendil-works/pi-agent-core";
|
||||||
|
import { contentText } from "@earendil-works/pi-ai";
|
||||||
import type { Model, SimpleStreamOptions } from "@earendil-works/pi-ai/compat";
|
import type { Model, SimpleStreamOptions } from "@earendil-works/pi-ai/compat";
|
||||||
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
||||||
import {
|
import {
|
||||||
@@ -351,10 +352,7 @@ export async function generateBranchSummary(
|
|||||||
return { error: response.errorMessage || "Summarization failed" };
|
return { error: response.errorMessage || "Summarization failed" };
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = response.content
|
let summary = contentText(response.content);
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
// 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;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AgentMessage, StreamFn, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
import type { AgentMessage, StreamFn, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
||||||
|
import { contentText } from "@earendil-works/pi-ai";
|
||||||
import type { AssistantMessage, Context, Model, SimpleStreamOptions, Usage } from "@earendil-works/pi-ai/compat";
|
import type { AssistantMessage, Context, Model, SimpleStreamOptions, Usage } from "@earendil-works/pi-ai/compat";
|
||||||
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
||||||
import { convertToLlm } from "../messages.ts";
|
import { convertToLlm } from "../messages.ts";
|
||||||
@@ -600,10 +601,7 @@ export async function generateSummary(
|
|||||||
throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
|
throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const textContent = response.content
|
const textContent = contentText(response.content);
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
return textContent;
|
return textContent;
|
||||||
}
|
}
|
||||||
@@ -865,8 +863,5 @@ async function generateTurnPrefixSummary(
|
|||||||
throw new Error(`Turn prefix summarization failed: ${response.errorMessage || "Unknown error"}`);
|
throw new Error(`Turn prefix summarization failed: ${response.errorMessage || "Unknown error"}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.content
|
return contentText(response.content);
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("\n");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
||||||
import type { Message } from "@earendil-works/pi-ai";
|
import { contentText, type Message } from "@earendil-works/pi-ai";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// File Operation Tracking
|
// File Operation Tracking
|
||||||
@@ -111,23 +111,14 @@ export function serializeConversation(messages: Message[]): string {
|
|||||||
|
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
if (msg.role === "user") {
|
if (msg.role === "user") {
|
||||||
const content =
|
const content = contentText(msg.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}`);
|
if (content) parts.push(`[User]: ${content}`);
|
||||||
} else if (msg.role === "assistant") {
|
} else if (msg.role === "assistant") {
|
||||||
const textParts: string[] = [];
|
|
||||||
const thinkingParts: string[] = [];
|
const thinkingParts: string[] = [];
|
||||||
const toolCalls: string[] = [];
|
const toolCalls: string[] = [];
|
||||||
|
|
||||||
for (const block of msg.content) {
|
for (const block of msg.content) {
|
||||||
if (block.type === "text") {
|
if (block.type === "thinking") {
|
||||||
textParts.push(block.text);
|
|
||||||
} else if (block.type === "thinking") {
|
|
||||||
thinkingParts.push(block.thinking);
|
thinkingParts.push(block.thinking);
|
||||||
} else if (block.type === "toolCall") {
|
} else if (block.type === "toolCall") {
|
||||||
const args = block.arguments as Record<string, unknown>;
|
const args = block.arguments as Record<string, unknown>;
|
||||||
@@ -141,17 +132,14 @@ export function serializeConversation(messages: Message[]): string {
|
|||||||
if (thinkingParts.length > 0) {
|
if (thinkingParts.length > 0) {
|
||||||
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`);
|
||||||
}
|
}
|
||||||
if (textParts.length > 0) {
|
if (msg.content.some((block) => block.type === "text")) {
|
||||||
parts.push(`[Assistant]: ${textParts.join("\n")}`);
|
parts.push(`[Assistant]: ${contentText(msg.content)}`);
|
||||||
}
|
}
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`);
|
||||||
}
|
}
|
||||||
} else if (msg.role === "toolResult") {
|
} else if (msg.role === "toolResult") {
|
||||||
const content = msg.content
|
const content = contentText(msg.content, "");
|
||||||
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
||||||
.map((c) => c.text)
|
|
||||||
.join("");
|
|
||||||
if (content) {
|
if (content) {
|
||||||
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user