feat: upgrade claude-agent-sdk to 0.2.38 and adapt to new SDK types (#113)
* feat: upgrade claude-agent-sdk to 0.2.38 and adapt to new SDK types - Bump @anthropic-ai/claude-agent-sdk from 0.1.x to 0.2.38 (both root and mcp-server) - Bump zod from 3.x to 4.x (SDK peer dependency) - Add allowDangerouslySkipPermissions to query options (required for bypassPermissions) - Suppress new SDK message types (tool_progress, tool_use_summary, auth_status) - Use structured error field on assistant messages instead of text-sniffing - Add stop_reason to result message handling for diagnostics - Add SDKAssistantMessageError type matching SDK's string literal union * chore: remove CLAUDE_CODE_MAX_OUTPUT_TOKENS from all config and docs
This commit is contained in:
@@ -223,6 +223,7 @@ export async function runClaudePrompt(
|
||||
maxTurns: 10_000,
|
||||
cwd: sourceDir,
|
||||
permissionMode: 'bypassPermissions' as const,
|
||||
allowDangerouslySkipPermissions: true,
|
||||
mcpServers,
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import type { AuditLogger } from './audit-logger.js';
|
||||
import type { ProgressManager } from './progress-manager.js';
|
||||
import type {
|
||||
AssistantMessage,
|
||||
SDKAssistantMessageError,
|
||||
ResultMessage,
|
||||
ToolUseMessage,
|
||||
ToolResultMessage,
|
||||
@@ -100,13 +101,86 @@ export function detectApiError(content: string): ApiErrorDetection {
|
||||
return { detected: false };
|
||||
}
|
||||
|
||||
// Maps SDK structured error types to our error handling.
|
||||
function handleStructuredError(
|
||||
errorType: SDKAssistantMessageError,
|
||||
content: string
|
||||
): ApiErrorDetection {
|
||||
switch (errorType) {
|
||||
case 'billing_error':
|
||||
return {
|
||||
detected: true,
|
||||
shouldThrow: new PentestError(
|
||||
`Billing error (structured): ${content.slice(0, 100)}`,
|
||||
'billing',
|
||||
true // Retryable with backoff
|
||||
),
|
||||
};
|
||||
case 'rate_limit':
|
||||
return {
|
||||
detected: true,
|
||||
shouldThrow: new PentestError(
|
||||
`Rate limit hit (structured): ${content.slice(0, 100)}`,
|
||||
'network',
|
||||
true // Retryable with backoff
|
||||
),
|
||||
};
|
||||
case 'authentication_failed':
|
||||
return {
|
||||
detected: true,
|
||||
shouldThrow: new PentestError(
|
||||
`Authentication failed: ${content.slice(0, 100)}`,
|
||||
'config',
|
||||
false // Not retryable - needs API key fix
|
||||
),
|
||||
};
|
||||
case 'server_error':
|
||||
return {
|
||||
detected: true,
|
||||
shouldThrow: new PentestError(
|
||||
`Server error (structured): ${content.slice(0, 100)}`,
|
||||
'network',
|
||||
true // Retryable
|
||||
),
|
||||
};
|
||||
case 'invalid_request':
|
||||
return {
|
||||
detected: true,
|
||||
shouldThrow: new PentestError(
|
||||
`Invalid request: ${content.slice(0, 100)}`,
|
||||
'config',
|
||||
false // Not retryable - needs code fix
|
||||
),
|
||||
};
|
||||
case 'max_output_tokens':
|
||||
return {
|
||||
detected: true,
|
||||
shouldThrow: new PentestError(
|
||||
`Max output tokens reached: ${content.slice(0, 100)}`,
|
||||
'billing',
|
||||
true // Retryable - may succeed with different content
|
||||
),
|
||||
};
|
||||
case 'unknown':
|
||||
default:
|
||||
return { detected: true };
|
||||
}
|
||||
}
|
||||
|
||||
export function handleAssistantMessage(
|
||||
message: AssistantMessage,
|
||||
turnCount: number
|
||||
): AssistantResult {
|
||||
const content = extractMessageContent(message);
|
||||
const cleanedContent = filterJsonToolCalls(content);
|
||||
const errorDetection = detectApiError(content);
|
||||
|
||||
// Prefer structured error field from SDK, fall back to text-sniffing
|
||||
let errorDetection: ApiErrorDetection;
|
||||
if (message.error) {
|
||||
errorDetection = handleStructuredError(message.error, content);
|
||||
} else {
|
||||
errorDetection = detectApiError(content);
|
||||
}
|
||||
|
||||
const result: AssistantResult = {
|
||||
content,
|
||||
@@ -141,6 +215,14 @@ export function handleResultMessage(message: ResultMessage): ResultData {
|
||||
result.subtype = message.subtype;
|
||||
}
|
||||
|
||||
// Capture stop_reason for diagnostics (helps debug early stops, budget exceeded, etc.)
|
||||
if (message.stop_reason !== undefined) {
|
||||
result.stop_reason = message.stop_reason;
|
||||
if (message.stop_reason && message.stop_reason !== 'end_turn') {
|
||||
console.log(chalk.yellow(` Stop reason: ${message.stop_reason}`));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -247,6 +329,9 @@ export async function dispatchMessage(
|
||||
}
|
||||
|
||||
case 'user':
|
||||
case 'tool_progress':
|
||||
case 'tool_use_summary':
|
||||
case 'auth_status':
|
||||
return { type: 'continue' };
|
||||
|
||||
case 'tool_use': {
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface ResultData {
|
||||
cost: number;
|
||||
duration_ms: number;
|
||||
subtype?: string;
|
||||
stop_reason?: string | null;
|
||||
permissionDenials: number;
|
||||
}
|
||||
|
||||
@@ -66,8 +67,18 @@ export interface ContentBlock {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export type SDKAssistantMessageError =
|
||||
| 'authentication_failed'
|
||||
| 'billing_error'
|
||||
| 'rate_limit'
|
||||
| 'invalid_request'
|
||||
| 'server_error'
|
||||
| 'max_output_tokens'
|
||||
| 'unknown';
|
||||
|
||||
export interface AssistantMessage {
|
||||
type: 'assistant';
|
||||
error?: SDKAssistantMessageError;
|
||||
message: {
|
||||
content: ContentBlock[] | string;
|
||||
};
|
||||
@@ -79,6 +90,7 @@ export interface ResultMessage {
|
||||
total_cost_usd?: number;
|
||||
duration_ms?: number;
|
||||
subtype?: string;
|
||||
stop_reason?: string | null;
|
||||
permission_denials?: unknown[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user