From 7cfb2a54f8c2702f1537a8176b27ea4a9f5ee02d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:58:07 +0000 Subject: [PATCH] refactor(engines): thin the Anthropic adapter and DRY shared engine utilities - Use BetaMessageParam[], BetaMessage, BetaTextBlock, BetaTextBlockParam from @anthropic-ai/sdk/resources/beta instead of any[]/unknown, removing all unsafe type assertions except the unavoidable betaTool inputSchema cast - Simplify contentText() to a typed filter using the SDK BetaTextBlock discriminant instead of manual unknown type guards - Narrow the systemMessage cast from `as any` to `as string | BetaTextBlockParam[]`, aligning with the Anthropic API's actual system param type - Extract shared objectToolSchema() and toolResultText() helpers into skills/rig/engines/utils.ts; both anthropic.ts and pi.ts now import from there Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- skills/rig/engines/anthropic.ts | 37 +++++++-------------------------- skills/rig/engines/pi.ts | 19 +---------------- skills/rig/engines/utils.ts | 19 +++++++++++++++++ 3 files changed, 27 insertions(+), 48 deletions(-) create mode 100644 skills/rig/engines/utils.ts diff --git a/skills/rig/engines/anthropic.ts b/skills/rig/engines/anthropic.ts index fac6fac..35254c3 100644 --- a/skills/rig/engines/anthropic.ts +++ b/skills/rig/engines/anthropic.ts @@ -1,8 +1,10 @@ import Anthropic from "@anthropic-ai/sdk"; import type { ClientOptions } from "@anthropic-ai/sdk"; import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema"; +import type { BetaMessage, BetaMessageParam, BetaTextBlock, BetaTextBlockParam } from "@anthropic-ai/sdk/resources/beta"; import { debug } from "../rig.ts"; import type { AgentFactory, Tool } from "../rig.ts"; +import { objectToolSchema, toolResultText } from "./utils.ts"; const debugCreate = debug("engine:anthropic:create"); const debugAsk = debug("engine:anthropic:ask"); @@ -20,20 +22,20 @@ export function anthropicEngine(options: AnthropicEngineOptions = {}): AgentFact return (agentOptions) => { debugCreate({ model: agentOptions.model, tools: agentOptions.tools?.map((tool) => tool.name) ?? [] }); const client = new Anthropic(clientOptions); - let messages: any[] = []; + let messages: BetaMessageParam[] = []; const tools = agentOptions.tools?.map(toAnthropicTool) ?? []; return { async ask(prompt, askOptions = {}) { debugAsk({ model: agentOptions.model, prompt }); - const requestMessages = [...messages, { role: "user" as const, content: prompt }]; + const requestMessages: BetaMessageParam[] = [...messages, { role: "user" as const, content: prompt }]; const runner = client.beta.messages.toolRunner({ model: agentOptions.model, max_tokens: maxTokens, messages: requestMessages, tools, ...(maxIterations !== undefined && { max_iterations: maxIterations }), - ...(agentOptions.systemMessage !== undefined && { system: agentOptions.systemMessage as any }), + ...(agentOptions.systemMessage !== undefined && { system: agentOptions.systemMessage as string | BetaTextBlockParam[] }), }, askOptions.signal ? { signal: askOptions.signal } : undefined); const response = await runner.runUntilDone(); messages = [...runner.params.messages]; @@ -63,34 +65,9 @@ function toAnthropicTool(tool: Tool) { }); } -function objectToolSchema(tool: Tool): Record { - const parameters = tool.parameters ?? { type: "object", properties: {} }; - if (parameters["type"] !== "object") { - throw new TypeError(`${tool.name} tool parameters must be an object schema`); - } - return parameters; -} - -function toolResultText(result: unknown): string { - if (typeof result === "string") { - return result; - } - if (result === undefined) { - return ""; - } - return JSON.stringify(result); -} - -function contentText(content: unknown): string { - if (!Array.isArray(content)) { - return typeof content === "string" ? content : ""; - } +function contentText(content: BetaMessage["content"]): string { return content - .filter((block): block is { type: "text"; text: string } => - block !== null - && typeof block === "object" - && (block as { type?: unknown }).type === "text" - && typeof (block as { text?: unknown }).text === "string") + .filter((block): block is BetaTextBlock => block.type === "text") .map((block) => block.text) .join(""); } diff --git a/skills/rig/engines/pi.ts b/skills/rig/engines/pi.ts index 20e47bf..de863df 100644 --- a/skills/rig/engines/pi.ts +++ b/skills/rig/engines/pi.ts @@ -5,6 +5,7 @@ import type { Models } from "@earendil-works/pi-ai"; import { builtinModels } from "@earendil-works/pi-ai/providers/all"; import { debug } from "../rig.ts"; import type { AgentFactory, Tool } from "../rig.ts"; +import { objectToolSchema, toolResultText } from "./utils.ts"; const debugCreate = debug("engine:pi:create"); const debugAsk = debug("engine:pi:ask"); @@ -92,24 +93,6 @@ function toPiTool(tool: Tool): PiAgentTool { }; } -function objectToolSchema(tool: Tool): Record { - const parameters = tool.parameters ?? { type: "object", properties: {} }; - if (parameters["type"] !== "object") { - throw new TypeError(`${tool.name} tool parameters must be an object schema`); - } - return parameters; -} - -function toolResultText(result: unknown): string { - if (typeof result === "string") { - return result; - } - if (result === undefined) { - return ""; - } - return JSON.stringify(result); -} - function contentText(content: unknown): string { if (!Array.isArray(content)) { return typeof content === "string" ? content : ""; diff --git a/skills/rig/engines/utils.ts b/skills/rig/engines/utils.ts new file mode 100644 index 0000000..510acb5 --- /dev/null +++ b/skills/rig/engines/utils.ts @@ -0,0 +1,19 @@ +import type { Tool } from "../rig.ts"; + +export function objectToolSchema(tool: Tool): Record & { type: "object" } { + const parameters = tool.parameters ?? { type: "object", properties: {} }; + if (parameters["type"] !== "object") { + throw new TypeError(`${tool.name} tool parameters must be an object schema`); + } + return parameters as Record & { type: "object" }; +} + +export function toolResultText(result: unknown): string { + if (typeof result === "string") { + return result; + } + if (result === undefined) { + return ""; + } + return JSON.stringify(result); +}