@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.
- package/dist/formatters/index.d.ts +2 -0
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +1 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/formatters/native.d.ts.map +1 -1
- package/dist/formatters/native.js +11 -1
- package/dist/formatters/native.js.map +1 -1
- package/dist/formatters/normalize-tool-pairs.d.ts +75 -0
- package/dist/formatters/normalize-tool-pairs.d.ts.map +1 -0
- package/dist/formatters/normalize-tool-pairs.js +498 -0
- package/dist/formatters/normalize-tool-pairs.js.map +1 -0
- package/dist/formatters/types.d.ts +55 -0
- package/dist/formatters/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/formatters/index.ts +9 -0
- package/src/formatters/native.ts +12 -1
- package/src/formatters/normalize-tool-pairs.ts +622 -0
- package/src/formatters/types.ts +39 -0
|
@@ -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":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,UAAU,EACV,UAAU,EACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAGpF,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;AAEzE,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAM1C,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAErC,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,eAAe,EAAE,QAAQ,GAAG,WAAW,CAAC;IAExC,wCAAwC;IACxC,oBAAoB,EAAE,MAAM,CAAC;IAE7B,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,kCAAkC;IAClC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IAEzB,kCAAkC;IAClC,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEvD,4BAA4B;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE,CAAC;IAEvC,iDAAiD;IACjD,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEvB,2CAA2C;IAC3C,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnC,wDAAwD;IACxD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC,yFAAyF;IACzF,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;uGAEmG;IACnG,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/formatters/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/formatters/index.ts
CHANGED
|
@@ -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';
|
package/src/formatters/native.ts
CHANGED
|
@@ -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(
|
|
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
|
|