mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 12:35:35 +02:00
fix(agent): soften tool errors
This commit is contained in:
@@ -112,6 +112,14 @@ describe("output", () => {
|
||||
expect(extractResultDetails(result)).toEqual(result);
|
||||
});
|
||||
|
||||
it("should prefer details when present", () => {
|
||||
const result = {
|
||||
content: [{ type: "text", text: "not json" }],
|
||||
details: { count: 3, truncated: false },
|
||||
};
|
||||
expect(extractResultDetails(result)).toEqual({ count: 3, truncated: false });
|
||||
});
|
||||
|
||||
it("should return direct object if no content array", () => {
|
||||
const result = { count: 10, truncated: true };
|
||||
expect(extractResultDetails(result)).toEqual({ count: 10, truncated: true });
|
||||
|
||||
@@ -118,6 +118,11 @@ export function extractResultDetails(result: unknown): Record<string, unknown> |
|
||||
}
|
||||
}
|
||||
|
||||
const withDetails = result as { details?: unknown };
|
||||
if (withDetails.details && typeof withDetails.details === "object") {
|
||||
return withDetails.details as Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Try direct object access
|
||||
return result as Record<string, unknown>;
|
||||
}
|
||||
@@ -252,8 +257,18 @@ export function createAgentOutput(params: {
|
||||
}
|
||||
case "tool_execution_end": {
|
||||
// Stop spinner and show final result with summary
|
||||
if (event.isError) {
|
||||
const errorText = extractText(event.result) || "Tool failed";
|
||||
const details = extractResultDetails(event.result);
|
||||
const errorField = details?.error;
|
||||
const hasError =
|
||||
event.isError ||
|
||||
Boolean(errorField) ||
|
||||
details?.success === false;
|
||||
if (hasError) {
|
||||
const errorText =
|
||||
(typeof details?.message === "string" && details.message) ||
|
||||
(typeof errorField === "string" && errorField) ||
|
||||
extractText(event.result) ||
|
||||
"Tool failed";
|
||||
const bullet = colors.toolError("✗");
|
||||
const title = colors.toolName(toolDisplayName(event.toolName));
|
||||
spinner.stop(`${bullet} ${title}: ${colors.toolError(errorText)}`);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import type { AgentOptions } from "./types.js";
|
||||
import { getModel } from "@mariozechner/pi-ai";
|
||||
import { createCodingTools } from "@mariozechner/pi-coding-agent";
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { createExecTool } from "./tools/exec.js";
|
||||
import { createProcessTool } from "./tools/process.js";
|
||||
import { createGlobTool } from "./tools/glob.js";
|
||||
import { createWebFetchTool, createWebSearchTool } from "./tools/web/index.js";
|
||||
import { createMemoryTools } from "./tools/memory/index.js";
|
||||
import { filterTools } from "./tools/policy.js";
|
||||
import { isMulticaError, isRetryableError } from "../shared/errors.js";
|
||||
|
||||
export function resolveModel(options: AgentOptions) {
|
||||
if (options.provider && options.model) {
|
||||
@@ -29,6 +30,66 @@ export interface CreateToolsOptions {
|
||||
profileBaseDir?: string;
|
||||
}
|
||||
|
||||
type ToolErrorPayload = {
|
||||
error: true;
|
||||
message: string;
|
||||
name?: string;
|
||||
code?: string;
|
||||
retryable?: boolean;
|
||||
details?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function toToolErrorPayload(error: unknown): ToolErrorPayload {
|
||||
if (isMulticaError(error)) {
|
||||
return {
|
||||
error: true,
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
code: error.code,
|
||||
retryable: error.retryable,
|
||||
details: error.details,
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return {
|
||||
error: true,
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
retryable: isRetryableError(error),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
error: true,
|
||||
message: String(error),
|
||||
};
|
||||
}
|
||||
|
||||
function toolErrorResult(error: unknown): AgentToolResult<ToolErrorPayload> {
|
||||
const payload = toToolErrorPayload(error);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
||||
details: payload,
|
||||
};
|
||||
}
|
||||
|
||||
function wrapTool<TParams, TResult>(
|
||||
tool: AgentTool<TParams, TResult>,
|
||||
): AgentTool<TParams, TResult> {
|
||||
const execute = tool.execute;
|
||||
return {
|
||||
...tool,
|
||||
execute: async (...args) => {
|
||||
try {
|
||||
return await execute(...args);
|
||||
} catch (error) {
|
||||
return toolErrorResult(error) as AgentToolResult<TResult>;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all available tools.
|
||||
* This returns the full set before policy filtering.
|
||||
@@ -95,7 +156,7 @@ export function resolveTools(options: AgentOptions): AgentTool<any>[] {
|
||||
isSubagent: options.isSubagent,
|
||||
});
|
||||
|
||||
return filtered;
|
||||
return filtered.map((tool) => wrapTool(tool));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user