@animalabs/membrane 0.5.72 → 0.5.73

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 (40) hide show
  1. package/dist/cache-thinking-guard.test.d.ts +2 -0
  2. package/dist/cache-thinking-guard.test.d.ts.map +1 -0
  3. package/dist/cache-thinking-guard.test.js +32 -0
  4. package/dist/cache-thinking-guard.test.js.map +1 -0
  5. package/dist/formatters/native.d.ts +5 -0
  6. package/dist/formatters/native.d.ts.map +1 -1
  7. package/dist/formatters/native.js +1 -1
  8. package/dist/formatters/native.js.map +1 -1
  9. package/dist/membrane.d.ts.map +1 -1
  10. package/dist/membrane.js +51 -6
  11. package/dist/membrane.js.map +1 -1
  12. package/dist/providers/openai-compatible.d.ts.map +1 -1
  13. package/dist/providers/openai-compatible.js +18 -1
  14. package/dist/providers/openai-compatible.js.map +1 -1
  15. package/dist/providers/openai.d.ts.map +1 -1
  16. package/dist/providers/openai.js +20 -1
  17. package/dist/providers/openai.js.map +1 -1
  18. package/dist/providers/openrouter.d.ts.map +1 -1
  19. package/dist/providers/openrouter.js +18 -1
  20. package/dist/providers/openrouter.js.map +1 -1
  21. package/dist/types/index.d.ts +1 -1
  22. package/dist/types/index.d.ts.map +1 -1
  23. package/dist/types/index.js.map +1 -1
  24. package/dist/types/yielding-stream.d.ts +47 -1
  25. package/dist/types/yielding-stream.d.ts.map +1 -1
  26. package/dist/types/yielding-stream.js.map +1 -1
  27. package/dist/yielding-stream.d.ts +12 -3
  28. package/dist/yielding-stream.d.ts.map +1 -1
  29. package/dist/yielding-stream.js +21 -2
  30. package/dist/yielding-stream.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/cache-thinking-guard.test.ts +34 -0
  33. package/src/formatters/native.ts +1 -1
  34. package/src/membrane.ts +55 -6
  35. package/src/providers/openai-compatible.ts +18 -1
  36. package/src/providers/openai.ts +20 -1
  37. package/src/providers/openrouter.ts +18 -1
  38. package/src/types/index.ts +2 -0
  39. package/src/types/yielding-stream.ts +50 -1
  40. package/src/yielding-stream.ts +41 -5
@@ -1 +1 @@
1
- {"version":3,"file":"yielding-stream.js","sourceRoot":"","sources":["../src/yielding-stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"yielding-stream.js","sourceRoot":"","sources":["../src/yielding-stream.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.72",
3
+ "version": "0.5.73",
4
4
  "description": "LLM middleware - a selective boundary that transforms what passes through",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Regression: a cache breakpoint must never land on a thinking block.
3
+ *
4
+ * The API rejects `cache_control` on thinking / redacted_thinking with
5
+ * 400 "thinking.cache_control: Extra inputs are not permitted". The rule was
6
+ * fixed in NativeFormatter (2026-07-01) but NOT in Membrane.buildNativeToolRequest
7
+ * — the live Connectome path — so the 400 returned on 2026-07-14 the moment a
8
+ * breakpoint landed on a thinking-terminated turn. One shared helper now.
9
+ */
10
+ import { describe, it, expect } from 'vitest';
11
+ import { lastCacheableBlockIndex } from './formatters/native.js';
12
+
13
+ describe('lastCacheableBlockIndex', () => {
14
+ it('steps back past a trailing thinking block', () => {
15
+ expect(lastCacheableBlockIndex([
16
+ { type: 'text', text: 'hi' },
17
+ { type: 'thinking', thinking: '', signature: 'sig' },
18
+ ])).toBe(0);
19
+ });
20
+
21
+ it('skips the breakpoint entirely for a thinking-only message', () => {
22
+ expect(lastCacheableBlockIndex([
23
+ { type: 'thinking', thinking: '', signature: 'sig' },
24
+ { type: 'redacted_thinking', data: 'x' },
25
+ ])).toBe(-1);
26
+ });
27
+
28
+ it('uses the last block when it is cacheable', () => {
29
+ expect(lastCacheableBlockIndex([
30
+ { type: 'thinking', thinking: '', signature: 'sig' },
31
+ { type: 'tool_use', name: 't', input: {} },
32
+ ])).toBe(1);
33
+ });
34
+ });
@@ -35,7 +35,7 @@ import { isAcceptedImageMediaType, strippedImagePlaceholder } from '../utils/ima
35
35
  * rejects cache_control on thinking / redacted_thinking blocks, so a cache
36
36
  * breakpoint must attach to the last NON-thinking block. Returns -1 when the
37
37
  * message has only thinking blocks (the breakpoint is then skipped). */
38
- function lastCacheableBlockIndex(blocks: Array<Record<string, unknown>>): number {
38
+ export function lastCacheableBlockIndex(blocks: Array<Record<string, unknown>>): number {
39
39
  for (let k = blocks.length - 1; k >= 0; k--) {
40
40
  const t = blocks[k]?.type as string | undefined;
41
41
  if (t !== 'thinking' && t !== 'redacted_thinking') return k;
package/src/membrane.ts CHANGED
@@ -26,6 +26,7 @@ import type {
26
26
  ToolMode,
27
27
  ToolDefinition,
28
28
  } from './types/index.js';
29
+ import { lastCacheableBlockIndex } from './formatters/native.js';
29
30
  import {
30
31
  DEFAULT_RETRY_CONFIG,
31
32
  MembraneError,
@@ -1087,10 +1088,24 @@ export class Membrane {
1087
1088
  }
1088
1089
  }
1089
1090
 
1090
- // Apply cache_control to last block of messages with cacheBreakpoint
1091
+ // Apply cache_control to the last CACHEABLE block of messages with a
1092
+ // cacheBreakpoint. The API rejects cache_control on thinking /
1093
+ // redacted_thinking blocks (400 "thinking.cache_control: Extra inputs
1094
+ // are not permitted"), so a breakpoint landing on a thinking-terminated
1095
+ // message must step back to the last non-thinking block — and is skipped
1096
+ // entirely when the message is thinking-only.
1097
+ //
1098
+ // 2026-07-14: this is the THIRD request builder to need the rule. The
1099
+ // 2026-07-01 fix hardened NativeFormatter's two sites but not this one,
1100
+ // which is the live Connectome path (native tools + thinking) — so the
1101
+ // 400 came back the moment a breakpoint landed on a thinking-only turn.
1102
+ // The rule now lives in ONE exported helper that every builder calls.
1091
1103
  if (msg.cacheBreakpoint && cacheControl && content.length > 0) {
1092
- content[content.length - 1].cache_control = cacheControl;
1093
- messageBreakpoints++;
1104
+ const bpIdx = lastCacheableBlockIndex(content as Array<Record<string, unknown>>);
1105
+ if (bpIdx >= 0) {
1106
+ content[bpIdx].cache_control = cacheControl;
1107
+ messageBreakpoints++;
1108
+ }
1094
1109
  }
1095
1110
 
1096
1111
  providerMessages.push({ role, content });
@@ -2001,6 +2016,8 @@ export class Membrane {
2001
2016
  const formatter = this.formatter;
2002
2017
  const parser = formatter.createStreamParser();
2003
2018
  let toolDepth = 0;
2019
+ // Once-per-stream latch for the injectedMessages-unsupported warning.
2020
+ let warnedInjectionUnsupported = false;
2004
2021
  let totalUsage: DetailedUsage = { inputTokens: 0, outputTokens: 0 };
2005
2022
  const pricing = this.resolvePricing(request.config.model);
2006
2023
  const contentBlocks: ContentBlock[] = [];
@@ -2224,7 +2241,21 @@ export class Membrane {
2224
2241
  context,
2225
2242
  };
2226
2243
 
2227
- const results = await stream.requestToolExecution(toolCallsEvent);
2244
+ const { results, injectedMessages } = await stream.requestToolExecution(toolCallsEvent);
2245
+
2246
+ // Mid-turn injected messages are not supported on the XML prefill
2247
+ // path: the continuation is an assistant prefill over an XML
2248
+ // transcript, not a message array, so there is no user envelope
2249
+ // to append to. Warn (once per stream — long turns have many
2250
+ // rounds) rather than drop silently; the messages remain in the
2251
+ // caller's context window and reach the model on the next turn.
2252
+ if (injectedMessages && injectedMessages.length > 0 && !warnedInjectionUnsupported) {
2253
+ warnedInjectionUnsupported = true;
2254
+ console.warn(
2255
+ `[membrane] provideToolResults injectedMessages ignored: XML tool mode ` +
2256
+ `does not support mid-turn injection (delivered next turn instead)`
2257
+ );
2258
+ }
2228
2259
 
2229
2260
  // Track the tool results
2230
2261
  executedToolResults.push(...results);
@@ -2647,7 +2678,7 @@ export class Membrane {
2647
2678
  context,
2648
2679
  };
2649
2680
 
2650
- const results = await stream.requestToolExecution(toolCallsEvent);
2681
+ const { results, injectedMessages } = await stream.requestToolExecution(toolCallsEvent);
2651
2682
 
2652
2683
  // Track tool results
2653
2684
  executedToolResults.push(...results);
@@ -2665,13 +2696,14 @@ export class Membrane {
2665
2696
  // Add messages for next iteration — use the request's participant names
2666
2697
  const assistantName = request.assistantParticipant
2667
2698
  ?? this.config.assistantParticipant ?? 'Claude';
2699
+ const userName = assistantName === 'Claude' ? 'User' : 'user';
2668
2700
  messages.push({
2669
2701
  participant: assistantName,
2670
2702
  content: responseBlocks,
2671
2703
  });
2672
2704
 
2673
2705
  messages.push({
2674
- participant: assistantName === 'Claude' ? 'User' : 'user',
2706
+ participant: userName,
2675
2707
  content: results.map(r => ({
2676
2708
  type: 'tool_result' as const,
2677
2709
  toolUseId: r.toolUseId,
@@ -2680,6 +2712,23 @@ export class Membrane {
2680
2712
  })),
2681
2713
  });
2682
2714
 
2715
+ // Mid-turn injection: messages that arrived while this round's
2716
+ // tools were executing, appended AFTER the tool_result envelope so
2717
+ // the next inference round sees them. Placed here (not co-mingled
2718
+ // with the results) so provider conversions that special-case
2719
+ // tool_result envelopes (e.g. ChatCompletions role:'tool') carry
2720
+ // them as ordinary user messages. Signed-thinking adjacency is
2721
+ // unaffected: the assistant turn above is round-tripped verbatim.
2722
+ if (injectedMessages) {
2723
+ for (const injected of injectedMessages) {
2724
+ messages.push({
2725
+ participant: injected.participant ?? userName,
2726
+ content: injected.content,
2727
+ ...(injected.metadata ? { metadata: injected.metadata } : {}),
2728
+ });
2729
+ }
2730
+ }
2731
+
2683
2732
  toolDepth++;
2684
2733
  continue;
2685
2734
  }
@@ -409,8 +409,25 @@ export class OpenAICompatibleAdapter implements ProviderAdapter {
409
409
  }
410
410
  }
411
411
 
412
- // If we have tool results, return them (possibly multiple)
412
+ // If we have tool results, emit them (possibly multiple) as
413
+ // role:'tool' messages — plus a FOLLOWING user message for any other
414
+ // content sharing the envelope (e.g. a mid-turn injected user message
415
+ // that mergeConsecutiveRoles folded into the tool_result envelope),
416
+ // instead of silently dropping it. Mirrors openai.ts / openrouter.ts.
413
417
  if (toolResults.length > 0) {
418
+ const hasImagesInterloper = contentParts.some(p => p.type === 'image_url');
419
+ const interloperText = hasImagesInterloper
420
+ ? ''
421
+ : contentParts.filter(p => p.type === 'text').map(p => p.text!).join('\n');
422
+ if (hasImagesInterloper || interloperText) {
423
+ return [
424
+ ...toolResults,
425
+ {
426
+ role: 'user' as const,
427
+ content: hasImagesInterloper ? contentParts : interloperText,
428
+ },
429
+ ];
430
+ }
414
431
  return toolResults;
415
432
  }
416
433
 
@@ -472,8 +472,27 @@ export class OpenAIAdapter implements ProviderAdapter {
472
472
  }
473
473
  }
474
474
 
475
- // If we have tool results, return them (possibly multiple)
475
+ // If we have tool results, emit them (possibly multiple) as
476
+ // role:'tool' messages. Any OTHER content sharing the envelope
477
+ // (e.g. a mid-turn user message injected alongside tool_results —
478
+ // mergeConsecutiveRoles folds the separately-pushed injected user
479
+ // message INTO the tool_result envelope) is emitted as a FOLLOWING
480
+ // user message rather than dropped — this used to silently swallow
481
+ // such text. Same fix in openrouter.ts / openai-compatible.ts.
476
482
  if (toolResults.length > 0) {
483
+ const hasImagesInterloper = contentParts.some(p => p.type === 'image_url');
484
+ const interloperText = hasImagesInterloper
485
+ ? '' // unused: array form carries the parts
486
+ : contentParts.filter(p => p.type === 'text').map(p => p.text!).join('\n');
487
+ if (hasImagesInterloper || interloperText) {
488
+ return [
489
+ ...toolResults,
490
+ {
491
+ role: 'user' as const,
492
+ content: hasImagesInterloper ? contentParts : interloperText,
493
+ },
494
+ ];
495
+ }
477
496
  return toolResults;
478
497
  }
479
498
 
@@ -514,8 +514,25 @@ export class OpenRouterAdapter implements ProviderAdapter {
514
514
  }
515
515
  }
516
516
 
517
- // If we have tool results, return them (possibly multiple)
517
+ // If we have tool results, emit them (possibly multiple) as
518
+ // role:'tool' messages — plus a FOLLOWING user message for any other
519
+ // content sharing the envelope (e.g. a mid-turn injected user message
520
+ // that mergeConsecutiveRoles folded into the tool_result envelope),
521
+ // instead of silently dropping it. Mirrors openai.ts / openai-compatible.ts.
518
522
  if (toolResults.length > 0) {
523
+ const interloperText = textParts.join('\n');
524
+ if (contentBlocks.length > 0) {
525
+ return [
526
+ ...toolResults,
527
+ { role: 'user' as const, content: contentBlocks },
528
+ ];
529
+ }
530
+ if (interloperText) {
531
+ return [
532
+ ...toolResults,
533
+ { role: 'user' as const, content: interloperText },
534
+ ];
535
+ }
519
536
  return toolResults;
520
537
  }
521
538
 
@@ -132,6 +132,8 @@ export type {
132
132
  StreamEvent,
133
133
  YieldingStream,
134
134
  YieldingStreamOptions,
135
+ InjectedMessage,
136
+ ProvideToolResultsOptions,
135
137
  } from './yielding-stream.js';
136
138
 
137
139
  export {
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { ContentBlock } from './content.js';
12
+ import type { NormalizedMessage } from './message.js';
12
13
  import type { ToolCall, ToolResult, ToolContext } from './tools.js';
13
14
  import type { DetailedUsage, NormalizedResponse, StopReason } from './response.js';
14
15
  import type { ChunkMeta, BlockEvent } from './streaming.js';
@@ -122,15 +123,63 @@ export type StreamEvent =
122
123
  * }
123
124
  * ```
124
125
  */
126
+ /**
127
+ * A user-side message injected into the conversation between tool rounds.
128
+ *
129
+ * This is how a consumer lets the model see events that arrived while the
130
+ * turn was in flight (e.g. a chat reply landing mid-way through a long
131
+ * tool-using turn): pass it alongside the tool results and the next
132
+ * inference round's request includes it as a user message AFTER the
133
+ * tool_result envelope.
134
+ *
135
+ * Placement guarantee: injected messages always land after the round's
136
+ * tool_results (the wire normalizer additionally enforces results-first
137
+ * ordering inside a merged envelope), so they never break the
138
+ * tool_use → tool_result adjacency or signed-thinking constraints.
139
+ *
140
+ * Shape: NormalizedMessage minus cacheBreakpoint (breakpoints are the
141
+ * request compiler's concern), with `participant` optional — it defaults to
142
+ * the generic user participant. Non-assistant participants get the standard
143
+ * "Name: " text prefix when rendered to the provider. Content must be
144
+ * user-side blocks only (text/image); tool blocks are stripped with a
145
+ * warning. NOTE: a participant equal to the request's assistantParticipant
146
+ * would render as an ASSISTANT turn (a prefill) — callers should not inject
147
+ * messages named as the assistant.
148
+ */
149
+ export type InjectedMessage =
150
+ Omit<NormalizedMessage, 'participant' | 'cacheBreakpoint'> & {
151
+ participant?: string;
152
+ };
153
+
154
+ /**
155
+ * Options for provideToolResults().
156
+ */
157
+ export interface ProvideToolResultsOptions {
158
+ /**
159
+ * Messages that arrived while the turn was in flight, to be appended to
160
+ * the conversation after this round's tool_result envelope so the NEXT
161
+ * inference round sees them.
162
+ *
163
+ * Supported in native tool mode (Anthropic Messages, OpenAI Responses,
164
+ * OpenRouter). The XML prefill path currently ignores these (the
165
+ * continuation is an assistant prefill, not a message array) — callers
166
+ * on XML-mode models should deliver mid-turn events on the next turn
167
+ * instead.
168
+ */
169
+ injectedMessages?: InjectedMessage[];
170
+ }
171
+
125
172
  export interface YieldingStream extends AsyncIterable<StreamEvent> {
126
173
  /**
127
174
  * Provide tool results after receiving a 'tool-calls' event.
128
175
  * The stream will resume and continue generating.
129
176
  *
130
177
  * @param results - Results for the tool calls (must match call IDs)
178
+ * @param options - Optionally inject mid-turn user messages into the
179
+ * next round (see ProvideToolResultsOptions.injectedMessages)
131
180
  * @throws Error if called when not waiting for tool results
132
181
  */
133
- provideToolResults(results: ToolResult[]): void;
182
+ provideToolResults(results: ToolResult[], options?: ProvideToolResultsOptions): void;
134
183
 
135
184
  /**
136
185
  * Cancel the stream. Any in-flight requests will be aborted.
@@ -10,9 +10,21 @@ import type {
10
10
  YieldingStream,
11
11
  YieldingStreamOptions,
12
12
  ToolCallsEvent,
13
+ InjectedMessage,
14
+ ProvideToolResultsOptions,
13
15
  } from './types/yielding-stream.js';
14
16
  import type { ToolResult } from './types/tools.js';
15
17
 
18
+ /**
19
+ * Payload handed from provideToolResults() to the inference loop awaiting
20
+ * requestToolExecution(): the round's tool results plus any mid-turn
21
+ * messages the consumer wants the next inference round to see.
22
+ */
23
+ export interface ToolResultsPayload {
24
+ results: ToolResult[];
25
+ injectedMessages?: InjectedMessage[];
26
+ }
27
+
16
28
  // ============================================================================
17
29
  // Internal State Types
18
30
  // ============================================================================
@@ -25,7 +37,7 @@ type StreamState =
25
37
  | { status: 'error'; error: Error };
26
38
 
27
39
  interface PendingToolResults {
28
- resolve: (results: ToolResult[]) => void;
40
+ resolve: (payload: ToolResultsPayload) => void;
29
41
  reject: (error: Error) => void;
30
42
  }
31
43
 
@@ -98,7 +110,7 @@ export class YieldingStreamImpl implements YieldingStream {
98
110
  return this.abortController.signal;
99
111
  }
100
112
 
101
- provideToolResults(results: ToolResult[]): void {
113
+ provideToolResults(results: ToolResult[], options?: ProvideToolResultsOptions): void {
102
114
  if (this.state.status !== 'waiting_for_tools') {
103
115
  throw new Error(
104
116
  `Cannot provide tool results: stream is not waiting for tools (status: ${this.state.status})`
@@ -119,8 +131,32 @@ export class YieldingStreamImpl implements YieldingStream {
119
131
  }
120
132
  }
121
133
 
134
+ // Injected messages must be user-side content only — tool blocks here
135
+ // would corrupt the tool-cycle structure the normalizer guarantees.
136
+ // Offending BLOCKS are stripped (loudly) rather than thrown on: a throw
137
+ // at this point would leave the stream parked in waiting_for_tools with
138
+ // an unresolvable promise — wedging the caller's whole turn over a
139
+ // notification is far worse than delivering it without its tool blocks.
140
+ const injectedMessages = options?.injectedMessages
141
+ ?.map((m) => {
142
+ const clean = m.content.filter(
143
+ (block) => block.type !== 'tool_use' && block.type !== 'tool_result'
144
+ );
145
+ if (clean.length !== m.content.length) {
146
+ console.warn(
147
+ `[membrane] provideToolResults: stripped ${m.content.length - clean.length} ` +
148
+ `tool block(s) from an injected mid-turn message (user-side content only)`
149
+ );
150
+ }
151
+ return { ...m, content: clean };
152
+ })
153
+ .filter((m) => m.content.length > 0);
154
+
122
155
  // Resolve the promise and transition state
123
- this.pendingToolResults.resolve(results);
156
+ this.pendingToolResults.resolve({
157
+ results,
158
+ ...(injectedMessages && injectedMessages.length > 0 ? { injectedMessages } : {}),
159
+ });
124
160
  this.pendingToolResults = null;
125
161
  this.state = { status: 'streaming' };
126
162
  this._toolDepth++;
@@ -198,7 +234,7 @@ export class YieldingStreamImpl implements YieldingStream {
198
234
  * Request tool execution and wait for results.
199
235
  * Called by the inference loop when tool calls are detected.
200
236
  */
201
- async requestToolExecution(event: ToolCallsEvent): Promise<ToolResult[]> {
237
+ async requestToolExecution(event: ToolCallsEvent): Promise<ToolResultsPayload> {
202
238
  // Emit the tool calls event
203
239
  this.emit(event);
204
240
 
@@ -209,7 +245,7 @@ export class YieldingStreamImpl implements YieldingStream {
209
245
  };
210
246
 
211
247
  // Create a promise that will be resolved by provideToolResults()
212
- return new Promise<ToolResult[]>((resolve, reject) => {
248
+ return new Promise<ToolResultsPayload>((resolve, reject) => {
213
249
  this.pendingToolResults = { resolve, reject };
214
250
  });
215
251
  }