Skip to content

feat(core): add claude provider using @anthropic-ai/claude-agent-sdk #213

@christso

Description

@christso

Summary

Replace the current claude-code provider (subprocess scraper) with a new claude provider using the official @anthropic-ai/claude-agent-sdk. This follows the same pattern as the copilot provider (PR #211) which replaced the subprocess-based copilot-cli with the typed SDK.

Motivation

The current claude-code provider spawns the Claude CLI as a subprocess and scrapes stdout. The Agent SDK provides:

  • Structured messages via AsyncGenerator<SDKMessage> - typed SDKAssistantMessage, SDKResultMessage
  • Token usage & cost from SDKResultMessage.usage, modelUsage, total_cost_usd
  • Duration from SDKResultMessage.duration_ms
  • Tool call tracking via assistant message content blocks or PostToolUse hooks
  • Permission control via permissionMode: 'bypassPermissions' (no interactive prompts)
  • Model selection via options.model
  • Budget control via options.maxBudgetUsd and options.maxTurns
  • Abort support via options.abortController

SDK API Overview

import { query } from '@anthropic-ai/claude-agent-sdk';

const q = query({
  prompt: 'What is 2+2?',
  options: {
    model: 'claude-sonnet-4-5-20250929',
    permissionMode: 'bypassPermissions',
    allowDangerouslySkipPermissions: true,
    cwd: '/path/to/workspace',
    systemPrompt: 'custom system prompt',
    maxTurns: 50,
    maxBudgetUsd: 1.0,
    abortController: new AbortController(),
  }
});

for await (const message of q) {
  if (message.type === 'assistant') {
    // message.message contains APIAssistantMessage with content blocks
  }
  if (message.type === 'result') {
    // message.result - final text
    // message.usage - { input_tokens, output_tokens, cache_* }
    // message.modelUsage - per-model breakdown with costUSD
    // message.total_cost_usd - total cost
    // message.duration_ms - total duration
    // message.num_turns - number of turns
  }
}

Implementation Plan

Follow the same pattern as the copilot provider (see PR #211):

Files to modify

  1. types.ts - Rename claude-codeclaude in ProviderKind, add claude-code as alias
  2. targets.ts - Update resolved config and target resolution
  3. index.ts - Wire new provider, remove old
  4. targets-validator.ts - Update settings validation

Files to create

  1. claude.ts - New provider using @anthropic-ai/claude-agent-sdk
    • Lazy-load SDK (same pattern as copilot provider)
    • Use query() with permissionMode: 'bypassPermissions'
    • Map SDKAssistantMessage content blocks → ToolCall[] and OutputMessage[]
    • Extract usage, total_cost_usd, duration_ms from SDKResultMessage
  2. claude-log-tracker.ts - Log tracker (rename from claude-code variant)
  3. claude.test.ts - Unit tests with mocked SDK

Files to delete

  1. claude-code.ts - Old subprocess provider
  2. claude-code-log-tracker.ts - Old log tracker
  3. claude-code.test.ts - Old tests (if exists)

Dependencies

  • Add @anthropic-ai/claude-agent-sdk to packages/core/package.json and apps/cli/package.json
  • Add to CLI's tsup.config.ts external list

Key Design Decisions

  • Naming: claude (canonical), claude-code (alias for backward compat)
  • Permissions: Use bypassPermissions + allowDangerouslySkipPermissions for unattended eval
  • Tool calls: Extract from SDKAssistantMessage.message.content blocks (type tool_use / tool_result)
  • Streaming: Iterate the async generator, collect all messages, extract result at end

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions