@animalabs/membrane 0.5.81 → 0.5.82

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 (43) hide show
  1. package/dist/cache-wire-receipt.d.ts +13 -0
  2. package/dist/cache-wire-receipt.d.ts.map +1 -0
  3. package/dist/cache-wire-receipt.js +108 -0
  4. package/dist/cache-wire-receipt.js.map +1 -0
  5. package/dist/formatters/anthropic-xml.d.ts.map +1 -1
  6. package/dist/formatters/anthropic-xml.js +9 -6
  7. package/dist/formatters/anthropic-xml.js.map +1 -1
  8. package/dist/formatters/native.d.ts.map +1 -1
  9. package/dist/formatters/native.js +16 -3
  10. package/dist/formatters/native.js.map +1 -1
  11. package/dist/formatters/types.d.ts +2 -0
  12. package/dist/formatters/types.d.ts.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/membrane.d.ts.map +1 -1
  18. package/dist/membrane.js +15 -4
  19. package/dist/membrane.js.map +1 -1
  20. package/dist/providers/anthropic.d.ts +12 -1
  21. package/dist/providers/anthropic.d.ts.map +1 -1
  22. package/dist/providers/anthropic.js +4 -4
  23. package/dist/providers/anthropic.js.map +1 -1
  24. package/dist/providers/index.d.ts +1 -1
  25. package/dist/providers/index.d.ts.map +1 -1
  26. package/dist/providers/index.js.map +1 -1
  27. package/dist/types/request.d.ts +6 -0
  28. package/dist/types/request.d.ts.map +1 -1
  29. package/dist/utils/cache-marker-budget.d.ts +9 -0
  30. package/dist/utils/cache-marker-budget.d.ts.map +1 -1
  31. package/dist/utils/cache-marker-budget.js +19 -0
  32. package/dist/utils/cache-marker-budget.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/cache-wire-receipt.ts +125 -0
  35. package/src/formatters/anthropic-xml.ts +9 -8
  36. package/src/formatters/native.ts +23 -2
  37. package/src/formatters/types.ts +3 -0
  38. package/src/index.ts +6 -0
  39. package/src/membrane.ts +14 -3
  40. package/src/providers/anthropic.ts +18 -6
  41. package/src/providers/index.ts +1 -0
  42. package/src/types/request.ts +7 -0
  43. package/src/utils/cache-marker-budget.ts +29 -0
@@ -130,6 +130,18 @@ export function thinkingEnabled(request: ProviderRequest): boolean {
130
130
  // Adapter Configuration
131
131
  // ============================================================================
132
132
 
133
+ /**
134
+ * What the dynamicHeaders callback is told about the request it stamps.
135
+ * `lane` names the transport shape: 'stream' is the conversational turn loop,
136
+ * 'complete' the non-streamed lane (compression, side-calls, keepalive
137
+ * touches). A stamp that describes WHY the agent's turn fired belongs on the
138
+ * stream lane only — a compression call running in the background is not the
139
+ * turn, and stamping it with the turn's cause would lie to the ledger.
140
+ */
141
+ export interface DynamicHeadersContext {
142
+ lane: 'stream' | 'complete';
143
+ }
144
+
133
145
  export interface AnthropicAdapterConfig {
134
146
  /** API key (defaults to ANTHROPIC_API_KEY env var) */
135
147
  apiKey?: string | null;
@@ -157,7 +169,7 @@ export interface AnthropicAdapterConfig {
157
169
  * never replayed stale — an unstamped touch is honest, a stale stamp lies.
158
170
  * null/undefined/'' values are dropped.
159
171
  */
160
- dynamicHeaders?: () => Record<string, string | number | null | undefined>;
172
+ dynamicHeaders?: (ctx?: DynamicHeadersContext) => Record<string, string | number | null | undefined>;
161
173
 
162
174
  /** Default max tokens */
163
175
  defaultMaxTokens?: number;
@@ -196,7 +208,7 @@ export class AnthropicAdapter implements ProviderAdapter {
196
208
  /** Holds idle agents' cached prefixes warm; undefined when disabled. */
197
209
  readonly cacheKeepalive: CacheKeepalive | undefined;
198
210
  /** Live per-request header source (see AnthropicAdapterConfig.dynamicHeaders). */
199
- private readonly dynamicHeaders?: () => Record<string, string | number | null | undefined>;
211
+ private readonly dynamicHeaders?: (ctx?: DynamicHeadersContext) => Record<string, string | number | null | undefined>;
200
212
 
201
213
  constructor(config: AnthropicAdapterConfig = {}) {
202
214
  const clientOptions: ClientOptions = {
@@ -250,7 +262,7 @@ export class AnthropicAdapter implements ProviderAdapter {
250
262
  try {
251
263
  const response = await this.client.messages.create(fullRequest, {
252
264
  signal: options?.signal,
253
- headers: this.liveHeaders(headers),
265
+ headers: this.liveHeaders(headers, 'complete'),
254
266
  });
255
267
 
256
268
  return this.parseResponse(response, fullRequest);
@@ -323,7 +335,7 @@ export class AnthropicAdapter implements ProviderAdapter {
323
335
  try {
324
336
  const stream = await this.client.messages.stream(anthropicRequest, {
325
337
  signal: idleAbort.signal,
326
- headers: this.liveHeaders(this.betaHeaders(request)),
338
+ headers: this.liveHeaders(this.betaHeaders(request), 'stream'),
327
339
  });
328
340
 
329
341
  // Accumulate response metadata from SSE events directly, so we can
@@ -573,8 +585,8 @@ export class AnthropicAdapter implements ProviderAdapter {
573
585
  /** Base headers + the live dynamicHeaders stamp. Request time only: the
574
586
  * keepalive recorder receives the base headers BEFORE this merge, so
575
587
  * replayed touches never carry a stale telemetry value. */
576
- private liveHeaders(base: Record<string, string> | undefined): Record<string, string> | undefined {
577
- const dyn = this.dynamicHeaders?.();
588
+ private liveHeaders(base: Record<string, string> | undefined, lane: DynamicHeadersContext['lane']): Record<string, string> | undefined {
589
+ const dyn = this.dynamicHeaders?.({ lane });
578
590
  if (!dyn) return base;
579
591
  const out: Record<string, string> = { ...(base ?? {}) };
580
592
  for (const [k, v] of Object.entries(dyn)) {
@@ -7,6 +7,7 @@ export {
7
7
  toAnthropicContent,
8
8
  fromAnthropicContent,
9
9
  type AnthropicAdapterConfig,
10
+ type DynamicHeadersContext,
10
11
  } from './anthropic.js';
11
12
 
12
13
  export { flattenRootSchemaUnion } from './anthropic-tool-schema.js';
@@ -112,6 +112,9 @@ export type ToolMode =
112
112
  // ============================================================================
113
113
 
114
114
  export interface NormalizedRequest {
115
+ /** Receives the exact post-format/post-hook cache receipt immediately before
116
+ * network submission. Observability only; never forwarded to providers. */
117
+ onCacheWireReceipt?: (receipt: import('../cache-wire-receipt.js').CacheWireReceipt) => void;
115
118
  /**
116
119
  * Explicitly own the loss of old inline images when the serialized request
117
120
  * exceeds the API byte cap: oldest images are replaced with loud
@@ -152,6 +155,10 @@ export interface NormalizedRequest {
152
155
  */
153
156
  promptCaching?: boolean;
154
157
 
158
+ /** Marker ownership policy. `cm-owned` disables every formatter-generated
159
+ * system/context-prefix marker; only normalized message breakpoints survive. */
160
+ cacheMarkers?: 'membrane-system' | 'cm-owned';
161
+
155
162
  /**
156
163
  * Cache TTL for Anthropic prompt caching.
157
164
  * '5m' (default) = 5 minute TTL
@@ -149,6 +149,35 @@ export function countWireCacheMarkers(surfaces: WireCacheSurfaces): number {
149
149
  return collectMarkedBlocks(surfaces).length;
150
150
  }
151
151
 
152
+ /**
153
+ * Validate a caller-owned marker layout without changing it.
154
+ *
155
+ * In `cm-owned` mode the caller has deliberately allocated the complete
156
+ * breakpoint set. Silently stripping or reordering those markers would make
157
+ * the cache receipt describe a request that was never sent, so invalid
158
+ * layouts fail before submission instead of using the legacy repair clamp.
159
+ */
160
+ export function assertCacheMarkersWithinLimit(
161
+ surfaces: WireCacheSurfaces,
162
+ site: string
163
+ ): number {
164
+ const marked = collectMarkedBlocks(surfaces);
165
+ const invalidThinking = marked.filter(
166
+ (block) => block.type === 'thinking' || block.type === 'redacted_thinking'
167
+ ).length;
168
+ if (invalidThinking > 0) {
169
+ throw new Error(
170
+ `${site}: caller-owned cache_control cannot be attached to thinking/redacted_thinking blocks`
171
+ );
172
+ }
173
+ if (marked.length > MAX_CACHE_BREAKPOINTS) {
174
+ throw new Error(
175
+ `${site}: cache_control limit exceeded: ${marked.length} markers (maximum ${MAX_CACHE_BREAKPOINTS})`
176
+ );
177
+ }
178
+ return marked.length;
179
+ }
180
+
152
181
  /**
153
182
  * Bring a request inside the breakpoint budget, in place, at the last exit
154
183
  * before the adapter call. Two repairs, both loud: