refactor(engines): thin the Anthropic adapter and DRY shared engine utilities - #296
Conversation
…tilities - 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>
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /tdd and /codebase-design — commenting with two non-blocking suggestions.
📋 Key Themes & Highlights
Key Themes
- Missing unit tests for the newly shared
utils.tsfunctions — the branching logic inobjectToolSchemaandtoolResultTextdeserves coverage now that two adapters depend on a single copy. - Intentional dead-code removal in
contentText— the old non-array guard is correctly dropped given the narrower SDK type, but a comment would prevent future reader confusion.
Positive Highlights
- ✅ Clean extraction of duplicated helpers into a well-named
utils.ts— exactly the right seam. - ✅ Improved return type
Record<string, unknown> & { type: "object" }makes the post-check narrowing visible to callers. - ✅
BetaTextBlockdiscriminant replaces four lines of manual structural guards — simpler and more maintainable. - ✅
systemcastas any→as string | BetaTextBlockParam[]correctly documents the Anthropic API contract.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 28.3 AIC · ⌖ 4.3 AIC · ⊞ 6.3K
Comment /matt to run again
| @@ -0,0 +1,19 @@ | |||
| import type { Tool } from "../rig.ts"; | |||
There was a problem hiding this comment.
[/tdd] Both exported functions have branching logic but no tests were added alongside them — the throw branch in objectToolSchema and the three-way dispatch in toolResultText are prime regression targets.
💡 Suggested tests
// src/rig.test.ts or a new engines/utils.test.ts
import { objectToolSchema, toolResultText } from './engines/utils.ts';
it('objectToolSchema returns parameters when type is object', () => {
const tool = { name: 'x', parameters: { type: 'object', properties: {} } };
expect(objectToolSchema(tool)).toMatchObject({ type: 'object' });
});
it('objectToolSchema throws for non-object schema', () => {
const tool = { name: 'x', parameters: { type: 'array' } };
expect(() => objectToolSchema(tool)).toThrow(TypeError);
});
it('toolResultText: string passthrough', () => expect(toolResultText('hi')).toBe('hi'));
it('toolResultText: undefined → empty string', () => expect(toolResultText(undefined)).toBe(''));
it('toolResultText: object → JSON', () => expect(toolResultText({ a: 1 })).toBe('{"a":1}'));Extracting these into a shared module is the right call; pairing the move with a small test file locks the contract before two adapters depend on it.
| @@ -63,34 +65,9 @@ function toAnthropicTool(tool: Tool<any>) { | |||
| }); | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
[/codebase-design] The removal of the non-array guard in contentText is correct — BetaMessage["content"] is always an array, so the old typeof content === "string" branch was dead code. Consider adding a brief inline comment so future readers don't re-introduce the guard thinking it was accidentally lost.
// BetaMessage["content"] is always an array — no plain-string path
function contentText(content: BetaMessage["content"]): string {
The Anthropic adapter was using
any[]for conversation history andunknown-typed manual type guards for content extraction, obscuring the SDK's own type contract.objectToolSchemaandtoolResultTextwere also duplicated verbatim betweenanthropic.tsandpi.ts.anthropic.tsmessages: any[]→messages: BetaMessageParam[]contentText(content: unknown)with 4-line structural guards →contentText(content: BetaMessage["content"])using the SDK'sBetaTextBlockdiscriminant:system: agentOptions.systemMessage as any→as string | BetaTextBlockParam[]— matches the Anthropic API's actualsystemparam type and satisfiesexactOptionalPropertyTypesengines/utils.ts(new)Extracts
objectToolSchemaandtoolResultTextinto a shared module; bothanthropic.tsandpi.tsimport from here.