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
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { t as fnv1aBase36 } from "./hash-DJRzmcPI.js";
|
|
2
|
-
import { n as estimateTokens } from "./utils-ngQzYzZD.js";
|
|
3
1
|
import { i as documentBlockMarker, n as assertResolvedMediaBlock } from "./types-CyVGdbia.js";
|
|
2
|
+
import { n as estimateTokens } from "./utils-ngQzYzZD.js";
|
|
4
3
|
import { c as classifyErrorPrelude, d as isRetryableHttpStatus, f as matchesContextExceeded, l as errorMessage, p as matchesToolPairingError } from "./errors-DJUxZg9b.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { renderSystemForWire, splitSystemPrompt } from "./prompt.js";
|
|
5
|
+
import { t as audioFormatFromMediaType } from "./media-sniff-Bn76JxAu.js";
|
|
7
6
|
//#region src/prompt.ts
|
|
8
7
|
/**
|
|
9
8
|
* Coerce the run-level prompt into a `PromptPart[]`.
|
|
@@ -123,214 +122,12 @@ function buildPromptMessage(provider, parts) {
|
|
|
123
122
|
return defaultPromptMessage(parts);
|
|
124
123
|
}
|
|
125
124
|
//#endregion
|
|
126
|
-
//#region src/system-prompt.ts
|
|
127
|
-
/**
|
|
128
|
-
* System-prompt boundary marker — splits a system prompt into a stable static
|
|
129
|
-
* prefix (cached) and a per-turn dynamic suffix (NOT cached).
|
|
130
|
-
*
|
|
131
|
-
* Why this exists: providers attach `cache_control` markers on the last block
|
|
132
|
-
* of the system prompt, so the cached prefix covers the entire system text.
|
|
133
|
-
* Any byte change anywhere — including a per-turn `<env>` rewrite — busts the
|
|
134
|
-
* cache for the doctrine that sits below. A literal marker in the system
|
|
135
|
-
* string lets providers split it into:
|
|
136
|
-
*
|
|
137
|
-
* ┌──────────────┐ cache_control: ephemeral
|
|
138
|
-
* │ STATIC half │ — doctrine, skills catalog, tool catalog,
|
|
139
|
-
* │ │ user instructions
|
|
140
|
-
* ├──────────────┤ ← SYSTEM_PROMPT_BOUNDARY
|
|
141
|
-
* │ DYNAMIC half │ — env, cwd, mtimes, anything per-turn
|
|
142
|
-
* └──────────────┘ (no cache_control)
|
|
143
|
-
*
|
|
144
|
-
* The static prefix rides the prompt cache across turns/sessions; the dynamic
|
|
145
|
-
* suffix re-bills per turn. Net effect: a cwd change between turns no longer
|
|
146
|
-
* invalidates 4 KB of doctrine.
|
|
147
|
-
*
|
|
148
|
-
* Wire contract:
|
|
149
|
-
*
|
|
150
|
-
* - `splitSystemPrompt(s)` is pure; missing marker ⇒ entire string is static
|
|
151
|
-
* (current behavior — no caller is forced to opt in).
|
|
152
|
-
* - `renderSystemForWire(s)` strips the marker so it never reaches the model;
|
|
153
|
-
* used by every provider before the bytes hit the wire, including the
|
|
154
|
-
* cache-disabled path on providers that DO support `cache_control`.
|
|
155
|
-
* - The marker uses underscores rather than XML/punctuation so it's
|
|
156
|
-
* unambiguous when scanning prompts manually and unlikely to collide with
|
|
157
|
-
* model-written content.
|
|
158
|
-
* - Providers handle the split internally (Anthropic emits a 2-block array;
|
|
159
|
-
* OpenAI-compat splits the leading `system` message into multi-part text).
|
|
160
|
-
* Callers always pass a single `string` — no API break.
|
|
161
|
-
*/
|
|
162
|
-
/**
|
|
163
|
-
* Literal marker inserted in a system prompt to separate cacheable doctrine
|
|
164
|
-
* from per-turn dynamic content. Providers split on this token.
|
|
165
|
-
*
|
|
166
|
-
* Underscored on both sides for visual distinctiveness — a stray instance in
|
|
167
|
-
* model-written prose is implausible. Don't change the value without shipping
|
|
168
|
-
* a migration; existing sessions carry the old marker in their cached
|
|
169
|
-
* prompts.
|
|
170
|
-
*/
|
|
171
|
-
const SYSTEM_PROMPT_BOUNDARY = "__ZIDANE_SYSTEM_PROMPT_BOUNDARY__";
|
|
172
|
-
/**
|
|
173
|
-
* Split a system prompt around the first {@link SYSTEM_PROMPT_BOUNDARY}.
|
|
174
|
-
*
|
|
175
|
-
* Splits on the FIRST occurrence — subsequent markers are folded into the
|
|
176
|
-
* dynamic half. This way callers can append additional `<env>` style blocks
|
|
177
|
-
* with extra markers without each one creating a new cache layer (Anthropic
|
|
178
|
-
* caps breakpoints; we use the budget elsewhere). The marker itself is
|
|
179
|
-
* stripped from both sides — providers attach `cache_control` directly to
|
|
180
|
-
* the static half's text content.
|
|
181
|
-
*
|
|
182
|
-
* A single blank line (`\n\n`) immediately adjacent to the marker on each
|
|
183
|
-
* side is trimmed — callers conventionally write
|
|
184
|
-
* `<doctrine>\n\n<MARKER>\n\n<env>` and expect the rendered wire bytes to
|
|
185
|
-
* read as one logical paragraph. Blank lines beyond that immediate pair
|
|
186
|
-
* (e.g. `\n\n\n<env>`) are preserved verbatim so callers composing their
|
|
187
|
-
* own spacing don't lose intentional gaps.
|
|
188
|
-
*
|
|
189
|
-
* Pure / no I/O / no allocation when the marker is absent (returns the input
|
|
190
|
-
* verbatim on the static side and the empty string on the dynamic side).
|
|
191
|
-
*/
|
|
192
|
-
function splitSystemPrompt(system) {
|
|
193
|
-
if (system.length === 0) return {
|
|
194
|
-
static: "",
|
|
195
|
-
dynamic: ""
|
|
196
|
-
};
|
|
197
|
-
const idx = system.indexOf(SYSTEM_PROMPT_BOUNDARY);
|
|
198
|
-
if (idx < 0) return {
|
|
199
|
-
static: system,
|
|
200
|
-
dynamic: ""
|
|
201
|
-
};
|
|
202
|
-
const staticPart = system.slice(0, idx);
|
|
203
|
-
const dynamicPart = system.slice(idx + 33);
|
|
204
|
-
return {
|
|
205
|
-
static: trimTrailingBlankLine(staticPart),
|
|
206
|
-
dynamic: trimLeadingBlankLine(dynamicPart)
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Compose a system prompt from a static prefix and an optional dynamic
|
|
211
|
-
* suffix. Inserts {@link SYSTEM_PROMPT_BOUNDARY} between them only when the
|
|
212
|
-
* dynamic side is non-empty — single-block prompts stay marker-free, so
|
|
213
|
-
* callers that never opt in pay zero overhead and providers fall back to the
|
|
214
|
-
* existing whole-string caching path.
|
|
215
|
-
*
|
|
216
|
-
* Spacing is `\n\n` on both sides of the marker so doctrine fragments,
|
|
217
|
-
* which conventionally separate sections with a blank line, read cleanly
|
|
218
|
-
* around the boundary.
|
|
219
|
-
*/
|
|
220
|
-
function joinSystemPrompt(staticPart, dynamicPart) {
|
|
221
|
-
if (dynamicPart.length === 0) return staticPart;
|
|
222
|
-
if (staticPart.length === 0) return `${SYSTEM_PROMPT_BOUNDARY}\n\n${dynamicPart}`;
|
|
223
|
-
return `${staticPart}\n\n${SYSTEM_PROMPT_BOUNDARY}\n\n${dynamicPart}`;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Append `extra` to the STATIC half of a system prompt, preserving any
|
|
227
|
-
* existing dynamic suffix.
|
|
228
|
-
*
|
|
229
|
-
* Used by the agent to fold in run-stable content (skills catalog, lazy tool
|
|
230
|
-
* catalog) without bumping it into the dynamic half — both catalogs are
|
|
231
|
-
* built once per run and remain byte-stable for the duration, so they
|
|
232
|
-
* belong in the cached prefix.
|
|
233
|
-
*
|
|
234
|
-
* Returns a new string; the input is not mutated. When `extra` is empty,
|
|
235
|
-
* returns the input verbatim.
|
|
236
|
-
*/
|
|
237
|
-
function appendStaticSection(system, extra) {
|
|
238
|
-
if (extra.length === 0) return system;
|
|
239
|
-
const { static: staticPart, dynamic } = splitSystemPrompt(system);
|
|
240
|
-
return joinSystemPrompt(staticPart.length === 0 ? extra : `${staticPart}\n\n${extra}`, dynamic);
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Append `extra` to the DYNAMIC half of a system prompt. Inserts the boundary
|
|
244
|
-
* marker if the input didn't already carry one.
|
|
245
|
-
*
|
|
246
|
-
* Used by hosts (typically the TUI) to inject per-turn state — current cwd,
|
|
247
|
-
* IDE selection, project root — without forcing every caller to know the
|
|
248
|
-
* marker format. The host's `system:transform` hook rewrites the dynamic
|
|
249
|
-
* half each turn; the static doctrine above stays byte-stable and rides the
|
|
250
|
-
* cache.
|
|
251
|
-
*
|
|
252
|
-
* Returns a new string; the input is not mutated. When `extra` is empty,
|
|
253
|
-
* returns the input verbatim.
|
|
254
|
-
*/
|
|
255
|
-
function appendDynamicSection(system, extra) {
|
|
256
|
-
if (extra.length === 0) return system;
|
|
257
|
-
const { static: staticPart, dynamic } = splitSystemPrompt(system);
|
|
258
|
-
return joinSystemPrompt(staticPart, dynamic.length === 0 ? extra : `${dynamic}\n\n${extra}`);
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Replace the entire dynamic half of a system prompt with `next`. Used by
|
|
262
|
-
* the TUI's `<env>` rewriter where the entire dynamic section is regenerated
|
|
263
|
-
* each turn rather than appended to.
|
|
264
|
-
*
|
|
265
|
-
* When `next` is empty, drops the dynamic half (and the marker) entirely.
|
|
266
|
-
*/
|
|
267
|
-
function replaceDynamicSection(system, next) {
|
|
268
|
-
const { static: staticPart } = splitSystemPrompt(system);
|
|
269
|
-
return joinSystemPrompt(staticPart, next);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Strip the boundary marker so it never reaches the wire — collapses
|
|
273
|
-
* `<static>${BOUNDARY}<dynamic>` into `<static>\n\n<dynamic>` for providers
|
|
274
|
-
* that can't honor `cache_control` (vanilla OpenAI Chat Completions, Codex,
|
|
275
|
-
* Cerebras, ...) or for the cache-disabled path on any provider.
|
|
276
|
-
*
|
|
277
|
-
* Cache-aware providers re-derive the split from the original (un-rendered)
|
|
278
|
-
* system string via `splitSystemPrompt` — `renderSystemForWire` is the
|
|
279
|
-
* marker-free counterpart used to build the actual wire bytes.
|
|
280
|
-
*
|
|
281
|
-
* No-op when the input has no marker. Pure / no I/O.
|
|
282
|
-
*/
|
|
283
|
-
function renderSystemForWire(system) {
|
|
284
|
-
if (system.length === 0) return system;
|
|
285
|
-
const parts = splitSystemPrompt(system);
|
|
286
|
-
if (parts.dynamic.length === 0) return parts.static;
|
|
287
|
-
if (parts.static.length === 0) return parts.dynamic;
|
|
288
|
-
return `${parts.static}\n\n${parts.dynamic}`;
|
|
289
|
-
}
|
|
290
|
-
/** True when `system` contains the boundary marker. */
|
|
291
|
-
function hasSystemPromptBoundary(system) {
|
|
292
|
-
return system.includes(SYSTEM_PROMPT_BOUNDARY);
|
|
293
|
-
}
|
|
294
|
-
/** Strip a single trailing `\n\n` (and any leftover trailing whitespace within that pair). */
|
|
295
|
-
function trimTrailingBlankLine(s) {
|
|
296
|
-
let end = s.length;
|
|
297
|
-
let newlines = 0;
|
|
298
|
-
while (end > 0 && newlines < 2) {
|
|
299
|
-
const ch = s.charCodeAt(end - 1);
|
|
300
|
-
if (ch === 10) {
|
|
301
|
-
newlines += 1;
|
|
302
|
-
end -= 1;
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
if (ch === 32 || ch === 9) {
|
|
306
|
-
end -= 1;
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
return newlines > 0 ? s.slice(0, end) : s;
|
|
312
|
-
}
|
|
313
|
-
/** Strip a single leading `\n\n` (and any leftover leading whitespace within that pair). */
|
|
314
|
-
function trimLeadingBlankLine(s) {
|
|
315
|
-
let start = 0;
|
|
316
|
-
let newlines = 0;
|
|
317
|
-
while (start < s.length && newlines < 2) {
|
|
318
|
-
const ch = s.charCodeAt(start);
|
|
319
|
-
if (ch === 10) {
|
|
320
|
-
newlines += 1;
|
|
321
|
-
start += 1;
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (ch === 32 || ch === 9) {
|
|
325
|
-
start += 1;
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
return newlines > 0 ? s.slice(start) : s;
|
|
331
|
-
}
|
|
332
|
-
//#endregion
|
|
333
125
|
//#region src/providers/cost.ts
|
|
126
|
+
let getModelPromise;
|
|
127
|
+
function loadGetModel() {
|
|
128
|
+
getModelPromise ??= import("@earendil-works/pi-ai").then((mod) => mod.getModel).catch(() => null);
|
|
129
|
+
return getModelPromise;
|
|
130
|
+
}
|
|
334
131
|
/**
|
|
335
132
|
* Per-MTok rates for models not yet in pi-ai's bundled registry. Keyed by
|
|
336
133
|
* `provider:modelId`. Consulted only when pi-ai's `getModel` misses, so a
|
|
@@ -412,13 +209,13 @@ const EXTRA_COST_RATES = {
|
|
|
412
209
|
* will drift until the dep updates. An optional `multiplier` scales the
|
|
413
210
|
* base rates for premium modes (e.g. fast mode) before totaling.
|
|
414
211
|
*/
|
|
415
|
-
function fillEstimatedCost(usage, provider, multiplier) {
|
|
212
|
+
async function fillEstimatedCost(usage, provider, multiplier) {
|
|
416
213
|
if (usage.cost !== void 0) return usage;
|
|
417
214
|
if (!usage.modelId) return usage;
|
|
418
|
-
const
|
|
215
|
+
const getModel = await loadGetModel();
|
|
419
216
|
let model;
|
|
420
|
-
try {
|
|
421
|
-
model =
|
|
217
|
+
if (getModel) try {
|
|
218
|
+
model = getModel(provider, usage.modelId);
|
|
422
219
|
} catch {
|
|
423
220
|
model = void 0;
|
|
424
221
|
}
|
|
@@ -995,7 +792,7 @@ function toVideoUrlPart(video) {
|
|
|
995
792
|
video_url: { url: `data:${video.mediaType};base64,${video.data}` }
|
|
996
793
|
};
|
|
997
794
|
}
|
|
998
|
-
function documentMarker
|
|
795
|
+
function documentMarker(doc) {
|
|
999
796
|
return documentBlockMarker(doc, "document omitted");
|
|
1000
797
|
}
|
|
1001
798
|
/**
|
|
@@ -1072,7 +869,7 @@ function summarizeToolResultOutput(output, documentsEnabled = false) {
|
|
|
1072
869
|
});
|
|
1073
870
|
} else if (block.type === "document") {
|
|
1074
871
|
assertResolvedMediaBlock(block, "OpenAI-compatible wire messages");
|
|
1075
|
-
texts.push(documentMarker
|
|
872
|
+
texts.push(documentMarker(block));
|
|
1076
873
|
}
|
|
1077
874
|
return {
|
|
1078
875
|
text: texts.join("\n"),
|
|
@@ -1245,7 +1042,7 @@ function toOAIMessages(system, messages, options = {}) {
|
|
|
1245
1042
|
if (documentsEnabled && isForwardableDocument(b)) parts.push(toFilePart(b));
|
|
1246
1043
|
else parts.push({
|
|
1247
1044
|
type: "text",
|
|
1248
|
-
text: documentMarker
|
|
1045
|
+
text: documentMarker(b)
|
|
1249
1046
|
});
|
|
1250
1047
|
}
|
|
1251
1048
|
for (const b of textBlocks) parts.push({
|
|
@@ -1791,1175 +1588,26 @@ function openaiCompat(params) {
|
|
|
1791
1588
|
model: modelId
|
|
1792
1589
|
};
|
|
1793
1590
|
}
|
|
1591
|
+
const filledUsage = await fillEstimatedCost({
|
|
1592
|
+
input: usage.input,
|
|
1593
|
+
output: usage.output,
|
|
1594
|
+
...usage.cacheRead !== void 0 ? { cacheRead: usage.cacheRead } : {},
|
|
1595
|
+
...usage.cacheCreation !== void 0 ? { cacheCreation: usage.cacheCreation } : {},
|
|
1596
|
+
...usage.cost !== void 0 ? { cost: usage.cost } : {},
|
|
1597
|
+
...finishReason ? { finishReason } : {},
|
|
1598
|
+
modelId
|
|
1599
|
+
}, name);
|
|
1794
1600
|
return {
|
|
1795
1601
|
assistantMessage: buildAssistantContent(result.text, result.toolCalls, thinkingForBlock, reasoningArg),
|
|
1796
1602
|
text: result.text,
|
|
1797
1603
|
toolCalls: result.toolCalls,
|
|
1798
1604
|
done: result.toolCalls.length === 0,
|
|
1799
|
-
usage:
|
|
1800
|
-
input: usage.input,
|
|
1801
|
-
output: usage.output,
|
|
1802
|
-
...usage.cacheRead !== void 0 ? { cacheRead: usage.cacheRead } : {},
|
|
1803
|
-
...usage.cacheCreation !== void 0 ? { cacheCreation: usage.cacheCreation } : {},
|
|
1804
|
-
...usage.cost !== void 0 ? { cost: usage.cost } : {},
|
|
1805
|
-
...finishReason ? { finishReason } : {},
|
|
1806
|
-
modelId
|
|
1807
|
-
}, name)
|
|
1605
|
+
usage: filledUsage
|
|
1808
1606
|
};
|
|
1809
1607
|
}
|
|
1810
1608
|
};
|
|
1811
1609
|
}
|
|
1812
1610
|
//#endregion
|
|
1813
|
-
|
|
1814
|
-
/**
|
|
1815
|
-
* Decode an Anthropic `tool_result.content` wire value back to zidane's canonical
|
|
1816
|
-
* `string | ToolResultContent[]` shape.
|
|
1817
|
-
*
|
|
1818
|
-
* Anthropic accepts three shapes in the wire: `string`, `Array<{type:'text',text}>`,
|
|
1819
|
-
* or `Array<{type:'text'|'image', ...}>`. We collapse to `string` only when every
|
|
1820
|
-
* block is text to keep simple cases readable.
|
|
1821
|
-
*/
|
|
1822
|
-
function decodeAnthropicToolResultContent(content) {
|
|
1823
|
-
if (typeof content === "string") return content;
|
|
1824
|
-
if (!Array.isArray(content)) return JSON.stringify(content);
|
|
1825
|
-
const blocks = [];
|
|
1826
|
-
for (const raw of content) {
|
|
1827
|
-
if (!raw || typeof raw !== "object") continue;
|
|
1828
|
-
const b = raw;
|
|
1829
|
-
if (b.type === "text" && typeof b.text === "string") {
|
|
1830
|
-
blocks.push({
|
|
1831
|
-
type: "text",
|
|
1832
|
-
text: b.text
|
|
1833
|
-
});
|
|
1834
|
-
continue;
|
|
1835
|
-
}
|
|
1836
|
-
if (b.type === "image" && b.source && typeof b.source === "object") {
|
|
1837
|
-
const src = b.source;
|
|
1838
|
-
if (src.type === "base64" && typeof src.data === "string" && typeof src.media_type === "string") {
|
|
1839
|
-
blocks.push({
|
|
1840
|
-
type: "image",
|
|
1841
|
-
mediaType: src.media_type,
|
|
1842
|
-
data: src.data
|
|
1843
|
-
});
|
|
1844
|
-
continue;
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
blocks.push({
|
|
1848
|
-
type: "text",
|
|
1849
|
-
text: JSON.stringify(raw)
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
if (blocks.length === 0) return "";
|
|
1853
|
-
if (!blocks.some((b) => b.type !== "text")) return blocks.map((b) => b.text).join("\n");
|
|
1854
|
-
return blocks;
|
|
1855
|
-
}
|
|
1856
|
-
/**
|
|
1857
|
-
* Build an Anthropic `image.source` block, reconciling the declared media
|
|
1858
|
-
* type against the actual magic bytes.
|
|
1859
|
-
*
|
|
1860
|
-
* Anthropic's API rejects the entire request when `media_type` disagrees
|
|
1861
|
-
* with the payload (`messages.N.content.K.image.source.base64: The image
|
|
1862
|
-
* was specified using the X media type, but the image appears to be a
|
|
1863
|
-
* Y image`). The upstream sources of `{type:'image'}` blocks each sniff
|
|
1864
|
-
* before producing the block (read_file / MCP / TUI paste), but this
|
|
1865
|
-
* wire-boundary reconciliation is a final safety net for any future code
|
|
1866
|
-
* path or host-supplied prompt part that slips through without sniffing.
|
|
1867
|
-
*
|
|
1868
|
-
* The reconciler is conservative: it overrides only when the magic bytes
|
|
1869
|
-
* positively identify a *different* known format. Unknown / too-short
|
|
1870
|
-
* payloads pass `mediaType` through unchanged.
|
|
1871
|
-
*/
|
|
1872
|
-
function anthropicImageSource(mediaType, data) {
|
|
1873
|
-
return {
|
|
1874
|
-
type: "base64",
|
|
1875
|
-
media_type: reconcileImageMediaType(mediaType, data),
|
|
1876
|
-
data
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
function anthropicDocumentBlocks(doc) {
|
|
1880
|
-
if (doc.encoding === "base64" && doc.mediaType === "application/pdf") return [{
|
|
1881
|
-
type: "document",
|
|
1882
|
-
source: {
|
|
1883
|
-
type: "base64",
|
|
1884
|
-
media_type: "application/pdf",
|
|
1885
|
-
data: doc.data
|
|
1886
|
-
},
|
|
1887
|
-
...doc.name ? { title: doc.name } : {}
|
|
1888
|
-
}];
|
|
1889
|
-
return [{
|
|
1890
|
-
type: "text",
|
|
1891
|
-
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>`
|
|
1892
|
-
}];
|
|
1893
|
-
}
|
|
1894
|
-
function documentMarker(doc) {
|
|
1895
|
-
return documentBlockMarker(doc, "document attached");
|
|
1896
|
-
}
|
|
1897
|
-
function isContentRefBlock(block) {
|
|
1898
|
-
return (block.type === "image" || block.type === "document") && typeof block.ref === "string";
|
|
1899
|
-
}
|
|
1900
|
-
function unresolvedContentRefError(block) {
|
|
1901
|
-
return /* @__PURE__ */ new Error(`Cannot project content ref "${block.ref}" to provider wire messages without resolveContentRef.`);
|
|
1902
|
-
}
|
|
1903
|
-
/**
|
|
1904
|
-
* Coerce a tool-call id to Anthropic's wire constraint `^[a-zA-Z0-9_-]+$`.
|
|
1905
|
-
*
|
|
1906
|
-
* Sessions started under another provider (OpenAI-compat / OpenRouter /
|
|
1907
|
-
* Cerebras) or carrying MCP-namespaced calls can hold ids with `.`, `:`,
|
|
1908
|
-
* `/` or other characters that 400 the request the moment the user switches
|
|
1909
|
-
* to an Anthropic model (`messages.N.content.M.tool_use.id: String should
|
|
1910
|
-
* match pattern '^[a-zA-Z0-9_-]+$'`).
|
|
1911
|
-
*
|
|
1912
|
-
* The mapping is deterministic — every disallowed char becomes `_` — so a
|
|
1913
|
-
* `tool_use` and its paired `tool_result` (sanitized identically below) keep
|
|
1914
|
-
* matching ids and the pairing survives. When sanitization actually changed
|
|
1915
|
-
* the id, a short hash of the ORIGINAL id is appended so distinct inputs
|
|
1916
|
-
* that would collapse to the same replacement (`call.1` vs `call_1` vs
|
|
1917
|
-
* `call:1`) stay distinct on the wire. Empty ids get a stable placeholder
|
|
1918
|
-
* so the block stays well-formed.
|
|
1919
|
-
*/
|
|
1920
|
-
const ANTHROPIC_TOOL_ID_INVALID = /[^\w-]/g;
|
|
1921
|
-
function sanitizeAnthropicToolId(id) {
|
|
1922
|
-
if (!id) return "tool_0";
|
|
1923
|
-
const sanitized = id.replace(ANTHROPIC_TOOL_ID_INVALID, "_");
|
|
1924
|
-
if (sanitized === id) return id;
|
|
1925
|
-
return `${sanitized}_${fnv1aBase36(id)}`;
|
|
1926
|
-
}
|
|
1927
|
-
/**
|
|
1928
|
-
* Encode zidane's canonical `string | ToolResultContent[]` shape to Anthropic's
|
|
1929
|
-
* wire format for `tool_result.content`.
|
|
1930
|
-
*/
|
|
1931
|
-
function encodeAnthropicToolResultContent(output) {
|
|
1932
|
-
if (typeof output === "string") return output;
|
|
1933
|
-
return output.map((b) => {
|
|
1934
|
-
if (b.type === "text") return {
|
|
1935
|
-
type: "text",
|
|
1936
|
-
text: b.text
|
|
1937
|
-
};
|
|
1938
|
-
if (b.type === "document") {
|
|
1939
|
-
assertResolvedMediaBlock(b, "Anthropic wire messages");
|
|
1940
|
-
return {
|
|
1941
|
-
type: "text",
|
|
1942
|
-
text: documentMarker(b)
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
|
-
if (b.type === "audio") return {
|
|
1946
|
-
type: "text",
|
|
1947
|
-
text: `[audio omitted: ${b.mediaType}]`
|
|
1948
|
-
};
|
|
1949
|
-
if (b.type === "video") return {
|
|
1950
|
-
type: "text",
|
|
1951
|
-
text: `[video omitted: ${b.mediaType}]`
|
|
1952
|
-
};
|
|
1953
|
-
assertResolvedMediaBlock(b, "Anthropic wire messages");
|
|
1954
|
-
return {
|
|
1955
|
-
type: "image",
|
|
1956
|
-
source: anthropicImageSource(b.mediaType, b.data)
|
|
1957
|
-
};
|
|
1958
|
-
});
|
|
1959
|
-
}
|
|
1960
|
-
function fromAnthropic(msg) {
|
|
1961
|
-
const role = msg.role;
|
|
1962
|
-
const content = [];
|
|
1963
|
-
if (typeof msg.content === "string") {
|
|
1964
|
-
content.push({
|
|
1965
|
-
type: "text",
|
|
1966
|
-
text: msg.content
|
|
1967
|
-
});
|
|
1968
|
-
return {
|
|
1969
|
-
role,
|
|
1970
|
-
content
|
|
1971
|
-
};
|
|
1972
|
-
}
|
|
1973
|
-
if (Array.isArray(msg.content)) for (const block of msg.content) {
|
|
1974
|
-
if (!block || typeof block !== "object") continue;
|
|
1975
|
-
const b = block;
|
|
1976
|
-
if (b.type === "text") content.push({
|
|
1977
|
-
type: "text",
|
|
1978
|
-
text: b.text
|
|
1979
|
-
});
|
|
1980
|
-
else if (b.type === "image") {
|
|
1981
|
-
const source = b.source;
|
|
1982
|
-
if (source?.type === "base64") content.push({
|
|
1983
|
-
type: "image",
|
|
1984
|
-
mediaType: source.media_type,
|
|
1985
|
-
data: source.data
|
|
1986
|
-
});
|
|
1987
|
-
} else if (b.type === "document") {
|
|
1988
|
-
const source = b.source;
|
|
1989
|
-
if (source?.type === "base64" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
|
|
1990
|
-
type: "document",
|
|
1991
|
-
mediaType: source.media_type,
|
|
1992
|
-
data: source.data,
|
|
1993
|
-
encoding: "base64",
|
|
1994
|
-
...typeof b.title === "string" ? { name: b.title } : {}
|
|
1995
|
-
});
|
|
1996
|
-
else if (source?.type === "text" && typeof source.media_type === "string" && typeof source.data === "string") content.push({
|
|
1997
|
-
type: "document",
|
|
1998
|
-
mediaType: source.media_type,
|
|
1999
|
-
data: source.data,
|
|
2000
|
-
encoding: "text",
|
|
2001
|
-
...typeof b.title === "string" ? { name: b.title } : {}
|
|
2002
|
-
});
|
|
2003
|
-
} else if (b.type === "tool_use") content.push({
|
|
2004
|
-
type: "tool_call",
|
|
2005
|
-
id: b.id,
|
|
2006
|
-
name: b.name,
|
|
2007
|
-
input: b.input
|
|
2008
|
-
});
|
|
2009
|
-
else if (b.type === "tool_result") {
|
|
2010
|
-
const output = decodeAnthropicToolResultContent(b.content);
|
|
2011
|
-
const block = {
|
|
2012
|
-
type: "tool_result",
|
|
2013
|
-
callId: b.tool_use_id,
|
|
2014
|
-
output
|
|
2015
|
-
};
|
|
2016
|
-
if (b.is_error === true) block.isError = true;
|
|
2017
|
-
content.push(block);
|
|
2018
|
-
} else if (b.type === "thinking") {
|
|
2019
|
-
const block = {
|
|
2020
|
-
type: "thinking",
|
|
2021
|
-
text: b.thinking ?? ""
|
|
2022
|
-
};
|
|
2023
|
-
if (typeof b.signature === "string") {
|
|
2024
|
-
block.signature = b.signature;
|
|
2025
|
-
block.signatureProducer = "anthropic";
|
|
2026
|
-
}
|
|
2027
|
-
content.push(block);
|
|
2028
|
-
} else if (b.type === "redacted_thinking") content.push({
|
|
2029
|
-
type: "redacted_thinking",
|
|
2030
|
-
data: b.data ?? ""
|
|
2031
|
-
});
|
|
2032
|
-
else if (b.type === "server_tool_use") content.push({
|
|
2033
|
-
type: "server_tool_use",
|
|
2034
|
-
producer: "anthropic",
|
|
2035
|
-
id: b.id ?? "",
|
|
2036
|
-
name: b.name ?? "",
|
|
2037
|
-
input: b.input ?? {}
|
|
2038
|
-
});
|
|
2039
|
-
else if (b.type === "web_search_tool_result") content.push({
|
|
2040
|
-
type: "server_tool_result",
|
|
2041
|
-
producer: "anthropic",
|
|
2042
|
-
toolUseId: b.tool_use_id ?? "",
|
|
2043
|
-
toolName: "web_search",
|
|
2044
|
-
content: b.content
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
return {
|
|
2048
|
-
role,
|
|
2049
|
-
content
|
|
2050
|
-
};
|
|
2051
|
-
}
|
|
2052
|
-
function fromOpenAI(msg) {
|
|
2053
|
-
const role = msg.role;
|
|
2054
|
-
const content = [];
|
|
2055
|
-
const c = msg.content;
|
|
2056
|
-
if (c == null) return {
|
|
2057
|
-
role,
|
|
2058
|
-
content
|
|
2059
|
-
};
|
|
2060
|
-
if (typeof c === "string") {
|
|
2061
|
-
content.push({
|
|
2062
|
-
type: "text",
|
|
2063
|
-
text: c
|
|
2064
|
-
});
|
|
2065
|
-
return {
|
|
2066
|
-
role,
|
|
2067
|
-
content
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_assistant_tc__") {
|
|
2071
|
-
const tagged = c;
|
|
2072
|
-
if (typeof tagged.text === "string" && tagged.text) content.push({
|
|
2073
|
-
type: "text",
|
|
2074
|
-
text: tagged.text
|
|
2075
|
-
});
|
|
2076
|
-
if (Array.isArray(tagged.tool_calls)) for (const raw of tagged.tool_calls) {
|
|
2077
|
-
if (!raw || typeof raw !== "object") continue;
|
|
2078
|
-
const tc = raw;
|
|
2079
|
-
const rawArgs = tc.function?.arguments;
|
|
2080
|
-
let input = {};
|
|
2081
|
-
if (typeof rawArgs === "string") try {
|
|
2082
|
-
input = JSON.parse(rawArgs);
|
|
2083
|
-
} catch {
|
|
2084
|
-
input = {};
|
|
2085
|
-
}
|
|
2086
|
-
else if (rawArgs && typeof rawArgs === "object") input = rawArgs;
|
|
2087
|
-
content.push({
|
|
2088
|
-
type: "tool_call",
|
|
2089
|
-
id: tc.id ?? "",
|
|
2090
|
-
name: tc.function?.name ?? "",
|
|
2091
|
-
input
|
|
2092
|
-
});
|
|
2093
|
-
}
|
|
2094
|
-
return {
|
|
2095
|
-
role,
|
|
2096
|
-
content
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
if (typeof c === "object" && !Array.isArray(c) && c._tag === "__zidane_tool_results__") {
|
|
2100
|
-
const tagged = c;
|
|
2101
|
-
if (Array.isArray(tagged.results)) for (const raw of tagged.results) {
|
|
2102
|
-
if (!raw || typeof raw !== "object") continue;
|
|
2103
|
-
const r = raw;
|
|
2104
|
-
content.push({
|
|
2105
|
-
type: "tool_result",
|
|
2106
|
-
callId: r.tool_call_id ?? "",
|
|
2107
|
-
output: r.content ?? ""
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
if (typeof tagged.text === "string" && tagged.text) content.push({
|
|
2111
|
-
type: "text",
|
|
2112
|
-
text: tagged.text
|
|
2113
|
-
});
|
|
2114
|
-
return {
|
|
2115
|
-
role,
|
|
2116
|
-
content
|
|
2117
|
-
};
|
|
2118
|
-
}
|
|
2119
|
-
if (Array.isArray(c)) {
|
|
2120
|
-
for (const block of c) {
|
|
2121
|
-
if (!block || typeof block !== "object") continue;
|
|
2122
|
-
const b = block;
|
|
2123
|
-
if (b.type === "text") content.push({
|
|
2124
|
-
type: "text",
|
|
2125
|
-
text: b.text
|
|
2126
|
-
});
|
|
2127
|
-
else if (b.type === "image_url") {
|
|
2128
|
-
const imageUrl = b.image_url?.url;
|
|
2129
|
-
if (imageUrl?.startsWith("data:")) {
|
|
2130
|
-
const [meta, data] = imageUrl.slice(5).split(",", 2);
|
|
2131
|
-
const mediaType = meta.replace(";base64", "");
|
|
2132
|
-
content.push({
|
|
2133
|
-
type: "image",
|
|
2134
|
-
mediaType,
|
|
2135
|
-
data
|
|
2136
|
-
});
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
return {
|
|
2141
|
-
role,
|
|
2142
|
-
content
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2145
|
-
return {
|
|
2146
|
-
role,
|
|
2147
|
-
content
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
function toAnthropic(msg) {
|
|
2151
|
-
const blocks = msg.content.filter((b) => {
|
|
2152
|
-
if (b.type === "provider_reasoning") return false;
|
|
2153
|
-
if (b.type !== "thinking") return true;
|
|
2154
|
-
if (b.signatureProducer === "openai") return false;
|
|
2155
|
-
if (!b.signature) return false;
|
|
2156
|
-
return true;
|
|
2157
|
-
}).flatMap((block) => {
|
|
2158
|
-
switch (block.type) {
|
|
2159
|
-
case "text": return [{
|
|
2160
|
-
type: "text",
|
|
2161
|
-
text: block.text
|
|
2162
|
-
}];
|
|
2163
|
-
case "image":
|
|
2164
|
-
assertResolvedMediaBlock(block, "Anthropic wire messages");
|
|
2165
|
-
return [{
|
|
2166
|
-
type: "image",
|
|
2167
|
-
source: anthropicImageSource(block.mediaType, block.data)
|
|
2168
|
-
}];
|
|
2169
|
-
case "audio": return [{
|
|
2170
|
-
type: "text",
|
|
2171
|
-
text: `[audio omitted: ${block.mediaType}]`
|
|
2172
|
-
}];
|
|
2173
|
-
case "video": return [{
|
|
2174
|
-
type: "text",
|
|
2175
|
-
text: `[video omitted: ${block.mediaType}]`
|
|
2176
|
-
}];
|
|
2177
|
-
case "document":
|
|
2178
|
-
assertResolvedMediaBlock(block, "Anthropic wire messages");
|
|
2179
|
-
return anthropicDocumentBlocks(block);
|
|
2180
|
-
case "tool_call": return [{
|
|
2181
|
-
type: "tool_use",
|
|
2182
|
-
id: sanitizeAnthropicToolId(block.id),
|
|
2183
|
-
name: block.name,
|
|
2184
|
-
input: block.input
|
|
2185
|
-
}];
|
|
2186
|
-
case "tool_result": {
|
|
2187
|
-
const out = {
|
|
2188
|
-
type: "tool_result",
|
|
2189
|
-
tool_use_id: sanitizeAnthropicToolId(block.callId),
|
|
2190
|
-
content: encodeAnthropicToolResultContent(block.output)
|
|
2191
|
-
};
|
|
2192
|
-
if (block.isError) out.is_error = true;
|
|
2193
|
-
return [out, ...(typeof block.output === "string" ? [] : block.output.filter((part) => part.type === "document" && typeof part.data === "string")).flatMap(anthropicDocumentBlocks)];
|
|
2194
|
-
}
|
|
2195
|
-
case "thinking": return [{
|
|
2196
|
-
type: "thinking",
|
|
2197
|
-
thinking: block.text,
|
|
2198
|
-
signature: block.signature ?? ""
|
|
2199
|
-
}];
|
|
2200
|
-
case "redacted_thinking": return [{
|
|
2201
|
-
type: "redacted_thinking",
|
|
2202
|
-
data: block.data
|
|
2203
|
-
}];
|
|
2204
|
-
case "server_tool_use": return [{
|
|
2205
|
-
type: "server_tool_use",
|
|
2206
|
-
id: sanitizeAnthropicToolId(block.id),
|
|
2207
|
-
name: block.name,
|
|
2208
|
-
input: block.input
|
|
2209
|
-
}];
|
|
2210
|
-
case "server_tool_result": return [{
|
|
2211
|
-
type: "web_search_tool_result",
|
|
2212
|
-
tool_use_id: sanitizeAnthropicToolId(block.toolUseId),
|
|
2213
|
-
content: block.content
|
|
2214
|
-
}];
|
|
2215
|
-
default: return [{
|
|
2216
|
-
type: "text",
|
|
2217
|
-
text: ""
|
|
2218
|
-
}];
|
|
2219
|
-
}
|
|
2220
|
-
});
|
|
2221
|
-
const first = blocks[0];
|
|
2222
|
-
if (blocks.length === 1 && first.type === "text") return {
|
|
2223
|
-
role: msg.role,
|
|
2224
|
-
content: first.text
|
|
2225
|
-
};
|
|
2226
|
-
return {
|
|
2227
|
-
role: msg.role,
|
|
2228
|
-
content: blocks
|
|
2229
|
-
};
|
|
2230
|
-
}
|
|
2231
|
-
function toOpenAI(msg) {
|
|
2232
|
-
const toolCalls = msg.content.filter((b) => b.type === "tool_call");
|
|
2233
|
-
const toolResults = msg.content.filter((b) => b.type === "tool_result");
|
|
2234
|
-
const textBlocks = msg.content.filter((b) => b.type === "text");
|
|
2235
|
-
const imageBlocks = msg.content.filter((b) => b.type === "image");
|
|
2236
|
-
const audioBlocks = msg.content.filter((b) => b.type === "audio");
|
|
2237
|
-
const videoBlocks = msg.content.filter((b) => b.type === "video");
|
|
2238
|
-
const documentBlocks = msg.content.filter((b) => b.type === "document");
|
|
2239
|
-
if (toolResults.length > 0) {
|
|
2240
|
-
const siblingText = textBlocks.map((b) => b.text).join("\n");
|
|
2241
|
-
return {
|
|
2242
|
-
role: msg.role,
|
|
2243
|
-
content: {
|
|
2244
|
-
_tag: TOOL_RESULTS_TAG,
|
|
2245
|
-
results: toolResults.map((tr) => ({
|
|
2246
|
-
tool_call_id: tr.callId,
|
|
2247
|
-
content: tr.output
|
|
2248
|
-
})),
|
|
2249
|
-
...siblingText ? { text: siblingText } : {}
|
|
2250
|
-
}
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
if (toolCalls.length > 0) {
|
|
2254
|
-
const textContent = textBlocks.length > 0 ? textBlocks[0].text : null;
|
|
2255
|
-
return {
|
|
2256
|
-
role: msg.role,
|
|
2257
|
-
content: {
|
|
2258
|
-
_tag: ASSISTANT_TOOL_CALLS_TAG,
|
|
2259
|
-
text: textContent,
|
|
2260
|
-
tool_calls: toolCalls.map((tc) => ({
|
|
2261
|
-
id: tc.id,
|
|
2262
|
-
type: "function",
|
|
2263
|
-
function: {
|
|
2264
|
-
name: tc.name,
|
|
2265
|
-
arguments: JSON.stringify(tc.input)
|
|
2266
|
-
}
|
|
2267
|
-
}))
|
|
2268
|
-
}
|
|
2269
|
-
};
|
|
2270
|
-
}
|
|
2271
|
-
if (imageBlocks.length > 0 || audioBlocks.length > 0 || videoBlocks.length > 0 || documentBlocks.length > 0) {
|
|
2272
|
-
const parts = [];
|
|
2273
|
-
for (const img of imageBlocks) {
|
|
2274
|
-
assertResolvedMediaBlock(img, "OpenAI-compatible wire messages");
|
|
2275
|
-
parts.push({
|
|
2276
|
-
type: "image_url",
|
|
2277
|
-
image_url: { url: `data:${img.mediaType};base64,${img.data}` }
|
|
2278
|
-
});
|
|
2279
|
-
}
|
|
2280
|
-
for (const b of audioBlocks) parts.push({
|
|
2281
|
-
type: "input_audio",
|
|
2282
|
-
input_audio: {
|
|
2283
|
-
data: b.data,
|
|
2284
|
-
format: audioFormatFromMediaType(b.mediaType)
|
|
2285
|
-
}
|
|
2286
|
-
});
|
|
2287
|
-
for (const b of videoBlocks) parts.push({
|
|
2288
|
-
type: "video_url",
|
|
2289
|
-
video_url: { url: `data:${b.mediaType};base64,${b.data}` }
|
|
2290
|
-
});
|
|
2291
|
-
for (const b of documentBlocks) {
|
|
2292
|
-
assertResolvedMediaBlock(b, "OpenAI-compatible wire messages");
|
|
2293
|
-
parts.push({
|
|
2294
|
-
type: "text",
|
|
2295
|
-
text: documentMarker(b)
|
|
2296
|
-
});
|
|
2297
|
-
}
|
|
2298
|
-
for (const b of textBlocks) parts.push({
|
|
2299
|
-
type: "text",
|
|
2300
|
-
text: b.text
|
|
2301
|
-
});
|
|
2302
|
-
return {
|
|
2303
|
-
role: msg.role,
|
|
2304
|
-
content: parts
|
|
2305
|
-
};
|
|
2306
|
-
}
|
|
2307
|
-
if (textBlocks.length === 1) return {
|
|
2308
|
-
role: msg.role,
|
|
2309
|
-
content: textBlocks[0].text
|
|
2310
|
-
};
|
|
2311
|
-
if (textBlocks.length > 1) return {
|
|
2312
|
-
role: msg.role,
|
|
2313
|
-
content: textBlocks.map((b) => ({
|
|
2314
|
-
type: "text",
|
|
2315
|
-
text: b.text
|
|
2316
|
-
}))
|
|
2317
|
-
};
|
|
2318
|
-
return {
|
|
2319
|
-
role: msg.role,
|
|
2320
|
-
content: null
|
|
2321
|
-
};
|
|
2322
|
-
}
|
|
2323
|
-
/**
|
|
2324
|
-
* Placeholder content inserted into a synthetic `tool_result` when the harness
|
|
2325
|
-
* has to repair an orphan `tool_use`. Exported so downstream consumers
|
|
2326
|
-
* (training-data collectors, HFI submission) can reject any payload containing
|
|
2327
|
-
* it — the marker satisfies the wire-level pairing contract structurally but
|
|
2328
|
-
* the content itself is fake and would poison fine-tuning data.
|
|
2329
|
-
*/
|
|
2330
|
-
const SYNTHETIC_TOOL_RESULT_PLACEHOLDER = "[Tool result missing due to internal error]";
|
|
2331
|
-
/**
|
|
2332
|
-
* Replacement text for an assistant message whose every content block was
|
|
2333
|
-
* stripped during pairing repair (e.g. the only blocks were orphan
|
|
2334
|
-
* `tool_call`s). Providers reject empty `content` arrays — the marker keeps
|
|
2335
|
-
* the turn shape valid while signaling "this assistant turn lost its
|
|
2336
|
-
* outputs" to the model so it can recover.
|
|
2337
|
-
*/
|
|
2338
|
-
const TOOL_USE_INTERRUPTED_MARKER = "[Tool use interrupted]";
|
|
2339
|
-
/**
|
|
2340
|
-
* Replacement text for a user message whose every content block was a
|
|
2341
|
-
* `tool_result` with no matching upstream `tool_call` (e.g. the assistant
|
|
2342
|
-
* pair was stripped by an earlier compaction). Same role as
|
|
2343
|
-
* {@link TOOL_USE_INTERRUPTED_MARKER} on the user side.
|
|
2344
|
-
*/
|
|
2345
|
-
const ORPHANED_TOOL_RESULT_MARKER = "[Orphaned tool result removed due to conversation resume]";
|
|
2346
|
-
/**
|
|
2347
|
-
* Defensive repair pass that rewrites a message list so it satisfies the
|
|
2348
|
-
* wire-level `tool_use` ↔ `tool_result` adjacency contract every modern
|
|
2349
|
-
* provider enforces.
|
|
2350
|
-
*
|
|
2351
|
-
* Anthropic 400s on orphans with `'tool_use' ids were found without
|
|
2352
|
-
* 'tool_result' blocks immediately after` (and its inverse, `tool_result
|
|
2353
|
-
* must be preceded by a tool_call with the same toolCallId`). OpenAI's
|
|
2354
|
-
* Chat Completions API rejects most mismatches with a 400 too.
|
|
2355
|
-
*
|
|
2356
|
-
* Six repair modes — modeled after Anthropic's Claude Code defenses:
|
|
2357
|
-
*
|
|
2358
|
-
* | # | Corruption | Repair |
|
|
2359
|
-
* |---|------------|--------|
|
|
2360
|
-
* | 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` |
|
|
2361
|
-
* | 2 | assistant `tool_use` followed by nothing (or by a non-user msg) | **Insert** a new synthetic user message with the same placeholder |
|
|
2362
|
-
* | 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 |
|
|
2363
|
-
* | 4 | duplicate `tool_call.id` across assistant messages (CC-1212) | **Strip** later instances + their matching tool_results |
|
|
2364
|
-
* | 5 | duplicate `tool_result.callId` within a single user message | **Dedupe** by `callId`, keep first |
|
|
2365
|
-
* | 6 | assistant message emptied by mode-4 stripping | **Replace** content with {@link TOOL_USE_INTERRUPTED_MARKER} text block |
|
|
2366
|
-
*
|
|
2367
|
-
* **Repair, not drop.** Earlier versions of this pass simply dropped orphan
|
|
2368
|
-
* blocks, which (a) silently rewrote history the model had reasoned over and
|
|
2369
|
-
* (b) cascaded — dropping an assistant tool_call would orphan its
|
|
2370
|
-
* tool_result, which would then get dropped too, removing any trace of
|
|
2371
|
-
* "what the model tried to do" from the transcript. The repair-based pass
|
|
2372
|
-
* preserves the model's tool_use shape and patches the dangling result with
|
|
2373
|
-
* an `is_error` placeholder so the model sees "I tried X, the result was
|
|
2374
|
-
* lost" and can retry intelligently.
|
|
2375
|
-
*
|
|
2376
|
-
* Adjacency contract: `tool_result` blocks must live in the user message
|
|
2377
|
-
* IMMEDIATELY following the assistant message that emitted the matching
|
|
2378
|
-
* `tool_use`. The pass enforces strict adjacency — a tool_result two
|
|
2379
|
-
* messages downstream of its tool_call is still an orphan.
|
|
2380
|
-
*
|
|
2381
|
-
* Idempotent: returns the input reference unchanged when no repairs were
|
|
2382
|
-
* necessary. Re-running on already-repaired output is a no-op.
|
|
2383
|
-
*
|
|
2384
|
-
* Pure: does not mutate input messages or their content arrays — every
|
|
2385
|
-
* repair allocates a fresh array / object.
|
|
2386
|
-
*/
|
|
2387
|
-
function ensureToolResultPairing(messages, options = {}) {
|
|
2388
|
-
if (messages.length === 0) return messages;
|
|
2389
|
-
const fireRepair = options.onRepair ?? (() => {});
|
|
2390
|
-
const firstSeenAt = /* @__PURE__ */ new Map();
|
|
2391
|
-
for (let i = 0; i < messages.length; i++) {
|
|
2392
|
-
const msg = messages[i];
|
|
2393
|
-
if (msg.role !== "assistant") continue;
|
|
2394
|
-
for (const block of msg.content) if (block.type === "tool_call" && !firstSeenAt.has(block.id)) firstSeenAt.set(block.id, i);
|
|
2395
|
-
}
|
|
2396
|
-
const pendingOrphansByUserIndex = /* @__PURE__ */ new Map();
|
|
2397
|
-
let changed = false;
|
|
2398
|
-
const markChanged = () => {
|
|
2399
|
-
changed = true;
|
|
2400
|
-
};
|
|
2401
|
-
const out = [];
|
|
2402
|
-
for (let i = 0; i < messages.length; i++) {
|
|
2403
|
-
const msg = messages[i];
|
|
2404
|
-
if (msg.role === "assistant") processAssistantMessage({
|
|
2405
|
-
index: i,
|
|
2406
|
-
msg,
|
|
2407
|
-
messages,
|
|
2408
|
-
firstSeenAt,
|
|
2409
|
-
pendingOrphansByUserIndex,
|
|
2410
|
-
out,
|
|
2411
|
-
fireRepair,
|
|
2412
|
-
markChanged
|
|
2413
|
-
});
|
|
2414
|
-
else processUserMessage({
|
|
2415
|
-
index: i,
|
|
2416
|
-
msg,
|
|
2417
|
-
pendingOrphansByUserIndex,
|
|
2418
|
-
out,
|
|
2419
|
-
fireRepair,
|
|
2420
|
-
markChanged
|
|
2421
|
-
});
|
|
2422
|
-
}
|
|
2423
|
-
return changed ? out : messages;
|
|
2424
|
-
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Process one assistant message: strip mode-4 duplicates, queue mode-1 / 2
|
|
2427
|
-
* orphan-tool-use repairs for the next user message (or insert a synthetic
|
|
2428
|
-
* one), and emit a mode-6 marker when dedup empties the message.
|
|
2429
|
-
*/
|
|
2430
|
-
function processAssistantMessage(args) {
|
|
2431
|
-
const { index, msg, messages, firstSeenAt, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
|
|
2432
|
-
const dedupedContent = [];
|
|
2433
|
-
const seenHere = /* @__PURE__ */ new Set();
|
|
2434
|
-
let dedupedChanged = false;
|
|
2435
|
-
for (const block of msg.content) {
|
|
2436
|
-
if (block.type === "tool_call") {
|
|
2437
|
-
const isCrossMsgDup = firstSeenAt.get(block.id) !== index;
|
|
2438
|
-
const isIntraMsgDup = seenHere.has(block.id);
|
|
2439
|
-
if (isCrossMsgDup || isIntraMsgDup) {
|
|
2440
|
-
dedupedChanged = true;
|
|
2441
|
-
fireRepair({
|
|
2442
|
-
mode: "duplicate-tool-use-strip",
|
|
2443
|
-
callId: block.id,
|
|
2444
|
-
messageIndex: index
|
|
2445
|
-
});
|
|
2446
|
-
continue;
|
|
2447
|
-
}
|
|
2448
|
-
seenHere.add(block.id);
|
|
2449
|
-
}
|
|
2450
|
-
dedupedContent.push(block);
|
|
2451
|
-
}
|
|
2452
|
-
const surviving = collectIds(dedupedContent, "tool_call", (b) => b.id);
|
|
2453
|
-
const next = messages[index + 1];
|
|
2454
|
-
const nextIsUser = next && next.role === "user";
|
|
2455
|
-
const nextResultIds = nextIsUser ? collectResultIds(next.content) : /* @__PURE__ */ new Set();
|
|
2456
|
-
const orphans = [];
|
|
2457
|
-
for (const id of surviving) if (!nextResultIds.has(id)) orphans.push(id);
|
|
2458
|
-
emitAssistantAfterDedup({
|
|
2459
|
-
index,
|
|
2460
|
-
msg,
|
|
2461
|
-
dedupedContent,
|
|
2462
|
-
dedupedChanged,
|
|
2463
|
-
out,
|
|
2464
|
-
fireRepair,
|
|
2465
|
-
markChanged
|
|
2466
|
-
});
|
|
2467
|
-
if (orphans.length === 0) return;
|
|
2468
|
-
if (nextIsUser) {
|
|
2469
|
-
markChanged();
|
|
2470
|
-
pendingOrphansByUserIndex.set(index + 1, orphans);
|
|
2471
|
-
for (const callId of orphans) fireRepair({
|
|
2472
|
-
mode: "orphan-tool-use-prepend",
|
|
2473
|
-
callId,
|
|
2474
|
-
messageIndex: index
|
|
2475
|
-
});
|
|
2476
|
-
return;
|
|
2477
|
-
}
|
|
2478
|
-
markChanged();
|
|
2479
|
-
out.push({
|
|
2480
|
-
role: "user",
|
|
2481
|
-
content: orphans.map((callId) => syntheticResultBlock(callId))
|
|
2482
|
-
});
|
|
2483
|
-
for (const callId of orphans) fireRepair({
|
|
2484
|
-
mode: "orphan-tool-use-append",
|
|
2485
|
-
callId,
|
|
2486
|
-
messageIndex: index
|
|
2487
|
-
});
|
|
2488
|
-
}
|
|
2489
|
-
function emitAssistantAfterDedup(args) {
|
|
2490
|
-
const { index, msg, dedupedContent, dedupedChanged, out, fireRepair, markChanged } = args;
|
|
2491
|
-
if (!dedupedChanged) {
|
|
2492
|
-
out.push(msg);
|
|
2493
|
-
return;
|
|
2494
|
-
}
|
|
2495
|
-
markChanged();
|
|
2496
|
-
if (dedupedContent.length === 0) {
|
|
2497
|
-
out.push({
|
|
2498
|
-
...msg,
|
|
2499
|
-
content: [{
|
|
2500
|
-
type: "text",
|
|
2501
|
-
text: TOOL_USE_INTERRUPTED_MARKER
|
|
2502
|
-
}]
|
|
2503
|
-
});
|
|
2504
|
-
fireRepair({
|
|
2505
|
-
mode: "empty-assistant-marker",
|
|
2506
|
-
messageIndex: index
|
|
2507
|
-
});
|
|
2508
|
-
return;
|
|
2509
|
-
}
|
|
2510
|
-
out.push({
|
|
2511
|
-
...msg,
|
|
2512
|
-
content: dedupedContent
|
|
2513
|
-
});
|
|
2514
|
-
}
|
|
2515
|
-
/**
|
|
2516
|
-
* Process one user message: dedup mode-5 duplicate tool_results, strip
|
|
2517
|
-
* mode-3 orphans whose tool_call is missing from the previously-emitted
|
|
2518
|
-
* assistant message, and prepend any synthetic results queued by the prior
|
|
2519
|
-
* assistant's mode-1 repair.
|
|
2520
|
-
*/
|
|
2521
|
-
function processUserMessage(args) {
|
|
2522
|
-
const { index, msg, pendingOrphansByUserIndex, out, fireRepair, markChanged } = args;
|
|
2523
|
-
const queuedOrphans = pendingOrphansByUserIndex.get(index);
|
|
2524
|
-
pendingOrphansByUserIndex.delete(index);
|
|
2525
|
-
const prev = out.length > 0 ? out[out.length - 1] : null;
|
|
2526
|
-
const validIds = prev && prev.role === "assistant" ? collectIds(prev.content, "tool_call", (b) => b.id) : /* @__PURE__ */ new Set();
|
|
2527
|
-
const seenCallIds = /* @__PURE__ */ new Set();
|
|
2528
|
-
let modifiedHere = false;
|
|
2529
|
-
const afterDedup = [];
|
|
2530
|
-
for (const block of msg.content) {
|
|
2531
|
-
if (block.type !== "tool_result") {
|
|
2532
|
-
afterDedup.push(block);
|
|
2533
|
-
continue;
|
|
2534
|
-
}
|
|
2535
|
-
if (seenCallIds.has(block.callId)) {
|
|
2536
|
-
modifiedHere = true;
|
|
2537
|
-
fireRepair({
|
|
2538
|
-
mode: "duplicate-tool-result-strip",
|
|
2539
|
-
callId: block.callId,
|
|
2540
|
-
messageIndex: index
|
|
2541
|
-
});
|
|
2542
|
-
continue;
|
|
2543
|
-
}
|
|
2544
|
-
seenCallIds.add(block.callId);
|
|
2545
|
-
afterDedup.push(block);
|
|
2546
|
-
}
|
|
2547
|
-
const afterStrip = [];
|
|
2548
|
-
for (const block of afterDedup) {
|
|
2549
|
-
if (block.type === "tool_result" && !validIds.has(block.callId)) {
|
|
2550
|
-
modifiedHere = true;
|
|
2551
|
-
fireRepair({
|
|
2552
|
-
mode: "orphan-tool-result-strip",
|
|
2553
|
-
callId: block.callId,
|
|
2554
|
-
messageIndex: index
|
|
2555
|
-
});
|
|
2556
|
-
continue;
|
|
2557
|
-
}
|
|
2558
|
-
afterStrip.push(block);
|
|
2559
|
-
}
|
|
2560
|
-
const finalContent = queuedOrphans ? [...queuedOrphans.map(syntheticResultBlock), ...afterStrip] : afterStrip;
|
|
2561
|
-
if (!modifiedHere && queuedOrphans === void 0) {
|
|
2562
|
-
out.push(msg);
|
|
2563
|
-
return;
|
|
2564
|
-
}
|
|
2565
|
-
markChanged();
|
|
2566
|
-
if (finalContent.length === 0) {
|
|
2567
|
-
out.push({
|
|
2568
|
-
...msg,
|
|
2569
|
-
content: [{
|
|
2570
|
-
type: "text",
|
|
2571
|
-
text: ORPHANED_TOOL_RESULT_MARKER
|
|
2572
|
-
}]
|
|
2573
|
-
});
|
|
2574
|
-
return;
|
|
2575
|
-
}
|
|
2576
|
-
out.push({
|
|
2577
|
-
...msg,
|
|
2578
|
-
content: finalContent
|
|
2579
|
-
});
|
|
2580
|
-
}
|
|
2581
|
-
/**
|
|
2582
|
-
* Re-mint tool-call ids that collide with ids already present in the
|
|
2583
|
-
* transcript (or with an earlier sibling in the same turn), BEFORE the turn
|
|
2584
|
-
* is persisted and its tools dispatched.
|
|
2585
|
-
*
|
|
2586
|
-
* Why this exists: some providers/serving stacks emit non-globally-unique
|
|
2587
|
-
* tool_use ids (per-turn counters like `call_0`, retry-replay duplicates,
|
|
2588
|
-
* Kimi-K2.6 on Fireworks). The wire-time pairing pass
|
|
2589
|
-
* ({@link ensureToolResultPairing}) can only STRIP such duplicates — which
|
|
2590
|
-
* erases the model's tool calls and results from the wire view, stalls
|
|
2591
|
-
* progress, and re-accumulates repairs quadratically because the persisted
|
|
2592
|
-
* session keeps the corrupted ids. Re-minting at persist time keeps the
|
|
2593
|
-
* pairing intact instead: the call, its execution, and its tool_result all
|
|
2594
|
-
* carry the fresh id, so the pairing pass has nothing to strip on any later
|
|
2595
|
-
* turn.
|
|
2596
|
-
*
|
|
2597
|
-
* Fresh ids are `${oldId}_r{n}` with `n` bumped until unique — deterministic
|
|
2598
|
-
* shape, Anthropic-wire-safe charset (given a safe input id), and visibly
|
|
2599
|
-
* traceable back to the original.
|
|
2600
|
-
*
|
|
2601
|
-
* Alignment contract: `toolCalls` and the `tool_call` blocks inside
|
|
2602
|
-
* `content` are two views of the same emission and MUST be in the same
|
|
2603
|
-
* order (true for every provider — both are built from one source array).
|
|
2604
|
-
* Occurrences are matched positionally per old id so intra-turn duplicates
|
|
2605
|
-
* map to distinct fresh ids on both views.
|
|
2606
|
-
*
|
|
2607
|
-
* Pure: returns the input references unchanged when no id collides.
|
|
2608
|
-
*/
|
|
2609
|
-
function remintDuplicateToolCallIds(toolCalls, content, priorIds) {
|
|
2610
|
-
const seen = new Set(priorIds);
|
|
2611
|
-
const reminted = [];
|
|
2612
|
-
const finalIdQueues = /* @__PURE__ */ new Map();
|
|
2613
|
-
let changed = false;
|
|
2614
|
-
const outCalls = toolCalls.map((tc) => {
|
|
2615
|
-
let finalId = tc.id;
|
|
2616
|
-
if (seen.has(tc.id)) {
|
|
2617
|
-
let n = 1;
|
|
2618
|
-
while (seen.has(`${tc.id}_r${n}`)) n++;
|
|
2619
|
-
finalId = `${tc.id}_r${n}`;
|
|
2620
|
-
reminted.push({
|
|
2621
|
-
oldId: tc.id,
|
|
2622
|
-
newId: finalId
|
|
2623
|
-
});
|
|
2624
|
-
changed = true;
|
|
2625
|
-
}
|
|
2626
|
-
seen.add(finalId);
|
|
2627
|
-
const queue = finalIdQueues.get(tc.id);
|
|
2628
|
-
if (queue) queue.push(finalId);
|
|
2629
|
-
else finalIdQueues.set(tc.id, [finalId]);
|
|
2630
|
-
return finalId === tc.id ? tc : {
|
|
2631
|
-
...tc,
|
|
2632
|
-
id: finalId
|
|
2633
|
-
};
|
|
2634
|
-
});
|
|
2635
|
-
if (!changed) return {
|
|
2636
|
-
toolCalls,
|
|
2637
|
-
content,
|
|
2638
|
-
reminted
|
|
2639
|
-
};
|
|
2640
|
-
return {
|
|
2641
|
-
toolCalls: outCalls,
|
|
2642
|
-
content: content.map((block) => {
|
|
2643
|
-
if (block.type !== "tool_call") return block;
|
|
2644
|
-
const finalId = finalIdQueues.get(block.id)?.shift();
|
|
2645
|
-
if (finalId === void 0 || finalId === block.id) return block;
|
|
2646
|
-
return {
|
|
2647
|
-
...block,
|
|
2648
|
-
id: finalId
|
|
2649
|
-
};
|
|
2650
|
-
}),
|
|
2651
|
-
reminted
|
|
2652
|
-
};
|
|
2653
|
-
}
|
|
2654
|
-
function syntheticResultBlock(callId) {
|
|
2655
|
-
return {
|
|
2656
|
-
type: "tool_result",
|
|
2657
|
-
callId,
|
|
2658
|
-
output: SYNTHETIC_TOOL_RESULT_PLACEHOLDER,
|
|
2659
|
-
isError: true
|
|
2660
|
-
};
|
|
2661
|
-
}
|
|
2662
|
-
function collectResultIds(content) {
|
|
2663
|
-
const ids = /* @__PURE__ */ new Set();
|
|
2664
|
-
for (const block of content) if (block.type === "tool_result") ids.add(block.callId);
|
|
2665
|
-
return ids;
|
|
2666
|
-
}
|
|
2667
|
-
function collectIds(content, type, getId) {
|
|
2668
|
-
const ids = /* @__PURE__ */ new Set();
|
|
2669
|
-
for (const block of content) if (block.type === type) ids.add(getId(block));
|
|
2670
|
-
return ids;
|
|
2671
|
-
}
|
|
2672
|
-
/**
|
|
2673
|
-
* Drop ASSISTANT turns whose every `tool_call` block is unresolved
|
|
2674
|
-
* (no matching `tool_result` block anywhere later in the transcript).
|
|
2675
|
-
* Tool_call blocks with at least one matching tool_result are kept — modes
|
|
2676
|
-
* 1/3 of {@link ensureToolResultPairing} handle the partial-pair case at
|
|
2677
|
-
* wire-send time.
|
|
2678
|
-
*
|
|
2679
|
-
* Use case: session resume. A turn that emitted three `tool_use` blocks but
|
|
2680
|
-
* never persisted the matching `tool_result` turn (process death, crash,
|
|
2681
|
-
* `kill -9`) leaves the transcript with an orphan that Anthropic rejects on
|
|
2682
|
-
* the FIRST API call after reload. Dropping the whole assistant turn — not
|
|
2683
|
-
* just the orphan blocks — preserves text-only turns that legitimately
|
|
2684
|
-
* carry reasoning the model wants to see again.
|
|
2685
|
-
*
|
|
2686
|
-
* Does NOT mint fresh ids: re-id'ing on every resume would cause
|
|
2687
|
-
* exponential transcript growth across repeated resumes of an interrupted
|
|
2688
|
-
* session.
|
|
2689
|
-
*
|
|
2690
|
-
* Pure: returns the input reference unchanged when no turn was dropped.
|
|
2691
|
-
*/
|
|
2692
|
-
function filterUnresolvedToolUses(turns) {
|
|
2693
|
-
if (turns.length === 0) return turns;
|
|
2694
|
-
const resolvedIds = /* @__PURE__ */ new Set();
|
|
2695
|
-
for (const turn of turns) for (const block of turn.content) if (block.type === "tool_result") resolvedIds.add(block.callId);
|
|
2696
|
-
let changed = false;
|
|
2697
|
-
const out = [];
|
|
2698
|
-
for (const turn of turns) {
|
|
2699
|
-
if (turn.role !== "assistant") {
|
|
2700
|
-
out.push(turn);
|
|
2701
|
-
continue;
|
|
2702
|
-
}
|
|
2703
|
-
const toolCalls = turn.content.filter((b) => b.type === "tool_call");
|
|
2704
|
-
if (toolCalls.length === 0) {
|
|
2705
|
-
out.push(turn);
|
|
2706
|
-
continue;
|
|
2707
|
-
}
|
|
2708
|
-
if (toolCalls.every((b) => b.type === "tool_call" && !resolvedIds.has(b.id))) {
|
|
2709
|
-
changed = true;
|
|
2710
|
-
continue;
|
|
2711
|
-
}
|
|
2712
|
-
out.push(turn);
|
|
2713
|
-
}
|
|
2714
|
-
return changed ? out : turns;
|
|
2715
|
-
}
|
|
2716
|
-
/**
|
|
2717
|
-
* Inspect a session's trailing turn to classify whether the run was
|
|
2718
|
-
* interrupted mid-tool-call. Pure / synchronous — does not modify the input.
|
|
2719
|
-
*
|
|
2720
|
-
* Pair with {@link filterUnresolvedToolUses} on the resume path: filter
|
|
2721
|
-
* first so the result reflects the post-cleanup state (a turn whose orphans
|
|
2722
|
-
* were stripped reads as 'completed', not 'interrupted').
|
|
2723
|
-
*/
|
|
2724
|
-
function detectTurnInterruption(turns) {
|
|
2725
|
-
if (turns.length === 0) return "clean";
|
|
2726
|
-
const last = turns[turns.length - 1];
|
|
2727
|
-
if (last.role === "user") return "clean";
|
|
2728
|
-
if (last.role !== "assistant") return "clean";
|
|
2729
|
-
const resolvedIds = /* @__PURE__ */ new Set();
|
|
2730
|
-
for (const turn of turns) for (const block of turn.content) if (block.type === "tool_result") resolvedIds.add(block.callId);
|
|
2731
|
-
for (const block of last.content) if (block.type === "tool_call" && !resolvedIds.has(block.id)) return "interrupted";
|
|
2732
|
-
return "completed";
|
|
2733
|
-
}
|
|
2734
|
-
/**
|
|
2735
|
-
* Default directive injected by {@link ensureEndsWithUserMessage} when the
|
|
2736
|
-
* caller doesn't pass one. Neutral enough to read sensibly in any context —
|
|
2737
|
-
* compaction passes its own directive, the agent loop falls back to this.
|
|
2738
|
-
*
|
|
2739
|
-
* The trailing newline is omitted on purpose; provider `userMessage()`
|
|
2740
|
-
* helpers wrap the content in the right block shape and any trailing
|
|
2741
|
-
* whitespace would land in the wire payload as-is.
|
|
2742
|
-
*/
|
|
2743
|
-
const DEFAULT_USER_TAIL_DIRECTIVE = "Continue.";
|
|
2744
|
-
/**
|
|
2745
|
-
* Guarantee a message list ends with a `role: 'user'` message, appending a
|
|
2746
|
-
* synthetic directive when it doesn't.
|
|
2747
|
-
*
|
|
2748
|
-
* Why: Anthropic's API (and several OpenAI-compat reasoning models — opus
|
|
2749
|
-
* 4.7, the o-series translation layers on Bedrock/Vertex) rejects requests
|
|
2750
|
-
* whose final message is `role: 'assistant'` with
|
|
2751
|
-
* "This model does not support assistant message prefill. The conversation
|
|
2752
|
-
* must end with a user message."
|
|
2753
|
-
*
|
|
2754
|
-
* Triggers in zidane:
|
|
2755
|
-
* - Compaction over a session whose last turn is the agent's response
|
|
2756
|
-
* (the common case — autocompact fires right after `agent.run()`).
|
|
2757
|
-
* - Schema enforcement after an assistant text turn (no `tool_use`, so
|
|
2758
|
-
* `ensureToolResultPairing` doesn't synthesize a user tail).
|
|
2759
|
-
* - `context:transform` hooks that strip the trailing user turn.
|
|
2760
|
-
*
|
|
2761
|
-
* The pair-repair pass {@link ensureToolResultPairing} already covers the
|
|
2762
|
-
* orphan-`tool_use` case via mode 2; this helper closes the plain-text
|
|
2763
|
-
* assistant-tail case. They compose: run pair-repair first, then this
|
|
2764
|
-
* pass, so an assistant turn with orphan tool_uses gets its synthetic
|
|
2765
|
-
* `tool_result` user turn AND this pass is a no-op (the synthetic turn
|
|
2766
|
-
* is already user-role).
|
|
2767
|
-
*
|
|
2768
|
-
* Empty input passes through unchanged — the caller's "nothing to send"
|
|
2769
|
-
* guard is responsible for that case; we don't materialize a conversation
|
|
2770
|
-
* out of an empty list.
|
|
2771
|
-
*
|
|
2772
|
-
* Idempotent: returns the input reference unchanged when the list is
|
|
2773
|
-
* already user-terminated (or empty).
|
|
2774
|
-
*
|
|
2775
|
-
* @param messages Wire-ready message list.
|
|
2776
|
-
* @param provider Provider whose `userMessage()` shape we use so the
|
|
2777
|
-
* synthetic turn matches the wire format the rest of
|
|
2778
|
-
* the list already follows.
|
|
2779
|
-
* @param directive Text to put in the synthetic user message. Defaults
|
|
2780
|
-
* to {@link DEFAULT_USER_TAIL_DIRECTIVE} ("Continue.").
|
|
2781
|
-
* Compaction overrides this with a summary-specific cue.
|
|
2782
|
-
*/
|
|
2783
|
-
function ensureEndsWithUserMessage(messages, provider, directive = DEFAULT_USER_TAIL_DIRECTIVE) {
|
|
2784
|
-
if (messages.length === 0) return messages;
|
|
2785
|
-
if (messages[messages.length - 1].role === "user") return messages;
|
|
2786
|
-
return [...messages, provider.userMessage(directive)];
|
|
2787
|
-
}
|
|
2788
|
-
async function resolveRefContentBlock(block, resolveContentRef) {
|
|
2789
|
-
if (isContentRefBlock(block)) {
|
|
2790
|
-
if (!resolveContentRef) throw unresolvedContentRefError(block);
|
|
2791
|
-
const { data } = await resolveContentRef({
|
|
2792
|
-
type: block.type,
|
|
2793
|
-
ref: block.ref,
|
|
2794
|
-
mediaType: block.mediaType,
|
|
2795
|
-
...block.type === "document" ? { encoding: block.encoding } : {},
|
|
2796
|
-
...block.name ? { name: block.name } : {}
|
|
2797
|
-
});
|
|
2798
|
-
if (block.type === "image") return {
|
|
2799
|
-
block: {
|
|
2800
|
-
type: "image",
|
|
2801
|
-
mediaType: block.mediaType,
|
|
2802
|
-
data,
|
|
2803
|
-
...block.name ? { name: block.name } : {}
|
|
2804
|
-
},
|
|
2805
|
-
changed: true
|
|
2806
|
-
};
|
|
2807
|
-
return {
|
|
2808
|
-
block: {
|
|
2809
|
-
type: "document",
|
|
2810
|
-
mediaType: block.mediaType,
|
|
2811
|
-
encoding: block.encoding,
|
|
2812
|
-
data,
|
|
2813
|
-
...block.name ? { name: block.name } : {}
|
|
2814
|
-
},
|
|
2815
|
-
changed: true
|
|
2816
|
-
};
|
|
2817
|
-
}
|
|
2818
|
-
if (block.type !== "tool_result" || typeof block.output === "string") return {
|
|
2819
|
-
block,
|
|
2820
|
-
changed: false
|
|
2821
|
-
};
|
|
2822
|
-
const resolved = await resolveToolResultContent(block.output, resolveContentRef);
|
|
2823
|
-
if (!resolved.changed) return {
|
|
2824
|
-
block,
|
|
2825
|
-
changed: false
|
|
2826
|
-
};
|
|
2827
|
-
return {
|
|
2828
|
-
block: {
|
|
2829
|
-
...block,
|
|
2830
|
-
output: resolved.output
|
|
2831
|
-
},
|
|
2832
|
-
changed: true
|
|
2833
|
-
};
|
|
2834
|
-
}
|
|
2835
|
-
async function resolveToolResultContent(output, resolveContentRef) {
|
|
2836
|
-
let changed = false;
|
|
2837
|
-
const next = [];
|
|
2838
|
-
for (const part of output) {
|
|
2839
|
-
if (!isContentRefBlock(part)) {
|
|
2840
|
-
next.push(part);
|
|
2841
|
-
continue;
|
|
2842
|
-
}
|
|
2843
|
-
if (!resolveContentRef) throw unresolvedContentRefError(part);
|
|
2844
|
-
const { data } = await resolveContentRef({
|
|
2845
|
-
type: part.type,
|
|
2846
|
-
ref: part.ref,
|
|
2847
|
-
mediaType: part.mediaType,
|
|
2848
|
-
...part.type === "document" ? { encoding: part.encoding } : {},
|
|
2849
|
-
...part.name ? { name: part.name } : {}
|
|
2850
|
-
});
|
|
2851
|
-
changed = true;
|
|
2852
|
-
if (part.type === "image") next.push({
|
|
2853
|
-
type: "image",
|
|
2854
|
-
mediaType: part.mediaType,
|
|
2855
|
-
data,
|
|
2856
|
-
...part.name ? { name: part.name } : {}
|
|
2857
|
-
});
|
|
2858
|
-
else next.push({
|
|
2859
|
-
type: "document",
|
|
2860
|
-
mediaType: part.mediaType,
|
|
2861
|
-
encoding: part.encoding,
|
|
2862
|
-
data,
|
|
2863
|
-
...part.name ? { name: part.name } : {}
|
|
2864
|
-
});
|
|
2865
|
-
}
|
|
2866
|
-
return {
|
|
2867
|
-
output: changed ? next : output,
|
|
2868
|
-
changed
|
|
2869
|
-
};
|
|
2870
|
-
}
|
|
2871
|
-
/**
|
|
2872
|
-
* Resolve image/document ref blocks into data blocks for provider wire use.
|
|
2873
|
-
* This is intentionally wire-only: callers must never persist the returned
|
|
2874
|
-
* messages back into `session.turns`.
|
|
2875
|
-
*
|
|
2876
|
-
* Returns the input array reference unchanged when no block carried a ref —
|
|
2877
|
-
* the common case (most turns hold no refs, and re-running on already-resolved
|
|
2878
|
-
* output is a no-op), so the multi-pass wire pipeline doesn't pay per-message
|
|
2879
|
-
* copies for nothing.
|
|
2880
|
-
*/
|
|
2881
|
-
async function resolveContentRefsInMessages(messages, options = {}) {
|
|
2882
|
-
let anyChanged = false;
|
|
2883
|
-
const out = [];
|
|
2884
|
-
for (const msg of messages) {
|
|
2885
|
-
let changed = false;
|
|
2886
|
-
const content = [];
|
|
2887
|
-
for (const block of msg.content) {
|
|
2888
|
-
const resolved = await resolveRefContentBlock(block, options.resolveContentRef);
|
|
2889
|
-
if (resolved.changed) changed = true;
|
|
2890
|
-
content.push(resolved.block);
|
|
2891
|
-
}
|
|
2892
|
-
if (changed) anyChanged = true;
|
|
2893
|
-
out.push(changed ? {
|
|
2894
|
-
...msg,
|
|
2895
|
-
content
|
|
2896
|
-
} : msg);
|
|
2897
|
-
}
|
|
2898
|
-
return anyChanged ? out : messages;
|
|
2899
|
-
}
|
|
2900
|
-
function finalizeWireMessages(messages, options) {
|
|
2901
|
-
const paired = ensureToolResultPairing(messages, options.onRepair ? { onRepair: options.onRepair } : {});
|
|
2902
|
-
if (!options.provider) return paired;
|
|
2903
|
-
return ensureEndsWithUserMessage(paired, options.provider, options.userTailDirective);
|
|
2904
|
-
}
|
|
2905
|
-
/**
|
|
2906
|
-
* Build a wire-ready `SessionMessage[]` from raw persisted `SessionTurn[]`.
|
|
2907
|
-
*
|
|
2908
|
-
* **The canonical "I want to send these to a provider" projection.** Use
|
|
2909
|
-
* this — not raw `session.turns` / `store.getTurns()` — when constructing a
|
|
2910
|
-
* provider request outside `agent.run()`. The agent's own wire pipeline
|
|
2911
|
-
* (`executeTurn` in `src/loop.ts`) applies the same repair just-in-time;
|
|
2912
|
-
* downstream consumers that bypass the loop hit `tool_result must be
|
|
2913
|
-
* preceded by a tool_call` 400s precisely because they shipped raw turns.
|
|
2914
|
-
*
|
|
2915
|
-
* Pipeline:
|
|
2916
|
-
* 1. Filter `system`-role turns (the wire only carries user/assistant).
|
|
2917
|
-
* 2. {@link ensureToolResultPairing} — repairs all six orphan / dup
|
|
2918
|
-
* corruption modes by inserting synthetic placeholders rather than
|
|
2919
|
-
* dropping (preserves the model's tool_use shape).
|
|
2920
|
-
* 3. {@link ensureEndsWithUserMessage} when `options.provider` is set —
|
|
2921
|
-
* guards against assistant-tail prefill rejection (opus 4.7, o-series).
|
|
2922
|
-
*
|
|
2923
|
-
* Pure + idempotent. Does not mutate the input turns. Re-running on the
|
|
2924
|
-
* output is a no-op.
|
|
2925
|
-
*
|
|
2926
|
-
* **Do not feed the result back into `session.setTurns` / `appendTurns`.**
|
|
2927
|
-
* The repair can insert `SYNTHETIC_TOOL_RESULT_PLACEHOLDER` / `[Orphaned
|
|
2928
|
-
* tool result removed …]` markers; round-tripping those into persisted
|
|
2929
|
-
* state contaminates the session's reasoning history and defeats the
|
|
2930
|
-
* "don't rewrite the past" invariant the wire-only repair was designed
|
|
2931
|
-
* around.
|
|
2932
|
-
*/
|
|
2933
|
-
async function toWireMessages(turns, options = {}) {
|
|
2934
|
-
const messages = [];
|
|
2935
|
-
for (const t of turns) {
|
|
2936
|
-
if (t.role === "system") continue;
|
|
2937
|
-
messages.push({
|
|
2938
|
-
role: t.role,
|
|
2939
|
-
content: t.content
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
2942
|
-
return finalizeWireMessages(await resolveContentRefsInMessages(messages, { resolveContentRef: options.resolveContentRef }), options);
|
|
2943
|
-
}
|
|
2944
|
-
function autoDetectAndConvert(msg) {
|
|
2945
|
-
const c = msg.content;
|
|
2946
|
-
if (c && typeof c === "object" && !Array.isArray(c)) {
|
|
2947
|
-
const tag = c._tag;
|
|
2948
|
-
if (typeof tag === "string" && tag.startsWith("__zidane_")) return fromOpenAI(msg);
|
|
2949
|
-
}
|
|
2950
|
-
if (Array.isArray(c)) {
|
|
2951
|
-
for (const block of c) {
|
|
2952
|
-
if (!block || typeof block !== "object") continue;
|
|
2953
|
-
const b = block;
|
|
2954
|
-
if (b.type === "tool_use" || b.type === "tool_result" && "tool_use_id" in b) return fromAnthropic(msg);
|
|
2955
|
-
if (b.type === "image_url") return fromOpenAI(msg);
|
|
2956
|
-
}
|
|
2957
|
-
return fromAnthropic(msg);
|
|
2958
|
-
}
|
|
2959
|
-
if (typeof c === "string") return fromAnthropic(msg);
|
|
2960
|
-
return fromAnthropic(msg);
|
|
2961
|
-
}
|
|
2962
|
-
//#endregion
|
|
2963
|
-
export { joinSystemPrompt as A, sanitizeToolSchema as C, appendDynamicSection as D, SYSTEM_PROMPT_BOUNDARY as E, canonicalizePrompt as F, defaultPromptMessage as I, unsupportedMediaError as L, replaceDynamicSection as M, splitSystemPrompt as N, appendStaticSection as O, buildPromptMessage as P, userMessage as S, fillEstimatedCost as T, assistantMessage as _, detectTurnInterruption as a, openaiCompat as b, filterUnresolvedToolUses as c, remintDuplicateToolCallIds as d, resolveContentRefsInMessages as f, OpenAICompatHttpError as g, toWireMessages as h, autoDetectAndConvert as i, renderSystemForWire as j, hasSystemPromptBoundary as k, 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, classifyOpenAICompatError as v, sanitizeToolSpecs as w, toolResultsMessage as x, mapOAIFinishReason as y };
|
|
1611
|
+
export { defaultPromptMessage as C, canonicalizePrompt as S, userMessage as _, applyOAICacheBreakpoints as a, fillEstimatedCost as b, buildAssistantContent as c, formatTools as d, mapOAIFinishReason as f, toolResultsMessage as g, toOAIMessages as h, TOOL_RESULTS_TAG as i, classifyOpenAICompatError as l, openaiCompat as m, OpenAICompatHttpError as n, applyOAIToolCacheBreakpoint as o, openAICompatPromptMessage as p, OpenAICompatStreamError as r, assistantMessage as s, ASSISTANT_TOOL_CALLS_TAG as t, consumeSSE as u, sanitizeToolSchema as v, unsupportedMediaError as w, buildPromptMessage as x, sanitizeToolSpecs as y };
|
|
2964
1612
|
|
|
2965
|
-
//# sourceMappingURL=
|
|
1613
|
+
//# sourceMappingURL=openai-compat-0Y7jcncn.js.map
|