Module: ClaudeAgentSDK::SessionSummary
- Defined in:
- lib/claude_agent_sdk/session_summary.rb
Overview
Incremental session-summary derivation for SessionStore adapters.
fold_session_summary lets a store maintain a per-session summary sidecar incrementally inside #append so list_sessions_from_store can fetch all metadata in a single #list_session_summaries call instead of N per-session #load calls. Every derived field is append-incremental (set-once or last-wins) so adapters never need to re-read previously appended entries.
All structures use STRING keys throughout: entries are raw JSONL objects (string keys from JSON), and the summary’s opaque data dict is persisted verbatim by adapters — string keys survive a JSON round-trip (Postgres JSONB, Redis) losslessly, whereas symbol keys would not.
Constant Summary collapse
- LAST_WINS_FIELDS =
JSONL entry keys -> summary data keys for last-wins string fields. Each appended entry overwrites the previous value when present.
{ 'customTitle' => 'custom_title', 'aiTitle' => 'ai_title', 'lastPrompt' => 'last_prompt', 'summary' => 'summary_hint', 'gitBranch' => 'git_branch' }.freeze
Class Method Summary collapse
-
.fold_session_summary(prev, key, entries) ⇒ Hash
Fold a batch of appended entries into the running summary for
key. -
.summary_entry_to_sdk_info(entry, project_path) ⇒ SDKSessionInfo?
Convert a summary entry to SDKSessionInfo.
Class Method Details
.fold_session_summary(prev, key, entries) ⇒ Hash
Fold a batch of appended entries into the running summary for key.
Stores call this from inside #append to keep a summary sidecar up to date without re-reading the transcript. prev is the previous summary for the same key (or nil for the first append).
Do NOT call this for keys with a subpath — subagent transcripts must not contribute to the main session’s summary. Guard with ‘if key.nil?` before calling.
mtime is NOT touched by the fold — it is the sidecar’s storage write time and must be stamped by the adapter after persisting (sharing a clock with the mtime returned by SessionStore#list_sessions). For a new session (prev nil) the fold returns mtime 0 as a placeholder for the adapter to overwrite.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/claude_agent_sdk/session_summary.rb', line 52 def fold_session_summary(prev, key, entries) summary = if prev { 'session_id' => prev['session_id'], 'mtime' => prev['mtime'], 'data' => prev['data'].dup } else { 'session_id' => key['session_id'], 'mtime' => 0, 'data' => {} } end data = summary['data'] entries.each do |entry| next unless entry.is_a?(Hash) data['is_sidechain'] = (entry['isSidechain'] == true) unless data.key?('is_sidechain') # created_at is set-once, so skip the (regex + Time.iso8601) parse for # every entry after the first timestamped one — folds run over whole # transcripts on the store read paths. ms = data.key?('created_at') ? nil : Sessions.(entry['timestamp']) data['created_at'] = ms if ms unless data.key?('cwd') cwd = entry['cwd'] data['cwd'] = cwd if cwd.is_a?(String) && !cwd.empty? end fold_first_prompt(data, entry) LAST_WINS_FIELDS.each do |src, dst| val = entry[src] data[dst] = val if val.is_a?(String) end next unless entry['type'] == 'tag' tag_val = entry['tag'] if tag_val.is_a?(String) && !tag_val.empty? data['tag'] = tag_val else # Empty string or absent tag clears the tag. data.delete('tag') end end summary end |
.summary_entry_to_sdk_info(entry, project_path) ⇒ SDKSessionInfo?
Convert a summary entry to SDKSessionInfo. Returns nil for sidechain sessions or sessions with no extractable summary, matching the disk lite-parse’s filtering in Sessions#build_session_info.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/claude_agent_sdk/session_summary.rb', line 104 def summary_entry_to_sdk_info(entry, project_path) data = entry['data'] || {} return nil if data['is_sidechain'] first_prompt = presence(data['first_prompt_locked'] ? data['first_prompt'] : data['command_fallback']) custom_title = presence(data['custom_title']) || presence(data['ai_title']) summary = custom_title || presence(data['last_prompt']) || presence(data['summary_hint']) || first_prompt return nil unless summary SDKSessionInfo.new( session_id: entry['session_id'], summary: summary, last_modified: entry['mtime'], # file_size is a JSONL byte count — meaningful only for the local-disk # path. Stores have no equivalent. file_size: nil, custom_title: custom_title, first_prompt: first_prompt, git_branch: presence(data['git_branch']), cwd: presence(data['cwd']) || presence(project_path), tag: presence(data['tag']), created_at: data['created_at'] ) end |