第 47 章

Claude Code SDK 模式:--print / JSON output / 程序化调用与自动化集成

第四十七章:Claude Code SDK:以编程方式驱动 AI 编程 Agent

47.1 SDK 的定位:嵌入你的应用程序

Claude Code 本身是一个命令行工具,但 Anthropic 提供了 @anthropic-ai/claude-code npm 包,让你可以在自己的 Node.js 或 TypeScript 应用程序中以编程方式调用 Claude Code 的全部功能。

这解锁了一类全新的使用场景:将 AI 编程 Agent 嵌入到现有工具、平台和自动化系统中

使用 SDK 你可以:

与直接调用 Anthropic Messages API 相比,Claude Code SDK 的核心优势是:它内置了完整的工具调用能力(读文件、写文件、运行命令)和 Agent 编排逻辑,你不需要从零开始构建这些基础设施。

47.2 安装与基础配置

# 安装 SDK
npm install @anthropic-ai/claude-code

# 或使用 pnpm
pnpm add @anthropic-ai/claude-code

SDK 需要 Node.js 18+,并且需要系统中安装有 Claude Code CLI(SDK 在底层调用 CLI):

# 确保 Claude Code CLI 已安装
npm install -g @anthropic-ai/claude-code

# 验证安装
claude --version

设置 API 密钥:

# 方式一:环境变量(推荐)
export ANTHROPIC_API_KEY="your-api-key-here"

# 方式二:在代码中设置(不推荐在生产环境使用)
process.env.ANTHROPIC_API_KEY = "your-api-key-here";

47.3 ClaudeCode 类:核心 API

SDK 的核心是 ClaudeCode 类,它提供了与 Claude Code 交互的主要接口:

import { ClaudeCode } from '@anthropic-ai/claude-code';

// 创建实例
const claude = new ClaudeCode({
  // 可选配置
  apiKey: process.env.ANTHROPIC_API_KEY,  // 默认从环境变量读取
  model: 'claude-opus-4-5',              // 使用的模型
  maxTurns: 10,                            // 最大对话轮次
  cwd: '/path/to/project',               // 工作目录
});

基础对话:query 方法

最基础的使用方式是 query 方法,它发送一个提示词并返回完整响应:

import { ClaudeCode } from '@anthropic-ai/claude-code';

async function analyzeCode() {
  const claude = new ClaudeCode({
    cwd: '/path/to/my-project',
  });

  const result = await claude.query(
    '请分析 src/utils/date.ts 文件,找出潜在的 bug 和改进点'
  );

  console.log(result.response);
  console.log(`使用了 ${result.usage.inputTokens} 个输入 token`);
  console.log(`使用了 ${result.usage.outputTokens} 个输出 token`);
}

analyzeCode().catch(console.error);

流式响应:stream 方法

对于长时间运行的任务,使用 stream 方法获取实时流式输出:

import { ClaudeCode } from '@anthropic-ai/claude-code';

async function streamedQuery() {
  const claude = new ClaudeCode({
    cwd: '/path/to/project',
  });

  const stream = await claude.stream(
    '重构 src/api/ 目录下的所有文件,统一错误处理方式'
  );

  // 监听不同类型的事件
  for await (const event of stream) {
    switch (event.type) {
      case 'text':
        // Claude 的文本输出
        process.stdout.write(event.content);
        break;

      case 'tool_use':
        // Claude 正在使用工具(读/写文件等)
        console.log(`\n[工具调用] ${event.tool}: ${event.input}`);
        break;

      case 'tool_result':
        // 工具执行结果
        console.log(`[工具结果] ${event.result.substring(0, 100)}...`);
        break;

      case 'done':
        // 任务完成
        console.log('\n\n任务完成');
        console.log(`总共使用 ${event.usage.totalTokens} tokens`);
        break;
    }
  }
}

47.4 完整示例:批量代码迁移工具

以下是一个完整的实际应用示例:批量将 JavaScript 文件迁移到 TypeScript。

// scripts/migrate-to-typescript.ts

import { ClaudeCode } from '@anthropic-ai/claude-code';
import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';

interface MigrationResult {
  file: string;
  success: boolean;
  tokensUsed: number;
  error?: string;
}

async function migrateFile(
  claude: ClaudeCode,
  filePath: string
): Promise<MigrationResult> {
  console.log(`\n迁移文件: ${filePath}`);

  try {
    const result = await claude.query(`
      将以下 JavaScript 文件迁移到 TypeScript:
      文件路径:${filePath}
      
      要求:
      1. 读取文件内容
      2. 添加 TypeScript 类型注解(推断 + 显式声明)
      3. 将文件重命名为 .ts(保留 .js 作为备份)
      4. 确保没有 TypeScript 编译错误
      5. 不要修改任何逻辑
    `);

    return {
      file: filePath,
      success: true,
      tokensUsed: result.usage.totalTokens,
    };
  } catch (error) {
    return {
      file: filePath,
      success: false,
      tokensUsed: 0,
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

async function batchMigrate(projectDir: string, targetFiles: string[]) {
  console.log(`\n开始批量迁移 ${targetFiles.length} 个文件...`);

  const claude = new ClaudeCode({
    cwd: projectDir,
    maxTurns: 15,  // 迁移单个文件可能需要多轮
  });

  const results: MigrationResult[] = [];
  let totalTokens = 0;

  for (let i = 0; i < targetFiles.length; i++) {
    const file = targetFiles[i];
    console.log(`\n进度: ${i + 1}/${targetFiles.length}`);

    const result = await migrateFile(claude, file);
    results.push(result);
    totalTokens += result.tokensUsed;

    if (result.success) {
      console.log(`✓ 成功迁移: ${file}`);
    } else {
      console.log(`✗ 迁移失败: ${file}`);
      console.log(`  原因: ${result.error}`);
    }

    // 避免速率限制:每个文件后等待 1 秒
    if (i < targetFiles.length - 1) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }

  // 生成迁移报告
  const successful = results.filter(r => r.success).length;
  const failed = results.filter(r => !r.success).length;

  console.log('\n========== 迁移报告 ==========');
  console.log(`成功: ${successful}/${targetFiles.length}`);
  console.log(`失败: ${failed}/${targetFiles.length}`);
  console.log(`总计 token 使用: ${totalTokens.toLocaleString()}`);
  console.log(`估计费用: $${(totalTokens * 0.000003).toFixed(4)}`);

  if (failed > 0) {
    console.log('\n失败的文件:');
    results
      .filter(r => !r.success)
      .forEach(r => console.log(`  - ${r.file}: ${r.error}`));
  }

  // 将报告写入文件
  const report = {
    timestamp: new Date().toISOString(),
    projectDir,
    results,
    summary: { successful, failed, totalTokens },
  };
  fs.writeFileSync('migration-report.json', JSON.stringify(report, null, 2));
  console.log('\n详细报告已写入 migration-report.json');
}

// 主函数
async function main() {
  const projectDir = process.argv[2] || process.cwd();

  // 查找所有 .js 文件(排除 node_modules 和测试文件)
  const jsFiles = fs
    .readdirSync(path.join(projectDir, 'src'), { recursive: true })
    .filter((f): f is string => typeof f === 'string')
    .filter(f => f.endsWith('.js') && !f.includes('.test.'))
    .map(f => `src/${f}`);

  if (jsFiles.length === 0) {
    console.log('没有找到需要迁移的 .js 文件');
    return;
  }

  console.log(`找到 ${jsFiles.length} 个文件:`);
  jsFiles.forEach(f => console.log(`  - ${f}`));

  // 预估费用
  const estimatedTokens = jsFiles.length * 2000;  // 粗略估计
  const estimatedCost = (estimatedTokens * 0.000003).toFixed(4);
  console.log(`\n预估 token 消耗: ~${estimatedTokens.toLocaleString()}`);
  console.log(`预估费用: ~$${estimatedCost}`);

  // 确认执行
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const answer = await new Promise<string>(resolve =>
    rl.question('\n是否继续?(y/n) ', resolve)
  );
  rl.close();

  if (answer.toLowerCase() !== 'y') {
    console.log('已取消');
    return;
  }

  await batchMigrate(projectDir, jsFiles);
}

main().catch(console.error);

47.5 高级功能:会话管理与上下文复用

SDK 支持多轮对话,可以维护上下文状态:

import { ClaudeCode } from '@anthropic-ai/claude-code';

async function multiTurnSession() {
  const claude = new ClaudeCode({
    cwd: '/path/to/project',
  });

  // 第一轮:分析代码库
  console.log('第一轮:分析代码库...');
  const analysis = await claude.query(
    '分析 src/ 目录的整体架构,识别主要模块和它们之间的依赖关系'
  );
  console.log('架构分析完成');

  // 第二轮:基于分析提出改进方案(继承上一轮的上下文)
  console.log('\n第二轮:提出改进方案...');
  const improvements = await claude.query(
    '基于刚才的分析,提出 3 个最重要的架构改进建议,并估计每个改进的工作量'
  );
  console.log(improvements.response);

  // 第三轮:实施第一个改进
  console.log('\n第三轮:实施改进...');
  const implementation = await claude.query(
    '开始实施第一个改进建议,先写测试,再实现'
  );
}

管理会话 ID

如果需要跨进程恢复会话,可以保存和恢复会话 ID:

import { ClaudeCode } from '@anthropic-ai/claude-code';
import * as fs from 'fs';

const SESSION_FILE = '.claude-session-id';

async function continueSession() {
  const claude = new ClaudeCode({ cwd: process.cwd() });

  // 尝试恢复上次的会话
  let sessionId: string | undefined;
  if (fs.existsSync(SESSION_FILE)) {
    sessionId = fs.readFileSync(SESSION_FILE, 'utf8').trim();
    console.log(`恢复会话: ${sessionId}`);
  }

  const result = await claude.query(
    '继续上次未完成的重构任务',
    { sessionId }  // 传入 session ID 恢复上下文
  );

  // 保存新的会话 ID
  if (result.sessionId) {
    fs.writeFileSync(SESSION_FILE, result.sessionId);
  }

  console.log(result.response);
}

47.6 工具权限控制

在生产应用中,你可能需要限制 Claude 可以使用的工具:

import { ClaudeCode } from '@anthropic-ai/claude-code';

// 只读模式:只允许读取文件,不允许修改
const readOnlyClaude = new ClaudeCode({
  cwd: '/path/to/project',
  allowedTools: ['Read', 'Grep', 'Glob'],  // 只允许这些工具
});

// 分析模式:允许读取和运行只读命令
const analysisClaude = new ClaudeCode({
  cwd: '/path/to/project',
  allowedTools: ['Read', 'Grep', 'Glob', 'Bash'],
  // 还可以设置 Bash 工具的允许命令列表
  bashAllowedCommands: ['npm test', 'tsc --noEmit', 'eslint'],
});

// 完整权限(默认)
const fullClaude = new ClaudeCode({
  cwd: '/path/to/project',
  // 不设置 allowedTools,则默认使用所有工具
});

47.7 错误处理与重试策略

import { ClaudeCode, ClaudeCodeError, RateLimitError } from '@anthropic-ai/claude-code';

async function robustQuery(
  claude: ClaudeCode,
  prompt: string,
  maxRetries = 3
): Promise<string> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await claude.query(prompt);
      return result.response;
    } catch (error) {
      if (error instanceof RateLimitError) {
        // 速率限制:等待后重试
        const waitTime = Math.pow(2, attempt) * 1000;  // 指数退避
        console.log(`速率限制,等待 ${waitTime/1000}s 后重试 (${attempt}/${maxRetries})`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }

      if (error instanceof ClaudeCodeError) {
        // Claude Code 特定错误
        console.error(`Claude Code 错误: ${error.message}`);
        if (error.code === 'CONTEXT_TOO_LONG') {
          // 上下文太长,需要压缩
          console.log('上下文过长,尝试压缩...');
          // 可以在这里调用 /compact 或减少输入
        }
        throw error;  // 非速率限制错误,直接抛出
      }

      throw error;  // 未知错误,直接抛出
    }
  }

  throw new Error(`经过 ${maxRetries} 次重试后仍然失败`);
}

47.8 构建 Web 服务:Claude Code API Server

你可以将 Claude Code SDK 包装成一个 HTTP API,为 Web 前端提供 AI 编程功能:

// server.ts
import express from 'express';
import { ClaudeCode } from '@anthropic-ai/claude-code';
import * as path from 'path';

const app = express();
app.use(express.json());

// POST /analyze — 分析代码
app.post('/analyze', async (req, res) => {
  const { projectPath, prompt } = req.body;

  if (!projectPath || !prompt) {
    return res.status(400).json({ error: 'projectPath and prompt are required' });
  }

  // 安全检查:确保路径在允许的范围内
  const allowedBasePath = '/workspace/projects';
  const resolvedPath = path.resolve(projectPath);
  if (!resolvedPath.startsWith(allowedBasePath)) {
    return res.status(403).json({ error: 'Access denied' });
  }

  try {
    const claude = new ClaudeCode({
      cwd: resolvedPath,
      allowedTools: ['Read', 'Grep', 'Glob'],  // 只读模式
    });

    const result = await claude.query(prompt);

    res.json({
      response: result.response,
      usage: result.usage,
    });
  } catch (error) {
    console.error('Analysis error:', error);
    res.status(500).json({
      error: error instanceof Error ? error.message : 'Unknown error',
    });
  }
});

// POST /stream — 流式分析
app.post('/stream', async (req, res) => {
  const { projectPath, prompt } = req.body;

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  try {
    const claude = new ClaudeCode({ cwd: projectPath });
    const stream = await claude.stream(prompt);

    for await (const event of stream) {
      // 发送 Server-Sent Events
      res.write(`data: ${JSON.stringify(event)}\n\n`);
    }

    res.write('data: [DONE]\n\n');
    res.end();
  } catch (error) {
    res.write(`data: ${JSON.stringify({ type: 'error', message: String(error) })}\n\n`);
    res.end();
  }
});

app.listen(3000, () => {
  console.log('Claude Code API Server running on port 3000');
});

47.9 SDK 与直接 API 调用的选择

场景 推荐方案
需要文件系统操作(读写代码) ClaudeCode SDK
只需要文本生成(聊天、摘要) Anthropic Messages API
需要运行 Shell 命令 ClaudeCode SDK
需要精细控制工具调用 手动实现 Tool Use API
需要多 Agent 协作 ClaudeCode SDK(利用 Agent 工具)
简单的单次文本问答 Anthropic Messages API(更轻量)

小结

Claude Code SDK 将 AI 编程 Agent 的能力从命令行工具扩展到了可编程接口。

关键要点:

本章评分
4.8  / 5  (3 评分)

💬 留言讨论