TypeScript Agent 开发基础
掌握 Agent 开发的核心 TypeScript 模式:异步操作、流式响应、类型安全的 schema 以及支撑 Claude Agent SDK 的接口设计。
为什么 Agent 开发要用 TypeScript?
Agent 开发需要处理异步操作、流式数据和结构化输出。TypeScript 提供了:
- 类型安全 - 在编译时捕获错误,而非运行时
- Async/Await - 简洁的语法处理 LLM 响应和工具调用
- 流式支持 - 原生
for await..of处理 Agent 消息 - Schema 验证 - Zod schema 确保结构化输出符合预期
Claude Agent SDK 使用 TypeScript 构建,并在整个 API 中充分利用这些特性。
模式 1:Async/Await 基础
Agent 操作本质上是异步的——LLM 调用、工具执行和文件 I/O 都需要时间。TypeScript 的 async/await 为异步操作提供了同步风格的语法。
// @lesson-illustrative
// async/await 语法的通用示例——这里的 `apiCall` 与 `processResponse`
// 只是占位符函数,并非 SDK 的 `query()`。(真正的 Agent SDK 形态见下面的 Pattern 2。)
// 没有 async/await(回调地狱)
function oldStyle() {
apiCall(prompt).then((response) => {
processResponse(response).then((result) => {
console.log(result);
});
});
}
// 使用 async/await(清晰易读)
async function modernStyle() {
const response = await apiCall(prompt);
const result = await processResponse(response);
console.log(result);
}核心规则:
- 使用
await的函数必须标记为async await会暂停执行直到 Promise 完成- 错误处理使用标准的
try/catch块
import { query } from "@anthropic-ai/claude-agent-sdk";
async function runAgent() {
try {
const response = query({
prompt: "What is 2+2?",
options: { model: "sonnet", tools: [] },
});
// 处理流式响应
for await (const message of response) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
} catch (error) {
console.error("Agent failed:", error);
}
}SDK 洞察:Query 返回 AsyncIterable
Claude Agent SDK 的 query() 函数返回的是 AsyncIterable,而非
Promise。这使得流式响应成为可能,无需等待整个输出完成。
模式 2:使用 for await..of 处理流式数据
Agent 逐步生成响应。for await..of 循环可以在每条消息到达时进行处理:
import { query, type SDKAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
function getAssistantText(message: SDKAssistantMessage): string {
return message.message.content
.filter((block): block is Extract<typeof block, { type: "text" }> => block.type === "text")
.map((block) => block.text)
.join("\n");
}
const response = query({
prompt: "List files and calculate their total size",
options: { tools: ["Bash", "Read"], allowedTools: ["Read"] },
});
// 实时处理每条消息
for await (const message of response) {
switch (message.type) {
case "system":
console.log(`Session: ${message.session_id}`);
break;
case "assistant":
console.log(`Assistant: ${getAssistantText(message)}`);
break;
case "tool_use_summary":
console.log(`Using tool: ${message.summary}`);
break;
case "result":
if (message.subtype === "success") {
console.log(`Final: ${message.result}`);
}
break;
}
}为什么流式很重要:
- 用户体验 - 展示进度而非空白屏幕
- 调试 - 实时观察 Agent 推理过程
- 控制 - 可以提前取消长时间运行的操作
- 内存 - 增量处理大型输出
SDK 洞察:消息类型
在入门循环里,最重要的消息类型通常是 system、assistant、tool_use_summary 和 result。通过
message.type 做类型判别依然是类型安全的,因为 SDK 为完整消息流暴露了可辨识联合类型。
模式 3:使用 Zod Schema 实现结构化输出
Agent 默认生成非结构化文本。Zod schema 可以约束输出以匹配你的数据结构:
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// 定义预期的输出结构
const TaskSchema = z.object({
title: z.string(),
priority: z.enum(["low", "medium", "high"]),
subtasks: z.array(z.string()),
estimatedHours: z.number().positive(),
});
type Task = z.infer<typeof TaskSchema>;
const response = query({
prompt: "Break down 'Build a chat app' into subtasks",
options: {
outputFormat: {
type: "json_schema",
schema: z.toJSONSchema(TaskSchema) as Record<string, unknown>,
},
},
});
for await (const message of response) {
if (
message.type === "result" &&
message.subtype === "success" &&
message.structured_output !== undefined
) {
const task: Task = TaskSchema.parse(message.structured_output);
console.log(`Task: ${task.title}`);
console.log(`Priority: ${task.priority}`);
console.log(`Subtasks: ${task.subtasks.length}`);
}
}Zod Schema 的优势:
- 验证 - 如果 LLM 输出不匹配 schema,会抛出错误
- 类型推断 -
z.infer<typeof Schema>生成 TypeScript 类型 - 文档化 - Schema 同时作为运行时验证器和编译时类型
动手试试Schema 细化
用 .refine() 强制执行真实审批规则。先明确谁使用结果、错误拒绝的代价,再选择边界:
const BudgetSchema = z
.object({
amount: z.number(),
currency: z.string(),
})
.refine((data) => data.amount > 0 && data.amount < 1000000, {
message: "Budget must be between $0 and $1M",
});测试有效、无效和边界值,并说明错误信息能否帮助用户修正输入。
模式 4:类型注解基础
TypeScript 类型注解通过在编译时捕获类型不匹配来防止 bug:
没有类型时,bug 只能在运行时被发现:
// @lesson-illustrative
// 未加类型——这段能编译,但只要调用方传入数字或 null,
// `content.substring` 就会在运行时炸掉,TypeScript 帮不上忙。
function summarize(content) {
return content.substring(0, 100);
}加上参数类型,错误就会被前移到编译期:
// 加上类型——TypeScript 会拒绝任何传入非字符串的调用方。
function summarize(content: string): string {
return content.substring(0, 100);
}Agent 代码中常见的类型注解:
import { query, type Options, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
// 函数参数和返回类型
async function runAgent(prompt: string, tools: string[]): Promise<string> {
const options: Options = { tools, allowedTools: tools };
const response = query({ prompt, options });
let finalContent: string = "";
for await (const message of response) {
if (message.type === "result" && message.subtype === "success") {
finalContent = message.result;
}
}
return finalContent;
}
// 变量类型推断(TypeScript 推断类型)
const config: Options = {
model: "sonnet",
maxTurns: 10,
};
// 显式类型注解(当推断需要帮助时)
const messages: SDKMessage[] = [];类型推断 vs 显式类型
TypeScript 在大多数情况下会推断类型。在以下情况需要显式注解:1. 函数参数(无法推断)2. 空数组/对象(有歧义)3. 复杂返回类型(提高清晰度)
模式 5:使用接口定义 Agent 数据
接口为 Agent 数据结构定义契约。它们对于工具结果和 Agent 状态特别有用:
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// 工具结果接口
interface FileAnalysis {
path: string;
sizeBytes: number;
lineCount: number;
language: string;
}
// Agent 状态接口
interface AgentSession {
sessionId: string;
turnCount: number;
toolCallsUsed: string[];
memorySlots: Map<string, unknown>;
}
// 在 SDK 中使用接口
async function analyzeFile(filePath: string): Promise<FileAnalysis> {
const response = query({
prompt: `Analyze ${filePath}`,
options: {
tools: ["Read", "Bash"],
allowedTools: ["Read"],
outputFormat: {
type: "json_schema",
schema: z.toJSONSchema(
z.object({
path: z.string(),
sizeBytes: z.number(),
lineCount: z.number(),
language: z.string(),
})
) as Record<string, unknown>,
},
},
});
for await (const message of response) {
if (
message.type === "result" &&
message.subtype === "success" &&
message.structured_output !== undefined
) {
return z
.object({
path: z.string(),
sizeBytes: z.number(),
lineCount: z.number(),
language: z.string(),
})
.parse(message.structured_output);
}
}
throw new Error("No result received");
}接口 vs 类型:
- Interface - 可扩展,更适合对象形状,错误信息更清晰
- Type - 联合类型、工具类型、别名
// Interface(对象首选)
interface AgentConfig {
model: string;
maxTurns: number;
}
// Type(联合类型首选)
type MessageType = SDKMessage["type"];
// 扩展接口
interface AdvancedAgentConfig extends AgentConfig {
temperature: number;
topP: number;
}动手试试为你的第一个 Agent 添加类型
选择一个小型团队工作流,创建带完整类型注解的 Agent:
- 为 Agent 配置定义一个
interface - 编写一个接收类型化参数的
async function - 使用
for await..of处理响应流 - 为结构化输出添加 Zod schema
明确下游用户,以及 TypeScript 或 Zod 必须在运行前阻止的一种畸形输出。
综合运用
这是一个结合全部 5 种模式的完整示例:
import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";
// 模式 5:配置接口
interface CodeReviewConfig {
filePath: string;
focusAreas: string[];
maxIssues: number;
}
// 模式 3:输出的 Zod schema
const IssueSchema = z.object({
severity: z.enum(["low", "medium", "high", "critical"]),
line: z.number(),
description: z.string(),
suggestion: z.string(),
});
const ReviewSchema = z.object({
summary: z.string(),
issues: z.array(IssueSchema),
rating: z.number().min(0).max(10),
});
type CodeReview = z.infer<typeof ReviewSchema>;
// 模式 1 & 4:带类型注解的异步函数
async function reviewCode(config: CodeReviewConfig): Promise<CodeReview> {
const response = query({
prompt: `Review ${config.filePath} focusing on: ${config.focusAreas.join(", ")}`,
options: {
model: "sonnet",
tools: ["Read", "Grep"],
allowedTools: ["Read", "Grep"],
outputFormat: {
type: "json_schema",
schema: z.toJSONSchema(ReviewSchema) as Record<string, unknown>,
},
},
});
// 模式 2:使用 for await..of 处理流式数据
for await (const message of response) {
if (message.type === "tool_use_summary") {
console.log(`Reading: ${message.summary}`);
}
if (
message.type === "result" &&
message.subtype === "success" &&
message.structured_output !== undefined
) {
return ReviewSchema.parse(message.structured_output);
}
}
throw new Error("Review failed");
}
// 使用示例
const review = await reviewCode({
filePath: "src/agent.ts",
focusAreas: ["security", "performance"],
maxIssues: 10,
});
console.log(`Rating: ${review.rating}/10`);
console.log(`Issues found: ${review.issues.length}`);在继续学习 P1 Agent 基础之前,确认你能够:
- 编写使用
await的async函数 - 使用
for await..of处理 Agent 响应 - 定义 Zod schema 并使用
z.infer<typeof Schema> - 为函数参数添加类型注解
- 为结构化数据创建
interface
这些模式出现在每个 Claude Agent SDK 示例中——现在掌握它们将加速你在后续学习路径中的进展。
进阶提示
随着课程的深入,你会接触到更高级的 TypeScript 模式,如品牌类型、用于消息处理的可辨识联合类型,以及严格的空值检查。这些都自然地建立在上述 5 个模式之上。
延伸阅读
有了这些 TypeScript 基础,你已准备好:
- P1 · Agent 基础 - 使用完整类型安全构建你的第一个 Agent 循环
- P2 · 工具使用 - 定义类型化的工具 schema 和结果处理器
- P3 · 提示词 - 使用 Zod schema 进行结构化提示词工程
- P4 · 记忆 - 为会话状态和记忆管理添加类型
Claude Agent SDK 假定你具备 TypeScript 能力——这 5 种模式是后续所有内容的基础。