326 lines · 9.8 KB · TypeScript
RawDownload
/** * JSONL format types - raw data structures from Claude Code session files. * * These types represent the exact format stored in .jsonl files at: * ~/.claude/projects/{project_name}/{session_uuid}.jsonl * * Content type guards and entry type guards are included here as they * operate directly on the raw JSONL structures. */ // =============================================================================// Core Type Aliases// ============================================================================= type EntryType =  | 'user'  | 'assistant'  | 'system'  | 'summary'  | 'file-history-snapshot'  | 'queue-operation'; type ContentType = 'text' | 'thinking' | 'tool_use' | 'tool_result' | 'image'; type StopReason = 'end_turn' | 'tool_use' | 'max_tokens' | 'stop_sequence' | null; // =============================================================================// Content Blocks// ============================================================================= interface BaseContent {  type: ContentType;} export interface TextContent extends BaseContent {  type: 'text';  text: string;} export interface ThinkingContent extends BaseContent {  type: 'thinking';  thinking: string;  signature: string;} export interface ToolUseContent extends BaseContent {  type: 'tool_use';  id: string;  name: string;  input: Record<string, unknown>;} export interface ToolResultContent extends BaseContent {  type: 'tool_result';  tool_use_id: string;  content: string | ContentBlock[];  is_error?: boolean;} export interface ImageContent extends BaseContent {  type: 'image';  source: {    type: 'base64';    media_type: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp';    data: string;  };} export type ContentBlock =  | TextContent  | ThinkingContent  | ToolUseContent  | ToolResultContent  | ImageContent; // =============================================================================// Usage Metadata// ============================================================================= export interface UsageMetadata {  input_tokens: number;  output_tokens: number;  cache_read_input_tokens?: number;  cache_creation_input_tokens?: number;} // =============================================================================// Messages// ============================================================================= interface UserMessage {  role: 'user';  content: string | ContentBlock[];} interface AssistantMessage {  role: 'assistant';  model: string;  id: string;  type: 'message';  content: ContentBlock[];  stop_reason: StopReason;  stop_sequence: string | null;  usage: UsageMetadata;} // =============================================================================// JSONL Entries// ============================================================================= interface BaseEntry {  type: EntryType;  timestamp?: string;  uuid?: string;} /** * Base for conversational entries (user, assistant, system). * * Sidechain behavior: * - isSidechain: false -> Main agent message * - isSidechain: true -> Subagent message * - sessionId: For subagents, points to parent session UUID */interface ConversationalEntry extends BaseEntry {  parentUuid: string | null;  isSidechain: boolean;  userType: 'external';  cwd: string;  sessionId: string;  version: string;  gitBranch: string;  slug?: string;} /** * Tool use result data - preserves full structure from JSONL entries. * * The structure varies significantly by tool type: * - File tools: { type, success, filePath, content, structuredPatch, ... } * - Task tools: { status, prompt, agentId, content, totalDurationMs, totalTokens, usage, ... } * - AskUserQuestion: { questions, answers } * - Bash: { stdout, stderr, exitCode, ... } * * Using Record<string, unknown> to preserve all data without loss. */export type ToolUseResultData = Record<string, unknown>; /** * CRITICAL: User entries serve two purposes: * * 1. Real User Input (chunk starters): *    - isMeta: false or undefined *    - content: string *    - These START new chunks * * 2. Response Messages (part of response flow): *    a) Internal (tool results): *       - isMeta: true *       - content: array with tool_result blocks *    b) Interruptions: *       - isMeta: false *       - content: array (not string) */export interface UserEntry extends ConversationalEntry {  type: 'user';  message: UserMessage;  isMeta?: boolean;  agentId?: string;   toolUseResult?: ToolUseResultData;  sourceToolUseID?: string;  sourceToolAssistantUUID?: string;} export interface AssistantEntry extends ConversationalEntry {  type: 'assistant';  message: AssistantMessage;  requestId: string;  agentId?: string;} export interface SystemEntry extends ConversationalEntry {  type: 'system';  subtype: 'turn_duration' | 'init';  durationMs: number;  isMeta: boolean;} export interface SummaryEntry extends BaseEntry {  type: 'summary';  summary: string;  leafUuid: string;} export interface FileHistorySnapshotEntry extends BaseEntry {  type: 'file-history-snapshot';  messageId: string;  snapshot: {    messageId: string;    trackedFileBackups: Record<string, string>;    timestamp: string;  };  isSnapshotUpdate: boolean;} export interface QueueOperationEntry extends BaseEntry {  type: 'queue-operation';  operation: string;} export type ChatHistoryEntry =  | UserEntry  | AssistantEntry  | SystemEntry  | SummaryEntry  | FileHistorySnapshotEntry  | QueueOperationEntry; /** * Conversational entries - entries that represent chat messages. * These share common properties like message, cwd, gitBranch, etc. */export type ConversationalChatEntry = UserEntry | AssistantEntry | SystemEntry; // =============================================================================// Content Type Guards// ============================================================================= export function isTextContent(content: ContentBlock): content is TextContent {  return content.type === 'text';} export function isToolResultContent(content: ContentBlock): content is ToolResultContent {  return content.type === 'tool_result';} /** * Type guard to check if an entry is a conversational entry. */export function isConversationalEntry(entry: ChatHistoryEntry): entry is ConversationalChatEntry {  return entry.type === 'user' || entry.type === 'assistant' || entry.type === 'system';} // =============================================================================// Subagent Directory Structures// ============================================================================= /** * Claude Code supports two subagent directory structures: * * NEW STRUCTURE (Current): * ~/.claude/projects/ *   {project_name}/ *     {session_uuid}.jsonl              <- Main agent *     {session_uuid}/ *       agent_{agent_uuid}.jsonl         <- Subagents * * OLD STRUCTURE (Legacy, still supported): * ~/.claude/projects/ *   {project_name}/ *     {session_uuid}.jsonl              <- Main agent *     agent_{agent_uuid}.jsonl           <- Subagents (at root) * * Identification: * - Main agent: isSidechain: false (or undefined) * - Subagent: isSidechain: true * - Linking: subagent.sessionId === parent session UUID * * When scanning for subagents: * 1. First check {session_uuid}/ subdirectory (new structure) * 2. Fall back to project root for agent_*.jsonl (old structure) * 3. Match by sessionId field to link to parent */ // =============================================================================// Message Flow Pattern// ============================================================================= /** * Typical conversation flow: * * 1. User types -> type: "user", isMeta: false, content: string -> TRIGGER MESSAGE (STARTS CHUNK) * 2. Assistant responds -> type: "assistant", may contain tool_use -> FLOW MESSAGE (PART OF RESPONSE) * 3. Tool executes -> type: "user", isMeta: true, contains tool_result -> FLOW MESSAGE (PART OF RESPONSE) * 4. User interrupts -> type: "user", isMeta: false, content: array -> FLOW MESSAGE (PART OF RESPONSE) * 5. Assistant continues -> type: "assistant" -> FLOW MESSAGE (PART OF RESPONSE) * * Message Categories (New 4-Category System): * * 1. USER MESSAGES (create UserChunks): *    - Genuine user input that initiates a new request/response cycle *    - Detected by: isParsedUserChunkMessage() type guard *    - Requirements: type='user', isMeta!=true, has text/image content *    - Excludes: <local-command-stdout>, <local-command-caveat>, <system-reminder> *    - Allows: <command-name> (slash commands like /model are visible user input) * * 2. SYSTEM MESSAGES (create SystemChunks): *    - Command output from slash commands *    - Detected by: isParsedSystemChunkMessage() type guard *    - Contains <local-command-stdout> tag *    - Renders on LEFT side like AI responses * * 3. HARD NOISE MESSAGES (filtered out): *    - System-generated metadata that should NEVER be displayed *    - Detected by: isParsedHardNoiseMessage() type guard *    - Includes: system/summary/file-history-snapshot/queue-operation entries *    - Includes: User messages with ONLY <local-command-caveat> or <system-reminder> * * 4. AI MESSAGES (create AIChunks): *    - All assistant messages and flow messages between User/System/HardNoise *    - Includes: assistant messages, tool results, interruptions *    - Consecutive AI messages are grouped into single AIChunk *    - AIChunks are INDEPENDENT - no longer paired with UserChunks * * Key Rules: * - User messages START UserChunks (render on RIGHT) * - System messages START SystemChunks (render on LEFT) * - AI messages are GROUPED into independent AIChunks (render on LEFT) * - Hard noise messages are FILTERED OUT entirely * * Tool Linking: * - tool_use.id in assistant message * - tool_result.tool_use_id in internal user message * - Also: sourceToolUseID field directly on internal user entry */