diff --git a/src/_internal/client.ts b/src/_internal/client.ts index b15a31b..95173d7 100644 --- a/src/_internal/client.ts +++ b/src/_internal/client.ts @@ -1,5 +1,5 @@ import { SubprocessCLITransport } from './transport/subprocess-cli.js'; -import type { ClaudeCodeOptions, Message, CLIOutput, AssistantMessage, CLIAssistantOutput, CLIErrorOutput } from '../types.js'; +import type { ClaudeCodeOptions, Message, CLIOutput, AssistantMessage, CLIAssistantOutput, CLIErrorOutput, CLISystemOutput, CLIResultOutput, SystemMessage, ResultMessage } from '../types.js'; import { detectErrorType, createTypedError } from '../errors.js'; import { loadSafeEnvironmentOptions } from '../environment.js'; import { applyEnvironmentOptions } from './options-merger.js'; @@ -53,33 +53,58 @@ export class InternalClient { session_id: assistantMsg.session_id } as AssistantMessage; } - - case 'system': - // System messages (like init) - skip these - return null; - + + case 'system': { + // System init messages - now expose full capabilities data + const systemMsg = output as CLISystemOutput; + return { + type: 'system', + subtype: systemMsg.subtype, + session_id: systemMsg.session_id, + model: systemMsg.model, + claude_code_version: systemMsg.claude_code_version, + permissionMode: systemMsg.permissionMode, + apiKeySource: systemMsg.apiKeySource, + output_style: systemMsg.output_style, + cwd: systemMsg.cwd, + uuid: systemMsg.uuid, + tools: systemMsg.tools, + mcp_servers: systemMsg.mcp_servers, + slash_commands: systemMsg.slash_commands, + agents: systemMsg.agents, + skills: systemMsg.skills + } as SystemMessage; + } + case 'result': { - // Result message with usage stats - return it - const resultMsg = output as any; // Type assertion for now + // Result message with full metadata - return all fields + const resultMsg = output as CLIResultOutput; return { type: 'result', subtype: resultMsg.subtype, content: resultMsg.content || '', session_id: resultMsg.session_id, + duration_ms: resultMsg.duration_ms, + duration_api_ms: resultMsg.duration_api_ms, + num_turns: resultMsg.num_turns, + is_error: resultMsg.is_error, + result: resultMsg.result, usage: resultMsg.usage, - cost: { - total_cost: resultMsg.cost?.total_cost_usd - } - } as Message; + cost: resultMsg.cost, + modelUsage: resultMsg.modelUsage, + permission_denials: resultMsg.permission_denials, + uuid: resultMsg.uuid, + total_cost_usd: resultMsg.total_cost_usd + } as ResultMessage; } - + case 'error': { const errorOutput = output as CLIErrorOutput; const errorMessage = errorOutput.error?.message || 'Unknown error'; const errorType = detectErrorType(errorMessage); throw createTypedError(errorType, errorMessage, errorOutput.error); } - + default: // Skip unknown message types return null; diff --git a/src/index.ts b/src/index.ts index cf7c310..62db43b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,7 +58,15 @@ export { API_KEY_SAFETY_WARNING } from './environment.js'; // Export new fluent API (backward compatible - original query function still available) export { claude, QueryBuilder } from './fluent.js'; -export { ResponseParser, type ToolExecution, type UsageStats } from './parser.js'; +export { + ResponseParser, + type ToolExecution, + type UsageStats, + type PerformanceMetrics, + type SystemCapabilities, + type WebSearchUsage, + type CacheBreakdown +} from './parser.js'; export { Logger, LogLevel, diff --git a/src/parser.ts b/src/parser.ts index 8363d96..b685d92 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,4 +1,4 @@ -import type { Message, ToolUseBlock, ResultMessage } from './types.js'; +import type { Message, ToolUseBlock, ResultMessage, SystemMessage, ModelUsageInfo } from './types.js'; import type { Logger } from './logger.js'; /** @@ -188,6 +188,122 @@ export class ResponseParser { return null; } + /** + * Get performance metrics (duration, turns, etc) + */ + async getPerformanceMetrics(): Promise { + await this.consume(); + + const resultMsg = this.messages.findLast((msg): msg is ResultMessage => msg.type === 'result'); + if (!resultMsg) return null; + + return { + durationMs: resultMsg.duration_ms, + durationApiMs: resultMsg.duration_api_ms, + numTurns: resultMsg.num_turns + }; + } + + /** + * Get permission denials if any + */ + async getPermissionDenials(): Promise { + await this.consume(); + + const resultMsg = this.messages.findLast((msg): msg is ResultMessage => msg.type === 'result'); + return resultMsg?.permission_denials ?? []; + } + + /** + * Get system capabilities and metadata + */ + async getSystemCapabilities(): Promise { + await this.consume(); + + const systemMsg = this.messages.find((msg): msg is SystemMessage => msg.type === 'system'); + if (!systemMsg) return null; + + return { + model: systemMsg.model, + claudeCodeVersion: systemMsg.claude_code_version, + permissionMode: systemMsg.permissionMode, + apiKeySource: systemMsg.apiKeySource, + outputStyle: systemMsg.output_style, + cwd: systemMsg.cwd, + uuid: systemMsg.uuid, + tools: systemMsg.tools ?? [], + mcpServers: systemMsg.mcp_servers ?? [], + slashCommands: systemMsg.slash_commands ?? [], + agents: systemMsg.agents ?? [], + skills: systemMsg.skills ?? [] + }; + } + + /** + * Get per-model usage breakdown + */ + async getModelUsageBreakdown(): Promise | null> { + await this.consume(); + + const resultMsg = this.messages.findLast((msg): msg is ResultMessage => msg.type === 'result'); + return resultMsg?.modelUsage ?? null; + } + + /** + * Get request UUID for tracking + */ + async getRequestUUID(): Promise { + await this.consume(); + + const resultMsg = this.messages.findLast((msg): msg is ResultMessage => msg.type === 'result'); + if (resultMsg?.uuid) return resultMsg.uuid; + + // Fallback to system message UUID + const systemMsg = this.messages.find((msg): msg is SystemMessage => msg.type === 'system'); + return systemMsg?.uuid ?? null; + } + + /** + * Get web search usage statistics + */ + async getWebSearchUsage(): Promise { + await this.consume(); + + const resultMsg = this.messages.findLast((msg): msg is ResultMessage => msg.type === 'result'); + if (!resultMsg) return null; + + const totalSearches = resultMsg.usage?.server_tool_use?.web_search_requests ?? 0; + const byModel: Record = {}; + + if (resultMsg.modelUsage) { + for (const [model, stats] of Object.entries(resultMsg.modelUsage)) { + if (stats.webSearchRequests) { + byModel[model] = stats.webSearchRequests; + } + } + } + + return { + total: totalSearches, + byModel + }; + } + + /** + * Get cache tier breakdown (ephemeral 5m vs 1h) + */ + async getCacheBreakdown(): Promise { + await this.consume(); + + const resultMsg = this.messages.findLast((msg): msg is ResultMessage => msg.type === 'result'); + if (!resultMsg?.usage?.cache_creation) return null; + + return { + ephemeral5m: resultMsg.usage.cache_creation.ephemeral_5m_input_tokens ?? 0, + ephemeral1h: resultMsg.usage.cache_creation.ephemeral_1h_input_tokens ?? 0 + }; + } + /** * Stream messages with a callback (doesn't consume for other methods) */ @@ -313,4 +429,47 @@ export interface UsageStats { cacheReadTokens: number; totalTokens: number; totalCost: number; +} + +/** + * Performance metrics for a query + */ +export interface PerformanceMetrics { + durationMs?: number; + durationApiMs?: number; + numTurns?: number; +} + +/** + * System capabilities and metadata + */ +export interface SystemCapabilities { + model?: string; + claudeCodeVersion?: string; + permissionMode?: string; + apiKeySource?: string; + outputStyle?: string; + cwd?: string; + uuid?: string; + tools: string[]; + mcpServers: Array<{ name: string; status: string }>; + slashCommands: string[]; + agents: string[]; + skills: string[]; +} + +/** + * Web search usage statistics + */ +export interface WebSearchUsage { + total: number; + byModel: Record; +} + +/** + * Cache tier breakdown (ephemeral 5m vs 1h) + */ +export interface CacheBreakdown { + ephemeral5m: number; + ephemeral1h: number; } \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index d79fcb4..de7d1e7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -42,6 +42,17 @@ export interface ToolResultBlock { export type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock; +// Model usage breakdown information +export interface ModelUsageInfo { + inputTokens: number; + outputTokens: number; + cacheReadInputTokens: number; + cacheCreationInputTokens: number; + webSearchRequests?: number; + costUSD: number; + contextWindow?: number; +} + // Message types export interface UserMessage { type: 'user'; @@ -60,6 +71,19 @@ export interface SystemMessage { subtype?: string; data?: unknown; session_id?: string; + // Full system init data + model?: string; + claude_code_version?: string; + permissionMode?: string; + apiKeySource?: string; + output_style?: string; + cwd?: string; + uuid?: string; + tools?: string[]; + mcp_servers?: Array<{ name: string; status: string }>; + slash_commands?: string[]; + agents?: string[]; + skills?: string[]; } export interface ResultMessage { @@ -67,11 +91,30 @@ export interface ResultMessage { subtype?: string; content: string; session_id?: string; + // Performance metrics + duration_ms?: number; + duration_api_ms?: number; + num_turns?: number; + // Error status + is_error?: boolean; + // Result text (alternative simplified access to content) + result?: string; + // Token and cost usage usage?: { input_tokens?: number; output_tokens?: number; cache_creation_input_tokens?: number; cache_read_input_tokens?: number; + service_tier?: string; + // Server tool usage + server_tool_use?: { + web_search_requests?: number; + }; + // Cache tier breakdown + cache_creation?: { + ephemeral_5m_input_tokens?: number; + ephemeral_1h_input_tokens?: number; + }; }; cost?: { input_cost?: number; @@ -80,6 +123,14 @@ export interface ResultMessage { cache_read_cost?: number; total_cost?: number; }; + // Per-model usage breakdown + modelUsage?: Record; + // Permission denials + permission_denials?: string[]; + // Request tracking + uuid?: string; + // Total cost in USD + total_cost_usd?: number; } export type Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage; @@ -158,6 +209,19 @@ export interface CLISystemOutput { type: 'system'; subtype?: string; session_id?: string; + // System init data + model?: string; + claude_code_version?: string; + permissionMode?: string; + apiKeySource?: string; + output_style?: string; + cwd?: string; + uuid?: string; + tools?: string[]; + mcp_servers?: Array<{ name: string; status: string }>; + slash_commands?: string[]; + agents?: string[]; + skills?: string[]; } export interface CLIResultOutput { @@ -165,15 +229,47 @@ export interface CLIResultOutput { subtype?: string; content?: string; session_id?: string; + // Performance metrics + duration_ms?: number; + duration_api_ms?: number; + num_turns?: number; + // Error status + is_error?: boolean; + // Result text + result?: string; + // Usage information usage?: { input_tokens?: number; output_tokens?: number; cache_creation_input_tokens?: number; cache_read_input_tokens?: number; + service_tier?: string; + // Server tool usage + server_tool_use?: { + web_search_requests?: number; + }; + // Cache tier breakdown + cache_creation?: { + ephemeral_5m_input_tokens?: number; + ephemeral_1h_input_tokens?: number; + }; }; cost?: { + input_cost?: number; + output_cost?: number; + cache_creation_cost?: number; + cache_read_cost?: number; + total_cost?: number; total_cost_usd?: number; }; + // Per-model usage breakdown + modelUsage?: Record; + // Permission denials + permission_denials?: string[]; + // Request UUID + uuid?: string; + // Total cost in USD + total_cost_usd?: number; } export interface CLIErrorOutput {