@animalabs/membrane 0.5.52 → 0.5.53

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.
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Tool-Pair Normalizer
3
+ *
4
+ * Anthropic's API enforces structural rules on tool cycles that any of
5
+ * Membrane's upstreams can accidentally violate:
6
+ *
7
+ * - `tool_use` blocks must live in assistant-role messages.
8
+ * - `tool_result` blocks must live in user-role messages.
9
+ * - Every `tool_use` must be matched by its `tool_result` in the very
10
+ * next user-role message.
11
+ * - `thinking` blocks must live in assistant turns.
12
+ *
13
+ * When these are violated, the API returns 400 (e.g. `tool_use blocks can
14
+ * only be in assistant messages`). This module is the wire-boundary safety
15
+ * net: every formatter funnels through `normalizeToolPairs` before its
16
+ * output is shipped, so producer-side bugs cannot leak the same 400 family
17
+ * (compression-bug 5/6/7/8/9, agent-framework #37, 2026-05-22 miner stall).
18
+ *
19
+ * Algorithm overview (six phases): reclassify blocks by required role,
20
+ * reflow into role-correct envelopes, hoist matching tool_results across
21
+ * the assistant→user boundary, evict interlopers wedged between use and
22
+ * result, synthesize `[pending]` results for trailing orphans (or signal
23
+ * not-ready when the id is in the caller-supplied pending set), validate.
24
+ */
25
+ export class MembraneNormalizerError extends Error {
26
+ input;
27
+ output;
28
+ constructor(message, input, output) {
29
+ super(message);
30
+ this.input = input;
31
+ this.output = output;
32
+ this.name = 'MembraneNormalizerError';
33
+ }
34
+ }
35
+ // ============================================================================
36
+ // Implementation
37
+ // ============================================================================
38
+ /**
39
+ * Normalize a sequence of provider messages so the output is API-valid
40
+ * with respect to Anthropic's tool-cycle structural rules.
41
+ *
42
+ * This function does NOT merge consecutive same-role envelopes — that
43
+ * remains the caller's responsibility (NativeFormatter.mergeConsecutiveRoles)
44
+ * so existing cache-control / breakpoint logic continues to work.
45
+ */
46
+ export function normalizeToolPairs(input, options = {}) {
47
+ const pending = options.pendingToolCallIds ?? new Set();
48
+ const onEvent = options.onEvent ?? noop;
49
+ // ---------------------------------------------------------------------
50
+ // Phase 1 + 2: reclassify blocks by required role and reflow envelopes
51
+ // ---------------------------------------------------------------------
52
+ let envelopes = rebuildEnvelopes(input, onEvent);
53
+ // ---------------------------------------------------------------------
54
+ // Phase 3: pair tool_use → tool_result across assistant→user boundary
55
+ // ---------------------------------------------------------------------
56
+ envelopes = hoistMatchingResults(envelopes, onEvent);
57
+ // ---------------------------------------------------------------------
58
+ // Phase 4: evict interlopers wedged between a tool_use and its result
59
+ // ---------------------------------------------------------------------
60
+ envelopes = evictInterlopers(envelopes, onEvent);
61
+ // ---------------------------------------------------------------------
62
+ // Phase 5: resolve orphans
63
+ // ---------------------------------------------------------------------
64
+ const orphanRes = resolveOrphans(envelopes, pending, onEvent);
65
+ envelopes = orphanRes.envelopes;
66
+ const ready = orphanRes.ready;
67
+ // ---------------------------------------------------------------------
68
+ // Phase 5.5: suppress cache_control on/after any envelope containing
69
+ // a synthetic block, so cache keys don't get invalidated when the
70
+ // real result arrives in a later round.
71
+ // ---------------------------------------------------------------------
72
+ if (orphanRes.firstSyntheticEnvelope !== null) {
73
+ suppressCacheControlFrom(envelopes, orphanRes.firstSyntheticEnvelope, onEvent);
74
+ }
75
+ // ---------------------------------------------------------------------
76
+ // Phase 6: drop empty envelopes (can arise from phase 4 dropping or
77
+ // phase 3 hoisting), repair first-message-must-be-user, validate. We
78
+ // deliberately do NOT merge consecutive same-role envelopes here —
79
+ // that's the formatter's job.
80
+ // ---------------------------------------------------------------------
81
+ envelopes = envelopes.filter((e) => e.content.length > 0);
82
+ // First-message-must-be-user repair: only repair the case where the
83
+ // original input's first message WAS user, but re-roling moved blocks
84
+ // to a leading assistant envelope (e.g. misplaced thinking block).
85
+ // If the producer genuinely shipped an assistant-first conversation,
86
+ // that's a real bug and validate() will throw.
87
+ const originalFirstRole = input.length > 0 ? input[0].role : 'user';
88
+ if (envelopes.length > 0 &&
89
+ envelopes[0].role === 'assistant' &&
90
+ originalFirstRole === 'user') {
91
+ envelopes.unshift({ role: 'user', content: [{ type: 'text', text: '[continuing]' }] });
92
+ }
93
+ // Validate. When `ready === false` we intentionally have unmatched
94
+ // tool_uses — but ONLY the ones in `pending` are allowed to remain
95
+ // unsynthesized. Any other gap is a bug in phase 5 and must throw.
96
+ validate(envelopes, input, pending);
97
+ return { messages: envelopes.map(toProviderMessage), ready };
98
+ }
99
+ /**
100
+ * Role-strict block types. Extending Anthropic's tool surface
101
+ * (e.g. `server_tool_use`, `web_search_tool_result`, `computer_use`)
102
+ * means adding entries here. Unknown block types whose `type` starts
103
+ * with `tool_` or `thinking` fall through to 'inherit' and trigger a
104
+ * one-shot console warning so the next addition doesn't sail silently
105
+ * through the safety net.
106
+ */
107
+ function requiredRoleOf(block) {
108
+ switch (block.type) {
109
+ case 'tool_use':
110
+ case 'thinking':
111
+ case 'redacted_thinking':
112
+ return 'assistant';
113
+ case 'tool_result':
114
+ return 'user';
115
+ default:
116
+ if (block.type.startsWith('tool_') || block.type.startsWith('thinking')) {
117
+ warnUnknownStrictType(block.type);
118
+ }
119
+ return 'inherit';
120
+ }
121
+ }
122
+ const _warnedTypes = new Set();
123
+ function warnUnknownStrictType(blockType) {
124
+ if (_warnedTypes.has(blockType))
125
+ return;
126
+ _warnedTypes.add(blockType);
127
+ // eslint-disable-next-line no-console
128
+ console.warn(`[membrane:normalize-tool-pairs] Unknown strict-role block type '${blockType}' — ` +
129
+ `falling through as 'inherit'. If this type has role placement rules at the API, ` +
130
+ `add it to requiredRoleOf in normalize-tool-pairs.ts.`);
131
+ }
132
+ function rebuildEnvelopes(input, onEvent) {
133
+ const out = [];
134
+ let current = null;
135
+ for (const msg of input) {
136
+ if (!Array.isArray(msg.content)) {
137
+ // Defensive: provider message with non-array content (e.g. a plain
138
+ // string). Treat it as a single text block under the message's
139
+ // declared role.
140
+ const role = msg.role;
141
+ if (current === null || current.role !== role) {
142
+ if (current)
143
+ out.push(current);
144
+ current = { role, content: [] };
145
+ }
146
+ current.content.push({ type: 'text', text: String(msg.content ?? '') });
147
+ continue;
148
+ }
149
+ for (const block of msg.content) {
150
+ const req = requiredRoleOf(block);
151
+ const targetRole = req === 'inherit' ? msg.role : req;
152
+ if (req !== 'inherit' && req !== msg.role) {
153
+ onEvent({
154
+ kind: 'block_re_roled',
155
+ blockType: block.type,
156
+ from: msg.role,
157
+ to: req,
158
+ });
159
+ }
160
+ if (current === null || current.role !== targetRole) {
161
+ if (current)
162
+ out.push(current);
163
+ current = { role: targetRole, content: [] };
164
+ }
165
+ current.content.push(block);
166
+ }
167
+ }
168
+ if (current)
169
+ out.push(current);
170
+ return out;
171
+ }
172
+ function hoistMatchingResults(envelopes, onEvent) {
173
+ // For every assistant envelope, ensure its tool_use ids have matching
174
+ // tool_results in the immediately-following user envelope. If a
175
+ // matching tool_result lives further downstream, hoist it forward.
176
+ for (let i = 0; i < envelopes.length; i++) {
177
+ const env = envelopes[i];
178
+ if (env.role !== 'assistant')
179
+ continue;
180
+ const useIds = collectToolUseIds(env);
181
+ if (useIds.length === 0)
182
+ continue;
183
+ // Ensure there is a user envelope at i+1. If not, insert an empty one.
184
+ let nextIdx = i + 1;
185
+ if (nextIdx >= envelopes.length || envelopes[nextIdx].role !== 'user') {
186
+ envelopes.splice(nextIdx, 0, { role: 'user', content: [] });
187
+ }
188
+ const nextEnv = envelopes[nextIdx];
189
+ const presentIds = new Set(nextEnv.content
190
+ .filter((b) => b.type === 'tool_result')
191
+ .map(getToolUseId)
192
+ .filter((id) => typeof id === 'string'));
193
+ for (const useId of useIds) {
194
+ if (presentIds.has(useId))
195
+ continue;
196
+ // Search downstream envelopes for this id; hoist the first match.
197
+ const found = removeFirstMatchingResult(envelopes, nextIdx + 1, useId);
198
+ if (found) {
199
+ // Place the hoisted result at the front of nextEnv to keep
200
+ // tool_results adjacent to (and before) any interloping content
201
+ // already present.
202
+ nextEnv.content.unshift(found.block);
203
+ presentIds.add(useId);
204
+ onEvent({
205
+ kind: 'tool_result_hoisted',
206
+ toolUseId: useId,
207
+ fromEnvelope: found.fromEnvelope,
208
+ toEnvelope: nextIdx,
209
+ });
210
+ }
211
+ // If not found downstream, leave it — phase 5 will synthesize.
212
+ }
213
+ }
214
+ return envelopes;
215
+ }
216
+ function evictInterlopers(envelopes, onEvent) {
217
+ // For every assistant envelope ending with a tool_use, the
218
+ // immediately-following user envelope's tool_results should appear
219
+ // BEFORE any interloping text/image/etc. — otherwise the agent's
220
+ // forward timeline reads "tool called, then [unrelated event], then
221
+ // tool result." Phase 3 already places hoisted results at the front,
222
+ // but locally-present results may sit after text in the same envelope
223
+ // (e.g. user sent a chat message and the tool_result is appended
224
+ // afterward by the producer). We always defer interlopers — never
225
+ // drop — so that a mid-cycle user event isn't lost to the agent's
226
+ // long-term memory after the chunk gets summarized. A summarizer LLM
227
+ // can tolerate slight temporal reordering; it cannot reconstruct a
228
+ // message that was discarded.
229
+ for (let i = 0; i < envelopes.length; i++) {
230
+ const env = envelopes[i];
231
+ if (env.role !== 'assistant')
232
+ continue;
233
+ const useIds = new Set(collectToolUseIds(env));
234
+ if (useIds.size === 0)
235
+ continue;
236
+ const next = envelopes[i + 1];
237
+ if (!next || next.role !== 'user')
238
+ continue;
239
+ const matching = [];
240
+ const interlopers = [];
241
+ const rest = [];
242
+ let seenMatching = false;
243
+ for (const block of next.content) {
244
+ const isResult = block.type === 'tool_result';
245
+ const resultId = isResult ? getToolUseId(block) : undefined;
246
+ const isMatching = isResult && typeof resultId === 'string' && useIds.has(resultId);
247
+ if (isMatching) {
248
+ matching.push(block);
249
+ seenMatching = true;
250
+ }
251
+ else if (!seenMatching && !isResult) {
252
+ // Block precedes the first matching tool_result. Treat as
253
+ // interloper only if it would sit between the assistant's
254
+ // tool_use and its result.
255
+ interlopers.push(block);
256
+ }
257
+ else {
258
+ rest.push(block);
259
+ }
260
+ }
261
+ if (interlopers.length === 0)
262
+ continue;
263
+ for (const block of interlopers) {
264
+ onEvent({
265
+ kind: 'interloper_deferred',
266
+ blockType: block.type,
267
+ fromEnvelope: i + 1,
268
+ });
269
+ }
270
+ next.content = [...matching, ...interlopers, ...rest];
271
+ }
272
+ return envelopes;
273
+ }
274
+ function resolveOrphans(envelopes, pending, onEvent) {
275
+ let ready = true;
276
+ let firstSyntheticEnvelope = null;
277
+ // First pass: textify any tool_result whose tool_use never appeared
278
+ // anywhere in the message list (orphan result).
279
+ const allUseIds = new Set();
280
+ for (const env of envelopes) {
281
+ for (const block of env.content) {
282
+ if (block.type === 'tool_use') {
283
+ const id = block.id;
284
+ if (typeof id === 'string')
285
+ allUseIds.add(id);
286
+ }
287
+ }
288
+ }
289
+ for (const env of envelopes) {
290
+ if (env.role !== 'user')
291
+ continue;
292
+ env.content = env.content.map((block) => {
293
+ if (block.type !== 'tool_result')
294
+ return block;
295
+ const id = getToolUseId(block);
296
+ if (typeof id !== 'string' || !allUseIds.has(id)) {
297
+ const inner = block.content;
298
+ const innerText = typeof inner === 'string' ? inner : '';
299
+ onEvent({ kind: 'orphan_tool_result_textified', toolUseId: id ?? '<missing>' });
300
+ return {
301
+ type: 'text',
302
+ text: `[orphan tool_result for ${id ?? '<missing>'}]: ${innerText}`,
303
+ };
304
+ }
305
+ return block;
306
+ });
307
+ }
308
+ // Second pass: for each assistant envelope, every tool_use must have
309
+ // a matching tool_result in the immediately-following user envelope.
310
+ // If pending, signal not-ready. Else, synthesize.
311
+ for (let i = 0; i < envelopes.length; i++) {
312
+ const env = envelopes[i];
313
+ if (env.role !== 'assistant')
314
+ continue;
315
+ const useIds = collectToolUseIds(env);
316
+ if (useIds.length === 0)
317
+ continue;
318
+ let nextIdx = i + 1;
319
+ if (nextIdx >= envelopes.length || envelopes[nextIdx].role !== 'user') {
320
+ envelopes.splice(nextIdx, 0, { role: 'user', content: [] });
321
+ }
322
+ const nextEnv = envelopes[nextIdx];
323
+ // 'trailing' iff after the next user envelope there are no further
324
+ // envelopes AND the next envelope is empty (so it exists only to
325
+ // receive our synthetic). This must be computed *after* the splice
326
+ // because phase 3 may have already inserted an empty user envelope
327
+ // earlier in the pipeline.
328
+ const isTrailing = nextIdx + 1 >= envelopes.length && nextEnv.content.length === 0;
329
+ const presentIds = new Set(nextEnv.content
330
+ .filter((b) => b.type === 'tool_result')
331
+ .map(getToolUseId)
332
+ .filter((id) => typeof id === 'string'));
333
+ for (const useId of useIds) {
334
+ if (presentIds.has(useId))
335
+ continue;
336
+ if (pending.has(useId)) {
337
+ ready = false;
338
+ onEvent({ kind: 'pending_in_flight', toolUseId: useId });
339
+ continue;
340
+ }
341
+ const synth = syntheticToolResult(useId);
342
+ // Place at the front so it's adjacent to the tool_use.
343
+ nextEnv.content.unshift(synth);
344
+ presentIds.add(useId);
345
+ if (firstSyntheticEnvelope === null)
346
+ firstSyntheticEnvelope = nextIdx;
347
+ onEvent({
348
+ kind: 'synthetic_pending_result',
349
+ toolUseId: useId,
350
+ reason: isTrailing ? 'trailing' : 'mid_stream',
351
+ });
352
+ }
353
+ }
354
+ return { envelopes, ready, firstSyntheticEnvelope };
355
+ }
356
+ function suppressCacheControlFrom(envelopes, startIndex, onEvent) {
357
+ // Strip cache_control from blocks at-or-after startIndex. We must NOT
358
+ // mutate the caller's input blocks (envelopes share references with
359
+ // the input via rebuildEnvelopes), so clone-on-write: replace any
360
+ // block carrying cache_control with a shallow copy that omits it.
361
+ // The envelope's content array is replaced wholesale via .map; this
362
+ // is the only place in the normalizer that creates new block objects
363
+ // out of existing ones (synthetics aside).
364
+ let suppressed = false;
365
+ for (let i = startIndex; i < envelopes.length; i++) {
366
+ const env = envelopes[i];
367
+ env.content = env.content.map((block) => {
368
+ if (!('cache_control' in block))
369
+ return block;
370
+ suppressed = true;
371
+ const { cache_control: _drop, ...rest } = block;
372
+ return rest;
373
+ });
374
+ }
375
+ if (suppressed) {
376
+ onEvent({ kind: 'cache_suppressed_for_synthetic', envelopeIndex: startIndex });
377
+ }
378
+ }
379
+ function validate(envelopes, input, pending) {
380
+ // Empty input → empty output is fine.
381
+ if (envelopes.length === 0)
382
+ return;
383
+ // First message must be user (Anthropic requirement). We try to
384
+ // repair this in the caller; if it still isn't user here, fail.
385
+ if (envelopes[0].role !== 'user') {
386
+ throw new MembraneNormalizerError(`First message must have role 'user', got '${envelopes[0].role}'. ` +
387
+ `Repair (prepending '[continuing]') did not engage — internal bug.`, input.map(cloneMsg), envelopes.map(toProviderMessage));
388
+ }
389
+ // Every tool_use in an assistant envelope must have a matching
390
+ // tool_result in the immediately-following user envelope — except
391
+ // tool_uses whose id is in `pending` (the in-flight set the caller
392
+ // declared off-limits for synthesis). A gap on any other id is a
393
+ // phase-5 bug and must throw.
394
+ for (let i = 0; i < envelopes.length; i++) {
395
+ const env = envelopes[i];
396
+ if (env.role !== 'assistant')
397
+ continue;
398
+ const useIds = collectToolUseIds(env);
399
+ if (useIds.length === 0)
400
+ continue;
401
+ const next = envelopes[i + 1];
402
+ const presentIds = new Set(next?.role === 'user'
403
+ ? next.content
404
+ .filter((b) => b.type === 'tool_result')
405
+ .map(getToolUseId)
406
+ .filter((id) => typeof id === 'string')
407
+ : []);
408
+ for (const useId of useIds) {
409
+ if (presentIds.has(useId))
410
+ continue;
411
+ if (pending.has(useId))
412
+ continue; // legitimately in-flight
413
+ throw new MembraneNormalizerError(`tool_use id='${useId}' in envelope ${i} has no matching tool_result in envelope ${i + 1}, ` +
414
+ `and the id is not in pendingToolCallIds. This indicates a bug in the normalizer itself — ` +
415
+ `phase 5 should have synthesized a result for any non-pending unmatched id.`, input.map(cloneMsg), envelopes.map(toProviderMessage));
416
+ }
417
+ }
418
+ }
419
+ // ============================================================================
420
+ // Helpers
421
+ // ============================================================================
422
+ /**
423
+ * Read a tool_result's id, tolerating either Anthropic's canonical
424
+ * `tool_use_id` (snake_case) or the camelCase `toolUseId` some
425
+ * Membrane producers ship. Only used for *reading*; synthetic
426
+ * tool_results MUST be written in the canonical snake_case form
427
+ * (see {@link syntheticToolResult}) — the dual-form read is defensive
428
+ * against producers, not a license to mix.
429
+ */
430
+ function getToolUseId(block) {
431
+ const b = block;
432
+ if (typeof b.tool_use_id === 'string')
433
+ return b.tool_use_id;
434
+ if (typeof b.toolUseId === 'string')
435
+ return b.toolUseId;
436
+ return undefined;
437
+ }
438
+ function collectToolUseIds(env) {
439
+ const ids = [];
440
+ for (const block of env.content) {
441
+ if (block.type === 'tool_use') {
442
+ const id = block.id;
443
+ if (typeof id === 'string')
444
+ ids.push(id);
445
+ }
446
+ }
447
+ return ids;
448
+ }
449
+ function removeFirstMatchingResult(envelopes, fromIdx, useId) {
450
+ for (let i = fromIdx; i < envelopes.length; i++) {
451
+ const env = envelopes[i];
452
+ if (env.role !== 'user')
453
+ continue;
454
+ for (let j = 0; j < env.content.length; j++) {
455
+ const block = env.content[j];
456
+ if (block.type !== 'tool_result')
457
+ continue;
458
+ if (getToolUseId(block) === useId) {
459
+ // Mutates the envelope's content array in place. Caller
460
+ // (phase 3) is expected to handle the possibly-empty source
461
+ // envelope; phase 6's filter sweeps any envelope left empty.
462
+ env.content.splice(j, 1);
463
+ return { block, fromEnvelope: i };
464
+ }
465
+ }
466
+ }
467
+ return null;
468
+ }
469
+ /**
470
+ * Synthetic tool_result for an unmatched tool_use. Writes
471
+ * `tool_use_id` in Anthropic's canonical snake_case form — do NOT
472
+ * change to camelCase without auditing every consumer of the
473
+ * downstream message. The "[pending]" content is intentionally
474
+ * tombstone-shaped (is_error: false) — most synthesis triggers are
475
+ * normal-flow gaps (cancellations, stream restarts), not failures
476
+ * worth alarming the agent about.
477
+ */
478
+ function syntheticToolResult(toolUseId) {
479
+ return {
480
+ type: 'tool_result',
481
+ tool_use_id: toolUseId,
482
+ content: '[pending]',
483
+ is_error: false,
484
+ };
485
+ }
486
+ function toProviderMessage(env) {
487
+ return { role: env.role, content: env.content };
488
+ }
489
+ function cloneMsg(msg) {
490
+ return {
491
+ role: msg.role,
492
+ content: Array.isArray(msg.content) ? [...msg.content] : msg.content,
493
+ };
494
+ }
495
+ function noop() {
496
+ /* intentionally empty */
497
+ }
498
+ //# sourceMappingURL=normalize-tool-pairs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-tool-pairs.js","sourceRoot":"","sources":["../../src/formatters/normalize-tool-pairs.ts"],"names":[],"mappings":"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"}
@@ -51,7 +51,54 @@ export interface BuildOptions {
51
51
  * This enables per-message cache boundaries in the conversation.
52
52
  */
53
53
  hasCacheMarker?: (message: NormalizedMessage, index: number) => boolean;
54
+ /**
55
+ * IDs of tool_use blocks the caller knows are currently in-flight
56
+ * (e.g. a yielding stream that has emitted the tool_use but is still
57
+ * waiting on the result). If a trailing unmatched tool_use's id is in
58
+ * this set, the normalizer signals `ready: false` instead of injecting
59
+ * a synthetic `[pending]` result. Default: empty (always synthesize).
60
+ */
61
+ pendingToolCallIds?: ReadonlySet<string>;
62
+ /**
63
+ * Telemetry callback fired once per normalization action. Lets the
64
+ * framework count/log normalizations without coupling Membrane to a
65
+ * specific logger. See `NormalizeEvent` for the event shapes.
66
+ */
67
+ onNormalize?: (event: NormalizeEvent) => void;
54
68
  }
69
+ /**
70
+ * Events emitted by the tool-pair normalizer. Surfaced through
71
+ * `BuildOptions.onNormalize`. Every normalization action emits one
72
+ * event; treat non-zero counts as a producer-side bug to investigate.
73
+ */
74
+ export type NormalizeEvent = {
75
+ kind: 'block_re_roled';
76
+ blockType: string;
77
+ from: 'user' | 'assistant';
78
+ to: 'user' | 'assistant';
79
+ } | {
80
+ kind: 'tool_result_hoisted';
81
+ toolUseId: string;
82
+ fromEnvelope: number;
83
+ toEnvelope: number;
84
+ } | {
85
+ kind: 'interloper_deferred';
86
+ blockType: string;
87
+ fromEnvelope: number;
88
+ } | {
89
+ kind: 'synthetic_pending_result';
90
+ toolUseId: string;
91
+ reason: 'trailing' | 'mid_stream';
92
+ } | {
93
+ kind: 'orphan_tool_result_textified';
94
+ toolUseId: string;
95
+ } | {
96
+ kind: 'pending_in_flight';
97
+ toolUseId: string;
98
+ } | {
99
+ kind: 'cache_suppressed_for_synthetic';
100
+ envelopeIndex: number;
101
+ };
55
102
  export interface BuildResult {
56
103
  /** Messages in provider format */
57
104
  messages: ProviderMessage[];
@@ -65,6 +112,14 @@ export interface BuildResult {
65
112
  nativeTools?: unknown[];
66
113
  /** Number of cache control markers applied (for Anthropic prompt caching) */
67
114
  cacheMarkersApplied?: number;
115
+ /**
116
+ * `false` only when the tool-pair normalizer detected a trailing
117
+ * unmatched tool_use whose id is in `pendingToolCallIds` — i.e. the
118
+ * caller (yielding stream) is mid-cycle and the request should not be
119
+ * shipped yet. Callers that don't pass `pendingToolCallIds` will never
120
+ * see `false` here.
121
+ */
122
+ ready?: boolean;
68
123
  }
69
124
  export interface ProviderMessage {
70
125
  role: 'user' | 'assistant';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/formatters/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/formatters/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animalabs/membrane",
3
- "version": "0.5.52",
3
+ "version": "0.5.53",
4
4
  "description": "LLM middleware - a selective boundary that transforms what passes through",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,3 +17,12 @@ export type {
17
17
  export { AnthropicXmlFormatter, type AnthropicXmlFormatterConfig } from './anthropic-xml.js';
18
18
  export { NativeFormatter, type NativeFormatterConfig } from './native.js';
19
19
  export { CompletionsFormatter, type CompletionsFormatterConfig } from './completions.js';
20
+
21
+ export {
22
+ normalizeToolPairs,
23
+ MembraneNormalizerError,
24
+ type NormalizeOptions,
25
+ type NormalizeResult,
26
+ type ProviderBlock,
27
+ } from './normalize-tool-pairs.js';
28
+ export type { NormalizeEvent } from './types.js';
@@ -28,6 +28,7 @@ import type {
28
28
  BlockEvent,
29
29
  StreamEmission,
30
30
  } from './types.js';
31
+ import { normalizeToolPairs } from './normalize-tool-pairs.js';
31
32
 
32
33
  // ============================================================================
33
34
  // Configuration
@@ -245,8 +246,17 @@ export class NativeFormatter implements PrefillFormatter {
245
246
  }
246
247
  }
247
248
 
249
+ // Tool-pair normalizer: wire-boundary safety net for Anthropic's
250
+ // structural rules on tool cycles. See `normalize-tool-pairs.ts`
251
+ // for the full rationale. Runs BEFORE mergeConsecutiveRoles so the
252
+ // merge sees role-correct envelopes.
253
+ const normalized = normalizeToolPairs(providerMessages, {
254
+ pendingToolCallIds: options.pendingToolCallIds,
255
+ onEvent: options.onNormalize,
256
+ });
257
+
248
258
  // Merge consecutive same-role messages (API requires alternating)
249
- const mergedMessages = this.mergeConsecutiveRoles(providerMessages);
259
+ const mergedMessages = this.mergeConsecutiveRoles(normalized.messages);
250
260
 
251
261
  // Build system content with optional cache control
252
262
  let systemContent: unknown;
@@ -279,6 +289,7 @@ export class NativeFormatter implements PrefillFormatter {
279
289
  systemContent,
280
290
  stopSequences: additionalStopSequences ?? [],
281
291
  nativeTools,
292
+ ready: normalized.ready,
282
293
  };
283
294
  }
284
295