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.
- package/dist/{acp-CjAoILwj.js → acp-DgKEJH3P.js} +7 -7
- package/dist/{acp-CjAoILwj.js.map → acp-DgKEJH3P.js.map} +1 -1
- package/dist/acp-cli.js +11 -6
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +1 -1
- package/dist/{tools-B32lZXLE.js → agent-B-cKanSC.js} +8035 -9459
- package/dist/agent-B-cKanSC.js.map +1 -0
- package/dist/{agent-DQIL5BFk.d.ts → agent-BAgOgS37.d.ts} +367 -208
- package/dist/agent-BAgOgS37.d.ts.map +1 -0
- package/dist/agent.d.ts +3 -0
- package/dist/agent.js +5 -0
- package/dist/anthropic-RGqsJlC8.js +807 -0
- package/dist/anthropic-RGqsJlC8.js.map +1 -0
- package/dist/{atomic-write-Bgtr5JPu.js → atomic-write-B7zr1ddF.js} +2 -2
- package/dist/{atomic-write-Bgtr5JPu.js.map → atomic-write-B7zr1ddF.js.map} +1 -1
- package/dist/{auth-DPrSP1YV.js → auth-C-ms5N2x.js} +33 -5
- package/dist/{auth-DPrSP1YV.js.map → auth-C-ms5N2x.js.map} +1 -1
- package/dist/cache-telemetry-BKAwsNxm.js +169 -0
- package/dist/cache-telemetry-BKAwsNxm.js.map +1 -0
- package/dist/chat/pure.d.ts +3 -3
- package/dist/chat/pure.js +1 -1
- package/dist/chat.d.ts +21 -7
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +30 -5
- package/dist/chat.js.map +1 -1
- package/dist/contexts/daytona.d.ts +1 -1
- package/dist/contexts/docker.js +0 -1
- package/dist/contexts/docker.js.map +1 -1
- package/dist/contexts/e2b.d.ts +1 -1
- package/dist/errors-CkTvg97v.d.ts +203 -0
- package/dist/errors-CkTvg97v.d.ts.map +1 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +3 -0
- package/dist/eval.d.ts +1 -1
- package/dist/eval.js +2 -2
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +406 -1
- package/dist/headless.js.map +1 -0
- package/dist/{index-YY3vp2F2.d.ts → index-CLuLFFLa.d.ts} +88 -418
- package/dist/index-CLuLFFLa.d.ts.map +1 -0
- package/dist/{index-C5qw3pj7.d.ts → index-CgBIl2mX.d.ts} +2 -2
- package/dist/{index-C5qw3pj7.d.ts.map → index-CgBIl2mX.d.ts.map} +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +113 -167
- package/dist/index.js.map +1 -1
- package/dist/{logger-C2ngE8CY.d.ts → logger-CJ-lpKPy.d.ts} +2 -2
- package/dist/{logger-C2ngE8CY.d.ts.map → logger-CJ-lpKPy.d.ts.map} +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/messages-YZQLKaz2.js +1157 -0
- package/dist/messages-YZQLKaz2.js.map +1 -0
- package/dist/oauth-8LqbeI_Q.js +292 -0
- package/dist/oauth-8LqbeI_Q.js.map +1 -0
- package/dist/{messages-Iy_vEycX.js → openai-compat-0Y7jcncn.js} +27 -1379
- package/dist/openai-compat-0Y7jcncn.js.map +1 -0
- package/dist/output/stream-json.d.ts +2 -2
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +2 -2
- package/dist/{presets-B9nDwmaz.js → presets-Pr02ZNcy.js} +3 -2
- package/dist/{presets-B9nDwmaz.js.map → presets-Pr02ZNcy.js.map} +1 -1
- package/dist/presets.d.ts +2 -2
- package/dist/presets.js +1 -1
- package/dist/prompt.d.ts +2 -0
- package/dist/prompt.js +210 -0
- package/dist/prompt.js.map +1 -0
- package/dist/providers/anthropic.d.ts +2 -0
- package/dist/providers/anthropic.js +2 -0
- package/dist/providers/arcee.d.ts +2 -0
- package/dist/providers/arcee.js +42 -0
- package/dist/providers/arcee.js.map +1 -0
- package/dist/providers/baseten.d.ts +2 -0
- package/dist/providers/baseten.js +66 -0
- package/dist/providers/baseten.js.map +1 -0
- package/dist/providers/cerebras.d.ts +2 -0
- package/dist/providers/cerebras.js +31 -0
- package/dist/providers/cerebras.js.map +1 -0
- package/dist/providers/local.d.ts +2 -0
- package/dist/providers/local.js +44 -0
- package/dist/providers/local.js.map +1 -0
- package/dist/providers/openai-compat.d.ts +2 -0
- package/dist/providers/openai-compat.js +2 -0
- package/dist/providers/openai.d.ts +2 -0
- package/dist/providers/openai.js +504 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +2 -0
- package/dist/providers/openrouter.js +52 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/xai.d.ts +2 -0
- package/dist/providers/xai.js +2 -0
- package/dist/providers-DgmdYGKm.js +168 -0
- package/dist/providers-DgmdYGKm.js.map +1 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +10 -2
- package/dist/restate.d.ts +1 -1
- package/dist/run-summary-DAnBZnrt.d.ts +249 -0
- package/dist/run-summary-DAnBZnrt.d.ts.map +1 -0
- package/dist/run-summary-nD3IQ4ZQ.js +253 -0
- package/dist/run-summary-nD3IQ4ZQ.js.map +1 -0
- package/dist/session/sqlite.d.ts +1 -1
- package/dist/{session-CiA1_8SB.js → session-CqrAFAKJ.js} +2 -2
- package/dist/{session-CiA1_8SB.js.map → session-CqrAFAKJ.js.map} +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/skills.d.ts +2 -2
- package/dist/system-prompt-BlsdjcUi.d.ts +139 -0
- package/dist/system-prompt-BlsdjcUi.d.ts.map +1 -0
- package/dist/timeout-CpFm0jJ6.d.ts +10 -0
- package/dist/timeout-CpFm0jJ6.d.ts.map +1 -0
- package/dist/timeout-kGGSXagK.js +84 -0
- package/dist/timeout-kGGSXagK.js.map +1 -0
- package/dist/{tool-formatters-w5oaoCS4.d.ts → tool-formatters-Ch8oRpYR.d.ts} +2 -2
- package/dist/{tool-formatters-w5oaoCS4.d.ts.map → tool-formatters-Ch8oRpYR.d.ts.map} +1 -1
- package/dist/tools/fetch-url.d.ts +1 -1
- package/dist/tools/web-search.d.ts +1 -1
- package/dist/tools-00_Nqazv.js +1689 -0
- package/dist/tools-00_Nqazv.js.map +1 -0
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +2 -1
- package/dist/{transcript-anchors-BUtOd-qa.js → transcript-anchors-BUmvPnis.js} +10 -9
- package/dist/{transcript-anchors-BUtOd-qa.js.map → transcript-anchors-BUmvPnis.js.map} +1 -1
- package/dist/{transcript-anchors-BJ-ESWnm.d.ts → transcript-anchors-CL9UM_M4.d.ts} +4 -4
- package/dist/{transcript-anchors-BJ-ESWnm.d.ts.map → transcript-anchors-CL9UM_M4.d.ts.map} +1 -1
- package/dist/tui.d.ts +3 -3
- package/dist/tui.js +38 -13
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-BSSQHNq1.d.ts → turn-operations-Cj6v_ypW.d.ts} +3 -3
- package/dist/{turn-operations-BSSQHNq1.d.ts.map → turn-operations-Cj6v_ypW.d.ts.map} +1 -1
- package/dist/{turn-operations-B_XeuNp-.js → turn-operations-tG0vuBNc.js} +2 -2
- package/dist/{turn-operations-B_XeuNp-.js.map → turn-operations-tG0vuBNc.js.map} +1 -1
- package/dist/types-CyVGdbia.js.map +1 -1
- package/dist/types.d.ts +4 -3
- package/dist/xai-tPdbAGkq.js +379 -0
- package/dist/xai-tPdbAGkq.js.map +1 -0
- package/docs/ARCHITECTURE.md +4 -2
- package/docs/CHAT.md +4 -0
- package/docs/SKILL.md +31 -5
- package/package.json +63 -1
- package/dist/agent-DQIL5BFk.d.ts.map +0 -1
- package/dist/headless-2SH7dvoW.js +0 -656
- package/dist/headless-2SH7dvoW.js.map +0 -1
- package/dist/index-YY3vp2F2.d.ts.map +0 -1
- package/dist/messages-Iy_vEycX.js.map +0 -1
- package/dist/providers-BMw1D_lj.js +0 -2332
- package/dist/providers-BMw1D_lj.js.map +0 -1
- 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
|