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);
}
}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 推理过程
- 控制:可以提前取消长时间运行的操作
- 内存:增量处理大型输出
在入门循环里,最重要的消息类型通常是 system、assistant、tool_use_summary 和 result。SDK
为完整消息流提供了可辨识联合类型,所以按 message.type 判别依然类型安全。
模式 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 既是运行时验证器,也是编译时类型
用 .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[] = [];TypeScript 大多数时候能自己推断类型。以下三种情况要显式注解:函数参数(无法推断)、空数组/对象(有歧义)、复杂返回类型(写出来更清楚)。
模式 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 配置定义一个
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 种模式是后面所有内容的基础。