@zaly/agent 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/agent-BaD2kax9.mjs +68 -0
  4. package/dist/agent-BaD2kax9.mjs.map +1 -0
  5. package/dist/agent-GDLdDc2w.mjs +902 -0
  6. package/dist/agent-GDLdDc2w.mjs.map +1 -0
  7. package/dist/bash-QNMTQ1PD.mjs +142 -0
  8. package/dist/bash-QNMTQ1PD.mjs.map +1 -0
  9. package/dist/compactions-3AjPqjeZ.mjs +190 -0
  10. package/dist/compactions-3AjPqjeZ.mjs.map +1 -0
  11. package/dist/edit-Qxl4w1C2.mjs +170 -0
  12. package/dist/edit-Qxl4w1C2.mjs.map +1 -0
  13. package/dist/env-UUFv5Mye.mjs +30 -0
  14. package/dist/env-UUFv5Mye.mjs.map +1 -0
  15. package/dist/fetch-D8cmmA3E.mjs +111 -0
  16. package/dist/fetch-D8cmmA3E.mjs.map +1 -0
  17. package/dist/find-Bah9SKWX.mjs +87 -0
  18. package/dist/find-Bah9SKWX.mjs.map +1 -0
  19. package/dist/grep-B2Ax7ewy.mjs +244 -0
  20. package/dist/grep-B2Ax7ewy.mjs.map +1 -0
  21. package/dist/index-node-esm-D6YQRKEz.mjs +1537 -0
  22. package/dist/index-node-esm-D6YQRKEz.mjs.map +1 -0
  23. package/dist/index.d.mts +1605 -0
  24. package/dist/index.d.mts.map +1 -0
  25. package/dist/index.mjs +948 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/jsonl-Dw_VBp46.mjs +134 -0
  28. package/dist/jsonl-Dw_VBp46.mjs.map +1 -0
  29. package/dist/manager-D-lWBTHG.mjs +271 -0
  30. package/dist/manager-D-lWBTHG.mjs.map +1 -0
  31. package/dist/markdown-Bv4J8cg3.mjs +53 -0
  32. package/dist/markdown-Bv4J8cg3.mjs.map +1 -0
  33. package/dist/masker-Bv9KjkDF.mjs +396 -0
  34. package/dist/masker-Bv9KjkDF.mjs.map +1 -0
  35. package/dist/model-_3ZA2xqm.mjs +27 -0
  36. package/dist/model-_3ZA2xqm.mjs.map +1 -0
  37. package/dist/notify-DpG_m9iP.mjs +96 -0
  38. package/dist/notify-DpG_m9iP.mjs.map +1 -0
  39. package/dist/read-Djsx2NZl.mjs +205 -0
  40. package/dist/read-Djsx2NZl.mjs.map +1 -0
  41. package/dist/registry-CFmR5G3J.mjs +564 -0
  42. package/dist/registry-CFmR5G3J.mjs.map +1 -0
  43. package/dist/search-CRw247ES.mjs +121 -0
  44. package/dist/search-CRw247ES.mjs.map +1 -0
  45. package/dist/session/claude.d.mts +59 -0
  46. package/dist/session/claude.d.mts.map +1 -0
  47. package/dist/session/claude.mjs +455 -0
  48. package/dist/session/claude.mjs.map +1 -0
  49. package/dist/session-CUUdWFC5.mjs +450 -0
  50. package/dist/session-CUUdWFC5.mjs.map +1 -0
  51. package/dist/session-Ds5nJz5A.d.mts +346 -0
  52. package/dist/session-Ds5nJz5A.d.mts.map +1 -0
  53. package/dist/session.d.mts +37 -0
  54. package/dist/session.d.mts.map +1 -0
  55. package/dist/session.mjs +106 -0
  56. package/dist/session.mjs.map +1 -0
  57. package/dist/subagent-baqzjRMc.mjs +136 -0
  58. package/dist/subagent-baqzjRMc.mjs.map +1 -0
  59. package/dist/swarm-BJ3gQA1Z.mjs +194 -0
  60. package/dist/swarm-BJ3gQA1Z.mjs.map +1 -0
  61. package/dist/swarm-BuFx_xeY.mjs +109 -0
  62. package/dist/swarm-BuFx_xeY.mjs.map +1 -0
  63. package/dist/tasks-D8rARHZ1.mjs +65 -0
  64. package/dist/tasks-D8rARHZ1.mjs.map +1 -0
  65. package/dist/tasks-WmC270X2.mjs +615 -0
  66. package/dist/tasks-WmC270X2.mjs.map +1 -0
  67. package/dist/tools-DW1dbcCz.mjs +695 -0
  68. package/dist/tools-DW1dbcCz.mjs.map +1 -0
  69. package/dist/truncate-BSMXgiXj.mjs +102 -0
  70. package/dist/truncate-BSMXgiXj.mjs.map +1 -0
  71. package/dist/uuid-ByXP5SO5.mjs +59 -0
  72. package/dist/uuid-ByXP5SO5.mjs.map +1 -0
  73. package/dist/wakeup-BYqTnfSp.mjs +29 -0
  74. package/dist/wakeup-BYqTnfSp.mjs.map +1 -0
  75. package/dist/write-D3L-92vj.mjs +89 -0
  76. package/dist/write-D3L-92vj.mjs.map +1 -0
  77. package/package.json +63 -0
@@ -0,0 +1,455 @@
1
+ import { t as JsonlReader } from "../jsonl-Dw_VBp46.mjs";
2
+ import { normPath } from "@zaly/shared";
3
+ //#region src/session/claude.ts
4
+ async function loadClaudeSession(path, opts = {}) {
5
+ path = normPath(path);
6
+ const convert = defaultConvertTool;
7
+ const chain = opts.walk === "all" ? await collectAll(path) : await walkChainLazy(path);
8
+ const toolCalls = collectToolCalls(chain, convert);
9
+ const messages = [];
10
+ for (const rec of chain) {
11
+ const msg = toZalyMessage(rec, toolCalls);
12
+ if (!msg) continue;
13
+ if (msg.role === "assistant") {
14
+ const usage = recordUsage(rec);
15
+ if (usage) msg.meta = {
16
+ ...msg.meta,
17
+ usage
18
+ };
19
+ }
20
+ messages.push(msg);
21
+ }
22
+ messages.sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0));
23
+ dedupCallIds(messages);
24
+ return { messages };
25
+ }
26
+ /** Build a `Usage` from a Claude record's `message.usage` block.
27
+ * Returns undefined when no usage info is present (user messages,
28
+ * tool results). The caller stamps it onto `message.meta.usage`. */
29
+ function recordUsage(rec) {
30
+ const u = rec.message?.usage;
31
+ if (!u) return void 0;
32
+ const usage = {
33
+ input: u.input_tokens ?? 0,
34
+ output: u.output_tokens ?? 0
35
+ };
36
+ if (u.cache_read_input_tokens !== void 0) usage.cacheRead = u.cache_read_input_tokens;
37
+ if (u.cache_creation_input_tokens !== void 0) usage.cacheWrite = u.cache_creation_input_tokens;
38
+ return usage;
39
+ }
40
+ /** Walk the parentUuid chain backward from the most recent on-chain
41
+ * user/assistant message and return the chronologically-ordered
42
+ * conversation. Reads the file lazily (backward in chunks) — only the
43
+ * chunks containing the active chain get pulled from disk.
44
+ *
45
+ * Subtlety: Claude Code interleaves non-message records (`attachment`,
46
+ * `system`, `file-history-snapshot`, ...) into the parent chain — an
47
+ * assistant message's `parentUuid` may point at an `attachment`, whose
48
+ * `parentUuid` then points at the prior user message. We step *through*
49
+ * those records (using a uuid → ANY record map), collecting only the
50
+ * user/assistant messages we encounter. Sidechain messages are skipped.
51
+ *
52
+ * For long-running sessions with multiple compactions, the active chain
53
+ * typically lives in the file's most recent chunks — so a 300MB session
54
+ * loads from a few hundred KB rather than reading the whole file. */
55
+ async function walkChainLazy(path) {
56
+ const reader = new JsonlReader(path);
57
+ const byUuid = /* @__PURE__ */ new Map();
58
+ try {
59
+ let lastOnChain;
60
+ let value;
61
+ while ((value = await reader.next()) !== void 0) {
62
+ if (value.uuid !== void 0) byUuid.set(value.uuid, value);
63
+ if (isMessageRecord(value) && value.isSidechain !== true) {
64
+ lastOnChain = value.uuid;
65
+ break;
66
+ }
67
+ }
68
+ if (lastOnChain === void 0) return [];
69
+ const chain = [];
70
+ let cursor = lastOnChain;
71
+ while (cursor !== void 0) {
72
+ let rec = byUuid.get(cursor);
73
+ if (!rec) while ((value = await reader.next()) !== void 0) {
74
+ if (value.uuid !== void 0) byUuid.set(value.uuid, value);
75
+ if (value.uuid === cursor) {
76
+ rec = value;
77
+ break;
78
+ }
79
+ }
80
+ if (!rec) break;
81
+ if (rec.isCompactSummary === true) {
82
+ chain.push(rec);
83
+ break;
84
+ }
85
+ if (isMessageRecord(rec) && rec.isSidechain !== true) chain.push(rec);
86
+ cursor = rec.parentUuid;
87
+ }
88
+ return chain.toReversed();
89
+ } finally {
90
+ await reader.close();
91
+ }
92
+ }
93
+ function isMessageRecord(rec) {
94
+ return rec.type === "user" || rec.type === "assistant";
95
+ }
96
+ /** Every user/assistant message in file order, ignoring branches and
97
+ * compaction boundaries. Sidechain messages are still skipped — those
98
+ * belong to subagent loops and would scramble the main chain.
99
+ *
100
+ * This mode reads the entire file (necessary to find every record),
101
+ * but uses chunked I/O via `JsonlReader` instead of slurping the
102
+ * whole string at once. */
103
+ async function collectAll(path) {
104
+ const reader = new JsonlReader(path);
105
+ const records = [];
106
+ try {
107
+ let value;
108
+ while ((value = await reader.next()) !== void 0) if (isMessageRecord(value) && value.isSidechain !== true) records.push(value);
109
+ } finally {
110
+ await reader.close();
111
+ }
112
+ return records.toReversed();
113
+ }
114
+ /** Build a `tool_use_id → { name, params }` map from the assistant
115
+ * tool_use blocks in the chain, applying `convert` to remap names and
116
+ * params into zaly's tool shapes. Used both to render the converted
117
+ * `tool-call` parts on the assistant side and to fill in the (missing)
118
+ * `name` on Claude `tool_result` blocks at result-conversion time. */
119
+ function collectToolCalls(chain, convert) {
120
+ const out = /* @__PURE__ */ new Map();
121
+ for (const rec of chain) {
122
+ if (rec.type !== "assistant") continue;
123
+ const content = rec.message?.content;
124
+ if (!Array.isArray(content)) continue;
125
+ for (const block of content) if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
126
+ const zaly = convert({
127
+ input: block.input,
128
+ name: block.name
129
+ }) ?? {
130
+ name: block.name,
131
+ params: block.input
132
+ };
133
+ out.set(block.id, zaly);
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+ /** Default `convertTool` mapping. Handles the standard Claude Code
139
+ * toolbox; everything else falls through to a lowercased name with
140
+ * params untouched. Composable: custom converters can call this for
141
+ * unhandled cases. */
142
+ function defaultConvertTool(call) {
143
+ const input = call.input ?? {};
144
+ switch (call.name) {
145
+ case "Read": return {
146
+ name: "read",
147
+ params: renameKey(input, "file_path", "path")
148
+ };
149
+ case "Write": return {
150
+ name: "write",
151
+ params: renameKey(input, "file_path", "path")
152
+ };
153
+ case "Edit": {
154
+ const { file_path, old_string, new_string, replace_all, ...rest } = input;
155
+ return {
156
+ name: "edit",
157
+ params: {
158
+ edits: [{
159
+ newText: new_string,
160
+ oldText: old_string,
161
+ replaceAll: replace_all
162
+ }],
163
+ path: file_path,
164
+ ...rest
165
+ }
166
+ };
167
+ }
168
+ case "MultiEdit": {
169
+ const { file_path, edits, ...rest } = input;
170
+ return {
171
+ name: "edit",
172
+ params: {
173
+ edits: Array.isArray(edits) ? edits.map((e) => ({
174
+ newText: e.new_string,
175
+ oldText: e.old_string,
176
+ replaceAll: e.replace_all
177
+ })) : [],
178
+ path: file_path,
179
+ ...rest
180
+ }
181
+ };
182
+ }
183
+ case "Bash": return {
184
+ name: "bash",
185
+ params: input
186
+ };
187
+ default: return {
188
+ name: call.name.toLowerCase(),
189
+ params: input
190
+ };
191
+ }
192
+ }
193
+ function renameKey(obj, from, to) {
194
+ if (!(from in obj)) return obj;
195
+ const { [from]: value, ...rest } = obj;
196
+ return {
197
+ [to]: value,
198
+ ...rest
199
+ };
200
+ }
201
+ /** Walk messages in chronological order; when a tool-call id reappears,
202
+ * rename the duplicate (and the corresponding tool-result) to a fresh
203
+ * id. Pairs are matched FIFO per-id: each duplicate call's pending
204
+ * rename gets consumed by the next tool-result with that original id.
205
+ * Mutates the message parts in place. */
206
+ function dedupCallIds(messages) {
207
+ const seen = /* @__PURE__ */ new Set();
208
+ const pending = /* @__PURE__ */ new Map();
209
+ let counter = 0;
210
+ for (const m of messages) if (m.role === "assistant" && Array.isArray(m.content)) for (const p of m.content) {
211
+ if (p.type !== "tool-call") continue;
212
+ if (!seen.has(p.id)) {
213
+ seen.add(p.id);
214
+ continue;
215
+ }
216
+ const newId = `${p.id}-${++counter}`;
217
+ const list = pending.get(p.id) ?? [];
218
+ list.push(newId);
219
+ pending.set(p.id, list);
220
+ p.id = newId;
221
+ }
222
+ else if (m.role === "tool") for (const p of m.content) {
223
+ const origId = p.id;
224
+ const list = pending.get(origId);
225
+ if (!list || list.length === 0) continue;
226
+ p.id = list.shift();
227
+ if (list.length === 0) pending.delete(origId);
228
+ }
229
+ }
230
+ function toZalyMessage(rec, toolCalls) {
231
+ const inner = rec.message;
232
+ if (!inner) return void 0;
233
+ let m;
234
+ if (rec.type === "user") m = toUserMessage(inner, toolCalls, rec.toolUseResult);
235
+ else if (rec.type === "assistant") m = toAssistantMessage(inner, toolCalls);
236
+ if (!m) return void 0;
237
+ if (rec.timestamp) {
238
+ const ts = Date.parse(rec.timestamp);
239
+ if (!Number.isNaN(ts)) m = {
240
+ ...m,
241
+ ts
242
+ };
243
+ }
244
+ if (rec.uuid) m = {
245
+ ...m,
246
+ id: rec.uuid
247
+ };
248
+ return m;
249
+ }
250
+ function toUserMessage(inner, toolCalls, toolUseResult) {
251
+ const content = inner.content;
252
+ if (typeof content === "string") return {
253
+ content,
254
+ role: "user"
255
+ };
256
+ if (!Array.isArray(content) || content.length === 0) return void 0;
257
+ if (content.every((b) => b.type === "tool_result")) {
258
+ const parts = content.map((b) => toToolResultPart(b, toolCalls, toolUseResult)).filter((p) => p !== void 0);
259
+ if (parts.length === 0) return void 0;
260
+ return {
261
+ content: parts,
262
+ role: "tool"
263
+ };
264
+ }
265
+ const parts = content.map((b) => toUserPart(b)).filter((p) => p !== void 0);
266
+ if (parts.length === 0) return void 0;
267
+ return {
268
+ content: parts,
269
+ role: "user"
270
+ };
271
+ }
272
+ function toToolResultPart(block, toolCalls, toolUseResult) {
273
+ if (typeof block.tool_use_id !== "string") return void 0;
274
+ const name = toolCalls.get(block.tool_use_id)?.name ?? "";
275
+ const meta = toolUseResultMeta(name, toolUseResult);
276
+ return {
277
+ content: toToolResultContent(block.content),
278
+ id: block.tool_use_id,
279
+ isError: block.is_error === true,
280
+ name,
281
+ type: "tool-result",
282
+ ...meta !== void 0 ? { meta } : {}
283
+ };
284
+ }
285
+ /** Map Claude Code's per-tool `toolUseResult` shape onto our flat
286
+ * `meta` schema. mtime is unknown at import time — we set 0 so any
287
+ * later `assertFresh` check forces the agent to re-read before
288
+ * mutating, rather than trusting potentially-stale bytes. */
289
+ function toolUseResultMeta(name, result) {
290
+ if (result === void 0 || typeof result === "string") return void 0;
291
+ switch (name) {
292
+ case "read": {
293
+ const file = result.file;
294
+ if (!file?.filePath) return void 0;
295
+ return {
296
+ full: file.totalLines !== void 0 && file.numLines === file.totalLines,
297
+ kind: "read",
298
+ limit: file.numLines,
299
+ mtime: 0,
300
+ offset: file.startLine,
301
+ path: file.filePath
302
+ };
303
+ }
304
+ case "write":
305
+ if (!result.filePath) return void 0;
306
+ return {
307
+ full: true,
308
+ kind: "write",
309
+ mtime: 0,
310
+ original: result.originalFile,
311
+ path: result.filePath
312
+ };
313
+ case "edit": {
314
+ if (!result.filePath) return void 0;
315
+ const originalFile = typeof result.originalFile === "string" && result.originalFile.length > 0 ? result.originalFile : void 0;
316
+ let original = originalFile ?? "";
317
+ let content = "";
318
+ if (typeof result.content === "string") content = result.content;
319
+ else if (typeof result.oldString === "string" && typeof result.newString === "string") {
320
+ const { newString, oldString } = result;
321
+ const applied = applyClaudeEdit(originalFile, oldString, newString, result.replaceAll);
322
+ if (applied !== void 0) content = applied;
323
+ else {
324
+ const synth = synthesizeFromStructuredPatch(result.structuredPatch);
325
+ if (synth !== void 0) {
326
+ original = synth.original;
327
+ content = synth.modified;
328
+ } else {
329
+ original = oldString;
330
+ content = newString;
331
+ }
332
+ }
333
+ }
334
+ return {
335
+ content,
336
+ kind: "edit",
337
+ mtime: 0,
338
+ original,
339
+ path: result.filePath
340
+ };
341
+ }
342
+ default: return;
343
+ }
344
+ }
345
+ /** Apply a Claude single-Edit swap to the pre-edit content. Returns
346
+ * `undefined` when the edit can't be applied (no original file recorded
347
+ * or the search string doesn't appear in it) — caller falls back to
348
+ * showing just the swap. */
349
+ function applyClaudeEdit(original, oldString, newString, replaceAll) {
350
+ if (original === void 0 || !original.includes(oldString)) return void 0;
351
+ return replaceAll === true ? original.split(oldString).join(newString) : original.replace(oldString, newString);
352
+ }
353
+ /** Reconstruct synthetic pre/post strings from a Claude `structuredPatch`.
354
+ * Hunks are placed at their real line numbers (`oldStart`/`newStart`)
355
+ * with empty-line padding outside, so the diff widget's gutter shows
356
+ * accurate file line numbers — same effect as Claude Code's UI. Lines
357
+ * prefixed " "/" -" go to the original, " "/" +" go to modified. */
358
+ function synthesizeFromStructuredPatch(patch) {
359
+ if (!Array.isArray(patch) || patch.length === 0) return void 0;
360
+ const origLines = [];
361
+ const modLines = [];
362
+ for (const h of patch) {
363
+ padTo(origLines, h.oldStart);
364
+ padTo(modLines, h.newStart);
365
+ for (const raw of h.lines) {
366
+ const tag = raw[0];
367
+ const body = raw.slice(1);
368
+ if (tag === "-") origLines.push(body);
369
+ else if (tag === "+") modLines.push(body);
370
+ else {
371
+ origLines.push(body);
372
+ modLines.push(body);
373
+ }
374
+ }
375
+ }
376
+ return {
377
+ modified: modLines.join("\n"),
378
+ original: origLines.join("\n")
379
+ };
380
+ }
381
+ /** 1-based line indexing in the patch; pad with empty strings up to
382
+ * the target line so subsequent pushes land at the right index. */
383
+ function padTo(arr, idx) {
384
+ while (arr.length < idx - 1) arr.push("");
385
+ }
386
+ /** Tool result content: Claude allows string OR an array of text/image
387
+ * blocks. Map both into zaly's `Content` shape. */
388
+ function toToolResultContent(content) {
389
+ if (content === void 0) return "";
390
+ if (typeof content === "string") return content;
391
+ const parts = content.map((b) => toUserPart(b)).filter((p) => p !== void 0);
392
+ return parts.length > 0 ? parts : "";
393
+ }
394
+ function toUserPart(block) {
395
+ if (block.type === "text" && typeof block.text === "string") return {
396
+ text: block.text,
397
+ type: "text"
398
+ };
399
+ if (block.type === "image") return toImagePart(block);
400
+ }
401
+ function toImagePart(block) {
402
+ const src = block.source;
403
+ if (src.type === "base64") {
404
+ const mime = src.media_type;
405
+ if (mime !== "image/png" && mime !== "image/jpeg" && mime !== "image/webp") return void 0;
406
+ return {
407
+ mime,
408
+ source: {
409
+ data: src.data,
410
+ type: "base64"
411
+ },
412
+ type: "image"
413
+ };
414
+ }
415
+ }
416
+ function toAssistantMessage(inner, toolCalls) {
417
+ const content = inner.content;
418
+ if (typeof content === "string") return {
419
+ content,
420
+ role: "assistant"
421
+ };
422
+ if (!Array.isArray(content)) return void 0;
423
+ const parts = [];
424
+ for (const block of content) {
425
+ const part = toAssistantPart(block, toolCalls);
426
+ if (part) parts.push(part);
427
+ }
428
+ if (parts.length === 0) return void 0;
429
+ return {
430
+ content: parts,
431
+ role: "assistant"
432
+ };
433
+ }
434
+ function toAssistantPart(block, toolCalls) {
435
+ if (block.type === "text" && typeof block.text === "string") return {
436
+ text: block.text,
437
+ type: "text"
438
+ };
439
+ if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
440
+ const call = toolCalls.get(block.id) ?? {
441
+ name: block.name,
442
+ params: block.input
443
+ };
444
+ return {
445
+ id: block.id,
446
+ name: call.name,
447
+ params: call.params,
448
+ type: "tool-call"
449
+ };
450
+ }
451
+ }
452
+ //#endregion
453
+ export { loadClaudeSession };
454
+
455
+ //# sourceMappingURL=claude.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.mjs","names":[],"sources":["../../src/session/claude.ts"],"sourcesContent":["import type {\n Attachment,\n ImagePart,\n Message,\n ReasoningPart,\n TextPart,\n ToolCallPart,\n ToolResultPart,\n Usage,\n} from \"@zaly/ai\"\n\nimport { normPath } from \"@zaly/shared\"\nimport { JsonlReader } from \"./jsonl.ts\"\n\n/**\n * Read a Claude Code session file (`.jsonl` from `~/.claude/projects/...`)\n * and return its active conversation chain as zaly `Message[]` — pass\n * the result to `Agent.load({ messages, ... })` to seed a fresh agent\n * with the imported history.\n *\n * Claude Code stores its conversation as one JSON record per line. The\n * active chain is reconstructed by walking `parentUuid` backward from the\n * most recent on-chain user/assistant message. Non-message records\n * (`permission-mode`, `attachment`, `system`, `summary`, `file-history-\n * snapshot`, ...), sidechain branches (`isSidechain: true`), and orphan\n * lines outside the chain are skipped.\n *\n * Format mapping:\n *\n * - **User message, string content** → zaly `role: \"user\"`, content\n * passed through verbatim.\n * - **User message, array content with `tool_result` blocks** → zaly\n * `role: \"tool\"`. Anthropic's wire format puts tool results in a\n * user message; zaly splits them into a dedicated `tool` role. The\n * tool name (which Claude doesn't store on the result) is resolved\n * from the matching `tool_use` block earlier in the chain.\n * - **User message, array content with text/image blocks** → zaly\n * `role: \"user\"` with the parts converted (text passes through,\n * images become `ImagePart`).\n * - **Assistant message** → zaly `role: \"assistant\"` with each block\n * mapped: `text` → `TextPart`, `tool_use` → `ToolCallPart` (the\n * block's `input` becomes `params`). `thinking` / `redacted_thinking`\n * blocks are intentionally dropped on import — Anthropic requires\n * them to round-trip with the exact original `signature` or the next\n * request 400s, and any intermediate processing risks corrupting it.\n * The visible conversation state survives without them.\n *\n * Lossy by design — tokens, finish reasons, model swaps, and timing\n * info from Claude's records are dropped because zaly's `Session.add`\n * doesn't expose those for arbitrary imports. The returned messages\n * carry no `path` association; pass `session: { path: \"...\" }` to\n * `Agent.load` alongside them if you want to start persisting the\n * imported chain in zaly format.\n */\nexport interface ClaudeSessionOptions {\n /** Which messages to import:\n * - `\"active\"` (default) — walks `parentUuid` back from the most\n * recent on-chain message. Returns the conversation as the model\n * currently sees it: branches not on the active head are skipped,\n * pre-compact history (parented to a summary record) is dropped.\n * - `\"all\"` — every user/assistant message in the file, in\n * chronological order, regardless of branch or compaction. Useful\n * for analytics, fixtures, or recovering pre-compact context. */\n walk?: \"active\" | \"all\"\n}\n\ntype ConvertTool = (call: ClaudeToolCall) => ZalyToolCall | undefined\ninterface ClaudeToolCall {\n name: string\n input: unknown\n}\ninterface ZalyToolCall {\n name: string\n params: unknown\n}\n\nexport async function loadClaudeSession(\n path: string,\n opts: ClaudeSessionOptions = {}\n): Promise<{ messages: Message[] }> {\n // Honor `~` and relative shorthands — config-file / env paths often\n // include them and Node's fs APIs don't expand `~` natively.\n path = normPath(path)\n const convert = defaultConvertTool\n const chain = opts.walk === \"all\" ? await collectAll(path) : await walkChainLazy(path)\n const toolCalls = collectToolCalls(chain, convert)\n\n const messages: Message[] = []\n for (const rec of chain) {\n const msg = toZalyMessage(rec, toolCalls)\n if (!msg) continue\n if (msg.role === \"assistant\") {\n const usage = recordUsage(rec)\n if (usage) msg.meta = { ...msg.meta, usage }\n }\n messages.push(msg)\n }\n messages.sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0))\n // De-duplicate tool_use ids. Claude Code branching / sidechain replay\n // can emit the same tool_use_id across multiple assistant messages\n // (especially with `walk: \"all\"`); downstream consumers like the\n // masker or any (id → call) lookup expect uniqueness.\n dedupCallIds(messages)\n return { messages }\n}\n\n/** Build a `Usage` from a Claude record's `message.usage` block.\n * Returns undefined when no usage info is present (user messages,\n * tool results). The caller stamps it onto `message.meta.usage`. */\nfunction recordUsage(rec: ClaudeRecord): Usage | undefined {\n const u = rec.message?.usage\n if (!u) return undefined\n const usage: Usage = {\n input: u.input_tokens ?? 0,\n output: u.output_tokens ?? 0,\n }\n if (u.cache_read_input_tokens !== undefined) usage.cacheRead = u.cache_read_input_tokens\n if (u.cache_creation_input_tokens !== undefined) usage.cacheWrite = u.cache_creation_input_tokens\n return usage\n}\n\n// ── Parsing ──────────────────────────────────────────────────────────────\n\ninterface ClaudeRecord {\n type: string\n uuid?: string\n parentUuid?: string\n isSidechain?: boolean\n /** Set on the synthetic user-role record Claude Code writes for a\n * `/compact`. The record's content is the summary text; chronologically\n * it sits where the compaction happened, with its `parentUuid` still\n * pointing into pre-compaction history. The active conversation is\n * this summary plus everything after — pre-compaction messages are\n * unreachable from the live agent. */\n isCompactSummary?: boolean\n message?: ClaudeMessage\n /** Wall-clock time the original Claude session recorded the message,\n * ISO 8601 string. Preserved onto `Message.ts` so age-based logic\n * (masker `maxAge`, freshness, replay) sees realistic timestamps\n * rather than the import time. */\n timestamp?: string\n /** Claude Code attaches structured per-tool result info here, alongside\n * the model-visible `tool_result` block. Shape varies by tool:\n * - read → `{ type: \"text\"|\"image\", file: { filePath, ... } }`\n * - write → `{ type: \"create\", filePath, content, originalFile, ... }`\n * - edit → `{ type: \"update\", filePath, content, originalFile, ... }`\n * - bash → `{ stdout, stderr, interrupted, ... }`\n * We mine this for `meta` to populate on imported `ToolResultPart`s.\n */\n toolUseResult?: ClaudeToolUseResult\n}\n\ntype ClaudeToolUseResult =\n | string\n | {\n type?: \"text\" | \"image\" | \"create\" | \"update\" | \"file_unchanged\"\n file?: {\n filePath?: string\n content?: string\n numLines?: number\n startLine?: number\n totalLines?: number\n }\n filePath?: string\n /** Set on MultiEdit / Write results — post-state file content. */\n content?: string\n /** Set on Edit results (single replacement). The post-state must\n * be derived by applying `oldString` → `newString` to\n * `originalFile`. */\n oldString?: string\n newString?: string\n replaceAll?: boolean\n originalFile?: string\n structuredPatch?: ClaudeStructuredHunk[]\n [key: string]: unknown\n }\n\ninterface ClaudeStructuredHunk {\n oldStart: number\n oldLines: number\n newStart: number\n newLines: number\n lines: string[]\n}\n\ninterface ClaudeMessage {\n role?: \"user\" | \"assistant\"\n content?: string | ClaudeBlock[]\n model?: string\n usage?: ClaudeUsage\n stop_reason?: string\n}\n\ninterface ClaudeUsage {\n input_tokens?: number\n output_tokens?: number\n cache_read_input_tokens?: number\n cache_creation_input_tokens?: number\n}\n\ntype ClaudeBlock =\n | { type: \"text\"; text: string }\n | { type: \"thinking\"; thinking: string; signature?: string }\n | { type: \"tool_use\"; id: string; name: string; input: unknown }\n | {\n type: \"tool_result\"\n tool_use_id: string\n content?: string | ClaudeBlock[]\n is_error?: boolean\n }\n | {\n type: \"image\"\n source: { type: \"base64\"; media_type: string; data: string } | { type: \"url\"; url: string }\n }\n | { type: string; [key: string]: unknown } // unknown block — falls through to a placeholder\n\n// ── Chain walk ───────────────────────────────────────────────────────────\n\n/** Walk the parentUuid chain backward from the most recent on-chain\n * user/assistant message and return the chronologically-ordered\n * conversation. Reads the file lazily (backward in chunks) — only the\n * chunks containing the active chain get pulled from disk.\n *\n * Subtlety: Claude Code interleaves non-message records (`attachment`,\n * `system`, `file-history-snapshot`, ...) into the parent chain — an\n * assistant message's `parentUuid` may point at an `attachment`, whose\n * `parentUuid` then points at the prior user message. We step *through*\n * those records (using a uuid → ANY record map), collecting only the\n * user/assistant messages we encounter. Sidechain messages are skipped.\n *\n * For long-running sessions with multiple compactions, the active chain\n * typically lives in the file's most recent chunks — so a 300MB session\n * loads from a few hundred KB rather than reading the whole file. */\nasync function walkChainLazy(path: string): Promise<ClaudeRecord[]> {\n const reader = new JsonlReader<ClaudeRecord>(path)\n const byUuid = new Map<string, ClaudeRecord>()\n try {\n // Phase 1: read backward until we find the most recent on-chain\n // (non-sidechain) user/assistant message.\n let lastOnChain: string | undefined\n let value: ClaudeRecord | undefined\n // eslint-disable-next-line no-await-in-loop\n while ((value = await reader.next()) !== undefined) {\n if (value.uuid !== undefined) byUuid.set(value.uuid, value)\n if (isMessageRecord(value) && value.isSidechain !== true) {\n lastOnChain = value.uuid\n break\n }\n }\n if (lastOnChain === undefined) return []\n\n // Phase 2: walk parentUuid backward, fetching more records lazily\n // as needed to resolve each cursor. A `summary` record is a HARD\n // boundary — Claude Code compactions create a summary node and\n // anything before it is unreachable in the live conversation, so\n // we must not walk past it (would otherwise pull pre-compaction\n // history that the model can no longer see).\n const chain: ClaudeRecord[] = []\n let cursor: string | undefined = lastOnChain\n while (cursor !== undefined) {\n let rec = byUuid.get(cursor)\n if (!rec) {\n // Pull more records from the reader until we find this uuid\n // (or run out). Records pulled along the way get cached for\n // later cursor lookups.\n // eslint-disable-next-line no-await-in-loop\n while ((value = await reader.next()) !== undefined) {\n if (value.uuid !== undefined) byUuid.set(value.uuid, value)\n if (value.uuid === cursor) {\n rec = value\n break\n }\n }\n }\n if (!rec) break\n // Compaction boundary — Claude writes the synthetic summary as a\n // user-role record flagged `isCompactSummary`. Include it (it IS\n // the visible context for the resumed conversation) and stop —\n // anything older than this is unreachable from the live agent.\n if (rec.isCompactSummary === true) {\n chain.push(rec)\n break\n }\n if (isMessageRecord(rec) && rec.isSidechain !== true) chain.push(rec)\n cursor = rec.parentUuid\n }\n return chain.toReversed()\n } finally {\n await reader.close()\n }\n}\n\nfunction isMessageRecord(rec: ClaudeRecord): boolean {\n return rec.type === \"user\" || rec.type === \"assistant\"\n}\n\n/** Every user/assistant message in file order, ignoring branches and\n * compaction boundaries. Sidechain messages are still skipped — those\n * belong to subagent loops and would scramble the main chain.\n *\n * This mode reads the entire file (necessary to find every record),\n * but uses chunked I/O via `JsonlReader` instead of slurping the\n * whole string at once. */\nasync function collectAll(path: string): Promise<ClaudeRecord[]> {\n const reader = new JsonlReader<ClaudeRecord>(path)\n const records: ClaudeRecord[] = []\n try {\n let value: ClaudeRecord | undefined\n // eslint-disable-next-line no-await-in-loop\n while ((value = await reader.next()) !== undefined) {\n if (isMessageRecord(value) && value.isSidechain !== true) records.push(value)\n }\n } finally {\n await reader.close()\n }\n // Reader yields newest-first; flip to chronological.\n return records.toReversed()\n}\n\n/** Build a `tool_use_id → { name, params }` map from the assistant\n * tool_use blocks in the chain, applying `convert` to remap names and\n * params into zaly's tool shapes. Used both to render the converted\n * `tool-call` parts on the assistant side and to fill in the (missing)\n * `name` on Claude `tool_result` blocks at result-conversion time. */\nfunction collectToolCalls(\n chain: readonly ClaudeRecord[],\n convert: ConvertTool\n): Map<string, ZalyToolCall> {\n const out = new Map<string, ZalyToolCall>()\n for (const rec of chain) {\n if (rec.type !== \"assistant\") continue\n const content = rec.message?.content\n if (!Array.isArray(content)) continue\n for (const block of content) {\n if (\n block.type === \"tool_use\" &&\n typeof block.id === \"string\" &&\n typeof block.name === \"string\"\n ) {\n const claude = { input: block.input, name: block.name }\n const zaly = convert(claude) ?? { name: block.name, params: block.input }\n out.set(block.id, zaly)\n }\n }\n }\n return out\n}\n\n/** Default `convertTool` mapping. Handles the standard Claude Code\n * toolbox; everything else falls through to a lowercased name with\n * params untouched. Composable: custom converters can call this for\n * unhandled cases. */\nfunction defaultConvertTool(call: ClaudeToolCall): ZalyToolCall {\n const input = (call.input ?? {}) as Record<string, unknown>\n switch (call.name) {\n case \"Read\": {\n return { name: \"read\", params: renameKey(input, \"file_path\", \"path\") }\n }\n case \"Write\": {\n return { name: \"write\", params: renameKey(input, \"file_path\", \"path\") }\n }\n case \"Edit\": {\n const { file_path, old_string, new_string, replace_all, ...rest } = input\n return {\n name: \"edit\",\n params: {\n edits: [{ newText: new_string, oldText: old_string, replaceAll: replace_all }],\n path: file_path,\n ...rest,\n },\n }\n }\n case \"MultiEdit\": {\n const { file_path, edits, ...rest } = input\n const mapped = Array.isArray(edits)\n ? edits.map((e: Record<string, unknown>) => ({\n newText: e.new_string,\n oldText: e.old_string,\n replaceAll: e.replace_all,\n }))\n : []\n return { name: \"edit\", params: { edits: mapped, path: file_path, ...rest } }\n }\n case \"Bash\": {\n return { name: \"bash\", params: input }\n }\n default: {\n return { name: call.name.toLowerCase(), params: input }\n }\n }\n}\n\nfunction renameKey(\n obj: Record<string, unknown>,\n from: string,\n to: string\n): Record<string, unknown> {\n if (!(from in obj)) return obj\n const { [from]: value, ...rest } = obj\n return { [to]: value, ...rest }\n}\n\n/** Walk messages in chronological order; when a tool-call id reappears,\n * rename the duplicate (and the corresponding tool-result) to a fresh\n * id. Pairs are matched FIFO per-id: each duplicate call's pending\n * rename gets consumed by the next tool-result with that original id.\n * Mutates the message parts in place. */\nfunction dedupCallIds(messages: readonly Message[]): void {\n const seen = new Set<string>()\n // Per-id queue of renames awaiting their tool-result.\n const pending = new Map<string, string[]>()\n let counter = 0\n for (const m of messages) {\n if (m.role === \"assistant\" && Array.isArray(m.content)) {\n for (const p of m.content) {\n if (p.type !== \"tool-call\") continue\n if (!seen.has(p.id)) {\n seen.add(p.id)\n continue\n }\n const newId = `${p.id}-${++counter}`\n const list = pending.get(p.id) ?? []\n list.push(newId)\n pending.set(p.id, list)\n p.id = newId\n }\n } else if (m.role === \"tool\") {\n for (const p of m.content) {\n const origId = p.id\n const list = pending.get(origId)\n if (!list || list.length === 0) continue\n p.id = list.shift()!\n if (list.length === 0) pending.delete(origId)\n }\n }\n }\n}\n\n// ── Conversion ───────────────────────────────────────────────────────────\n\nfunction toZalyMessage(\n rec: ClaudeRecord,\n toolCalls: Map<string, ZalyToolCall>\n): Message | undefined {\n const inner = rec.message\n if (!inner) return undefined\n\n let m: Message | undefined\n if (rec.type === \"user\") m = toUserMessage(inner, toolCalls, rec.toolUseResult)\n else if (rec.type === \"assistant\") m = toAssistantMessage(inner, toolCalls)\n if (!m) return undefined\n\n // Preserve the original Claude timestamp so age-based logic (masker\n // `maxAge`, freshness checks, replay) sees realistic times instead\n // of when the import ran.\n if (rec.timestamp) {\n const ts = Date.parse(rec.timestamp)\n if (!Number.isNaN(ts)) m = { ...m, ts }\n }\n // Carry the record's uuid as the message id so the imported message\n // can be referenced by the session DAG (and so per-message meta lifted\n // off the same record can be looked up back here).\n if (rec.uuid) m = { ...m, id: rec.uuid }\n return m\n}\n\nfunction toUserMessage(\n inner: ClaudeMessage,\n toolCalls: Map<string, ZalyToolCall>,\n toolUseResult: ClaudeToolUseResult | undefined\n): Message | undefined {\n const content = inner.content\n if (typeof content === \"string\") {\n return { content, role: \"user\" }\n }\n if (!Array.isArray(content) || content.length === 0) return undefined\n\n // If every block is a tool_result, this is a zaly tool message.\n if (content.every((b) => b.type === \"tool_result\")) {\n const parts = content\n .map((b) =>\n toToolResultPart(\n b as Extract<ClaudeBlock, { type: \"tool_result\" }>,\n toolCalls,\n toolUseResult\n )\n )\n .filter((p): p is ToolResultPart => p !== undefined)\n if (parts.length === 0) return undefined\n return { content: parts, role: \"tool\" }\n }\n\n // Otherwise treat as a user message with text / image parts. Mixed\n // arrays carrying a tool_result alongside text are rare; we drop the\n // tool_result here (it would belong on its own message) and keep the\n // visible content.\n const parts = content\n .map((b) => toUserPart(b))\n .filter((p): p is TextPart | Attachment => p !== undefined)\n if (parts.length === 0) return undefined\n return { content: parts, role: \"user\" }\n}\n\nfunction toToolResultPart(\n block: Extract<ClaudeBlock, { type: \"tool_result\" }>,\n toolCalls: Map<string, ZalyToolCall>,\n toolUseResult: ClaudeToolUseResult | undefined\n): ToolResultPart | undefined {\n if (typeof block.tool_use_id !== \"string\") return undefined\n const name = toolCalls.get(block.tool_use_id)?.name ?? \"\"\n const meta = toolUseResultMeta(name, toolUseResult)\n return {\n content: toToolResultContent(block.content),\n id: block.tool_use_id,\n isError: block.is_error === true,\n name,\n type: \"tool-result\",\n ...(meta !== undefined ? { meta } : {}),\n }\n}\n\n/** Map Claude Code's per-tool `toolUseResult` shape onto our flat\n * `meta` schema. mtime is unknown at import time — we set 0 so any\n * later `assertFresh` check forces the agent to re-read before\n * mutating, rather than trusting potentially-stale bytes. */\nfunction toolUseResultMeta(\n name: string,\n result: ClaudeToolUseResult | undefined\n): Record<string, unknown> | undefined {\n if (result === undefined || typeof result === \"string\") return undefined\n switch (name) {\n case \"read\": {\n const file = result.file\n if (!file?.filePath) return undefined\n return {\n full: file.totalLines !== undefined && file.numLines === file.totalLines,\n kind: \"read\",\n limit: file.numLines,\n mtime: 0,\n offset: file.startLine,\n path: file.filePath,\n }\n }\n case \"write\": {\n if (!result.filePath) return undefined\n // Post-write content lives on the original tool call's\n // `params.content`; we only stash `original` (pre-write).\n return {\n full: true,\n kind: \"write\",\n mtime: 0,\n original: result.originalFile,\n path: result.filePath,\n }\n }\n case \"edit\": {\n if (!result.filePath) return undefined\n // Several Claude Code shapes to handle:\n // 1. MultiEdit / older Edit: `content` (post-state) is provided\n // directly alongside `originalFile` (pre-state).\n // 2. Single Edit with full pre-state: `oldString`/`newString`\n // swap on top of a populated `originalFile`. Derive content\n // by applying the swap.\n // 3. Single Edit without pre-state but with `structuredPatch`:\n // reconstruct synthetic pre/post strings from the patch hunks\n // (Claude Code's UI does the same trick — the patch carries\n // context + line numbers). The diff widget then renders with\n // the real file line numbers in the gutter.\n // 4. None of the above: fall back to showing just the swap.\n const originalFile =\n typeof result.originalFile === \"string\" && result.originalFile.length > 0\n ? result.originalFile\n : undefined\n let original = originalFile ?? \"\"\n let content = \"\"\n\n if (typeof result.content === \"string\") {\n content = result.content\n } else if (typeof result.oldString === \"string\" && typeof result.newString === \"string\") {\n const { newString, oldString } = result\n const applied = applyClaudeEdit(originalFile, oldString, newString, result.replaceAll)\n if (applied !== undefined) {\n content = applied\n } else {\n const synth = synthesizeFromStructuredPatch(result.structuredPatch)\n if (synth !== undefined) {\n original = synth.original\n content = synth.modified\n } else {\n original = oldString\n content = newString\n }\n }\n }\n return { content, kind: \"edit\", mtime: 0, original, path: result.filePath }\n }\n default: {\n return undefined\n }\n }\n}\n\n/** Apply a Claude single-Edit swap to the pre-edit content. Returns\n * `undefined` when the edit can't be applied (no original file recorded\n * or the search string doesn't appear in it) — caller falls back to\n * showing just the swap. */\nfunction applyClaudeEdit(\n original: string | undefined,\n oldString: string,\n newString: string,\n replaceAll: boolean | undefined\n): string | undefined {\n if (original === undefined || !original.includes(oldString)) return undefined\n return replaceAll === true\n ? original.split(oldString).join(newString)\n : original.replace(oldString, newString)\n}\n\n/** Reconstruct synthetic pre/post strings from a Claude `structuredPatch`.\n * Hunks are placed at their real line numbers (`oldStart`/`newStart`)\n * with empty-line padding outside, so the diff widget's gutter shows\n * accurate file line numbers — same effect as Claude Code's UI. Lines\n * prefixed \" \"/\" -\" go to the original, \" \"/\" +\" go to modified. */\nfunction synthesizeFromStructuredPatch(\n patch: ClaudeStructuredHunk[] | undefined\n): { original: string; modified: string } | undefined {\n if (!Array.isArray(patch) || patch.length === 0) return undefined\n const origLines: string[] = []\n const modLines: string[] = []\n for (const h of patch) {\n padTo(origLines, h.oldStart)\n padTo(modLines, h.newStart)\n for (const raw of h.lines) {\n // Each entry is a unified-diff row: \" ctx\", \"-removed\", \"+added\".\n const tag = raw[0]\n const body = raw.slice(1)\n if (tag === \"-\") origLines.push(body)\n else if (tag === \"+\") modLines.push(body)\n else {\n origLines.push(body)\n modLines.push(body)\n }\n }\n }\n return { modified: modLines.join(\"\\n\"), original: origLines.join(\"\\n\") }\n}\n\n/** 1-based line indexing in the patch; pad with empty strings up to\n * the target line so subsequent pushes land at the right index. */\nfunction padTo(arr: string[], idx: number): void {\n while (arr.length < idx - 1) arr.push(\"\")\n}\n\n/** Tool result content: Claude allows string OR an array of text/image\n * blocks. Map both into zaly's `Content` shape. */\nfunction toToolResultContent(\n content: string | ClaudeBlock[] | undefined\n): ToolResultPart[\"content\"] {\n if (content === undefined) return \"\"\n if (typeof content === \"string\") return content\n const parts = content\n .map((b) => toUserPart(b))\n .filter((p): p is TextPart | Attachment => p !== undefined)\n return parts.length > 0 ? parts : \"\"\n}\n\nfunction toUserPart(block: ClaudeBlock): TextPart | Attachment | undefined {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n return { text: block.text, type: \"text\" }\n }\n if (block.type === \"image\") {\n return toImagePart(block as Extract<ClaudeBlock, { type: \"image\" }>)\n }\n return undefined\n}\n\nfunction toImagePart(block: Extract<ClaudeBlock, { type: \"image\" }>): ImagePart | undefined {\n const src = block.source\n if (src.type === \"base64\") {\n const mime = src.media_type\n if (mime !== \"image/png\" && mime !== \"image/jpeg\" && mime !== \"image/webp\") return undefined\n return { mime, source: { data: src.data, type: \"base64\" }, type: \"image\" }\n }\n // url-source images: zaly's ImagePart only types png/jpeg/webp mimes,\n // and we don't know the mime from a bare URL here. Skip — the model\n // already saw it in the original Claude session; losing it on import\n // is less bad than fabricating a wrong mime.\n return undefined\n}\n\nfunction toAssistantMessage(\n inner: ClaudeMessage,\n toolCalls: Map<string, ZalyToolCall>\n): Message | undefined {\n const content = inner.content\n if (typeof content === \"string\") {\n return { content, role: \"assistant\" }\n }\n if (!Array.isArray(content)) return undefined\n\n const parts: (TextPart | ReasoningPart | ToolCallPart)[] = []\n for (const block of content) {\n const part = toAssistantPart(block, toolCalls)\n if (part) parts.push(part)\n }\n if (parts.length === 0) return undefined\n return { content: parts, role: \"assistant\" }\n}\n\nfunction toAssistantPart(\n block: ClaudeBlock,\n toolCalls: Map<string, ZalyToolCall>\n): TextPart | ReasoningPart | ToolCallPart | undefined {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n return { text: block.text, type: \"text\" }\n }\n // Reasoning / thinking blocks are intentionally dropped on import.\n // Anthropic requires them to round-trip with the exact original\n // `signature`, and any intermediate processing (truncation, reorder,\n // even just a different request shape) trips a 400 in the next turn.\n // The visible text and tool calls carry the conversation's effective\n // state; thinking is recoverable from re-running, so dropping it is\n // strictly safer than risking signature mismatch errors.\n if (block.type === \"tool_use\" && typeof block.id === \"string\" && typeof block.name === \"string\") {\n const call = toolCalls.get(block.id) ?? { name: block.name, params: block.input }\n return { id: block.id, name: call.name, params: call.params, type: \"tool-call\" }\n }\n return undefined\n}\n"],"mappings":";;;AA4EA,eAAsB,kBACpB,MACA,OAA6B,CAAC,GACI;CAGlC,OAAO,SAAS,IAAI;CACpB,MAAM,UAAU;CAChB,MAAM,QAAQ,KAAK,SAAS,QAAQ,MAAM,WAAW,IAAI,IAAI,MAAM,cAAc,IAAI;CACrF,MAAM,YAAY,iBAAiB,OAAO,OAAO;CAEjD,MAAM,WAAsB,CAAC;CAC7B,KAAK,MAAM,OAAO,OAAO;EACvB,MAAM,MAAM,cAAc,KAAK,SAAS;EACxC,IAAI,CAAC,KAAK;EACV,IAAI,IAAI,SAAS,aAAa;GAC5B,MAAM,QAAQ,YAAY,GAAG;GAC7B,IAAI,OAAO,IAAI,OAAO;IAAE,GAAG,IAAI;IAAM;GAAM;EAC7C;EACA,SAAS,KAAK,GAAG;CACnB;CACA,SAAS,MAAM,GAAG,OAAO,EAAE,MAAM,MAAM,EAAE,MAAM,EAAE;CAKjD,aAAa,QAAQ;CACrB,OAAO,EAAE,SAAS;AACpB;;;;AAKA,SAAS,YAAY,KAAsC;CACzD,MAAM,IAAI,IAAI,SAAS;CACvB,IAAI,CAAC,GAAG,OAAO,KAAA;CACf,MAAM,QAAe;EACnB,OAAO,EAAE,gBAAgB;EACzB,QAAQ,EAAE,iBAAiB;CAC7B;CACA,IAAI,EAAE,4BAA4B,KAAA,GAAW,MAAM,YAAY,EAAE;CACjE,IAAI,EAAE,gCAAgC,KAAA,GAAW,MAAM,aAAa,EAAE;CACtE,OAAO;AACT;;;;;;;;;;;;;;;;AAkHA,eAAe,cAAc,MAAuC;CAClE,MAAM,SAAS,IAAI,YAA0B,IAAI;CACjD,MAAM,yBAAS,IAAI,IAA0B;CAC7C,IAAI;EAGF,IAAI;EACJ,IAAI;EAEJ,QAAQ,QAAQ,MAAM,OAAO,KAAK,OAAO,KAAA,GAAW;GAClD,IAAI,MAAM,SAAS,KAAA,GAAW,OAAO,IAAI,MAAM,MAAM,KAAK;GAC1D,IAAI,gBAAgB,KAAK,KAAK,MAAM,gBAAgB,MAAM;IACxD,cAAc,MAAM;IACpB;GACF;EACF;EACA,IAAI,gBAAgB,KAAA,GAAW,OAAO,CAAC;EAQvC,MAAM,QAAwB,CAAC;EAC/B,IAAI,SAA6B;EACjC,OAAO,WAAW,KAAA,GAAW;GAC3B,IAAI,MAAM,OAAO,IAAI,MAAM;GAC3B,IAAI,CAAC,KAKH,QAAQ,QAAQ,MAAM,OAAO,KAAK,OAAO,KAAA,GAAW;IAClD,IAAI,MAAM,SAAS,KAAA,GAAW,OAAO,IAAI,MAAM,MAAM,KAAK;IAC1D,IAAI,MAAM,SAAS,QAAQ;KACzB,MAAM;KACN;IACF;GACF;GAEF,IAAI,CAAC,KAAK;GAKV,IAAI,IAAI,qBAAqB,MAAM;IACjC,MAAM,KAAK,GAAG;IACd;GACF;GACA,IAAI,gBAAgB,GAAG,KAAK,IAAI,gBAAgB,MAAM,MAAM,KAAK,GAAG;GACpE,SAAS,IAAI;EACf;EACA,OAAO,MAAM,WAAW;CAC1B,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,SAAS,gBAAgB,KAA4B;CACnD,OAAO,IAAI,SAAS,UAAU,IAAI,SAAS;AAC7C;;;;;;;;AASA,eAAe,WAAW,MAAuC;CAC/D,MAAM,SAAS,IAAI,YAA0B,IAAI;CACjD,MAAM,UAA0B,CAAC;CACjC,IAAI;EACF,IAAI;EAEJ,QAAQ,QAAQ,MAAM,OAAO,KAAK,OAAO,KAAA,GACvC,IAAI,gBAAgB,KAAK,KAAK,MAAM,gBAAgB,MAAM,QAAQ,KAAK,KAAK;CAEhF,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;CAEA,OAAO,QAAQ,WAAW;AAC5B;;;;;;AAOA,SAAS,iBACP,OACA,SAC2B;CAC3B,MAAM,sBAAM,IAAI,IAA0B;CAC1C,KAAK,MAAM,OAAO,OAAO;EACvB,IAAI,IAAI,SAAS,aAAa;EAC9B,MAAM,UAAU,IAAI,SAAS;EAC7B,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG;EAC7B,KAAK,MAAM,SAAS,SAClB,IACE,MAAM,SAAS,cACf,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,SAAS,UACtB;GAEA,MAAM,OAAO,QAAQ;IADJ,OAAO,MAAM;IAAO,MAAM,MAAM;GACvB,CAAC,KAAK;IAAE,MAAM,MAAM;IAAM,QAAQ,MAAM;GAAM;GACxE,IAAI,IAAI,MAAM,IAAI,IAAI;EACxB;CAEJ;CACA,OAAO;AACT;;;;;AAMA,SAAS,mBAAmB,MAAoC;CAC9D,MAAM,QAAS,KAAK,SAAS,CAAC;CAC9B,QAAQ,KAAK,MAAb;EACE,KAAK,QACH,OAAO;GAAE,MAAM;GAAQ,QAAQ,UAAU,OAAO,aAAa,MAAM;EAAE;EAEvE,KAAK,SACH,OAAO;GAAE,MAAM;GAAS,QAAQ,UAAU,OAAO,aAAa,MAAM;EAAE;EAExE,KAAK,QAAQ;GACX,MAAM,EAAE,WAAW,YAAY,YAAY,aAAa,GAAG,SAAS;GACpE,OAAO;IACL,MAAM;IACN,QAAQ;KACN,OAAO,CAAC;MAAE,SAAS;MAAY,SAAS;MAAY,YAAY;KAAY,CAAC;KAC7E,MAAM;KACN,GAAG;IACL;GACF;EACF;EACA,KAAK,aAAa;GAChB,MAAM,EAAE,WAAW,OAAO,GAAG,SAAS;GAQtC,OAAO;IAAE,MAAM;IAAQ,QAAQ;KAAE,OAPlB,MAAM,QAAQ,KAAK,IAC9B,MAAM,KAAK,OAAgC;MACzC,SAAS,EAAE;MACX,SAAS,EAAE;MACX,YAAY,EAAE;KAChB,EAAE,IACF,CAAC;KAC2C,MAAM;KAAW,GAAG;IAAK;GAAE;EAC7E;EACA,KAAK,QACH,OAAO;GAAE,MAAM;GAAQ,QAAQ;EAAM;EAEvC,SACE,OAAO;GAAE,MAAM,KAAK,KAAK,YAAY;GAAG,QAAQ;EAAM;CAE1D;AACF;AAEA,SAAS,UACP,KACA,MACA,IACyB;CACzB,IAAI,EAAE,QAAQ,MAAM,OAAO;CAC3B,MAAM,GAAG,OAAO,OAAO,GAAG,SAAS;CACnC,OAAO;GAAG,KAAK;EAAO,GAAG;CAAK;AAChC;;;;;;AAOA,SAAS,aAAa,UAAoC;CACxD,MAAM,uBAAO,IAAI,IAAY;CAE7B,MAAM,0BAAU,IAAI,IAAsB;CAC1C,IAAI,UAAU;CACd,KAAK,MAAM,KAAK,UACd,IAAI,EAAE,SAAS,eAAe,MAAM,QAAQ,EAAE,OAAO,GACnD,KAAK,MAAM,KAAK,EAAE,SAAS;EACzB,IAAI,EAAE,SAAS,aAAa;EAC5B,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,GAAG;GACnB,KAAK,IAAI,EAAE,EAAE;GACb;EACF;EACA,MAAM,QAAQ,GAAG,EAAE,GAAG,GAAG,EAAE;EAC3B,MAAM,OAAO,QAAQ,IAAI,EAAE,EAAE,KAAK,CAAC;EACnC,KAAK,KAAK,KAAK;EACf,QAAQ,IAAI,EAAE,IAAI,IAAI;EACtB,EAAE,KAAK;CACT;MACK,IAAI,EAAE,SAAS,QACpB,KAAK,MAAM,KAAK,EAAE,SAAS;EACzB,MAAM,SAAS,EAAE;EACjB,MAAM,OAAO,QAAQ,IAAI,MAAM;EAC/B,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;EAChC,EAAE,KAAK,KAAK,MAAM;EAClB,IAAI,KAAK,WAAW,GAAG,QAAQ,OAAO,MAAM;CAC9C;AAGN;AAIA,SAAS,cACP,KACA,WACqB;CACrB,MAAM,QAAQ,IAAI;CAClB,IAAI,CAAC,OAAO,OAAO,KAAA;CAEnB,IAAI;CACJ,IAAI,IAAI,SAAS,QAAQ,IAAI,cAAc,OAAO,WAAW,IAAI,aAAa;MACzE,IAAI,IAAI,SAAS,aAAa,IAAI,mBAAmB,OAAO,SAAS;CAC1E,IAAI,CAAC,GAAG,OAAO,KAAA;CAKf,IAAI,IAAI,WAAW;EACjB,MAAM,KAAK,KAAK,MAAM,IAAI,SAAS;EACnC,IAAI,CAAC,OAAO,MAAM,EAAE,GAAG,IAAI;GAAE,GAAG;GAAG;EAAG;CACxC;CAIA,IAAI,IAAI,MAAM,IAAI;EAAE,GAAG;EAAG,IAAI,IAAI;CAAK;CACvC,OAAO;AACT;AAEA,SAAS,cACP,OACA,WACA,eACqB;CACrB,MAAM,UAAU,MAAM;CACtB,IAAI,OAAO,YAAY,UACrB,OAAO;EAAE;EAAS,MAAM;CAAO;CAEjC,IAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,GAAG,OAAO,KAAA;CAG5D,IAAI,QAAQ,OAAO,MAAM,EAAE,SAAS,aAAa,GAAG;EAClD,MAAM,QAAQ,QACX,KAAK,MACJ,iBACE,GACA,WACA,aACF,CACF,CAAC,CACA,QAAQ,MAA2B,MAAM,KAAA,CAAS;EACrD,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;EAC/B,OAAO;GAAE,SAAS;GAAO,MAAM;EAAO;CACxC;CAMA,MAAM,QAAQ,QACX,KAAK,MAAM,WAAW,CAAC,CAAC,CAAC,CACzB,QAAQ,MAAkC,MAAM,KAAA,CAAS;CAC5D,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;CAC/B,OAAO;EAAE,SAAS;EAAO,MAAM;CAAO;AACxC;AAEA,SAAS,iBACP,OACA,WACA,eAC4B;CAC5B,IAAI,OAAO,MAAM,gBAAgB,UAAU,OAAO,KAAA;CAClD,MAAM,OAAO,UAAU,IAAI,MAAM,WAAW,CAAC,EAAE,QAAQ;CACvD,MAAM,OAAO,kBAAkB,MAAM,aAAa;CAClD,OAAO;EACL,SAAS,oBAAoB,MAAM,OAAO;EAC1C,IAAI,MAAM;EACV,SAAS,MAAM,aAAa;EAC5B;EACA,MAAM;EACN,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;CACvC;AACF;;;;;AAMA,SAAS,kBACP,MACA,QACqC;CACrC,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,UAAU,OAAO,KAAA;CAC/D,QAAQ,MAAR;EACE,KAAK,QAAQ;GACX,MAAM,OAAO,OAAO;GACpB,IAAI,CAAC,MAAM,UAAU,OAAO,KAAA;GAC5B,OAAO;IACL,MAAM,KAAK,eAAe,KAAA,KAAa,KAAK,aAAa,KAAK;IAC9D,MAAM;IACN,OAAO,KAAK;IACZ,OAAO;IACP,QAAQ,KAAK;IACb,MAAM,KAAK;GACb;EACF;EACA,KAAK;GACH,IAAI,CAAC,OAAO,UAAU,OAAO,KAAA;GAG7B,OAAO;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,UAAU,OAAO;IACjB,MAAM,OAAO;GACf;EAEF,KAAK,QAAQ;GACX,IAAI,CAAC,OAAO,UAAU,OAAO,KAAA;GAa7B,MAAM,eACJ,OAAO,OAAO,iBAAiB,YAAY,OAAO,aAAa,SAAS,IACpE,OAAO,eACP,KAAA;GACN,IAAI,WAAW,gBAAgB;GAC/B,IAAI,UAAU;GAEd,IAAI,OAAO,OAAO,YAAY,UAC5B,UAAU,OAAO;QACZ,IAAI,OAAO,OAAO,cAAc,YAAY,OAAO,OAAO,cAAc,UAAU;IACvF,MAAM,EAAE,WAAW,cAAc;IACjC,MAAM,UAAU,gBAAgB,cAAc,WAAW,WAAW,OAAO,UAAU;IACrF,IAAI,YAAY,KAAA,GACd,UAAU;SACL;KACL,MAAM,QAAQ,8BAA8B,OAAO,eAAe;KAClE,IAAI,UAAU,KAAA,GAAW;MACvB,WAAW,MAAM;MACjB,UAAU,MAAM;KAClB,OAAO;MACL,WAAW;MACX,UAAU;KACZ;IACF;GACF;GACA,OAAO;IAAE;IAAS,MAAM;IAAQ,OAAO;IAAG;IAAU,MAAM,OAAO;GAAS;EAC5E;EACA,SACE;CAEJ;AACF;;;;;AAMA,SAAS,gBACP,UACA,WACA,WACA,YACoB;CACpB,IAAI,aAAa,KAAA,KAAa,CAAC,SAAS,SAAS,SAAS,GAAG,OAAO,KAAA;CACpE,OAAO,eAAe,OAClB,SAAS,MAAM,SAAS,CAAC,CAAC,KAAK,SAAS,IACxC,SAAS,QAAQ,WAAW,SAAS;AAC3C;;;;;;AAOA,SAAS,8BACP,OACoD;CACpD,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG,OAAO,KAAA;CACxD,MAAM,YAAsB,CAAC;CAC7B,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,KAAK,OAAO;EACrB,MAAM,WAAW,EAAE,QAAQ;EAC3B,MAAM,UAAU,EAAE,QAAQ;EAC1B,KAAK,MAAM,OAAO,EAAE,OAAO;GAEzB,MAAM,MAAM,IAAI;GAChB,MAAM,OAAO,IAAI,MAAM,CAAC;GACxB,IAAI,QAAQ,KAAK,UAAU,KAAK,IAAI;QAC/B,IAAI,QAAQ,KAAK,SAAS,KAAK,IAAI;QACnC;IACH,UAAU,KAAK,IAAI;IACnB,SAAS,KAAK,IAAI;GACpB;EACF;CACF;CACA,OAAO;EAAE,UAAU,SAAS,KAAK,IAAI;EAAG,UAAU,UAAU,KAAK,IAAI;CAAE;AACzE;;;AAIA,SAAS,MAAM,KAAe,KAAmB;CAC/C,OAAO,IAAI,SAAS,MAAM,GAAG,IAAI,KAAK,EAAE;AAC1C;;;AAIA,SAAS,oBACP,SAC2B;CAC3B,IAAI,YAAY,KAAA,GAAW,OAAO;CAClC,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,MAAM,QAAQ,QACX,KAAK,MAAM,WAAW,CAAC,CAAC,CAAC,CACzB,QAAQ,MAAkC,MAAM,KAAA,CAAS;CAC5D,OAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAEA,SAAS,WAAW,OAAuD;CACzE,IAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UACjD,OAAO;EAAE,MAAM,MAAM;EAAM,MAAM;CAAO;CAE1C,IAAI,MAAM,SAAS,SACjB,OAAO,YAAY,KAAgD;AAGvE;AAEA,SAAS,YAAY,OAAuE;CAC1F,MAAM,MAAM,MAAM;CAClB,IAAI,IAAI,SAAS,UAAU;EACzB,MAAM,OAAO,IAAI;EACjB,IAAI,SAAS,eAAe,SAAS,gBAAgB,SAAS,cAAc,OAAO,KAAA;EACnF,OAAO;GAAE;GAAM,QAAQ;IAAE,MAAM,IAAI;IAAM,MAAM;GAAS;GAAG,MAAM;EAAQ;CAC3E;AAMF;AAEA,SAAS,mBACP,OACA,WACqB;CACrB,MAAM,UAAU,MAAM;CACtB,IAAI,OAAO,YAAY,UACrB,OAAO;EAAE;EAAS,MAAM;CAAY;CAEtC,IAAI,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO,KAAA;CAEpC,MAAM,QAAqD,CAAC;CAC5D,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,OAAO,gBAAgB,OAAO,SAAS;EAC7C,IAAI,MAAM,MAAM,KAAK,IAAI;CAC3B;CACA,IAAI,MAAM,WAAW,GAAG,OAAO,KAAA;CAC/B,OAAO;EAAE,SAAS;EAAO,MAAM;CAAY;AAC7C;AAEA,SAAS,gBACP,OACA,WACqD;CACrD,IAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UACjD,OAAO;EAAE,MAAM,MAAM;EAAM,MAAM;CAAO;CAS1C,IAAI,MAAM,SAAS,cAAc,OAAO,MAAM,OAAO,YAAY,OAAO,MAAM,SAAS,UAAU;EAC/F,MAAM,OAAO,UAAU,IAAI,MAAM,EAAE,KAAK;GAAE,MAAM,MAAM;GAAM,QAAQ,MAAM;EAAM;EAChF,OAAO;GAAE,IAAI,MAAM;GAAI,MAAM,KAAK;GAAM,QAAQ,KAAK;GAAQ,MAAM;EAAY;CACjF;AAEF"}