@animalabs/membrane 0.5.80 → 0.5.81

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 (151) hide show
  1. package/dist/context/index.d.ts +1 -1
  2. package/dist/context/index.d.ts.map +1 -1
  3. package/dist/context/index.js +1 -1
  4. package/dist/context/index.js.map +1 -1
  5. package/dist/context/process.d.ts +3 -1
  6. package/dist/context/process.d.ts.map +1 -1
  7. package/dist/context/process.js +230 -49
  8. package/dist/context/process.js.map +1 -1
  9. package/dist/context/types.d.ts +30 -0
  10. package/dist/context/types.d.ts.map +1 -1
  11. package/dist/context/types.js +16 -0
  12. package/dist/context/types.js.map +1 -1
  13. package/dist/floating-cache-marker.test.js +21 -0
  14. package/dist/floating-cache-marker.test.js.map +1 -1
  15. package/dist/formatters/anthropic-xml.d.ts +2 -0
  16. package/dist/formatters/anthropic-xml.d.ts.map +1 -1
  17. package/dist/formatters/anthropic-xml.js +57 -19
  18. package/dist/formatters/anthropic-xml.js.map +1 -1
  19. package/dist/formatters/normalize-tool-pairs.d.ts +7 -0
  20. package/dist/formatters/normalize-tool-pairs.d.ts.map +1 -1
  21. package/dist/formatters/normalize-tool-pairs.js +45 -0
  22. package/dist/formatters/normalize-tool-pairs.js.map +1 -1
  23. package/dist/formatters/types.d.ts +29 -0
  24. package/dist/formatters/types.d.ts.map +1 -1
  25. package/dist/membrane.d.ts +137 -8
  26. package/dist/membrane.d.ts.map +1 -1
  27. package/dist/membrane.js +812 -310
  28. package/dist/membrane.js.map +1 -1
  29. package/dist/providers/anthropic.d.ts +26 -0
  30. package/dist/providers/anthropic.d.ts.map +1 -1
  31. package/dist/providers/anthropic.js +142 -25
  32. package/dist/providers/anthropic.js.map +1 -1
  33. package/dist/providers/bedrock.d.ts +8 -0
  34. package/dist/providers/bedrock.d.ts.map +1 -1
  35. package/dist/providers/bedrock.js +24 -1
  36. package/dist/providers/bedrock.js.map +1 -1
  37. package/dist/providers/gemini.d.ts +11 -0
  38. package/dist/providers/gemini.d.ts.map +1 -1
  39. package/dist/providers/gemini.js +104 -82
  40. package/dist/providers/gemini.js.map +1 -1
  41. package/dist/providers/mock.d.ts +2 -0
  42. package/dist/providers/mock.d.ts.map +1 -1
  43. package/dist/providers/mock.js +2 -0
  44. package/dist/providers/mock.js.map +1 -1
  45. package/dist/providers/openai-compatible.d.ts +7 -0
  46. package/dist/providers/openai-compatible.d.ts.map +1 -1
  47. package/dist/providers/openai-compatible.js +99 -48
  48. package/dist/providers/openai-compatible.js.map +1 -1
  49. package/dist/providers/openai-completions.d.ts +5 -0
  50. package/dist/providers/openai-completions.d.ts.map +1 -1
  51. package/dist/providers/openai-completions.js +107 -44
  52. package/dist/providers/openai-completions.js.map +1 -1
  53. package/dist/providers/openai-responses-api.d.ts +16 -0
  54. package/dist/providers/openai-responses-api.d.ts.map +1 -1
  55. package/dist/providers/openai-responses-api.js +38 -10
  56. package/dist/providers/openai-responses-api.js.map +1 -1
  57. package/dist/providers/openai-responses.d.ts +2 -0
  58. package/dist/providers/openai-responses.d.ts.map +1 -1
  59. package/dist/providers/openai-responses.js +8 -1
  60. package/dist/providers/openai-responses.js.map +1 -1
  61. package/dist/providers/openai.d.ts +7 -0
  62. package/dist/providers/openai.d.ts.map +1 -1
  63. package/dist/providers/openai.js +92 -44
  64. package/dist/providers/openai.js.map +1 -1
  65. package/dist/providers/openrouter.d.ts +9 -0
  66. package/dist/providers/openrouter.d.ts.map +1 -1
  67. package/dist/providers/openrouter.js +118 -62
  68. package/dist/providers/openrouter.js.map +1 -1
  69. package/dist/providers/utils.d.ts +74 -2
  70. package/dist/providers/utils.d.ts.map +1 -1
  71. package/dist/providers/utils.js +184 -3
  72. package/dist/providers/utils.js.map +1 -1
  73. package/dist/registry/default-pricing.d.ts +22 -0
  74. package/dist/registry/default-pricing.d.ts.map +1 -1
  75. package/dist/registry/default-pricing.js +162 -28
  76. package/dist/registry/default-pricing.js.map +1 -1
  77. package/dist/types/content.d.ts +13 -0
  78. package/dist/types/content.d.ts.map +1 -1
  79. package/dist/types/content.js.map +1 -1
  80. package/dist/types/errors.d.ts +15 -0
  81. package/dist/types/errors.d.ts.map +1 -1
  82. package/dist/types/errors.js +26 -0
  83. package/dist/types/errors.js.map +1 -1
  84. package/dist/types/index.d.ts +3 -3
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +1 -1
  87. package/dist/types/index.js.map +1 -1
  88. package/dist/types/provider.d.ts +50 -0
  89. package/dist/types/provider.d.ts.map +1 -1
  90. package/dist/types/response.d.ts +85 -4
  91. package/dist/types/response.d.ts.map +1 -1
  92. package/dist/types/response.js.map +1 -1
  93. package/dist/types/streaming.d.ts +6 -1
  94. package/dist/types/streaming.d.ts.map +1 -1
  95. package/dist/utils/cache-marker-budget.d.ts +82 -0
  96. package/dist/utils/cache-marker-budget.d.ts.map +1 -0
  97. package/dist/utils/cache-marker-budget.js +189 -0
  98. package/dist/utils/cache-marker-budget.js.map +1 -0
  99. package/dist/utils/cost.d.ts +22 -0
  100. package/dist/utils/cost.d.ts.map +1 -1
  101. package/dist/utils/cost.js +55 -0
  102. package/dist/utils/cost.js.map +1 -1
  103. package/dist/utils/thinking-carriers.d.ts +80 -0
  104. package/dist/utils/thinking-carriers.d.ts.map +1 -0
  105. package/dist/utils/thinking-carriers.js +115 -0
  106. package/dist/utils/thinking-carriers.js.map +1 -0
  107. package/dist/utils/tool-parser.d.ts +24 -0
  108. package/dist/utils/tool-parser.d.ts.map +1 -1
  109. package/dist/utils/tool-parser.js +367 -161
  110. package/dist/utils/tool-parser.js.map +1 -1
  111. package/dist/utils/usage.d.ts +71 -0
  112. package/dist/utils/usage.d.ts.map +1 -0
  113. package/dist/utils/usage.js +161 -0
  114. package/dist/utils/usage.js.map +1 -0
  115. package/dist/yielding-stream.d.ts +8 -0
  116. package/dist/yielding-stream.d.ts.map +1 -1
  117. package/dist/yielding-stream.js +49 -3
  118. package/dist/yielding-stream.js.map +1 -1
  119. package/package.json +1 -1
  120. package/src/context/index.ts +1 -0
  121. package/src/context/process.ts +305 -59
  122. package/src/context/types.ts +38 -0
  123. package/src/floating-cache-marker.test.ts +24 -0
  124. package/src/formatters/anthropic-xml.ts +62 -20
  125. package/src/formatters/normalize-tool-pairs.ts +49 -0
  126. package/src/formatters/types.ts +32 -0
  127. package/src/membrane.ts +963 -313
  128. package/src/providers/anthropic.ts +161 -19
  129. package/src/providers/bedrock.ts +25 -1
  130. package/src/providers/gemini.ts +125 -87
  131. package/src/providers/mock.ts +3 -0
  132. package/src/providers/openai-compatible.ts +102 -45
  133. package/src/providers/openai-completions.ts +112 -46
  134. package/src/providers/openai-responses-api.ts +45 -15
  135. package/src/providers/openai-responses.ts +8 -1
  136. package/src/providers/openai.ts +95 -42
  137. package/src/providers/openrouter.ts +125 -63
  138. package/src/providers/utils.ts +227 -6
  139. package/src/registry/default-pricing.ts +165 -28
  140. package/src/types/content.ts +14 -0
  141. package/src/types/errors.ts +28 -0
  142. package/src/types/index.ts +6 -0
  143. package/src/types/provider.ts +55 -0
  144. package/src/types/response.ts +109 -8
  145. package/src/types/streaming.ts +8 -2
  146. package/src/utils/cache-marker-budget.ts +208 -0
  147. package/src/utils/cost.ts +64 -0
  148. package/src/utils/thinking-carriers.ts +125 -0
  149. package/src/utils/tool-parser.ts +473 -174
  150. package/src/utils/usage.ts +184 -0
  151. package/src/yielding-stream.ts +54 -3
@@ -0,0 +1,189 @@
1
+ /**
2
+ * One recount of the cache_control markers actually present on a wire
3
+ * request, plus the clamp that keeps that count legal.
4
+ *
5
+ * Anthropic accepts at most 4 cache_control breakpoints per request and
6
+ * rejects the fifth with a non-retryable 400 ("A maximum of 4 blocks with
7
+ * cache_control may be provided. Found 5." — measured live 2026-08-25).
8
+ * Markers arrive from many sites (context strategy breakpoints, block-level
9
+ * passthrough on imported histories, the tools/system fallback, the floating
10
+ * tool-loop marker, caller-marked system blocks, and any beforeRequest hook),
11
+ * and every one of them is invisible to the others' running tallies. The
12
+ * only trustworthy number is a recount of the constructed artifacts, so this
13
+ * module owns it and every site calls it.
14
+ *
15
+ * A marker can also ride a block NESTED inside another block's content array
16
+ * (`tool_result.content` is `string | ContentBlock[]`, passed through to the
17
+ * wire verbatim), so discovery recurses. A belt that cannot see a marker
18
+ * cannot clamp it, and a top-level-only count would report four while five
19
+ * shipped — the request rejected outright with the belt's blessing.
20
+ */
21
+ /** Anthropic's hard limit on cache_control breakpoints per request. */
22
+ export const MAX_CACHE_BREAKPOINTS = 4;
23
+ /**
24
+ * How many levels of nested `content` arrays marker discovery descends below
25
+ * a top-level block.
26
+ *
27
+ * A marker can ride a block inside another block's own content array —
28
+ * `ToolResultContent.content` is typed `string | ContentBlock[]` and every
29
+ * builder passes it through verbatim, so a caller-built tool_result whose
30
+ * members carry `cache_control` puts real markers on the wire. The API's own
31
+ * nesting is one level deep; 4 is headroom for shapes it grows later, and it
32
+ * is also what makes this walk TOTAL — a caller-built structure that points
33
+ * back at itself terminates at the cap instead of hanging the request.
34
+ * Markers below the cap are outside the belt's reach, by construction.
35
+ */
36
+ export const MAX_NESTED_CONTENT_DEPTH = 4;
37
+ /**
38
+ * Walk one candidate block and everything nested under it, in document order
39
+ * (a block before its own content), applying `visitBlock` to each object.
40
+ *
41
+ * This is the single traversal law: marker discovery and the ownership copy
42
+ * that protects caller-owned blocks from it both descend exactly here, so
43
+ * the clamp can never reach a grain that ownership did not copy.
44
+ */
45
+ function walkBlockTree(candidate, depth, visitBlock) {
46
+ if (!candidate || typeof candidate !== 'object')
47
+ return;
48
+ const block = candidate;
49
+ visitBlock(block);
50
+ const nested = nestedContentBlocks(block, depth);
51
+ if (!nested)
52
+ return;
53
+ for (const child of nested)
54
+ walkBlockTree(child, depth + 1, visitBlock);
55
+ }
56
+ /**
57
+ * The one rule for what counts as nested content: an array-valued `content`
58
+ * on a block, while still above the depth cap. Discovery and the ownership
59
+ * copy both ask here, so neither can descend where the other does not.
60
+ */
61
+ function nestedContentBlocks(block, depth) {
62
+ if (depth >= MAX_NESTED_CONTENT_DEPTH)
63
+ return undefined;
64
+ return Array.isArray(block.content) ? block.content : undefined;
65
+ }
66
+ /**
67
+ * Every marker-bearing block on the request, in WIRE ORDER (tools, then
68
+ * system blocks, then message blocks) and, within each, in DOCUMENT order —
69
+ * a block before anything nested inside its own content. Order is what makes
70
+ * "deepest" mean anything: a deeper marker caches a longer prefix.
71
+ *
72
+ * ONE walk serves both the count and the clamp's strip, so a marker the
73
+ * counter can see is always a marker the clamp can strip. A collector that
74
+ * stopped at the top level would count four while five rode to the wire, and
75
+ * the provider would reject the request the belt just declared legal.
76
+ */
77
+ function collectMarkedBlocks(surfaces) {
78
+ const marked = [];
79
+ const collectIfMarked = (candidate) => walkBlockTree(candidate, 0, (block) => {
80
+ if (block.cache_control)
81
+ marked.push(block);
82
+ });
83
+ if (Array.isArray(surfaces.tools)) {
84
+ for (const tool of surfaces.tools)
85
+ collectIfMarked(tool);
86
+ }
87
+ if (Array.isArray(surfaces.system)) {
88
+ for (const block of surfaces.system)
89
+ collectIfMarked(block);
90
+ }
91
+ if (Array.isArray(surfaces.messages)) {
92
+ for (const message of surfaces.messages) {
93
+ const content = message?.content;
94
+ if (Array.isArray(content)) {
95
+ for (const block of content)
96
+ collectIfMarked(block);
97
+ }
98
+ else {
99
+ collectIfMarked(content);
100
+ }
101
+ }
102
+ }
103
+ return marked;
104
+ }
105
+ /**
106
+ * Take ownership of a system surface before it can reach the clamp.
107
+ *
108
+ * `request.system` accepts caller-marked blocks, and the builders pass that
109
+ * array through by reference when they add no marker of their own. The clamp
110
+ * strips markers IN PLACE at the wire boundary — correct for blocks membrane
111
+ * built, catastrophic for the caller's own array, which a long-lived caller
112
+ * reuses turn after turn: one over-budget request would silently delete the
113
+ * caller's breakpoints for the life of that object. Copying the array and its
114
+ * blocks at build time keeps the clamp's mutations inside the request.
115
+ *
116
+ * The copy descends exactly as far as marker discovery does: a shallow
117
+ * `{...block}` leaves any nested `content` array shared with the caller, and
118
+ * the clamp now strips markers at that grain, so ownership has to reach it
119
+ * too or the leak just moves one level down.
120
+ */
121
+ export function ownSystemBlocks(system) {
122
+ if (!Array.isArray(system))
123
+ return system;
124
+ return system.map((block) => copyBlockTree(block, 0));
125
+ }
126
+ /**
127
+ * Shallow-copy a block and, recursively, its nested content blocks, to the
128
+ * same depth marker discovery walks. Non-objects pass through untouched.
129
+ */
130
+ function copyBlockTree(candidate, depth) {
131
+ if (!candidate || typeof candidate !== 'object')
132
+ return candidate;
133
+ const copy = { ...candidate };
134
+ const nested = nestedContentBlocks(copy, depth);
135
+ if (nested)
136
+ copy.content = nested.map((child) => copyBlockTree(child, depth + 1));
137
+ return copy;
138
+ }
139
+ /** How many cache_control markers this request would actually put on the wire. */
140
+ export function countWireCacheMarkers(surfaces) {
141
+ return collectMarkedBlocks(surfaces).length;
142
+ }
143
+ /**
144
+ * Bring a request inside the breakpoint budget, in place, at the last exit
145
+ * before the adapter call. Two repairs, both loud:
146
+ *
147
+ * 1. a marker riding a thinking / redacted_thinking block is stripped — the
148
+ * API rejects those outright ("thinking.cache_control: Extra inputs are
149
+ * not permitted"), and the three builders' `lastCacheableBlockIndex`
150
+ * discipline is only as good as its last caller. This is the runtime
151
+ * assertion; a fourth builder can no longer repeat that history.
152
+ * 2. markers past the limit are dropped SHALLOWEST-FIRST, keeping the 4
153
+ * deepest — the deepest marker caches the longest prefix, and every
154
+ * shallower prefix it subsumes.
155
+ *
156
+ * Dropping loudly beats a 400: the request still ships, cached less than the
157
+ * caller asked for, with the overspend named.
158
+ */
159
+ export function clampCacheMarkers(surfaces, site) {
160
+ const marked = collectMarkedBlocks(surfaces);
161
+ let strippedFromThinking = 0;
162
+ const cacheable = [];
163
+ for (const block of marked) {
164
+ if (block.type === 'thinking' || block.type === 'redacted_thinking') {
165
+ delete block.cache_control;
166
+ strippedFromThinking++;
167
+ continue;
168
+ }
169
+ cacheable.push(block);
170
+ }
171
+ let dropped = 0;
172
+ if (cacheable.length > MAX_CACHE_BREAKPOINTS) {
173
+ for (const block of cacheable.slice(0, cacheable.length - MAX_CACHE_BREAKPOINTS)) {
174
+ delete block.cache_control;
175
+ dropped++;
176
+ }
177
+ }
178
+ if (strippedFromThinking > 0) {
179
+ console.warn(`[membrane] ${site}: stripped ${strippedFromThinking} cache_control marker(s) from ` +
180
+ `thinking blocks — the API rejects cache_control on thinking/redacted_thinking.`);
181
+ }
182
+ if (dropped > 0) {
183
+ console.warn(`[membrane] ${site}: ${cacheable.length} cache_control markers exceed the limit of ` +
184
+ `${MAX_CACHE_BREAKPOINTS} — dropped the ${dropped} shallowest and kept the deepest ` +
185
+ `${MAX_CACHE_BREAKPOINTS}. The request would otherwise have been rejected outright.`);
186
+ }
187
+ return { total: cacheable.length - dropped, dropped, strippedFromThinking };
188
+ }
189
+ //# sourceMappingURL=cache-marker-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-marker-budget.js","sourceRoot":"","sources":["../../src/utils/cache-marker-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C;;;;;;;GAOG;AACH,SAAS,aAAa,CACpB,SAAkB,EAClB,KAAa,EACb,UAAoD;IAEpD,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO;IACxD,MAAM,KAAK,GAAG,SAAoC,CAAC;IACnD,UAAU,CAAC,KAAK,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM;QAAE,OAAO;IACpB,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,KAA8B,EAAE,KAAa;IACxE,IAAI,KAAK,IAAI,wBAAwB;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAClE,CAAC;AAQD;;;;;;;;;;GAUG;AACH,SAAS,mBAAmB,CAAC,QAA2B;IACtD,MAAM,MAAM,GAAmC,EAAE,CAAC;IAClD,MAAM,eAAe,GAAG,CAAC,SAAkB,EAAE,EAAE,CAC7C,aAAa,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,KAAK,CAAC,aAAa;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEL,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK;YAAE,eAAe,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACnC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM;YAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACxC,MAAM,OAAO,GAAI,OAAwC,EAAE,OAAO,CAAC;YACnE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3B,KAAK,MAAM,KAAK,IAAI,OAAO;oBAAE,eAAe,CAAC,KAAK,CAAC,CAAC;YACtD,CAAC;iBAAM,CAAC;gBACN,eAAe,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,SAAkB,EAAE,KAAa;IACtD,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAClE,MAAM,IAAI,GAAG,EAAE,GAAI,SAAqC,EAAE,CAAC;IAC3D,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChD,IAAI,MAAM;QAAE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,qBAAqB,CAAC,QAA2B;IAC/D,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAA2B,EAC3B,IAAY;IAEZ,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAE7C,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAmC,EAAE,CAAC;IACrD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;YACpE,OAAO,KAAK,CAAC,aAAa,CAAC;YAC3B,oBAAoB,EAAE,CAAC;YACvB,SAAS;QACX,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,SAAS,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QAC7C,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,qBAAqB,CAAC,EAAE,CAAC;YACjF,OAAO,KAAK,CAAC,aAAa,CAAC;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,IAAI,oBAAoB,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,CACV,cAAc,IAAI,cAAc,oBAAoB,gCAAgC;YACpF,gFAAgF,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CACV,cAAc,IAAI,KAAK,SAAS,CAAC,MAAM,6CAA6C;YACpF,GAAG,qBAAqB,kBAAkB,OAAO,mCAAmC;YACpF,GAAG,qBAAqB,4DAA4D,CACrF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAC9E,CAAC"}
@@ -6,5 +6,27 @@ export interface CostableUsage {
6
6
  cacheCreationTokens?: number;
7
7
  cacheReadTokens?: number;
8
8
  }
9
+ /**
10
+ * Say once, per model, that membrane has no rates for it — so an omitted
11
+ * `estimatedCost` reads as "unknown" rather than "free" to a caller who only
12
+ * ever sees the absence.
13
+ */
14
+ export declare function warnUnpricedModel(modelId: string): void;
15
+ /** Test seam: the once-per-model warn latch is process-wide otherwise. */
16
+ export declare function resetUnpricedModelWarnings(): void;
17
+ /**
18
+ * Add two breakdowns that may have been priced at DIFFERENT rates — the
19
+ * multi-round case, where each round is priced under the model that served it
20
+ * and the turn total is their sum.
21
+ *
22
+ * `pricingAsOf` takes the OLDER of the two: a total is only as fresh as its
23
+ * stalest input, and a round whose source vouches for NO date is stalest of
24
+ * all — one unstamped round leaves the sum unstamped. A currency mismatch is
25
+ * not summable at all (the caller's
26
+ * registry priced two rounds in different currencies and membrane has no rate
27
+ * to convert with), so it returns undefined rather than adding dollars to
28
+ * euros — an absent cost says "membrane does not know", which is true.
29
+ */
30
+ export declare function addCostBreakdowns(a: CostBreakdown, b: CostBreakdown): CostBreakdown | undefined;
9
31
  export declare function calculateCost(usage: CostableUsage, pricing: ModelPricing): CostBreakdown;
10
32
  //# sourceMappingURL=cost.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/utils/cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CAkBxF"}
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/utils/cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQvD;AAED,0EAA0E;AAC1E,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,aAAa,EAChB,CAAC,EAAE,aAAa,GACf,aAAa,GAAG,SAAS,CAsB3B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CAmBxF"}
@@ -1,3 +1,57 @@
1
+ /** Unpriced models warn once each, not once per call. */
2
+ const warnedUnpricedModels = new Set();
3
+ /**
4
+ * Say once, per model, that membrane has no rates for it — so an omitted
5
+ * `estimatedCost` reads as "unknown" rather than "free" to a caller who only
6
+ * ever sees the absence.
7
+ */
8
+ export function warnUnpricedModel(modelId) {
9
+ if (warnedUnpricedModels.has(modelId))
10
+ return;
11
+ warnedUnpricedModels.add(modelId);
12
+ console.warn(`[membrane:cost] no pricing for model "${modelId}" — neither the configured`
13
+ + ' ModelRegistry nor the built-in table has rates for it, so estimatedCost will be'
14
+ + ' omitted (this is NOT a cost of zero). Add it to your registry to get cost estimates.');
15
+ }
16
+ /** Test seam: the once-per-model warn latch is process-wide otherwise. */
17
+ export function resetUnpricedModelWarnings() {
18
+ warnedUnpricedModels.clear();
19
+ }
20
+ /**
21
+ * Add two breakdowns that may have been priced at DIFFERENT rates — the
22
+ * multi-round case, where each round is priced under the model that served it
23
+ * and the turn total is their sum.
24
+ *
25
+ * `pricingAsOf` takes the OLDER of the two: a total is only as fresh as its
26
+ * stalest input, and a round whose source vouches for NO date is stalest of
27
+ * all — one unstamped round leaves the sum unstamped. A currency mismatch is
28
+ * not summable at all (the caller's
29
+ * registry priced two rounds in different currencies and membrane has no rate
30
+ * to convert with), so it returns undefined rather than adding dollars to
31
+ * euros — an absent cost says "membrane does not know", which is true.
32
+ */
33
+ export function addCostBreakdowns(a, b) {
34
+ if (a.currency !== b.currency)
35
+ return undefined;
36
+ const cacheWrite = a.cacheWrite != null || b.cacheWrite != null
37
+ ? (a.cacheWrite ?? 0) + (b.cacheWrite ?? 0)
38
+ : undefined;
39
+ const cacheRead = a.cacheRead != null || b.cacheRead != null
40
+ ? (a.cacheRead ?? 0) + (b.cacheRead ?? 0)
41
+ : undefined;
42
+ const oldestAsOf = a.pricingAsOf && b.pricingAsOf
43
+ ? (a.pricingAsOf < b.pricingAsOf ? a.pricingAsOf : b.pricingAsOf)
44
+ : undefined;
45
+ return {
46
+ input: a.input + b.input,
47
+ output: a.output + b.output,
48
+ cacheWrite,
49
+ cacheRead,
50
+ total: a.total + b.total,
51
+ currency: a.currency,
52
+ ...(oldestAsOf ? { pricingAsOf: oldestAsOf } : {}),
53
+ };
54
+ }
1
55
  export function calculateCost(usage, pricing) {
2
56
  const input = usage.inputTokens * pricing.inputPerMillion / 1_000_000;
3
57
  const output = usage.outputTokens * pricing.outputPerMillion / 1_000_000;
@@ -14,6 +68,7 @@ export function calculateCost(usage, pricing) {
14
68
  cacheRead,
15
69
  total: input + output + (cacheWrite ?? 0) + (cacheRead ?? 0),
16
70
  currency: pricing.currency,
71
+ ...(pricing.asOf ? { pricingAsOf: pricing.asOf } : {}),
17
72
  };
18
73
  }
19
74
  //# sourceMappingURL=cost.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/utils/cost.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,aAAa,CAAC,KAAoB,EAAE,OAAqB;IACvE,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IACtE,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IACzE,MAAM,UAAU,GAAG,OAAO,CAAC,oBAAoB,IAAI,IAAI;QACrD,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS;QAC7E,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,SAAS,GAAG,OAAO,CAAC,mBAAmB,IAAI,IAAI;QACnD,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS;QACxE,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO;QACL,KAAK;QACL,MAAM;QACN,UAAU;QACV,SAAS;QACT,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,CAAC;QAC5D,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/utils/cost.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Helpers for pairing provider-native thinking carriers (which hold the
3
+ * signatures) with parser-derived thinking blocks (which hold the text).
4
+ * See Membrane.mergeProviderThinkingBlocks for the pairing rules.
5
+ */
6
+ import type { ContentBlock } from '../types/index.js';
7
+ /**
8
+ * Identity for thinking text, insensitive to exactly two artifacts that ride
9
+ * the parser's view of the same reasoning — and to nothing else:
10
+ *
11
+ * - stream scaffolding: the XML path prefills `Claude: <thinking>` and asks
12
+ * the adapter to wrap native thinking deltas, so a parsed block can carry
13
+ * a literal `<thinking>` / `</thinking>` tag the provider block never had.
14
+ * - OUTER boundary whitespace: the continuation path trims the accumulation
15
+ * at each round boundary (`buildContinuationRequest` trimEnds before
16
+ * re-prefilling), so a fragment and the parsed text can differ by leading
17
+ * or trailing whitespace.
18
+ *
19
+ * INTERNAL whitespace is payload and is compared verbatim. Erasing it (the
20
+ * first implementation stripped every `\s`) makes distinct signed payloads
21
+ * collide on nothing but where their spaces fall, and the caller then stamps
22
+ * one payload's signature onto the other's text — a carrier that fails
23
+ * Anthropic signature validation when the consumer ships its history back.
24
+ *
25
+ * Comparison only — stored text is never rewritten. Empty text is never
26
+ * identical to anything: signature-only carriers are prepend-only.
27
+ */
28
+ export declare function sameThinkingText(left: string, right: string): boolean;
29
+ /**
30
+ * Find a RUN of consecutive still-unpaired provider blocks whose concatenated
31
+ * thinking reconstructs `parsedText` (an auto-continuation split across a
32
+ * max_tokens boundary). Returns the provider indices in order, or undefined
33
+ * when no run of two or more reconstructs it.
34
+ *
35
+ * Two joins are tried, because the round boundary itself is the one place a
36
+ * fragment's whitespace can legitimately vanish: `buildContinuationRequest`
37
+ * trimEnds the accumulation before re-prefilling, so the resumed text may
38
+ * pick up exactly where the trim left off. Modelling that single
39
+ * transformation keeps internal whitespace comparable everywhere else —
40
+ * `sameThinkingText` treats it as payload.
41
+ */
42
+ export declare function findSpanningProviderRun(providerThinking: Array<{
43
+ thinking?: string;
44
+ signature?: string;
45
+ }>, pairedProviderBlocks: ReadonlySet<number>, parsedText: string): number[] | undefined;
46
+ /**
47
+ * Drop the extended-thinking config from a prefill-shaped provider request —
48
+ * from BOTH channels the `thinkingEnabled` resolver reads.
49
+ *
50
+ * Whether the API rejects thinking beside an assistant prefill is MODEL
51
+ * DEPENDENT, not a universal law. Measured live 2026-08-25 against the
52
+ * Anthropic Messages API: `claude-haiku-4-5-20251001` accepts an assistant
53
+ * prefill together with `thinking: {type: 'enabled'}` (HTTP 200, and the
54
+ * response carries no thinking block), while `claude-sonnet-4-6` refuses
55
+ * assistant prefill outright — HTTP 400, "This model does not support
56
+ * assistant message prefill."
57
+ *
58
+ * So the strip is the prefill path's DESIGN wherever prefill works: the XML
59
+ * formatter uses the thinking config to emit a literal `<thinking>` text
60
+ * prefix rather than the API feature, and sending both pays for API thinking
61
+ * the prefill format does not consume. On models that reject the combination
62
+ * it is additionally the 400 guard it always was. Either way both channels
63
+ * must agree, and the guard that only deleted the top-level field left
64
+ * `extra.thinking` (spread from `providerParams`) riding the adapter's
65
+ * `Object.assign(params, rest)` onto the wire — reproducing both the 400 and
66
+ * the interleaved-thinking beta header, since the resolver correctly saw the
67
+ * smuggled config. Mutates and returns the request; callers must own `extra`
68
+ * (never alias the caller's `providerParams`).
69
+ */
70
+ export declare function stripThinkingForPrefill<T extends {
71
+ thinking?: unknown;
72
+ extra?: unknown;
73
+ }>(providerRequest: T): T;
74
+ /**
75
+ * De-duplication key for a thinking / redacted_thinking carrier: two carriers
76
+ * are the same carrier when their payload and signature match. Any other block
77
+ * type gets a unique key so it can never collide.
78
+ */
79
+ export declare function thinkingCarrierKey(block: ContentBlock): string;
80
+ //# sourceMappingURL=thinking-carriers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-carriers.d.ts","sourceRoot":"","sources":["../../src/utils/thinking-carriers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOrE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,KAAK,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EAClE,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAC,EACzC,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,GAAG,SAAS,CAmBtB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EACvF,eAAe,EAAE,CAAC,GACjB,CAAC,CAOH;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAS9D"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Identity for thinking text, insensitive to exactly two artifacts that ride
3
+ * the parser's view of the same reasoning — and to nothing else:
4
+ *
5
+ * - stream scaffolding: the XML path prefills `Claude: <thinking>` and asks
6
+ * the adapter to wrap native thinking deltas, so a parsed block can carry
7
+ * a literal `<thinking>` / `</thinking>` tag the provider block never had.
8
+ * - OUTER boundary whitespace: the continuation path trims the accumulation
9
+ * at each round boundary (`buildContinuationRequest` trimEnds before
10
+ * re-prefilling), so a fragment and the parsed text can differ by leading
11
+ * or trailing whitespace.
12
+ *
13
+ * INTERNAL whitespace is payload and is compared verbatim. Erasing it (the
14
+ * first implementation stripped every `\s`) makes distinct signed payloads
15
+ * collide on nothing but where their spaces fall, and the caller then stamps
16
+ * one payload's signature onto the other's text — a carrier that fails
17
+ * Anthropic signature validation when the consumer ships its history back.
18
+ *
19
+ * Comparison only — stored text is never rewritten. Empty text is never
20
+ * identical to anything: signature-only carriers are prepend-only.
21
+ */
22
+ export function sameThinkingText(left, right) {
23
+ if (left === right)
24
+ return left !== '';
25
+ if (left === '' || right === '')
26
+ return false;
27
+ const normalized = (text) => text.replace(/<\/?(antml:)?thinking>/g, '').trim();
28
+ const normalizedLeft = normalized(left);
29
+ if (normalizedLeft === '')
30
+ return false;
31
+ return normalizedLeft === normalized(right);
32
+ }
33
+ /**
34
+ * Find a RUN of consecutive still-unpaired provider blocks whose concatenated
35
+ * thinking reconstructs `parsedText` (an auto-continuation split across a
36
+ * max_tokens boundary). Returns the provider indices in order, or undefined
37
+ * when no run of two or more reconstructs it.
38
+ *
39
+ * Two joins are tried, because the round boundary itself is the one place a
40
+ * fragment's whitespace can legitimately vanish: `buildContinuationRequest`
41
+ * trimEnds the accumulation before re-prefilling, so the resumed text may
42
+ * pick up exactly where the trim left off. Modelling that single
43
+ * transformation keeps internal whitespace comparable everywhere else —
44
+ * `sameThinkingText` treats it as payload.
45
+ */
46
+ export function findSpanningProviderRun(providerThinking, pairedProviderBlocks, parsedText) {
47
+ for (let start = 0; start < providerThinking.length; start++) {
48
+ if (pairedProviderBlocks.has(start))
49
+ continue;
50
+ const firstFragment = providerThinking[start].thinking ?? '';
51
+ let verbatimJoin = firstFragment;
52
+ let trimmedJoin = firstFragment;
53
+ const run = [start];
54
+ for (let next = start + 1; next < providerThinking.length; next++) {
55
+ if (pairedProviderBlocks.has(next))
56
+ break;
57
+ const fragment = providerThinking[next].thinking ?? '';
58
+ verbatimJoin += fragment;
59
+ trimmedJoin = trimmedJoin.trimEnd() + fragment;
60
+ run.push(next);
61
+ if (sameThinkingText(verbatimJoin, parsedText) || sameThinkingText(trimmedJoin, parsedText)) {
62
+ return [...run];
63
+ }
64
+ }
65
+ }
66
+ return undefined;
67
+ }
68
+ /**
69
+ * Drop the extended-thinking config from a prefill-shaped provider request —
70
+ * from BOTH channels the `thinkingEnabled` resolver reads.
71
+ *
72
+ * Whether the API rejects thinking beside an assistant prefill is MODEL
73
+ * DEPENDENT, not a universal law. Measured live 2026-08-25 against the
74
+ * Anthropic Messages API: `claude-haiku-4-5-20251001` accepts an assistant
75
+ * prefill together with `thinking: {type: 'enabled'}` (HTTP 200, and the
76
+ * response carries no thinking block), while `claude-sonnet-4-6` refuses
77
+ * assistant prefill outright — HTTP 400, "This model does not support
78
+ * assistant message prefill."
79
+ *
80
+ * So the strip is the prefill path's DESIGN wherever prefill works: the XML
81
+ * formatter uses the thinking config to emit a literal `<thinking>` text
82
+ * prefix rather than the API feature, and sending both pays for API thinking
83
+ * the prefill format does not consume. On models that reject the combination
84
+ * it is additionally the 400 guard it always was. Either way both channels
85
+ * must agree, and the guard that only deleted the top-level field left
86
+ * `extra.thinking` (spread from `providerParams`) riding the adapter's
87
+ * `Object.assign(params, rest)` onto the wire — reproducing both the 400 and
88
+ * the interleaved-thinking beta header, since the resolver correctly saw the
89
+ * smuggled config. Mutates and returns the request; callers must own `extra`
90
+ * (never alias the caller's `providerParams`).
91
+ */
92
+ export function stripThinkingForPrefill(providerRequest) {
93
+ delete providerRequest.thinking;
94
+ const extra = providerRequest.extra;
95
+ if (extra && typeof extra === 'object') {
96
+ delete extra.thinking;
97
+ }
98
+ return providerRequest;
99
+ }
100
+ /**
101
+ * De-duplication key for a thinking / redacted_thinking carrier: two carriers
102
+ * are the same carrier when their payload and signature match. Any other block
103
+ * type gets a unique key so it can never collide.
104
+ */
105
+ export function thinkingCarrierKey(block) {
106
+ if (block.type === 'thinking') {
107
+ const { thinking, signature } = block;
108
+ return `thinking\u0000${thinking ?? ''}\u0000${signature ?? ''}`;
109
+ }
110
+ if (block.type === 'redacted_thinking') {
111
+ return `redacted\u0000${block.data ?? ''}`;
112
+ }
113
+ return `other\u0000${JSON.stringify(block)}`;
114
+ }
115
+ //# sourceMappingURL=thinking-carriers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-carriers.js","sourceRoot":"","sources":["../../src/utils/thinking-carriers.ts"],"names":[],"mappings":"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"}
@@ -80,6 +80,30 @@ export declare function parseAccumulatedIntoBlocks(text: string, options?: {
80
80
  blocks: ContentBlock[];
81
81
  toolCalls: ToolCall[];
82
82
  toolResults: ToolResult[];
83
+ /**
84
+ * The text ends inside a tool block — an opener with no closer, or a cut
85
+ * mid-tag. The membrane loop does not resume on a length stop, so this is
86
+ * what a max_tokens truncation leaves behind, and persisting it bare lets the
87
+ * next round's closer splice onto the stale opener.
88
+ */
89
+ unclosedToolBlock: boolean;
90
+ /**
91
+ * function_calls blocks that yielded no invokes at all. Always a defect —
92
+ * never how a well-formed block ends.
93
+ */
94
+ emptyToolBlocks: number;
95
+ /**
96
+ * Block matches that spanned another opener and were re-anchored to the
97
+ * innermost one. Each is a truncated block that was persisted bare and is
98
+ * being repaired at read time.
99
+ */
100
+ splicedToolBlocks: number;
101
+ /**
102
+ * `<invoke>` heads that were left open and swallowed a later invoke, so the
103
+ * match was refused and re-anchored to the call it absorbed. Nothing was
104
+ * dispatched under the head's name, and the head itself never ran.
105
+ */
106
+ unclosedInvokeHeads: number;
83
107
  };
84
108
  import type { ToolDefinition } from '../types/index.js';
85
109
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tool-parser.d.ts","sourceRoot":"","sources":["../../src/utils/tool-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AAmDrH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAqFnE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ1D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAkCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAsB/D;AAqBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAE3D;AAMD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,uBAAuB,EAAE,GAAG,MAAM,CA0B9E;AA0BD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,gBAAgB,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,CAAA;CAAE,GACxE;IACD,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAqPA;AAMD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAUxD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAsBnE;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CASpE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IAEvB,yDAAyD;IACzD,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAE7B,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IAEtB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,gBAAgB,CAuFrF;AA8CD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOhD"}
1
+ {"version":3,"file":"tool-parser.d.ts","sourceRoot":"","sources":["../../src/utils/tool-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,YAAY,EAA0B,MAAM,mBAAmB,CAAC;AA0OrH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAiCnE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ1D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAkCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAsB/D;AAqBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAE3D;AAMD,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,uBAAuB,EAAE,GAAG,MAAM,CA0B9E;AAkKD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,gBAAgB,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,CAAA;CAAE,GACxE;IACD,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CA6PA;AAMD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAUxD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,CAsBnE;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CASpE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IAEvB,yDAAyD;IACzD,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAE7B,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IAEtB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,gBAAgB,CAuFrF;AA8CD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOhD"}