zidane 5.6.11 → 5.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +19 -2
  2. package/dist/{agent-C9AKTU_V.d.ts → agent-ClkpElCZ.d.ts} +540 -55
  3. package/dist/agent-ClkpElCZ.d.ts.map +1 -0
  4. package/dist/chat.d.ts +47 -17
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +3 -3
  7. package/dist/{index-6f4T7Gc0.d.ts → index-CTDMMdIy.d.ts} +348 -3
  8. package/dist/index-CTDMMdIy.d.ts.map +1 -0
  9. package/dist/{index-DPN7TcXK.d.ts → index-v3Tzobqr.d.ts} +2 -2
  10. package/dist/{index-DPN7TcXK.d.ts.map → index-v3Tzobqr.d.ts.map} +1 -1
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +169 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/{login-BindcfKi.js → login-DS3sf6b5.js} +4 -4
  15. package/dist/{login-BindcfKi.js.map → login-DS3sf6b5.js.map} +1 -1
  16. package/dist/{mcp-0jRkIV0g.js → mcp-DGeB7-3D.js} +13 -2
  17. package/dist/mcp-DGeB7-3D.js.map +1 -0
  18. package/dist/mcp.d.ts +1 -1
  19. package/dist/mcp.js +1 -1
  20. package/dist/{messages-BfmXLDT4.js → messages-Dym8S_YH.js} +303 -8
  21. package/dist/messages-Dym8S_YH.js.map +1 -0
  22. package/dist/{presets-CmzMeWg2.js → presets-CZXS_87d.js} +2 -2
  23. package/dist/{presets-CmzMeWg2.js.map → presets-CZXS_87d.js.map} +1 -1
  24. package/dist/presets.d.ts +2 -2
  25. package/dist/presets.js +1 -1
  26. package/dist/{providers-C_ahnRBS.js → providers-beXyD9W9.js} +137 -21
  27. package/dist/providers-beXyD9W9.js.map +1 -0
  28. package/dist/providers.d.ts +2 -2
  29. package/dist/providers.js +3 -3
  30. package/dist/restate.d.ts +1 -1
  31. package/dist/session/sqlite.d.ts +1 -1
  32. package/dist/{session-PUzXZlG6.js → session-BRIsmBSY.js} +5 -2
  33. package/dist/session-BRIsmBSY.js.map +1 -0
  34. package/dist/session.d.ts +2 -2
  35. package/dist/session.js +3 -3
  36. package/dist/skills.d.ts +2 -2
  37. package/dist/{tools-CxOfTt3R.js → tools-DE9pR_NG.js} +515 -116
  38. package/dist/tools-DE9pR_NG.js.map +1 -0
  39. package/dist/tools.d.ts +3 -3
  40. package/dist/tools.js +1 -1
  41. package/dist/{transcript-anchors-DDCHSDdX.d.ts → transcript-anchors-D0TR6djV.d.ts} +4 -4
  42. package/dist/transcript-anchors-D0TR6djV.d.ts.map +1 -0
  43. package/dist/tui.d.ts +2 -2
  44. package/dist/tui.d.ts.map +1 -1
  45. package/dist/tui.js +12 -8
  46. package/dist/tui.js.map +1 -1
  47. package/dist/{turn-operations-CxE8BBau.js → turn-operations-6Yls2HuG.js} +907 -42
  48. package/dist/turn-operations-6Yls2HuG.js.map +1 -0
  49. package/dist/types-oKPBdCmL.js.map +1 -1
  50. package/dist/types.d.ts +3 -3
  51. package/docs/ARCHITECTURE.md +101 -20
  52. package/docs/CHAT.md +27 -5
  53. package/docs/RESTATE.md +1 -1
  54. package/docs/SKILL.md +39 -3
  55. package/package.json +5 -2
  56. package/dist/agent-C9AKTU_V.d.ts.map +0 -1
  57. package/dist/index-6f4T7Gc0.d.ts.map +0 -1
  58. package/dist/mcp-0jRkIV0g.js.map +0 -1
  59. package/dist/messages-BfmXLDT4.js.map +0 -1
  60. package/dist/providers-C_ahnRBS.js.map +0 -1
  61. package/dist/session-PUzXZlG6.js.map +0 -1
  62. package/dist/tools-CxOfTt3R.js.map +0 -1
  63. package/dist/transcript-anchors-DDCHSDdX.d.ts.map +0 -1
  64. package/dist/turn-operations-CxE8BBau.js.map +0 -1
@@ -1,5 +1,212 @@
1
1
  import { c as errorMessage, l as matchesContextExceeded } from "./errors-DdZXnyXE.js";
2
- import { getModel } from "@mariozechner/pi-ai";
2
+ import { getModel } from "@earendil-works/pi-ai";
3
+ //#region src/system-prompt.ts
4
+ /**
5
+ * System-prompt boundary marker — splits a system prompt into a stable static
6
+ * prefix (cached) and a per-turn dynamic suffix (NOT cached).
7
+ *
8
+ * Why this exists: providers attach `cache_control` markers on the last block
9
+ * of the system prompt, so the cached prefix covers the entire system text.
10
+ * Any byte change anywhere — including a per-turn `<env>` rewrite — busts the
11
+ * cache for the doctrine that sits below. A literal marker in the system
12
+ * string lets providers split it into:
13
+ *
14
+ * ┌──────────────┐ cache_control: ephemeral
15
+ * │ STATIC half │ — doctrine, skills catalog, tool catalog,
16
+ * │ │ user instructions
17
+ * ├──────────────┤ ← SYSTEM_PROMPT_BOUNDARY
18
+ * │ DYNAMIC half │ — env, cwd, mtimes, anything per-turn
19
+ * └──────────────┘ (no cache_control)
20
+ *
21
+ * The static prefix rides the prompt cache across turns/sessions; the dynamic
22
+ * suffix re-bills per turn. Net effect: a cwd change between turns no longer
23
+ * invalidates 4 KB of doctrine.
24
+ *
25
+ * Wire contract:
26
+ *
27
+ * - `splitSystemPrompt(s)` is pure; missing marker ⇒ entire string is static
28
+ * (current behavior — no caller is forced to opt in).
29
+ * - `renderSystemForWire(s)` strips the marker so it never reaches the model;
30
+ * used by every provider before the bytes hit the wire, including the
31
+ * cache-disabled path on providers that DO support `cache_control`.
32
+ * - The marker uses underscores rather than XML/punctuation so it's
33
+ * unambiguous when scanning prompts manually and unlikely to collide with
34
+ * model-written content.
35
+ * - Providers handle the split internally (Anthropic emits a 2-block array;
36
+ * OpenAI-compat splits the leading `system` message into multi-part text).
37
+ * Callers always pass a single `string` — no API break.
38
+ */
39
+ /**
40
+ * Literal marker inserted in a system prompt to separate cacheable doctrine
41
+ * from per-turn dynamic content. Providers split on this token.
42
+ *
43
+ * Underscored on both sides for visual distinctiveness — a stray instance in
44
+ * model-written prose is implausible. Don't change the value without shipping
45
+ * a migration; existing sessions carry the old marker in their cached
46
+ * prompts.
47
+ */
48
+ const SYSTEM_PROMPT_BOUNDARY = "__ZIDANE_SYSTEM_PROMPT_BOUNDARY__";
49
+ /**
50
+ * Split a system prompt around the first {@link SYSTEM_PROMPT_BOUNDARY}.
51
+ *
52
+ * Splits on the FIRST occurrence — subsequent markers are folded into the
53
+ * dynamic half. This way callers can append additional `<env>` style blocks
54
+ * with extra markers without each one creating a new cache layer (Anthropic
55
+ * caps breakpoints; we use the budget elsewhere). The marker itself is
56
+ * stripped from both sides — providers attach `cache_control` directly to
57
+ * the static half's text content.
58
+ *
59
+ * A single blank line (`\n\n`) immediately adjacent to the marker on each
60
+ * side is trimmed — callers conventionally write
61
+ * `<doctrine>\n\n<MARKER>\n\n<env>` and expect the rendered wire bytes to
62
+ * read as one logical paragraph. Blank lines beyond that immediate pair
63
+ * (e.g. `\n\n\n<env>`) are preserved verbatim so callers composing their
64
+ * own spacing don't lose intentional gaps.
65
+ *
66
+ * Pure / no I/O / no allocation when the marker is absent (returns the input
67
+ * verbatim on the static side and the empty string on the dynamic side).
68
+ */
69
+ function splitSystemPrompt(system) {
70
+ if (system.length === 0) return {
71
+ static: "",
72
+ dynamic: ""
73
+ };
74
+ const idx = system.indexOf(SYSTEM_PROMPT_BOUNDARY);
75
+ if (idx < 0) return {
76
+ static: system,
77
+ dynamic: ""
78
+ };
79
+ const staticPart = system.slice(0, idx);
80
+ const dynamicPart = system.slice(idx + 33);
81
+ return {
82
+ static: trimTrailingBlankLine(staticPart),
83
+ dynamic: trimLeadingBlankLine(dynamicPart)
84
+ };
85
+ }
86
+ /**
87
+ * Compose a system prompt from a static prefix and an optional dynamic
88
+ * suffix. Inserts {@link SYSTEM_PROMPT_BOUNDARY} between them only when the
89
+ * dynamic side is non-empty — single-block prompts stay marker-free, so
90
+ * callers that never opt in pay zero overhead and providers fall back to the
91
+ * existing whole-string caching path.
92
+ *
93
+ * Spacing is `\n\n` on both sides of the marker so doctrine fragments,
94
+ * which conventionally separate sections with a blank line, read cleanly
95
+ * around the boundary.
96
+ */
97
+ function joinSystemPrompt(staticPart, dynamicPart) {
98
+ if (dynamicPart.length === 0) return staticPart;
99
+ if (staticPart.length === 0) return `${SYSTEM_PROMPT_BOUNDARY}\n\n${dynamicPart}`;
100
+ return `${staticPart}\n\n${SYSTEM_PROMPT_BOUNDARY}\n\n${dynamicPart}`;
101
+ }
102
+ /**
103
+ * Append `extra` to the STATIC half of a system prompt, preserving any
104
+ * existing dynamic suffix.
105
+ *
106
+ * Used by the agent to fold in run-stable content (skills catalog, lazy tool
107
+ * catalog) without bumping it into the dynamic half — both catalogs are
108
+ * built once per run and remain byte-stable for the duration, so they
109
+ * belong in the cached prefix.
110
+ *
111
+ * Returns a new string; the input is not mutated. When `extra` is empty,
112
+ * returns the input verbatim.
113
+ */
114
+ function appendStaticSection(system, extra) {
115
+ if (extra.length === 0) return system;
116
+ const { static: staticPart, dynamic } = splitSystemPrompt(system);
117
+ return joinSystemPrompt(staticPart.length === 0 ? extra : `${staticPart}\n\n${extra}`, dynamic);
118
+ }
119
+ /**
120
+ * Append `extra` to the DYNAMIC half of a system prompt. Inserts the boundary
121
+ * marker if the input didn't already carry one.
122
+ *
123
+ * Used by hosts (typically the TUI) to inject per-turn state — current cwd,
124
+ * IDE selection, project root — without forcing every caller to know the
125
+ * marker format. The host's `system:transform` hook rewrites the dynamic
126
+ * half each turn; the static doctrine above stays byte-stable and rides the
127
+ * cache.
128
+ *
129
+ * Returns a new string; the input is not mutated. When `extra` is empty,
130
+ * returns the input verbatim.
131
+ */
132
+ function appendDynamicSection(system, extra) {
133
+ if (extra.length === 0) return system;
134
+ const { static: staticPart, dynamic } = splitSystemPrompt(system);
135
+ return joinSystemPrompt(staticPart, dynamic.length === 0 ? extra : `${dynamic}\n\n${extra}`);
136
+ }
137
+ /**
138
+ * Replace the entire dynamic half of a system prompt with `next`. Used by
139
+ * the TUI's `<env>` rewriter where the entire dynamic section is regenerated
140
+ * each turn rather than appended to.
141
+ *
142
+ * When `next` is empty, drops the dynamic half (and the marker) entirely.
143
+ */
144
+ function replaceDynamicSection(system, next) {
145
+ const { static: staticPart } = splitSystemPrompt(system);
146
+ return joinSystemPrompt(staticPart, next);
147
+ }
148
+ /**
149
+ * Strip the boundary marker so it never reaches the wire — collapses
150
+ * `<static>${BOUNDARY}<dynamic>` into `<static>\n\n<dynamic>` for providers
151
+ * that can't honor `cache_control` (vanilla OpenAI Chat Completions, Codex,
152
+ * Cerebras, ...) or for the cache-disabled path on any provider.
153
+ *
154
+ * Cache-aware providers re-derive the split from the original (un-rendered)
155
+ * system string via `splitSystemPrompt` — `renderSystemForWire` is the
156
+ * marker-free counterpart used to build the actual wire bytes.
157
+ *
158
+ * No-op when the input has no marker. Pure / no I/O.
159
+ */
160
+ function renderSystemForWire(system) {
161
+ if (system.length === 0) return system;
162
+ const parts = splitSystemPrompt(system);
163
+ if (parts.dynamic.length === 0) return parts.static;
164
+ if (parts.static.length === 0) return parts.dynamic;
165
+ return `${parts.static}\n\n${parts.dynamic}`;
166
+ }
167
+ /** True when `system` contains the boundary marker. */
168
+ function hasSystemPromptBoundary(system) {
169
+ return system.includes(SYSTEM_PROMPT_BOUNDARY);
170
+ }
171
+ /** Strip a single trailing `\n\n` (and any leftover trailing whitespace within that pair). */
172
+ function trimTrailingBlankLine(s) {
173
+ let end = s.length;
174
+ let newlines = 0;
175
+ while (end > 0 && newlines < 2) {
176
+ const ch = s.charCodeAt(end - 1);
177
+ if (ch === 10) {
178
+ newlines += 1;
179
+ end -= 1;
180
+ continue;
181
+ }
182
+ if (ch === 32 || ch === 9) {
183
+ end -= 1;
184
+ continue;
185
+ }
186
+ break;
187
+ }
188
+ return newlines > 0 ? s.slice(0, end) : s;
189
+ }
190
+ /** Strip a single leading `\n\n` (and any leftover leading whitespace within that pair). */
191
+ function trimLeadingBlankLine(s) {
192
+ let start = 0;
193
+ let newlines = 0;
194
+ while (start < s.length && newlines < 2) {
195
+ const ch = s.charCodeAt(start);
196
+ if (ch === 10) {
197
+ newlines += 1;
198
+ start += 1;
199
+ continue;
200
+ }
201
+ if (ch === 32 || ch === 9) {
202
+ start += 1;
203
+ continue;
204
+ }
205
+ break;
206
+ }
207
+ return newlines > 0 ? s.slice(start) : s;
208
+ }
209
+ //#endregion
3
210
  //#region src/providers/cost.ts
4
211
  /**
5
212
  * Fill in `usage.cost` from pi-ai's bundled price registry when the
@@ -12,7 +219,7 @@ import { getModel } from "@mariozechner/pi-ai";
12
219
  * than fabricating a $0.
13
220
  *
14
221
  * The number is an estimate: token counts come from the API, rates from
15
- * the locally-bundled registry that refreshes with `@mariozechner/pi-ai`
222
+ * the locally-bundled registry that refreshes with `@earendil-works/pi-ai`
16
223
  * version bumps. If a provider changes prices between bumps the figure
17
224
  * will drift until the dep updates.
18
225
  */
@@ -545,7 +752,7 @@ function summarizeToolResultOutput(output) {
545
752
  function toOAIMessages(system, messages, options = {}) {
546
753
  const out = [{
547
754
  role: "system",
548
- content: system
755
+ content: renderSystemForWire(system)
549
756
  }];
550
757
  const nativeImageInTool = options.imageInToolResult === true;
551
758
  const reasoningEnabled = options.supportsReasoning === true;
@@ -682,15 +889,62 @@ const EPHEMERAL = { type: "ephemeral" };
682
889
  * - Assistant messages with no text (tool-call-only) — attaching a cache marker to a
683
890
  * `tool_calls` block has no defined semantics, so we skip and let the prior
684
891
  * system/tools breakpoints carry caching.
892
+ *
893
+ * System-prompt boundary handling: when `originalSystem` (the un-rendered
894
+ * system text passed to {@link toOAIMessages}) contains
895
+ * {@link SYSTEM_PROMPT_BOUNDARY}, the leading system message is rewritten as
896
+ * a two-part array — `cache_control` lands on the static prefix only, so
897
+ * per-turn churn in the dynamic suffix doesn't invalidate the cached
898
+ * doctrine above. `originalSystem` defaults to `undefined` for back-compat;
899
+ * omit it (or pass a marker-free string) for the single-block historic
900
+ * behavior.
685
901
  */
686
- function applyOAICacheBreakpoints(messages) {
902
+ function applyOAICacheBreakpoints(messages, originalSystem) {
687
903
  if (messages.length === 0) return;
688
904
  const first = messages[0];
689
- if (first.role === "system") markLastContentPart(first);
905
+ if (first.role === "system") markSystemMessage(first, originalSystem);
690
906
  const lastIdx = messages.length - 1;
691
907
  if (lastIdx > 0) markLastContentPart(messages[lastIdx]);
692
908
  }
693
909
  /**
910
+ * Place the cache marker on the static prefix of the system message. When
911
+ * `originalSystem` carried {@link SYSTEM_PROMPT_BOUNDARY}, the message becomes
912
+ * a two-part array — static (cached) then dynamic (uncached). Otherwise falls
913
+ * through to {@link markLastContentPart} for the historic single-block
914
+ * treatment.
915
+ *
916
+ * String-only branch — system messages produced by {@link toOAIMessages} are
917
+ * always plain strings (`{ role: 'system', content: <rendered> }`). Hosts
918
+ * that supply pre-built multi-part system messages bypass this branch and
919
+ * get the generic last-block treatment.
920
+ */
921
+ function markSystemMessage(msg, originalSystem) {
922
+ if (typeof msg.content !== "string" || msg.content.length === 0) {
923
+ markLastContentPart(msg);
924
+ return;
925
+ }
926
+ const parts = splitSystemPrompt(originalSystem && originalSystem.length > 0 ? originalSystem : msg.content);
927
+ if (parts.dynamic.length === 0) {
928
+ msg.content = [{
929
+ type: "text",
930
+ text: parts.static,
931
+ cache_control: EPHEMERAL
932
+ }];
933
+ return;
934
+ }
935
+ const next = [];
936
+ if (parts.static.length > 0) next.push({
937
+ type: "text",
938
+ text: parts.static,
939
+ cache_control: EPHEMERAL
940
+ });
941
+ next.push({
942
+ type: "text",
943
+ text: parts.dynamic
944
+ });
945
+ msg.content = next;
946
+ }
947
+ /**
694
948
  * Mark the last content part of an OAI message with `cache_control`. Normalizes
695
949
  * string content into a `[{ type: 'text', text, cache_control }]` array so the
696
950
  * marker has a block to attach to.
@@ -948,7 +1202,7 @@ function openaiCompat(params) {
948
1202
  model: modelId
949
1203
  });
950
1204
  const shouldCache = cacheBreakpointsEnabled && options.cache !== false;
951
- if (shouldCache) applyOAICacheBreakpoints(messages);
1205
+ if (shouldCache) applyOAICacheBreakpoints(messages, options.system);
952
1206
  const maxTokens = options.thinkingBudget ? options.thinkingBudget + options.maxTokens : options.maxTokens;
953
1207
  const body = {
954
1208
  ...params.extraBodyParams ?? {},
@@ -1737,6 +1991,47 @@ function ensureEndsWithUserMessage(messages, provider, directive = DEFAULT_USER_
1737
1991
  if (messages[messages.length - 1].role === "user") return messages;
1738
1992
  return [...messages, provider.userMessage(directive)];
1739
1993
  }
1994
+ /**
1995
+ * Build a wire-ready `SessionMessage[]` from raw persisted `SessionTurn[]`.
1996
+ *
1997
+ * **The canonical "I want to send these to a provider" projection.** Use
1998
+ * this — not raw `session.turns` / `store.getTurns()` — when constructing a
1999
+ * provider request outside `agent.run()`. The agent's own wire pipeline
2000
+ * (`executeTurn` in `src/loop.ts`) applies the same repair just-in-time;
2001
+ * downstream consumers that bypass the loop hit `tool_result must be
2002
+ * preceded by a tool_call` 400s precisely because they shipped raw turns.
2003
+ *
2004
+ * Pipeline:
2005
+ * 1. Filter `system`-role turns (the wire only carries user/assistant).
2006
+ * 2. {@link ensureToolResultPairing} — repairs all six orphan / dup
2007
+ * corruption modes by inserting synthetic placeholders rather than
2008
+ * dropping (preserves the model's tool_use shape).
2009
+ * 3. {@link ensureEndsWithUserMessage} when `options.provider` is set —
2010
+ * guards against assistant-tail prefill rejection (opus 4.7, o-series).
2011
+ *
2012
+ * Pure + idempotent. Does not mutate the input turns. Re-running on the
2013
+ * output is a no-op.
2014
+ *
2015
+ * **Do not feed the result back into `session.setTurns` / `appendTurns`.**
2016
+ * The repair can insert `SYNTHETIC_TOOL_RESULT_PLACEHOLDER` / `[Orphaned
2017
+ * tool result removed …]` markers; round-tripping those into persisted
2018
+ * state contaminates the session's reasoning history and defeats the
2019
+ * "don't rewrite the past" invariant the wire-only repair was designed
2020
+ * around.
2021
+ */
2022
+ function toWireMessages(turns, options = {}) {
2023
+ const messages = [];
2024
+ for (const t of turns) {
2025
+ if (t.role === "system") continue;
2026
+ messages.push({
2027
+ role: t.role,
2028
+ content: t.content
2029
+ });
2030
+ }
2031
+ const paired = ensureToolResultPairing(messages, options.onRepair ? { onRepair: options.onRepair } : {});
2032
+ if (!options.provider) return paired;
2033
+ return ensureEndsWithUserMessage(paired, options.provider, options.userTailDirective);
2034
+ }
1740
2035
  function autoDetectAndConvert(msg) {
1741
2036
  const c = msg.content;
1742
2037
  if (c && typeof c === "object" && !Array.isArray(c)) {
@@ -1756,6 +2051,6 @@ function autoDetectAndConvert(msg) {
1756
2051
  return fromAnthropic(msg);
1757
2052
  }
1758
2053
  //#endregion
1759
- export { fillEstimatedCost as S, openaiCompat as _, detectTurnInterruption as a, sanitizeToolSchema as b, filterUnresolvedToolUses as c, toAnthropic as d, toOpenAI as f, mapOAIFinishReason as g, classifyOpenAICompatError as h, autoDetectAndConvert as i, fromAnthropic as l, assistantMessage as m, SYNTHETIC_TOOL_RESULT_PLACEHOLDER as n, ensureEndsWithUserMessage as o, OpenAICompatHttpError as p, TOOL_USE_INTERRUPTED_MARKER as r, ensureToolResultPairing as s, ORPHANED_TOOL_RESULT_MARKER as t, fromOpenAI as u, toolResultsMessage as v, sanitizeToolSpecs as x, userMessage as y };
2054
+ export { replaceDynamicSection as A, fillEstimatedCost as C, hasSystemPromptBoundary as D, appendStaticSection as E, joinSystemPrompt as O, sanitizeToolSpecs as S, appendDynamicSection as T, mapOAIFinishReason as _, detectTurnInterruption as a, userMessage as b, filterUnresolvedToolUses as c, toAnthropic as d, toOpenAI as f, classifyOpenAICompatError as g, assistantMessage as h, autoDetectAndConvert as i, splitSystemPrompt as j, renderSystemForWire as k, fromAnthropic as l, OpenAICompatHttpError as m, SYNTHETIC_TOOL_RESULT_PLACEHOLDER as n, ensureEndsWithUserMessage as o, toWireMessages as p, TOOL_USE_INTERRUPTED_MARKER as r, ensureToolResultPairing as s, ORPHANED_TOOL_RESULT_MARKER as t, fromOpenAI as u, openaiCompat as v, SYSTEM_PROMPT_BOUNDARY as w, sanitizeToolSchema as x, toolResultsMessage as y };
1760
2055
 
1761
- //# sourceMappingURL=messages-BfmXLDT4.js.map
2056
+ //# sourceMappingURL=messages-Dym8S_YH.js.map