isError results replace thrown ToolInputError (pi 1.0 convention)
Authoring refinements (quizInputProblems, nodeId checks, ask/explain
refinements, submit-agent pipeline laws) now return invalidInputResult —
same model-facing failure and retry semantics pi minted from the throw,
but details keeps {tool, problems} as data instead of the throw path's
empty {}. ToolInputError deleted; wire shape unchanged (verified against
pi's own thrown-error conversion in agent-loop).
This commit is contained in:
@@ -64,29 +64,29 @@ const validQuiz = {
|
||||
};
|
||||
|
||||
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
||||
import { ToolInputError } from "../src/agent/tool-execute.ts";
|
||||
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
||||
|
||||
/** The Wave A boundary predicate (2026-09-26): pi's loop structurally
|
||||
* validates against the TypeBox parameters, then execute re-runs the
|
||||
* authoring refinements (the old superRefine) and throws ToolInputError
|
||||
* on problems — the model sees both as retryable input errors. This
|
||||
* helper replicates the old safeParse seam end to end. */
|
||||
* authoring refinements (the old superRefine) and returns an isError
|
||||
* result on problems (pi 1.0 convention — invalidInputResult; the model
|
||||
* sees both as retryable input errors). This helper replicates the old
|
||||
* safeParse seam end to end. */
|
||||
async function boundaryParse(
|
||||
// `input: never` param: contravariance lets any AgentTool's execute slot in.
|
||||
tool: { parameters: any; execute: (id: string, input: never) => Promise<unknown> },
|
||||
input: unknown,
|
||||
): Promise<{ success: boolean; error?: ToolInputError }> {
|
||||
): Promise<{ success: boolean; error?: AgentToolResult<{ tool: string; problems: string[] }> }> {
|
||||
if (!Value.Check(tool.parameters, input)) return { success: false };
|
||||
try {
|
||||
await tool.execute("tc", input as never);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
assert.ok(e instanceof ToolInputError, `expected ToolInputError, got: ${e}`);
|
||||
return { success: false, error: e };
|
||||
}
|
||||
const result = (await tool.execute("tc", input as never)) as AgentToolResult<{
|
||||
tool: string;
|
||||
problems: string[];
|
||||
}>;
|
||||
if (result.isError) return { success: false, error: result };
|
||||
return { success: true };
|
||||
}
|
||||
const problemLines = (e?: ToolInputError): string[] =>
|
||||
(e?.message.split("\n") ?? []).filter((l) => l.startsWith("- "));
|
||||
const problemLines = (r?: AgentToolResult<{ tool: string; problems: string[] }>): string[] =>
|
||||
r?.details.problems ?? [];
|
||||
|
||||
describe("teachQuizToolFor: the dynamic plan-nodeId validation (position signal)", () => {
|
||||
it("accepts a nodeId that matches a node of the session's tree", async () => {
|
||||
|
||||
+12
-18
@@ -15,7 +15,6 @@ import {
|
||||
questionResult,
|
||||
serverExecute,
|
||||
toolTimeoutMs,
|
||||
ToolInputError,
|
||||
toToolResult,
|
||||
type ToolExecuteContext,
|
||||
} from "../src/agent/tool-execute.ts";
|
||||
@@ -121,23 +120,18 @@ describe("serverExecute: cooperative settle, mapping parity, question bypass", (
|
||||
assert.equal(result.terminate, true, "owner Option A: question results end the batch natively");
|
||||
});
|
||||
|
||||
it("authoring failures throw ToolInputError with the retryable problem list", async () => {
|
||||
await assert.rejects(
|
||||
askUserQuestionTool.execute("c7", {
|
||||
question: "q",
|
||||
options: [
|
||||
{ label: "A", value: "same" },
|
||||
{ label: "B", value: "same" },
|
||||
],
|
||||
}),
|
||||
(e: unknown) => {
|
||||
assert.ok(e instanceof ToolInputError);
|
||||
assert.equal(e.toolName, "ask_user_question");
|
||||
assert.match(e.message, /Invalid tool input for tool "ask_user_question":/);
|
||||
assert.match(e.message, /- .*same value/s);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
it("authoring failures return an isError result with the retryable problem list", async () => {
|
||||
const result = (await askUserQuestionTool.execute("c7", {
|
||||
question: "q",
|
||||
options: [
|
||||
{ label: "A", value: "same" },
|
||||
{ label: "B", value: "same" },
|
||||
],
|
||||
})) as { isError?: boolean; content: Array<{ text?: string }>; details?: { tool: string; problems: string[] } };
|
||||
assert.equal(result.isError, true, "pi 1.0 convention: returned, not thrown");
|
||||
assert.equal(result.details?.tool, "ask_user_question");
|
||||
assert.match(result.content[0]!.text!, /Invalid tool input for tool "ask_user_question":/);
|
||||
assert.match(result.details!.problems.map((p) => `- ${p}`).join("\n"), /- .*same value/s);
|
||||
});
|
||||
|
||||
it("toToolResult parity: string verbatim, object as JSON, details rides the raw value", () => {
|
||||
|
||||
Reference in New Issue
Block a user