From agent-sdk-pro
Use this skill when developing TypeScript applications with the Claude Agent SDK (@anthropic-ai/claude-agent-sdk), implementing query() calls, configuring SDK options, handling streaming message iteration, working with SDKMessage and SDKResultMessage types, managing AbortSignal, passing custom env variables, or setting up the single-point-of-contact types file for SDK imports.
npx claudepluginhub itamarzand88/claude-code-agentic-engineering --plugin agent-sdk-proThis skill uses the workspace's default tool permissions.
Always centralize all SDK imports in one `types.ts` file. This prevents scattered `@anthropic-ai/claude-agent-sdk` imports and provides a single place to add type guards and helpers.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
Always centralize all SDK imports in one types.ts file. This prevents scattered @anthropic-ai/claude-agent-sdk imports and provides a single place to add type guards and helpers.
// types.ts — single point of contact with the SDK package
export type {
HookCallback,
HookInput,
HookJSONOutput,
Options,
PostToolUseHookInput,
PreToolUseHookInput,
SDKMessage,
SDKResultMessage,
} from "@anthropic-ai/claude-agent-sdk";
export { query } from "@anthropic-ai/claude-agent-sdk";
// Type guards
export function isResultMessage(message: SDKMessage): message is SDKResultMessage {
return message.type === "result";
}
Use query() directly — no wrappers. The SDK returns an AsyncIterable<SDKMessage>.
import { query, type SDKMessage } from "./types";
const messageStream: AsyncIterable<SDKMessage> = query({
prompt: userPrompt,
options: {
model: "claude-sonnet-4-6",
systemPrompt: params.systemPrompt,
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
permissionMode: "bypassPermissions",
maxTurns: 50,
maxBudgetUsd: 2,
cwd: params.workingDirectory,
abortController,
sessionId: requestId,
env: { ...process.env, ANTHROPIC_BASE_URL: proxyUrl, ANTHROPIC_AUTH_TOKEN: jwtToken },
hooks: {
PreToolUse: [{ matcher: "Write|Edit", hooks: [fileRestrictionHook] }],
PostToolUse: [{ matcher: "Bash", hooks: [testPruneHook] }],
},
},
});
Iterate the stream and act only on the result message:
for await (const message of messageStream) {
// log every message for visibility
messageLogger.logMessage(message);
if (isResultMessage(message)) {
// message.duration_ms, message.total_cost_usd, message.modelUsage
const code = readFileSync(params.testFilePath, "utf8");
return { code, costUsd: message.total_cost_usd };
}
}
// Fallback: stream ended without result message
return { code: "" };
Always create an AbortController internally. Forward external AbortSignal to it:
const abortController = new AbortController();
if (params.abortSignal) {
params.abortSignal.addEventListener("abort", () => abortController.abort());
}
// Pass to query() options
{ abortController }
Check signal.aborted at the start of every hook callback.
Always check is_error and handle all subtypes:
if (isResultMessage(message)) {
if (message.is_error) {
switch (message.subtype) {
case "error_max_turns": throw new Error("Agent hit turn limit");
case "error_max_budget_usd": throw new Error("Agent exceeded budget");
case "error_during_execution": throw new Error("Agent execution failed");
case "error_max_structured_output_retries": throw new Error("Output validation failed");
}
}
// Check what was blocked
if (message.permission_denials?.length) {
console.warn("Blocked calls:", message.permission_denials.map(d => d.tool_name));
}
}
See references/query-options.md for the full options object documentation including canUseTool, disallowedTools, permissionMode values, and settingSources.
See references/message-types.md for SDKMessage variant details including permission_denials and all error subtypes.
Define all SDK tuning constants in a dedicated *.const.ts file:
export const SDK_MODEL = "claude-sonnet-4-6" as const;
export const SDK_MAX_BUDGET_USD = 2;
export const SDK_MAX_TURNS = 50;
export const SDK_PERMISSION_MODE = "bypassPermissions" as const;
export const SDK_ALLOWED_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] as const;
Prepend run metadata to output files after SDK completes:
function buildMetadataComment(result: SDKResultMessage): string {
return [
"/**",
" * @generated Agent SDK",
` * @duration ${(result.duration_ms / 1000).toFixed(1)}s`,
` * @cost $${result.total_cost_usd.toFixed(4)}`,
" */",
].join("\n") + "\n";
}
Pass custom env to the SDK for proxy URLs and auth tokens:
env: {
...process.env,
ANTHROPIC_BASE_URL: this.globalConfigService.getAnthropicProxyUrl(),
ANTHROPIC_AUTH_TOKEN: await this.authStorage.getJWTToken(),
ANTHROPIC_CUSTOM_HEADERS: `x-request-id: ${requestId}`,
}