zidane 5.13.26 → 5.14.0

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 (145) hide show
  1. package/dist/{acp-CjAoILwj.js → acp-DgKEJH3P.js} +7 -7
  2. package/dist/{acp-CjAoILwj.js.map → acp-DgKEJH3P.js.map} +1 -1
  3. package/dist/acp-cli.js +11 -6
  4. package/dist/acp-cli.js.map +1 -1
  5. package/dist/acp.d.ts +2 -2
  6. package/dist/acp.js +1 -1
  7. package/dist/{tools-B32lZXLE.js → agent-B-cKanSC.js} +8035 -9459
  8. package/dist/agent-B-cKanSC.js.map +1 -0
  9. package/dist/{agent-DQIL5BFk.d.ts → agent-BAgOgS37.d.ts} +367 -208
  10. package/dist/agent-BAgOgS37.d.ts.map +1 -0
  11. package/dist/agent.d.ts +3 -0
  12. package/dist/agent.js +5 -0
  13. package/dist/anthropic-RGqsJlC8.js +807 -0
  14. package/dist/anthropic-RGqsJlC8.js.map +1 -0
  15. package/dist/{atomic-write-Bgtr5JPu.js → atomic-write-B7zr1ddF.js} +2 -2
  16. package/dist/{atomic-write-Bgtr5JPu.js.map → atomic-write-B7zr1ddF.js.map} +1 -1
  17. package/dist/{auth-DPrSP1YV.js → auth-C-ms5N2x.js} +33 -5
  18. package/dist/{auth-DPrSP1YV.js.map → auth-C-ms5N2x.js.map} +1 -1
  19. package/dist/cache-telemetry-BKAwsNxm.js +169 -0
  20. package/dist/cache-telemetry-BKAwsNxm.js.map +1 -0
  21. package/dist/chat/pure.d.ts +3 -3
  22. package/dist/chat/pure.js +1 -1
  23. package/dist/chat.d.ts +21 -7
  24. package/dist/chat.d.ts.map +1 -1
  25. package/dist/chat.js +30 -5
  26. package/dist/chat.js.map +1 -1
  27. package/dist/contexts/daytona.d.ts +1 -1
  28. package/dist/contexts/docker.js +0 -1
  29. package/dist/contexts/docker.js.map +1 -1
  30. package/dist/contexts/e2b.d.ts +1 -1
  31. package/dist/errors-CkTvg97v.d.ts +203 -0
  32. package/dist/errors-CkTvg97v.d.ts.map +1 -0
  33. package/dist/errors.d.ts +3 -0
  34. package/dist/errors.js +3 -0
  35. package/dist/eval.d.ts +1 -1
  36. package/dist/eval.js +2 -2
  37. package/dist/headless.d.ts +1 -1
  38. package/dist/headless.js +406 -1
  39. package/dist/headless.js.map +1 -0
  40. package/dist/{index-YY3vp2F2.d.ts → index-CLuLFFLa.d.ts} +88 -418
  41. package/dist/index-CLuLFFLa.d.ts.map +1 -0
  42. package/dist/{index-C5qw3pj7.d.ts → index-CgBIl2mX.d.ts} +2 -2
  43. package/dist/{index-C5qw3pj7.d.ts.map → index-CgBIl2mX.d.ts.map} +1 -1
  44. package/dist/index.d.ts +9 -5
  45. package/dist/index.js +113 -167
  46. package/dist/index.js.map +1 -1
  47. package/dist/{logger-C2ngE8CY.d.ts → logger-CJ-lpKPy.d.ts} +2 -2
  48. package/dist/{logger-C2ngE8CY.d.ts.map → logger-CJ-lpKPy.d.ts.map} +1 -1
  49. package/dist/mcp.d.ts +1 -1
  50. package/dist/messages-YZQLKaz2.js +1157 -0
  51. package/dist/messages-YZQLKaz2.js.map +1 -0
  52. package/dist/oauth-8LqbeI_Q.js +292 -0
  53. package/dist/oauth-8LqbeI_Q.js.map +1 -0
  54. package/dist/{messages-Iy_vEycX.js → openai-compat-0Y7jcncn.js} +27 -1379
  55. package/dist/openai-compat-0Y7jcncn.js.map +1 -0
  56. package/dist/output/stream-json.d.ts +2 -2
  57. package/dist/output/stream-json.js +1 -1
  58. package/dist/output/terminal.d.ts +2 -2
  59. package/dist/{presets-B9nDwmaz.js → presets-Pr02ZNcy.js} +3 -2
  60. package/dist/{presets-B9nDwmaz.js.map → presets-Pr02ZNcy.js.map} +1 -1
  61. package/dist/presets.d.ts +2 -2
  62. package/dist/presets.js +1 -1
  63. package/dist/prompt.d.ts +2 -0
  64. package/dist/prompt.js +210 -0
  65. package/dist/prompt.js.map +1 -0
  66. package/dist/providers/anthropic.d.ts +2 -0
  67. package/dist/providers/anthropic.js +2 -0
  68. package/dist/providers/arcee.d.ts +2 -0
  69. package/dist/providers/arcee.js +42 -0
  70. package/dist/providers/arcee.js.map +1 -0
  71. package/dist/providers/baseten.d.ts +2 -0
  72. package/dist/providers/baseten.js +66 -0
  73. package/dist/providers/baseten.js.map +1 -0
  74. package/dist/providers/cerebras.d.ts +2 -0
  75. package/dist/providers/cerebras.js +31 -0
  76. package/dist/providers/cerebras.js.map +1 -0
  77. package/dist/providers/local.d.ts +2 -0
  78. package/dist/providers/local.js +44 -0
  79. package/dist/providers/local.js.map +1 -0
  80. package/dist/providers/openai-compat.d.ts +2 -0
  81. package/dist/providers/openai-compat.js +2 -0
  82. package/dist/providers/openai.d.ts +2 -0
  83. package/dist/providers/openai.js +504 -0
  84. package/dist/providers/openai.js.map +1 -0
  85. package/dist/providers/openrouter.d.ts +2 -0
  86. package/dist/providers/openrouter.js +52 -0
  87. package/dist/providers/openrouter.js.map +1 -0
  88. package/dist/providers/xai.d.ts +2 -0
  89. package/dist/providers/xai.js +2 -0
  90. package/dist/providers-DgmdYGKm.js +168 -0
  91. package/dist/providers-DgmdYGKm.js.map +1 -0
  92. package/dist/providers.d.ts +1 -1
  93. package/dist/providers.js +10 -2
  94. package/dist/restate.d.ts +1 -1
  95. package/dist/run-summary-DAnBZnrt.d.ts +249 -0
  96. package/dist/run-summary-DAnBZnrt.d.ts.map +1 -0
  97. package/dist/run-summary-nD3IQ4ZQ.js +253 -0
  98. package/dist/run-summary-nD3IQ4ZQ.js.map +1 -0
  99. package/dist/session/sqlite.d.ts +1 -1
  100. package/dist/{session-CiA1_8SB.js → session-CqrAFAKJ.js} +2 -2
  101. package/dist/{session-CiA1_8SB.js.map → session-CqrAFAKJ.js.map} +1 -1
  102. package/dist/session.d.ts +1 -1
  103. package/dist/session.js +2 -2
  104. package/dist/skills.d.ts +2 -2
  105. package/dist/system-prompt-BlsdjcUi.d.ts +139 -0
  106. package/dist/system-prompt-BlsdjcUi.d.ts.map +1 -0
  107. package/dist/timeout-CpFm0jJ6.d.ts +10 -0
  108. package/dist/timeout-CpFm0jJ6.d.ts.map +1 -0
  109. package/dist/timeout-kGGSXagK.js +84 -0
  110. package/dist/timeout-kGGSXagK.js.map +1 -0
  111. package/dist/{tool-formatters-w5oaoCS4.d.ts → tool-formatters-Ch8oRpYR.d.ts} +2 -2
  112. package/dist/{tool-formatters-w5oaoCS4.d.ts.map → tool-formatters-Ch8oRpYR.d.ts.map} +1 -1
  113. package/dist/tools/fetch-url.d.ts +1 -1
  114. package/dist/tools/web-search.d.ts +1 -1
  115. package/dist/tools-00_Nqazv.js +1689 -0
  116. package/dist/tools-00_Nqazv.js.map +1 -0
  117. package/dist/tools.d.ts +2 -2
  118. package/dist/tools.js +2 -1
  119. package/dist/{transcript-anchors-BUtOd-qa.js → transcript-anchors-BUmvPnis.js} +10 -9
  120. package/dist/{transcript-anchors-BUtOd-qa.js.map → transcript-anchors-BUmvPnis.js.map} +1 -1
  121. package/dist/{transcript-anchors-BJ-ESWnm.d.ts → transcript-anchors-CL9UM_M4.d.ts} +4 -4
  122. package/dist/{transcript-anchors-BJ-ESWnm.d.ts.map → transcript-anchors-CL9UM_M4.d.ts.map} +1 -1
  123. package/dist/tui.d.ts +3 -3
  124. package/dist/tui.js +38 -13
  125. package/dist/tui.js.map +1 -1
  126. package/dist/{turn-operations-BSSQHNq1.d.ts → turn-operations-Cj6v_ypW.d.ts} +3 -3
  127. package/dist/{turn-operations-BSSQHNq1.d.ts.map → turn-operations-Cj6v_ypW.d.ts.map} +1 -1
  128. package/dist/{turn-operations-B_XeuNp-.js → turn-operations-tG0vuBNc.js} +2 -2
  129. package/dist/{turn-operations-B_XeuNp-.js.map → turn-operations-tG0vuBNc.js.map} +1 -1
  130. package/dist/types-CyVGdbia.js.map +1 -1
  131. package/dist/types.d.ts +4 -3
  132. package/dist/xai-tPdbAGkq.js +379 -0
  133. package/dist/xai-tPdbAGkq.js.map +1 -0
  134. package/docs/ARCHITECTURE.md +4 -2
  135. package/docs/CHAT.md +4 -0
  136. package/docs/SKILL.md +31 -5
  137. package/package.json +63 -1
  138. package/dist/agent-DQIL5BFk.d.ts.map +0 -1
  139. package/dist/headless-2SH7dvoW.js +0 -656
  140. package/dist/headless-2SH7dvoW.js.map +0 -1
  141. package/dist/index-YY3vp2F2.d.ts.map +0 -1
  142. package/dist/messages-Iy_vEycX.js.map +0 -1
  143. package/dist/providers-BMw1D_lj.js +0 -2332
  144. package/dist/providers-BMw1D_lj.js.map +0 -1
  145. package/dist/tools-B32lZXLE.js.map +0 -1
@@ -0,0 +1,1157 @@
1
+ import { t as fnv1aBase36 } from "./hash-DJRzmcPI.js";
2
+ import { i as documentBlockMarker, n as assertResolvedMediaBlock } from "./types-CyVGdbia.js";
3
+ import { i as TOOL_RESULTS_TAG, t as ASSISTANT_TOOL_CALLS_TAG } from "./openai-compat-0Y7jcncn.js";
4
+ import { n as reconcileImageMediaType, t as audioFormatFromMediaType } from "./media-sniff-Bn76JxAu.js";
5
+ //#region src/session/messages.ts
6
+ /**
7
+ * Decode an Anthropic `tool_result.content` wire value back to zidane's canonical
8
+ * `string | ToolResultContent[]` shape.
9
+ *
10
+ * Anthropic accepts three shapes in the wire: `string`, `Array<{type:'text',text}>`,
11
+ * or `Array<{type:'text'|'image', ...}>`. We collapse to `string` only when every
12
+ * block is text to keep simple cases readable.
13
+ */
14
+ function decodeAnthropicToolResultContent(content) {
15
+ if (typeof content === "string") return content;
16
+ if (!Array.isArray(content)) return JSON.stringify(content);
17
+ const blocks = [];
18
+ for (const raw of content) {
19
+ if (!raw || typeof raw !== "object") continue;
20
+ const b = raw;
21
+ if (b.type === "text" && typeof b.text === "string") {
22
+ blocks.push({
23
+ type: "text",
24
+ text: b.text
25
+ });
26
+ continue;
27
+ }
28
+ if (b.type === "image" && b.source && typeof b.source === "object") {
29
+ const src = b.source;
30
+ if (src.type === "base64" && typeof src.data === "string" && typeof src.media_type === "string") {
31
+ blocks.push({
32
+ type: "image",
33
+ mediaType: src.media_type,
34
+ data: src.data
35
+ });
36
+ continue;
37
+ }
38
+ }
39
+ blocks.push({
40
+ type: "text",
41
+ text: JSON.stringify(raw)
42
+ });
43
+ }
44
+ if (blocks.length === 0) return "";
45
+ if (!blocks.some((b) => b.type !== "text")) return blocks.map((b) => b.text).join("\n");
46
+ return blocks;
47
+ }
48
+ /**
49
+ * Build an Anthropic `image.source` block, reconciling the declared media
50
+ * type against the actual magic bytes.
51
+ *
52
+ * Anthropic's API rejects the entire request when `media_type` disagrees
53
+ * with the payload (`messages.N.content.K.image.source.base64: The image
54
+ * was specified using the X media type, but the image appears to be a
55
+ * Y image`). The upstream sources of `{type:'image'}` blocks each sniff
56
+ * before producing the block (read_file / MCP / TUI paste), but this
57
+ * wire-boundary reconciliation is a final safety net for any future code
58
+ * path or host-supplied prompt part that slips through without sniffing.
59
+ *
60
+ * The reconciler is conservative: it overrides only when the magic bytes
61
+ * positively identify a *different* known format. Unknown / too-short
62
+ * payloads pass `mediaType` through unchanged.
63
+ */
64
+ function anthropicImageSource(mediaType, data) {
65
+ return {
66
+ type: "base64",
67
+ media_type: reconcileImageMediaType(mediaType, data),
68
+ data
69
+ };
70
+ }
71
+ function anthropicDocumentBlocks(doc) {
72
+ if (doc.encoding === "base64" && doc.mediaType === "application/pdf") return [{
73
+ type: "document",
74
+ source: {
75
+ type: "base64",
76
+ media_type: "application/pdf",
77
+ data: doc.data
78
+ },
79
+ ...doc.name ? { title: doc.name } : {}
80
+ }];
81
+ return [{
82
+ type: "text",
83
+ text: `${doc.name ? `<attachment name="${doc.name}" media_type="${doc.mediaType}" encoding="${doc.encoding}">` : `<attachment media_type="${doc.mediaType}" encoding="${doc.encoding}">`}\n${doc.data}\n</attachment>`
84
+ }];
85
+ }
86
+ function documentMarker(doc) {
87
+ return documentBlockMarker(doc, "document attached");
88
+ }
89
+ function isContentRefBlock(block) {
90
+ return (block.type === "image" || block.type === "document") && typeof block.ref === "string";
91
+ }
92
+ function unresolvedContentRefError(block) {
93
+ return /* @__PURE__ */ new Error(`Cannot project content ref "${block.ref}" to provider wire messages without resolveContentRef.`);
94
+ }
95
+ /**
96
+ * Coerce a tool-call id to Anthropic's wire constraint `^[a-zA-Z0-9_-]+$`.
97
+ *
98
+ * Sessions started under another provider (OpenAI-compat / OpenRouter /
99
+ * Cerebras) or carrying MCP-namespaced calls can hold ids with `.`, `:`,
100
+ * `/` or other characters that 400 the request the moment the user switches
101
+ * to an Anthropic model (`messages.N.content.M.tool_use.id: String should
102
+ * match pattern '^[a-zA-Z0-9_-]+$'`).
103
+ *
104
+ * The mapping is deterministic — every disallowed char becomes `_` — so a
105
+ * `tool_use` and its paired `tool_result` (sanitized identically below) keep
106
+ * matching ids and the pairing survives. When sanitization actually changed
107
+ * the id, a short hash of the ORIGINAL id is appended so distinct inputs
108
+ * that would collapse to the same replacement (`call.1` vs `call_1` vs
109
+ * `call:1`) stay distinct on the wire. Empty ids get a stable placeholder
110
+ * so the block stays well-formed.
111
+ */
112
+ const ANTHROPIC_TOOL_ID_INVALID = /[^\w-]/g;
113
+ function sanitizeAnthropicToolId(id) {
114
+ if (!id) return "tool_0";
115
+ const sanitized = id.replace(ANTHROPIC_TOOL_ID_INVALID, "_");
116
+ if (sanitized === id) return id;
117
+ return `${sanitized}_${fnv1aBase36(id)}`;
118
+ }
119
+ /**
120
+ * Encode zidane's canonical `string | ToolResultContent[]` shape to Anthropic's
121
+ * wire format for `tool_result.content`.
122
+ */
123
+ function encodeAnthropicToolResultContent(output) {
124
+ if (typeof output === "string") return output;
125
+ return output.map((b) => {
126
+ if (b.type === "text") return {
127
+ type: "text",
128
+ text: b.text
129
+ };
130
+ if (b.type === "document") {
131
+ assertResolvedMediaBlock(b, "Anthropic wire messages");
132
+ return {
133
+ type: "text",
134
+ text: documentMarker(b)
135
+ };
136
+ }
137
+ if (b.type === "audio") return {
138
+ type: "text",
139
+ text: `[audio omitted: ${b.mediaType}]`
140
+ };
141
+ if (b.type === "video") return {
142
+ type: "text",
143
+ text: `[video omitted: ${b.mediaType}]`
144
+ };
145
+ assertResolvedMediaBlock(b, "Anthropic wire messages");
146
+ return {
147
+ type: "image",
148
+ source: anthropicImageSource(b.mediaType, b.data)
149
+ };
150
+ });
151
+ }
152
+ function fromAnthropic(msg) {
153
+ const role = msg.role;
154
+ const content = [];
155
+ if (typeof msg.content === "string") {
156
+ content.push({
157
+ type: "text",
158
+ text: msg.content
159
+ });
160
+ return {
161
+ role,
162
+ content
163
+ };
164
+ }
165
+ if (Array.isArray(msg.content)) for (const block of msg.content) {
166
+ if (!block || typeof block !== "object") continue;
167
+ const b = block;
168
+ if (b.type === "text") content.push({
169
+ type: "text",
170
+ text: b.text
171
+ });
172
+ else if (b.type === "image") {
173
+ const source = b.source;
174
+ if (source?.type === "base64") content.push({
175
+ type: "image",
176
+ mediaType: source.media_type,
177
+ data: source.data
178
+ });
179
+ } else if (b.type === "document") {
180
+ const source = b.source;
181
+ if (source?.type === "base64" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
182
+ type: "document",
183
+ mediaType: source.media_type,
184
+ data: source.data,
185
+ encoding: "base64",
186
+ ...typeof b.title === "string" ? { name: b.title } : {}
187
+ });
188
+ else if (source?.type === "text" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
189
+ type: "document",
190
+ mediaType: source.media_type,
191
+ data: source.data,
192
+ encoding: "text",
193
+ ...typeof b.title === "string" ? { name: b.title } : {}
194
+ });
195
+ } else if (b.type === "tool_use") content.push({
196
+ type: "tool_call",
197
+ id: b.id,
198
+ name: b.name,
199
+ input: b.input
200
+ });
201
+ else if (b.type === "tool_result") {
202
+ const output = decodeAnthropicToolResultContent(b.content);
203
+ const block = {
204
+ type: "tool_result",
205
+ callId: b.tool_use_id,
206
+ output
207
+ };
208
+ if (b.is_error === true) block.isError = true;
209
+ content.push(block);
210
+ } else if (b.type === "thinking") {
211
+ const block = {
212
+ type: "thinking",
213
+ text: b.thinking ?? ""
214
+ };
215
+ if (typeof b.signature === "string") {
216
+ block.signature = b.signature;
217
+ block.signatureProducer = "anthropic";
218
+ }
219
+ content.push(block);
220
+ } else if (b.type === "redacted_thinking") content.push({
221
+ type: "redacted_thinking",
222
+ data: b.data ?? ""
223
+ });
224
+ else if (b.type === "server_tool_use") content.push({
225
+ type: "server_tool_use",
226
+ producer: "anthropic",
227
+ id: b.id ?? "",
228
+ name: b.name ?? "",
229
+ input: b.input ?? {}
230
+ });
231
+ else if (b.type === "web_search_tool_result") content.push({
232
+ type: "server_tool_result",
233
+ producer: "anthropic",
234
+ toolUseId: b.tool_use_id ?? "",
235
+ toolName: "web_search",
236
+ content: b.content
237
+ });
238
+ }
239
+ return {
240
+ role,
241
+ content
242
+ };
243
+ }
244
+ function fromOpenAI(msg) {
245
+ const role = msg.role;
246
+ const content = [];
247
+ const c = msg.content;
248
+ if (c == null) return {
249
+ role,
250
+ content
251
+ };
252
+ if (typeof c === "string") {
253
+ content.push({
254
+ type: "text",
255
+ text: c
256
+ });
257
+ return {
258
+ role,
259
+ content
260
+ };
261
+ }
262
+ if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_assistant_tc__") {
263
+ const tagged = c;
264
+ if (typeof tagged.text === "string" && tagged.text) content.push({
265
+ type: "text",
266
+ text: tagged.text
267
+ });
268
+ if (Array.isArray(tagged.tool_calls)) for (const raw of tagged.tool_calls) {
269
+ if (!raw || typeof raw !== "object") continue;
270
+ const tc = raw;
271
+ const rawArgs = tc.function?.arguments;
272
+ let input = {};
273
+ if (typeof rawArgs === "string") try {
274
+ input = JSON.parse(rawArgs);
275
+ } catch {
276
+ input = {};
277
+ }
278
+ else if (rawArgs && typeof rawArgs === "object") input = rawArgs;
279
+ content.push({
280
+ type: "tool_call",
281
+ id: tc.id ?? "",
282
+ name: tc.function?.name ?? "",
283
+ input
284
+ });
285
+ }
286
+ return {
287
+ role,
288
+ content
289
+ };
290
+ }
291
+ if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_tool_results__") {
292
+ const tagged = c;
293
+ if (Array.isArray(tagged.results)) for (const raw of tagged.results) {
294
+ if (!raw || typeof raw !== "object") continue;
295
+ const r = raw;
296
+ content.push({
297
+ type: "tool_result",
298
+ callId: r.tool_call_id ?? "",
299
+ output: r.content ?? ""
300
+ });
301
+ }
302
+ if (typeof tagged.text === "string" && tagged.text) content.push({
303
+ type: "text",
304
+ text: tagged.text
305
+ });
306
+ return {
307
+ role,
308
+ content
309
+ };
310
+ }
311
+ if (Array.isArray(c)) {
312
+ for (const block of c) {
313
+ if (!block || typeof block !== "object") continue;
314
+ const b = block;
315
+ if (b.type === "text") content.push({
316
+ type: "text",
317
+ text: b.text
318
+ });
319
+ else if (b.type === "image_url") {
320
+ const imageUrl = b.image_url?.url;
321
+ if (imageUrl?.startsWith("data:")) {
322
+ const [meta, data] = imageUrl.slice(5).split(",", 2);
323
+ const mediaType = meta.replace(";base64", "");
324
+ content.push({
325
+ type: "image",
326
+ mediaType,
327
+ data
328
+ });
329
+ }
330
+ }
331
+ }
332
+ return {
333
+ role,
334
+ content
335
+ };
336
+ }
337
+ return {
338
+ role,
339
+ content
340
+ };
341
+ }
342
+ function toAnthropic(msg) {
343
+ const blocks = msg.content.filter((b) => {
344
+ if (b.type === "provider_reasoning") return false;
345
+ if (b.type !== "thinking") return true;
346
+ if (b.signatureProducer === "openai") return false;
347
+ if (!b.signature) return false;
348
+ return true;
349
+ }).flatMap((block) => {
350
+ switch (block.type) {
351
+ case "text": return [{
352
+ type: "text",
353
+ text: block.text
354
+ }];
355
+ case "image":
356
+ assertResolvedMediaBlock(block, "Anthropic wire messages");
357
+ return [{
358
+ type: "image",
359
+ source: anthropicImageSource(block.mediaType, block.data)
360
+ }];
361
+ case "audio": return [{
362
+ type: "text",
363
+ text: `[audio omitted: ${block.mediaType}]`
364
+ }];
365
+ case "video": return [{
366
+ type: "text",
367
+ text: `[video omitted: ${block.mediaType}]`
368
+ }];
369
+ case "document":
370
+ assertResolvedMediaBlock(block, "Anthropic wire messages");
371
+ return anthropicDocumentBlocks(block);
372
+ case "tool_call": return [{
373
+ type: "tool_use",
374
+ id: sanitizeAnthropicToolId(block.id),
375
+ name: block.name,
376
+ input: block.input
377
+ }];
378
+ case "tool_result": {
379
+ const out = {
380
+ type: "tool_result",
381
+ tool_use_id: sanitizeAnthropicToolId(block.callId),
382
+ content: encodeAnthropicToolResultContent(block.output)
383
+ };
384
+ if (block.isError) out.is_error = true;
385
+ return [out, ...(typeof block.output === "string" ? [] : block.output.filter((part) => part.type === "document" && typeof part.data === "string")).flatMap(anthropicDocumentBlocks)];
386
+ }
387
+ case "thinking": return [{
388
+ type: "thinking",
389
+ thinking: block.text,
390
+ signature: block.signature ?? ""
391
+ }];
392
+ case "redacted_thinking": return [{
393
+ type: "redacted_thinking",
394
+ data: block.data
395
+ }];
396
+ case "server_tool_use": return [{
397
+ type: "server_tool_use",
398
+ id: sanitizeAnthropicToolId(block.id),
399
+ name: block.name,
400
+ input: block.input
401
+ }];
402
+ case "server_tool_result": return [{
403
+ type: "web_search_tool_result",
404
+ tool_use_id: sanitizeAnthropicToolId(block.toolUseId),
405
+ content: block.content
406
+ }];
407
+ default: return [{
408
+ type: "text",
409
+ text: ""
410
+ }];
411
+ }
412
+ });
413
+ const first = blocks[0];
414
+ if (blocks.length === 1 && first.type === "text") return {
415
+ role: msg.role,
416
+ content: first.text
417
+ };
418
+ return {
419
+ role: msg.role,
420
+ content: blocks
421
+ };
422
+ }
423
+ function toOpenAI(msg) {
424
+ const toolCalls = msg.content.filter((b) => b.type === "tool_call");
425
+ const toolResults = msg.content.filter((b) => b.type === "tool_result");
426
+ const textBlocks = msg.content.filter((b) => b.type === "text");
427
+ const imageBlocks = msg.content.filter((b) => b.type === "image");
428
+ const audioBlocks = msg.content.filter((b) => b.type === "audio");
429
+ const videoBlocks = msg.content.filter((b) => b.type === "video");
430
+ const documentBlocks = msg.content.filter((b) => b.type === "document");
431
+ if (toolResults.length > 0) {
432
+ const siblingText = textBlocks.map((b) => b.text).join("\n");
433
+ return {
434
+ role: msg.role,
435
+ content: {
436
+ _tag: TOOL_RESULTS_TAG,
437
+ results: toolResults.map((tr) => ({
438
+ tool_call_id: tr.callId,
439
+ content: tr.output
440
+ })),
441
+ ...siblingText ? { text: siblingText } : {}
442
+ }
443
+ };
444
+ }
445
+ if (toolCalls.length > 0) {
446
+ const textContent = textBlocks.length > 0 ? textBlocks[0].text : null;
447
+ return {
448
+ role: msg.role,
449
+ content: {
450
+ _tag: ASSISTANT_TOOL_CALLS_TAG,
451
+ text: textContent,
452
+ tool_calls: toolCalls.map((tc) => ({
453
+ id: tc.id,
454
+ type: "function",
455
+ function: {
456
+ name: tc.name,
457
+ arguments: JSON.stringify(tc.input)
458
+ }
459
+ }))
460
+ }
461
+ };
462
+ }
463
+ if (imageBlocks.length > 0 || audioBlocks.length > 0 || videoBlocks.length > 0 || documentBlocks.length > 0) {
464
+ const parts = [];
465
+ for (const img of imageBlocks) {
466
+ assertResolvedMediaBlock(img, "OpenAI-compatible wire messages");
467
+ parts.push({
468
+ type: "image_url",
469
+ image_url: { url: `data:${img.mediaType};base64,${img.data}` }
470
+ });
471
+ }
472
+ for (const b of audioBlocks) parts.push({
473
+ type: "input_audio",
474
+ input_audio: {
475
+ data: b.data,
476
+ format: audioFormatFromMediaType(b.mediaType)
477
+ }
478
+ });
479
+ for (const b of videoBlocks) parts.push({
480
+ type: "video_url",
481
+ video_url: { url: `data:${b.mediaType};base64,${b.data}` }
482
+ });
483
+ for (const b of documentBlocks) {
484
+ assertResolvedMediaBlock(b, "OpenAI-compatible wire messages");
485
+ parts.push({
486
+ type: "text",
487
+ text: documentMarker(b)
488
+ });
489
+ }
490
+ for (const b of textBlocks) parts.push({
491
+ type: "text",
492
+ text: b.text
493
+ });
494
+ return {
495
+ role: msg.role,
496
+ content: parts
497
+ };
498
+ }
499
+ if (textBlocks.length === 1) return {
500
+ role: msg.role,
501
+ content: textBlocks[0].text
502
+ };
503
+ if (textBlocks.length > 1) return {
504
+ role: msg.role,
505
+ content: textBlocks.map((b) => ({
506
+ type: "text",
507
+ text: b.text
508
+ }))
509
+ };
510
+ return {
511
+ role: msg.role,
512
+ content: null
513
+ };
514
+ }
515
+ /**
516
+ * Placeholder content inserted into a synthetic `tool_result` when the harness
517
+ * has to repair an orphan `tool_use`. Exported so downstream consumers
518
+ * (training-data collectors, HFI submission) can reject any payload containing
519
+ * it — the marker satisfies the wire-level pairing contract structurally but
520
+ * the content itself is fake and would poison fine-tuning data.
521
+ */
522
+ const SYNTHETIC_TOOL_RESULT_PLACEHOLDER = "[Tool result missing due to internal error]";
523
+ /**
524
+ * Replacement text for an assistant message whose every content block was
525
+ * stripped during pairing repair (e.g. the only blocks were orphan
526
+ * `tool_call`s). Providers reject empty `content` arrays — the marker keeps
527
+ * the turn shape valid while signaling "this assistant turn lost its
528
+ * outputs" to the model so it can recover.
529
+ */
530
+ const TOOL_USE_INTERRUPTED_MARKER = "[Tool use interrupted]";
531
+ /**
532
+ * Replacement text for a user message whose every content block was a
533
+ * `tool_result` with no matching upstream `tool_call` (e.g. the assistant
534
+ * pair was stripped by an earlier compaction). Same role as
535
+ * {@link TOOL_USE_INTERRUPTED_MARKER} on the user side.
536
+ */
537
+ const ORPHANED_TOOL_RESULT_MARKER = "[Orphaned tool result removed due to conversation resume]";
538
+ /**
539
+ * Defensive repair pass that rewrites a message list so it satisfies the
540
+ * wire-level `tool_use` ↔ `tool_result` adjacency contract every modern
541
+ * provider enforces.
542
+ *
543
+ * Anthropic 400s on orphans with `'tool_use' ids were found without
544
+ * 'tool_result' blocks immediately after` (and its inverse, `tool_result
545
+ * must be preceded by a tool_call with the same toolCallId`). OpenAI's
546
+ * Chat Completions API rejects most mismatches with a 400 too.
547
+ *
548
+ * Six repair modes — modeled after Anthropic's Claude Code defenses:
549
+ *
550
+ * | # | Corruption | Repair |
551
+ * |---|------------|--------|
552
+ * | 1 | assistant `tool_use` whose next user msg lacks a matching `tool_result` | **Prepend** a synthetic `tool_result` block carrying {@link SYNTHETIC_TOOL_RESULT_PLACEHOLDER} with `isError: true` |
553
+ * | 2 | assistant `tool_use` followed by nothing (or by a non-user msg) | **Insert** a new synthetic user message with the same placeholder |
554
+ * | 3 | user `tool_result` with no preceding assistant `tool_call` | **Strip** the orphan block; if it empties the msg, replace with {@link ORPHANED_TOOL_RESULT_MARKER} text block |
555
+ * | 4 | duplicate `tool_call.id` across assistant messages (CC-1212) | **Strip** later instances + their matching tool_results |
556
+ * | 5 | duplicate `tool_result.callId` within a single user message | **Dedupe** by `callId`, keep first |
557
+ * | 6 | assistant message emptied by mode-4 stripping | **Replace** content with {@link TOOL_USE_INTERRUPTED_MARKER} text block |
558
+ *
559
+ * **Repair, not drop.** Earlier versions of this pass simply dropped orphan
560
+ * blocks, which (a) silently rewrote history the model had reasoned over and
561
+ * (b) cascaded — dropping an assistant tool_call would orphan its
562
+ * tool_result, which would then get dropped too, removing any trace of
563
+ * "what the model tried to do" from the transcript. The repair-based pass
564
+ * preserves the model's tool_use shape and patches the dangling result with
565
+ * an `is_error` placeholder so the model sees "I tried X, the result was
566
+ * lost" and can retry intelligently.
567
+ *
568
+ * Adjacency contract: `tool_result` blocks must live in the user message
569
+ * IMMEDIATELY following the assistant message that emitted the matching
570
+ * `tool_use`. The pass enforces strict adjacency — a tool_result two
571
+ * messages downstream of its tool_call is still an orphan.
572
+ *
573
+ * Idempotent: returns the input reference unchanged when no repairs were
574
+ * necessary. Re-running on already-repaired output is a no-op.
575
+ *
576
+ * Pure: does not mutate input messages or their content arrays — every
577
+ * repair allocates a fresh array / object.
578
+ */
579
+ function ensureToolResultPairing(messages, options = {}) {
580
+ if (messages.length === 0) return messages;
581
+ const fireRepair = options.onRepair ?? (() => {});
582
+ const firstSeenAt = /* @__PURE__ */ new Map();
583
+ for (let i = 0; i < messages.length; i++) {
584
+ const msg = messages[i];
585
+ if (msg.role !== "assistant") continue;
586
+ for (const block of msg.content) if (block.type === "tool_call" && !firstSeenAt.has(block.id)) firstSeenAt.set(block.id, i);
587
+ }
588
+ const pendingOrphansByUserIndex = /* @__PURE__ */ new Map();
589
+ let changed = false;
590
+ const markChanged = () => {
591
+ changed = true;
592
+ };
593
+ const out = [];
594
+ for (let i = 0; i < messages.length; i++) {
595
+ const msg = messages[i];
596
+ if (msg.role === "assistant") processAssistantMessage({
597
+ index: i,
598
+ msg,
599
+ messages,
600
+ firstSeenAt,
601
+ pendingOrphansByUserIndex,
602
+ out,
603
+ fireRepair,
604
+ markChanged
605
+ });
606
+ else processUserMessage({
607
+ index: i,
608
+ msg,
609
+ pendingOrphansByUserIndex,
610
+ out,
611
+ fireRepair,
612
+ markChanged
613
+ });
614
+ }
615
+ return changed ? out : messages;
616
+ }
617
+ /**
618
+ * Process one assistant message: strip mode-4 duplicates, queue mode-1 / 2
619
+ * orphan-tool-use repairs for the next user message (or insert a synthetic
620
+ * one), and emit a mode-6 marker when dedup empties the message.
621
+ */
622
+ function processAssistantMessage(args) {
623
+ const { index, msg, messages, firstSeenAt, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
624
+ const dedupedContent = [];
625
+ const seenHere = /* @__PURE__ */ new Set();
626
+ let dedupedChanged = false;
627
+ for (const block of msg.content) {
628
+ if (block.type === "tool_call") {
629
+ const isCrossMsgDup = firstSeenAt.get(block.id) !== index;
630
+ const isIntraMsgDup = seenHere.has(block.id);
631
+ if (isCrossMsgDup || isIntraMsgDup) {
632
+ dedupedChanged = true;
633
+ fireRepair({
634
+ mode: "duplicate-tool-use-strip",
635
+ callId: block.id,
636
+ messageIndex: index
637
+ });
638
+ continue;
639
+ }
640
+ seenHere.add(block.id);
641
+ }
642
+ dedupedContent.push(block);
643
+ }
644
+ const surviving = collectIds(dedupedContent, "tool_call", (b) => b.id);
645
+ const next = messages[index + 1];
646
+ const nextIsUser = next && next.role === "user";
647
+ const nextResultIds = nextIsUser ? collectResultIds(next.content) : /* @__PURE__ */ new Set();
648
+ const orphans = [];
649
+ for (const id of surviving) if (!nextResultIds.has(id)) orphans.push(id);
650
+ emitAssistantAfterDedup({
651
+ index,
652
+ msg,
653
+ dedupedContent,
654
+ dedupedChanged,
655
+ out,
656
+ fireRepair,
657
+ markChanged
658
+ });
659
+ if (orphans.length === 0) return;
660
+ if (nextIsUser) {
661
+ markChanged();
662
+ pendingOrphansByUserIndex.set(index + 1, orphans);
663
+ for (const callId of orphans) fireRepair({
664
+ mode: "orphan-tool-use-prepend",
665
+ callId,
666
+ messageIndex: index
667
+ });
668
+ return;
669
+ }
670
+ markChanged();
671
+ out.push({
672
+ role: "user",
673
+ content: orphans.map((callId) => syntheticResultBlock(callId))
674
+ });
675
+ for (const callId of orphans) fireRepair({
676
+ mode: "orphan-tool-use-append",
677
+ callId,
678
+ messageIndex: index
679
+ });
680
+ }
681
+ function emitAssistantAfterDedup(args) {
682
+ const { index, msg, dedupedContent, dedupedChanged, out, fireRepair, markChanged } = args;
683
+ if (!dedupedChanged) {
684
+ out.push(msg);
685
+ return;
686
+ }
687
+ markChanged();
688
+ if (dedupedContent.length === 0) {
689
+ out.push({
690
+ ...msg,
691
+ content: [{
692
+ type: "text",
693
+ text: TOOL_USE_INTERRUPTED_MARKER
694
+ }]
695
+ });
696
+ fireRepair({
697
+ mode: "empty-assistant-marker",
698
+ messageIndex: index
699
+ });
700
+ return;
701
+ }
702
+ out.push({
703
+ ...msg,
704
+ content: dedupedContent
705
+ });
706
+ }
707
+ /**
708
+ * Process one user message: dedup mode-5 duplicate tool_results, strip
709
+ * mode-3 orphans whose tool_call is missing from the previously-emitted
710
+ * assistant message, and prepend any synthetic results queued by the prior
711
+ * assistant's mode-1 repair.
712
+ */
713
+ function processUserMessage(args) {
714
+ const { index, msg, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
715
+ const queuedOrphans = pendingOrphansByUserIndex.get(index);
716
+ pendingOrphansByUserIndex.delete(index);
717
+ const prev = out.length > 0 ? out[out.length - 1] : null;
718
+ const validIds = prev && prev.role === "assistant" ? collectIds(prev.content, "tool_call", (b) => b.id) : /* @__PURE__ */ new Set();
719
+ const seenCallIds = /* @__PURE__ */ new Set();
720
+ let modifiedHere = false;
721
+ const afterDedup = [];
722
+ for (const block of msg.content) {
723
+ if (block.type !== "tool_result") {
724
+ afterDedup.push(block);
725
+ continue;
726
+ }
727
+ if (seenCallIds.has(block.callId)) {
728
+ modifiedHere = true;
729
+ fireRepair({
730
+ mode: "duplicate-tool-result-strip",
731
+ callId: block.callId,
732
+ messageIndex: index
733
+ });
734
+ continue;
735
+ }
736
+ seenCallIds.add(block.callId);
737
+ afterDedup.push(block);
738
+ }
739
+ const afterStrip = [];
740
+ for (const block of afterDedup) {
741
+ if (block.type === "tool_result" && !validIds.has(block.callId)) {
742
+ modifiedHere = true;
743
+ fireRepair({
744
+ mode: "orphan-tool-result-strip",
745
+ callId: block.callId,
746
+ messageIndex: index
747
+ });
748
+ continue;
749
+ }
750
+ afterStrip.push(block);
751
+ }
752
+ const finalContent = queuedOrphans ? [...queuedOrphans.map(syntheticResultBlock), ...afterStrip] : afterStrip;
753
+ if (!modifiedHere && queuedOrphans === void 0) {
754
+ out.push(msg);
755
+ return;
756
+ }
757
+ markChanged();
758
+ if (finalContent.length === 0) {
759
+ out.push({
760
+ ...msg,
761
+ content: [{
762
+ type: "text",
763
+ text: ORPHANED_TOOL_RESULT_MARKER
764
+ }]
765
+ });
766
+ return;
767
+ }
768
+ out.push({
769
+ ...msg,
770
+ content: finalContent
771
+ });
772
+ }
773
+ /**
774
+ * Re-mint tool-call ids that collide with ids already present in the
775
+ * transcript (or with an earlier sibling in the same turn), BEFORE the turn
776
+ * is persisted and its tools dispatched.
777
+ *
778
+ * Why this exists: some providers/serving stacks emit non-globally-unique
779
+ * tool_use ids (per-turn counters like `call_0`, retry-replay duplicates,
780
+ * Kimi-K2.6 on Fireworks). The wire-time pairing pass
781
+ * ({@link ensureToolResultPairing}) can only STRIP such duplicates — which
782
+ * erases the model's tool calls and results from the wire view, stalls
783
+ * progress, and re-accumulates repairs quadratically because the persisted
784
+ * session keeps the corrupted ids. Re-minting at persist time keeps the
785
+ * pairing intact instead: the call, its execution, and its tool_result all
786
+ * carry the fresh id, so the pairing pass has nothing to strip on any later
787
+ * turn.
788
+ *
789
+ * Fresh ids are `${oldId}_r{n}` with `n` bumped until unique — deterministic
790
+ * shape, Anthropic-wire-safe charset (given a safe input id), and visibly
791
+ * traceable back to the original.
792
+ *
793
+ * Alignment contract: `toolCalls` and the `tool_call` blocks inside
794
+ * `content` are two views of the same emission and MUST be in the same
795
+ * order (true for every provider — both are built from one source array).
796
+ * Occurrences are matched positionally per old id so intra-turn duplicates
797
+ * map to distinct fresh ids on both views.
798
+ *
799
+ * Pure: returns the input references unchanged when no id collides.
800
+ */
801
+ function remintDuplicateToolCallIds(toolCalls, content, priorIds) {
802
+ const seen = new Set(priorIds);
803
+ const reminted = [];
804
+ const finalIdQueues = /* @__PURE__ */ new Map();
805
+ let changed = false;
806
+ const outCalls = toolCalls.map((tc) => {
807
+ let finalId = tc.id;
808
+ if (seen.has(tc.id)) {
809
+ let n = 1;
810
+ while (seen.has(`${tc.id}_r${n}`)) n++;
811
+ finalId = `${tc.id}_r${n}`;
812
+ reminted.push({
813
+ oldId: tc.id,
814
+ newId: finalId
815
+ });
816
+ changed = true;
817
+ }
818
+ seen.add(finalId);
819
+ const queue = finalIdQueues.get(tc.id);
820
+ if (queue) queue.push(finalId);
821
+ else finalIdQueues.set(tc.id, [finalId]);
822
+ return finalId === tc.id ? tc : {
823
+ ...tc,
824
+ id: finalId
825
+ };
826
+ });
827
+ if (!changed) return {
828
+ toolCalls,
829
+ content,
830
+ reminted
831
+ };
832
+ return {
833
+ toolCalls: outCalls,
834
+ content: content.map((block) => {
835
+ if (block.type !== "tool_call") return block;
836
+ const finalId = finalIdQueues.get(block.id)?.shift();
837
+ if (finalId === void 0 || finalId === block.id) return block;
838
+ return {
839
+ ...block,
840
+ id: finalId
841
+ };
842
+ }),
843
+ reminted
844
+ };
845
+ }
846
+ function syntheticResultBlock(callId) {
847
+ return {
848
+ type: "tool_result",
849
+ callId,
850
+ output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
851
+ isError: true
852
+ };
853
+ }
854
+ function collectResultIds(content) {
855
+ const ids = /* @__PURE__ */ new Set();
856
+ for (const block of content) if (block.type === "tool_result") ids.add(block.callId);
857
+ return ids;
858
+ }
859
+ function collectIds(content, type, getId) {
860
+ const ids = /* @__PURE__ */ new Set();
861
+ for (const block of content) if (block.type === type) ids.add(getId(block));
862
+ return ids;
863
+ }
864
+ /**
865
+ * Drop ASSISTANT turns whose every `tool_call` block is unresolved
866
+ * (no matching `tool_result` block anywhere later in the transcript).
867
+ * Tool_call blocks with at least one matching tool_result are kept — modes
868
+ * 1/3 of {@link ensureToolResultPairing} handle the partial-pair case at
869
+ * wire-send time.
870
+ *
871
+ * Use case: session resume. A turn that emitted three `tool_use` blocks but
872
+ * never persisted the matching `tool_result` turn (process death, crash,
873
+ * `kill -9`) leaves the transcript with an orphan that Anthropic rejects on
874
+ * the FIRST API call after reload. Dropping the whole assistant turn — not
875
+ * just the orphan blocks — preserves text-only turns that legitimately
876
+ * carry reasoning the model wants to see again.
877
+ *
878
+ * Does NOT mint fresh ids: re-id'ing on every resume would cause
879
+ * exponential transcript growth across repeated resumes of an interrupted
880
+ * session.
881
+ *
882
+ * Pure: returns the input reference unchanged when no turn was dropped.
883
+ */
884
+ function filterUnresolvedToolUses(turns) {
885
+ if (turns.length === 0) return turns;
886
+ const resolvedIds = /* @__PURE__ */ new Set();
887
+ for (const turn of turns) for (const block of turn.content) if (block.type === "tool_result") resolvedIds.add(block.callId);
888
+ let changed = false;
889
+ const out = [];
890
+ for (const turn of turns) {
891
+ if (turn.role !== "assistant") {
892
+ out.push(turn);
893
+ continue;
894
+ }
895
+ const toolCalls = turn.content.filter((b) => b.type === "tool_call");
896
+ if (toolCalls.length === 0) {
897
+ out.push(turn);
898
+ continue;
899
+ }
900
+ if (toolCalls.every((b) => b.type === "tool_call" && !resolvedIds.has(b.id))) {
901
+ changed = true;
902
+ continue;
903
+ }
904
+ out.push(turn);
905
+ }
906
+ return changed ? out : turns;
907
+ }
908
+ /**
909
+ * Inspect a session's trailing turn to classify whether the run was
910
+ * interrupted mid-tool-call. Pure / synchronous — does not modify the input.
911
+ *
912
+ * Pair with {@link filterUnresolvedToolUses} on the resume path: filter
913
+ * first so the result reflects the post-cleanup state (a turn whose orphans
914
+ * were stripped reads as 'completed', not 'interrupted').
915
+ */
916
+ function detectTurnInterruption(turns) {
917
+ if (turns.length === 0) return "clean";
918
+ const last = turns[turns.length - 1];
919
+ if (last.role === "user") return "clean";
920
+ if (last.role !== "assistant") return "clean";
921
+ const resolvedIds = /* @__PURE__ */ new Set();
922
+ for (const turn of turns) for (const block of turn.content) if (block.type === "tool_result") resolvedIds.add(block.callId);
923
+ for (const block of last.content) if (block.type === "tool_call" && !resolvedIds.has(block.id)) return "interrupted";
924
+ return "completed";
925
+ }
926
+ /**
927
+ * Default directive injected by {@link ensureEndsWithUserMessage} when the
928
+ * caller doesn't pass one. Neutral enough to read sensibly in any context —
929
+ * compaction passes its own directive, the agent loop falls back to this.
930
+ *
931
+ * The trailing newline is omitted on purpose; provider `userMessage()`
932
+ * helpers wrap the content in the right block shape and any trailing
933
+ * whitespace would land in the wire payload as-is.
934
+ */
935
+ const DEFAULT_USER_TAIL_DIRECTIVE = "Continue.";
936
+ /**
937
+ * Guarantee a message list ends with a `role: 'user'` message, appending a
938
+ * synthetic directive when it doesn't.
939
+ *
940
+ * Why: Anthropic's API (and several OpenAI-compat reasoning models — opus
941
+ * 4.7, the o-series translation layers on Bedrock/Vertex) rejects requests
942
+ * whose final message is `role: 'assistant'` with
943
+ * "This model does not support assistant message prefill. The conversation
944
+ * must end with a user message."
945
+ *
946
+ * Triggers in zidane:
947
+ * - Compaction over a session whose last turn is the agent's response
948
+ * (the common case — autocompact fires right after `agent.run()`).
949
+ * - Schema enforcement after an assistant text turn (no `tool_use`, so
950
+ * `ensureToolResultPairing` doesn't synthesize a user tail).
951
+ * - `context:transform` hooks that strip the trailing user turn.
952
+ *
953
+ * The pair-repair pass {@link ensureToolResultPairing} already covers the
954
+ * orphan-`tool_use` case via mode 2; this helper closes the plain-text
955
+ * assistant-tail case. They compose: run pair-repair first, then this
956
+ * pass, so an assistant turn with orphan tool_uses gets its synthetic
957
+ * `tool_result` user turn AND this pass is a no-op (the synthetic turn
958
+ * is already user-role).
959
+ *
960
+ * Empty input passes through unchanged — the caller's "nothing to send"
961
+ * guard is responsible for that case; we don't materialize a conversation
962
+ * out of an empty list.
963
+ *
964
+ * Idempotent: returns the input reference unchanged when the list is
965
+ * already user-terminated (or empty).
966
+ *
967
+ * @param messages Wire-ready message list.
968
+ * @param provider Provider whose `userMessage()` shape we use so the
969
+ * synthetic turn matches the wire format the rest of
970
+ * the list already follows.
971
+ * @param directive Text to put in the synthetic user message. Defaults
972
+ * to {@link DEFAULT_USER_TAIL_DIRECTIVE} ("Continue.").
973
+ * Compaction overrides this with a summary-specific cue.
974
+ */
975
+ function ensureEndsWithUserMessage(messages, provider, directive = DEFAULT_USER_TAIL_DIRECTIVE) {
976
+ if (messages.length === 0) return messages;
977
+ if (messages[messages.length - 1].role === "user") return messages;
978
+ return [...messages, provider.userMessage(directive)];
979
+ }
980
+ async function resolveRefContentBlock(block, resolveContentRef) {
981
+ if (isContentRefBlock(block)) {
982
+ if (!resolveContentRef) throw unresolvedContentRefError(block);
983
+ const { data } = await resolveContentRef({
984
+ type: block.type,
985
+ ref: block.ref,
986
+ mediaType: block.mediaType,
987
+ ...block.type === "document" ? { encoding: block.encoding } : {},
988
+ ...block.name ? { name: block.name } : {}
989
+ });
990
+ if (block.type === "image") return {
991
+ block: {
992
+ type: "image",
993
+ mediaType: block.mediaType,
994
+ data,
995
+ ...block.name ? { name: block.name } : {}
996
+ },
997
+ changed: true
998
+ };
999
+ return {
1000
+ block: {
1001
+ type: "document",
1002
+ mediaType: block.mediaType,
1003
+ encoding: block.encoding,
1004
+ data,
1005
+ ...block.name ? { name: block.name } : {}
1006
+ },
1007
+ changed: true
1008
+ };
1009
+ }
1010
+ if (block.type !== "tool_result" || typeof block.output === "string") return {
1011
+ block,
1012
+ changed: false
1013
+ };
1014
+ const resolved = await resolveToolResultContent(block.output, resolveContentRef);
1015
+ if (!resolved.changed) return {
1016
+ block,
1017
+ changed: false
1018
+ };
1019
+ return {
1020
+ block: {
1021
+ ...block,
1022
+ output: resolved.output
1023
+ },
1024
+ changed: true
1025
+ };
1026
+ }
1027
+ async function resolveToolResultContent(output, resolveContentRef) {
1028
+ let changed = false;
1029
+ const next = [];
1030
+ for (const part of output) {
1031
+ if (!isContentRefBlock(part)) {
1032
+ next.push(part);
1033
+ continue;
1034
+ }
1035
+ if (!resolveContentRef) throw unresolvedContentRefError(part);
1036
+ const { data } = await resolveContentRef({
1037
+ type: part.type,
1038
+ ref: part.ref,
1039
+ mediaType: part.mediaType,
1040
+ ...part.type === "document" ? { encoding: part.encoding } : {},
1041
+ ...part.name ? { name: part.name } : {}
1042
+ });
1043
+ changed = true;
1044
+ if (part.type === "image") next.push({
1045
+ type: "image",
1046
+ mediaType: part.mediaType,
1047
+ data,
1048
+ ...part.name ? { name: part.name } : {}
1049
+ });
1050
+ else next.push({
1051
+ type: "document",
1052
+ mediaType: part.mediaType,
1053
+ encoding: part.encoding,
1054
+ data,
1055
+ ...part.name ? { name: part.name } : {}
1056
+ });
1057
+ }
1058
+ return {
1059
+ output: changed ? next : output,
1060
+ changed
1061
+ };
1062
+ }
1063
+ /**
1064
+ * Resolve image/document ref blocks into data blocks for provider wire use.
1065
+ * This is intentionally wire-only: callers must never persist the returned
1066
+ * messages back into `session.turns`.
1067
+ *
1068
+ * Returns the input array reference unchanged when no block carried a ref —
1069
+ * the common case (most turns hold no refs, and re-running on already-resolved
1070
+ * output is a no-op), so the multi-pass wire pipeline doesn't pay per-message
1071
+ * copies for nothing.
1072
+ */
1073
+ async function resolveContentRefsInMessages(messages, options = {}) {
1074
+ let anyChanged = false;
1075
+ const out = [];
1076
+ for (const msg of messages) {
1077
+ let changed = false;
1078
+ const content = [];
1079
+ for (const block of msg.content) {
1080
+ const resolved = await resolveRefContentBlock(block, options.resolveContentRef);
1081
+ if (resolved.changed) changed = true;
1082
+ content.push(resolved.block);
1083
+ }
1084
+ if (changed) anyChanged = true;
1085
+ out.push(changed ? {
1086
+ ...msg,
1087
+ content
1088
+ } : msg);
1089
+ }
1090
+ return anyChanged ? out : messages;
1091
+ }
1092
+ function finalizeWireMessages(messages, options) {
1093
+ const paired = ensureToolResultPairing(messages, options.onRepair ? { onRepair: options.onRepair } : {});
1094
+ if (!options.provider) return paired;
1095
+ return ensureEndsWithUserMessage(paired, options.provider, options.userTailDirective);
1096
+ }
1097
+ /**
1098
+ * Build a wire-ready `SessionMessage[]` from raw persisted `SessionTurn[]`.
1099
+ *
1100
+ * **The canonical "I want to send these to a provider" projection.** Use
1101
+ * this — not raw `session.turns` / `store.getTurns()` — when constructing a
1102
+ * provider request outside `agent.run()`. The agent's own wire pipeline
1103
+ * (`executeTurn` in `src/loop.ts`) applies the same repair just-in-time;
1104
+ * downstream consumers that bypass the loop hit `tool_result must be
1105
+ * preceded by a tool_call` 400s precisely because they shipped raw turns.
1106
+ *
1107
+ * Pipeline:
1108
+ * 1. Filter `system`-role turns (the wire only carries user/assistant).
1109
+ * 2. {@link ensureToolResultPairing} — repairs all six orphan / dup
1110
+ * corruption modes by inserting synthetic placeholders rather than
1111
+ * dropping (preserves the model's tool_use shape).
1112
+ * 3. {@link ensureEndsWithUserMessage} when `options.provider` is set —
1113
+ * guards against assistant-tail prefill rejection (opus 4.7, o-series).
1114
+ *
1115
+ * Pure + idempotent. Does not mutate the input turns. Re-running on the
1116
+ * output is a no-op.
1117
+ *
1118
+ * **Do not feed the result back into `session.setTurns` / `appendTurns`.**
1119
+ * The repair can insert `SYNTHETIC_TOOL_RESULT_PLACEHOLDER` / `[Orphaned
1120
+ * tool result removed …]` markers; round-tripping those into persisted
1121
+ * state contaminates the session's reasoning history and defeats the
1122
+ * "don't rewrite the past" invariant the wire-only repair was designed
1123
+ * around.
1124
+ */
1125
+ async function toWireMessages(turns, options = {}) {
1126
+ const messages = [];
1127
+ for (const t of turns) {
1128
+ if (t.role === "system") continue;
1129
+ messages.push({
1130
+ role: t.role,
1131
+ content: t.content
1132
+ });
1133
+ }
1134
+ return finalizeWireMessages(await resolveContentRefsInMessages(messages, { resolveContentRef: options.resolveContentRef }), options);
1135
+ }
1136
+ function autoDetectAndConvert(msg) {
1137
+ const c = msg.content;
1138
+ if (c && typeof c === "object" && !Array.isArray(c)) {
1139
+ const tag = c._tag;
1140
+ if (typeof tag === "string" && tag.startsWith("__zidane_")) return fromOpenAI(msg);
1141
+ }
1142
+ if (Array.isArray(c)) {
1143
+ for (const block of c) {
1144
+ if (!block || typeof block !== "object") continue;
1145
+ const b = block;
1146
+ if (b.type === "tool_use" || b.type === "tool_result" && "tool_use_id" in b) return fromAnthropic(msg);
1147
+ if (b.type === "image_url") return fromOpenAI(msg);
1148
+ }
1149
+ return fromAnthropic(msg);
1150
+ }
1151
+ if (typeof c === "string") return fromAnthropic(msg);
1152
+ return fromAnthropic(msg);
1153
+ }
1154
+ //#endregion
1155
+ export { detectTurnInterruption as a, filterUnresolvedToolUses as c, remintDuplicateToolCallIds as d, resolveContentRefsInMessages as f, toWireMessages as h, autoDetectAndConvert as i, fromAnthropic as l, toOpenAI as m, SYNTHETIC_TOOL_RESULT_PLACEHOLDER as n, ensureEndsWithUserMessage as o, toAnthropic as p, TOOL_USE_INTERRUPTED_MARKER as r, ensureToolResultPairing as s, ORPHANED_TOOL_RESULT_MARKER as t, fromOpenAI as u };
1156
+
1157
+ //# sourceMappingURL=messages-YZQLKaz2.js.map