mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 11:57:39 +00:00
refactor(agents): share tool call id extraction
This commit is contained in:
@@ -4,6 +4,7 @@ import type { SessionManager } from "@mariozechner/pi-coding-agent";
|
|||||||
import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
import { emitSessionTranscriptUpdate } from "../sessions/transcript-events.js";
|
||||||
import { HARD_MAX_TOOL_RESULT_CHARS } from "./pi-embedded-runner/tool-result-truncation.js";
|
import { HARD_MAX_TOOL_RESULT_CHARS } from "./pi-embedded-runner/tool-result-truncation.js";
|
||||||
import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
|
import { makeMissingToolResult, sanitizeToolCallInputs } from "./session-transcript-repair.js";
|
||||||
|
import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
|
||||||
|
|
||||||
const GUARD_TRUNCATION_SUFFIX =
|
const GUARD_TRUNCATION_SUFFIX =
|
||||||
"\n\n⚠️ [Content truncated during persistence — original exceeded size limit. " +
|
"\n\n⚠️ [Content truncated during persistence — original exceeded size limit. " +
|
||||||
@@ -71,45 +72,6 @@ function capToolResultSize(msg: AgentMessage): AgentMessage {
|
|||||||
return { ...msg, content: newContent } as AgentMessage;
|
return { ...msg, content: newContent } as AgentMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolCall = { id: string; name?: string };
|
|
||||||
|
|
||||||
function extractAssistantToolCalls(msg: Extract<AgentMessage, { role: "assistant" }>): ToolCall[] {
|
|
||||||
const content = msg.content;
|
|
||||||
if (!Array.isArray(content)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const toolCalls: ToolCall[] = [];
|
|
||||||
for (const block of content) {
|
|
||||||
if (!block || typeof block !== "object") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const rec = block as { type?: unknown; id?: unknown; name?: unknown };
|
|
||||||
if (typeof rec.id !== "string" || !rec.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
|
|
||||||
toolCalls.push({
|
|
||||||
id: rec.id,
|
|
||||||
name: typeof rec.name === "string" ? rec.name : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return toolCalls;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractToolResultId(msg: Extract<AgentMessage, { role: "toolResult" }>): string | null {
|
|
||||||
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
|
|
||||||
if (typeof toolCallId === "string" && toolCallId) {
|
|
||||||
return toolCallId;
|
|
||||||
}
|
|
||||||
const toolUseId = (msg as { toolUseId?: unknown }).toolUseId;
|
|
||||||
if (typeof toolUseId === "string" && toolUseId) {
|
|
||||||
return toolUseId;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function installSessionToolResultGuard(
|
export function installSessionToolResultGuard(
|
||||||
sessionManager: SessionManager,
|
sessionManager: SessionManager,
|
||||||
opts?: {
|
opts?: {
|
||||||
@@ -206,7 +168,7 @@ export function installSessionToolResultGuard(
|
|||||||
|
|
||||||
const toolCalls =
|
const toolCalls =
|
||||||
nextRole === "assistant"
|
nextRole === "assistant"
|
||||||
? extractAssistantToolCalls(nextMessage as Extract<AgentMessage, { role: "assistant" }>)
|
? extractToolCallsFromAssistant(nextMessage as Extract<AgentMessage, { role: "assistant" }>)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (allowSyntheticToolResults) {
|
if (allowSyntheticToolResults) {
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
|
import { extractToolCallsFromAssistant, extractToolResultId } from "./tool-call-id.js";
|
||||||
type ToolCallLike = {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TOOL_CALL_TYPES = new Set(["toolCall", "toolUse", "functionCall"]);
|
|
||||||
|
|
||||||
type ToolCallBlock = {
|
type ToolCallBlock = {
|
||||||
type?: unknown;
|
type?: unknown;
|
||||||
@@ -15,40 +9,15 @@ type ToolCallBlock = {
|
|||||||
arguments?: unknown;
|
arguments?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
function extractToolCallsFromAssistant(
|
|
||||||
msg: Extract<AgentMessage, { role: "assistant" }>,
|
|
||||||
): ToolCallLike[] {
|
|
||||||
const content = msg.content;
|
|
||||||
if (!Array.isArray(content)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const toolCalls: ToolCallLike[] = [];
|
|
||||||
for (const block of content) {
|
|
||||||
if (!block || typeof block !== "object") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const rec = block as { type?: unknown; id?: unknown; name?: unknown };
|
|
||||||
if (typeof rec.id !== "string" || !rec.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rec.type === "toolCall" || rec.type === "toolUse" || rec.type === "functionCall") {
|
|
||||||
toolCalls.push({
|
|
||||||
id: rec.id,
|
|
||||||
name: typeof rec.name === "string" ? rec.name : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return toolCalls;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isToolCallBlock(block: unknown): block is ToolCallBlock {
|
function isToolCallBlock(block: unknown): block is ToolCallBlock {
|
||||||
if (!block || typeof block !== "object") {
|
if (!block || typeof block !== "object") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const type = (block as { type?: unknown }).type;
|
const type = (block as { type?: unknown }).type;
|
||||||
return typeof type === "string" && TOOL_CALL_TYPES.has(type);
|
return (
|
||||||
|
typeof type === "string" &&
|
||||||
|
(type === "toolCall" || type === "toolUse" || type === "functionCall")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasToolCallInput(block: ToolCallBlock): boolean {
|
function hasToolCallInput(block: ToolCallBlock): boolean {
|
||||||
@@ -70,18 +39,6 @@ function hasToolCallName(block: ToolCallBlock): boolean {
|
|||||||
return hasNonEmptyStringField(block.name);
|
return hasNonEmptyStringField(block.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractToolResultId(msg: Extract<AgentMessage, { role: "toolResult" }>): string | null {
|
|
||||||
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
|
|
||||||
if (typeof toolCallId === "string" && toolCallId) {
|
|
||||||
return toolCallId;
|
|
||||||
}
|
|
||||||
const toolUseId = (msg as { toolUseId?: unknown }).toolUseId;
|
|
||||||
if (typeof toolUseId === "string" && toolUseId) {
|
|
||||||
return toolUseId;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeMissingToolResult(params: {
|
function makeMissingToolResult(params: {
|
||||||
toolCallId: string;
|
toolCallId: string;
|
||||||
toolName?: string;
|
toolName?: string;
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ import { createHash } from "node:crypto";
|
|||||||
export type ToolCallIdMode = "strict" | "strict9";
|
export type ToolCallIdMode = "strict" | "strict9";
|
||||||
|
|
||||||
const STRICT9_LEN = 9;
|
const STRICT9_LEN = 9;
|
||||||
|
const TOOL_CALL_TYPES = new Set(["toolCall", "toolUse", "functionCall"]);
|
||||||
|
|
||||||
|
export type ToolCallLike = {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize a tool call ID to be compatible with various providers.
|
* Sanitize a tool call ID to be compatible with various providers.
|
||||||
@@ -35,6 +41,47 @@ export function sanitizeToolCallId(id: string, mode: ToolCallIdMode = "strict"):
|
|||||||
return alphanumericOnly.length > 0 ? alphanumericOnly : "sanitizedtoolid";
|
return alphanumericOnly.length > 0 ? alphanumericOnly : "sanitizedtoolid";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractToolCallsFromAssistant(
|
||||||
|
msg: Extract<AgentMessage, { role: "assistant" }>,
|
||||||
|
): ToolCallLike[] {
|
||||||
|
const content = msg.content;
|
||||||
|
if (!Array.isArray(content)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolCalls: ToolCallLike[] = [];
|
||||||
|
for (const block of content) {
|
||||||
|
if (!block || typeof block !== "object") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const rec = block as { type?: unknown; id?: unknown; name?: unknown };
|
||||||
|
if (typeof rec.id !== "string" || !rec.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof rec.type === "string" && TOOL_CALL_TYPES.has(rec.type)) {
|
||||||
|
toolCalls.push({
|
||||||
|
id: rec.id,
|
||||||
|
name: typeof rec.name === "string" ? rec.name : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toolCalls;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractToolResultId(
|
||||||
|
msg: Extract<AgentMessage, { role: "toolResult" }>,
|
||||||
|
): string | null {
|
||||||
|
const toolCallId = (msg as { toolCallId?: unknown }).toolCallId;
|
||||||
|
if (typeof toolCallId === "string" && toolCallId) {
|
||||||
|
return toolCallId;
|
||||||
|
}
|
||||||
|
const toolUseId = (msg as { toolUseId?: unknown }).toolUseId;
|
||||||
|
if (typeof toolUseId === "string" && toolUseId) {
|
||||||
|
return toolUseId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function isValidCloudCodeAssistToolId(id: string, mode: ToolCallIdMode = "strict"): boolean {
|
export function isValidCloudCodeAssistToolId(id: string, mode: ToolCallIdMode = "strict"): boolean {
|
||||||
if (!id || typeof id !== "string") {
|
if (!id || typeof id !== "string") {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user