Skip to content

refactor(engines): thin the Anthropic adapter and DRY shared engine utilities - #296

Merged
pelikhan merged 1 commit into
mainfrom
copilot/review-claude-adapter-layer
Jul 30, 2026
Merged

refactor(engines): thin the Anthropic adapter and DRY shared engine utilities#296
pelikhan merged 1 commit into
mainfrom
copilot/review-claude-adapter-layer

Conversation

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

The Anthropic adapter was using any[] for conversation history and unknown-typed manual type guards for content extraction, obscuring the SDK's own type contract. objectToolSchema and toolResultText were also duplicated verbatim between anthropic.ts and pi.ts.

anthropic.ts

  • messages: any[]messages: BetaMessageParam[]
  • contentText(content: unknown) with 4-line structural guards → contentText(content: BetaMessage["content"]) using the SDK's BetaTextBlock discriminant:
// before
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")

// after
content.filter((block): block is BetaTextBlock => block.type === "text")
  • system: agentOptions.systemMessage as anyas string | BetaTextBlockParam[] — matches the Anthropic API's actual system param type and satisfies exactOptionalPropertyTypes

engines/utils.ts (new)

Extracts objectToolSchema and toolResultText into a shared module; both anthropic.ts and pi.ts import from here.

…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>
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts functions — the branching logic in objectToolSchema and toolResultText deserves 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.
  • BetaTextBlock discriminant replaces four lines of manual structural guards — simpler and more maintainable.
  • system cast as anyas 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";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/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>) {
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/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 {

@pelikhan
pelikhan merged commit eb773e9 into main Jul 30, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants