
























@@ -1,6 +1,10 @@
11import { spawn } from "node:child_process";
22import os from "node:os";
3-import type { AgentToolResult, AgentToolUpdateCallback } from "@mariozechner/pi-agent-core";
3+import type {
4+AgentMessage,
5+AgentToolResult,
6+AgentToolUpdateCallback,
7+} from "@mariozechner/pi-agent-core";
48import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
59import { Type } from "typebox";
610import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -37,11 +41,22 @@ export type ToolSearchCatalogToolExecutor = (params: {
3741tool: CatalogTool;
3842toolName: string;
3943toolCallId: string;
44+parentToolCallId?: string;
4045input: unknown;
4146signal?: AbortSignal;
4247onUpdate?: AgentToolUpdateCallback<unknown>;
4348}) => Promise<AgentToolResult<unknown>>;
444950+export type ToolSearchTargetTranscriptProjection = {
51+parentToolCallId?: string;
52+toolCallId: string;
53+toolName: string;
54+input: unknown;
55+result?: unknown;
56+isError?: boolean;
57+timestamp?: number;
58+};
59+4560export type ToolSearchConfig = {
4661enabled: boolean;
4762mode: ToolSearchMode;
@@ -518,6 +533,149 @@ function dropToolSearchControlTools(tools: AnyAgentTool[]): AnyAgentTool[] {
518533return tools.filter((tool) => !TOOL_SEARCH_CONTROL_TOOL_NAMES.has(tool.name));
519534}
520535536+function readMessageToolResultId(message: AgentMessage): string | undefined {
537+const record = message as unknown as Record<string, unknown>;
538+const role = typeof record.role === "string" ? record.role : "";
539+const canUseDirectId = role === "toolResult" || role === "tool";
540+const direct = record.toolCallId ?? record.toolUseId ?? record.tool_use_id;
541+if (canUseDirectId && typeof direct === "string" && direct.trim()) {
542+return direct;
543+}
544+const content = record.content;
545+if (!Array.isArray(content)) {
546+return undefined;
547+}
548+for (const block of content) {
549+if (!isRecord(block)) {
550+continue;
551+}
552+if (block.type !== "toolResult") {
553+continue;
554+}
555+const nested = block.toolCallId ?? block.toolUseId ?? block.tool_use_id ?? block.id;
556+if (typeof nested === "string" && nested.trim()) {
557+return nested;
558+}
559+}
560+return undefined;
561+}
562+563+function textFromToolSearchProjectionResult(result: unknown, isError: boolean): string {
564+if (isRecord(result)) {
565+const details = isRecord(result.details) ? result.details : undefined;
566+const detailError = details?.error;
567+if (typeof detailError === "string" && detailError.trim()) {
568+return detailError;
569+}
570+const content = result.content;
571+if (Array.isArray(content)) {
572+const text = content
573+.map((item) => (isRecord(item) && typeof item.text === "string" ? item.text : ""))
574+.filter(Boolean)
575+.join("\n");
576+if (text.trim()) {
577+return text;
578+}
579+}
580+}
581+const safe = toJsonSafe(result);
582+if (typeof safe === "string") {
583+return safe;
584+}
585+const encoded = JSON.stringify(safe);
586+if (typeof encoded === "string") {
587+return encoded;
588+}
589+return isError ? "Tool Search target tool failed." : "Tool Search target tool completed.";
590+}
591+592+function buildToolSearchTargetTranscriptMessages(
593+projection: ToolSearchTargetTranscriptProjection,
594+): AgentMessage[] {
595+const input = toJsonSafe(projection.input);
596+const timestamp = projection.timestamp ?? Date.now();
597+const resultRecord = isRecord(projection.result) ? projection.result : undefined;
598+const resultContent =
599+Array.isArray(resultRecord?.content) && resultRecord.content.length > 0
600+ ? toJsonSafe(resultRecord.content)
601+ : [
602+{
603+type: "text",
604+text: textFromToolSearchProjectionResult(
605+projection.result,
606+projection.isError === true,
607+),
608+},
609+];
610+return [
611+{
612+role: "assistant",
613+content: [
614+{
615+type: "toolCall",
616+id: projection.toolCallId,
617+name: projection.toolName,
618+arguments: input,
619+ input,
620+},
621+],
622+stopReason: "toolUse",
623+ timestamp,
624+} as unknown as AgentMessage,
625+{
626+role: "toolResult",
627+toolCallId: projection.toolCallId,
628+toolName: projection.toolName,
629+isError: projection.isError === true,
630+content: resultContent,
631+ timestamp,
632+} as unknown as AgentMessage,
633+];
634+}
635+636+export function projectToolSearchTargetTranscriptMessages(
637+messages: AgentMessage[],
638+projections: readonly ToolSearchTargetTranscriptProjection[],
639+): AgentMessage[] {
640+if (projections.length === 0) {
641+return messages;
642+}
643+const byParent = new Map<string, ToolSearchTargetTranscriptProjection[]>();
644+const unmatched: ToolSearchTargetTranscriptProjection[] = [];
645+for (const projection of projections) {
646+const parent = projection.parentToolCallId?.trim();
647+if (!parent) {
648+unmatched.push(projection);
649+continue;
650+}
651+const group = byParent.get(parent) ?? [];
652+group.push(projection);
653+byParent.set(parent, group);
654+}
655+const inserted = new Set<ToolSearchTargetTranscriptProjection>();
656+const projected: AgentMessage[] = [];
657+for (const message of messages) {
658+projected.push(message);
659+const toolResultId = readMessageToolResultId(message);
660+const group = toolResultId ? byParent.get(toolResultId) : undefined;
661+if (!group) {
662+continue;
663+}
664+for (const projection of group) {
665+projected.push(...buildToolSearchTargetTranscriptMessages(projection));
666+inserted.add(projection);
667+}
668+}
669+for (const projection of [...unmatched, ...projections]) {
670+if (inserted.has(projection)) {
671+continue;
672+}
673+projected.push(...buildToolSearchTargetTranscriptMessages(projection));
674+inserted.add(projection);
675+}
676+return projected;
677+}
678+521679export function createToolSearchCatalogRef(): ToolSearchCatalogRef {
522680return {};
523681}
@@ -888,6 +1046,7 @@ class ToolSearchRuntime {
8881046tool: entry.tool,
8891047toolName: entry.name,
8901048 toolCallId,
1049+parentToolCallId: options?.parentToolCallId,
8911050input: input ?? {},
8921051signal: options?.signal ?? this.ctx.abortSignal,
8931052onUpdate: options?.onUpdate,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。