@animalabs/membrane 0.5.74 → 0.5.75

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.
@@ -21,6 +21,11 @@ import {
21
21
  abortError,
22
22
  } from '../types/index.js';
23
23
  import { createCombinedSignal } from './utils.js';
24
+ import {
25
+ INTERLEAVED_THINKING_BETA,
26
+ needsInterleavedThinkingBeta,
27
+ thinkingEnabled,
28
+ } from './anthropic.js';
24
29
 
25
30
  // ============================================================================
26
31
  // Adapter Configuration
@@ -64,6 +69,8 @@ interface BedrockMessageRequest {
64
69
  stop_sequences?: string[];
65
70
  tools?: unknown[];
66
71
  thinking?: { type: 'enabled'; budget_tokens: number };
72
+ /** Beta flags. On bedrock-runtime these ride in the body, not a header. */
73
+ anthropic_beta?: string[];
67
74
  }
68
75
 
69
76
  interface BedrockMessageResponse {
@@ -407,6 +414,17 @@ export class BedrockAdapter implements ProviderAdapter {
407
414
  Object.assign(params, rest);
408
415
  }
409
416
 
417
+ // Interleaved thinking on pre-4.6 Claude 4: same gate as the Anthropic
418
+ // adapter, but bedrock-runtime takes betas as the `anthropic_beta` body
419
+ // field rather than an HTTP header. Runs after the extra-assign so a
420
+ // consumer-supplied anthropic_beta is merged (Set-deduped), not clobbered.
421
+ // The gate only matches Claude 4 <4.6 ids, so legacy 3.x models (which
422
+ // reject unknown beta flags) never receive the field.
423
+ if (thinkingEnabled(request) && needsInterleavedThinkingBeta(request.model)) {
424
+ const existing = Array.isArray(params.anthropic_beta) ? params.anthropic_beta : [];
425
+ params.anthropic_beta = [...new Set([...existing, INTERLEAVED_THINKING_BETA])];
426
+ }
427
+
410
428
  return params;
411
429
  }
412
430
 
@@ -110,6 +110,17 @@ export interface ToolUseContent {
110
110
  id: string;
111
111
  name: string;
112
112
  input: Record<string, unknown>;
113
+ /**
114
+ * Verbatim document text this call was parsed from in prefill/XML mode:
115
+ * the full `<function_calls>…</function_calls>` block, shared by every
116
+ * invoke parsed from that block. Prefill formatters replay it exactly
117
+ * instead of synthesizing a rendering — in prefill mode the context IS
118
+ * the document the agent authored, and a paraphrase of its own action
119
+ * both corrupts the record and teaches the model a syntax the parser
120
+ * does not accept (membrane#36). Analogous to `signature` on a thinking
121
+ * block. Absent on native-tools blocks and on legacy stored blocks.
122
+ */
123
+ rawXml?: string;
113
124
  /** See {@link TextContent.rawItem}. */
114
125
  rawItem?: unknown;
115
126
  }
@@ -119,6 +130,13 @@ export interface ToolResultContent {
119
130
  toolUseId: string;
120
131
  content: string | ContentBlock[];
121
132
  isError?: boolean;
133
+ /**
134
+ * Verbatim document text this result was parsed from in prefill/XML mode:
135
+ * the full `<function_results>…</function_results>` block as the harness
136
+ * originally placed it in the document, shared by every result parsed
137
+ * from that block. Replayed exactly on the prefill path (membrane#36).
138
+ */
139
+ rawXml?: string;
122
140
  /** See {@link TextContent.rawItem}. */
123
141
  rawItem?: unknown;
124
142
  }
@@ -350,6 +350,10 @@ export function parseAccumulatedIntoBlocks(
350
350
  let funcMatch: RegExpExecArray | null;
351
351
  while ((funcMatch = FUNCTION_BLOCK_WITH_CONTENT_REGEX.exec(processedText)) !== null) {
352
352
  const innerContent = funcMatch[2] ?? '';
353
+ // Verbatim document text of the whole block — carried on each parsed
354
+ // tool_use so prefill replay reproduces the generation exactly instead
355
+ // of synthesizing a paraphrase (membrane#36).
356
+ const rawXml = funcMatch[0];
353
357
  const blockToolCalls: ContentBlock[] = [];
354
358
 
355
359
  // Parse invoke tags in this block
@@ -378,6 +382,7 @@ export function parseAccumulatedIntoBlocks(
378
382
  id,
379
383
  name: toolName,
380
384
  input,
385
+ rawXml,
381
386
  });
382
387
  }
383
388
 
@@ -394,6 +399,7 @@ export function parseAccumulatedIntoBlocks(
394
399
  id,
395
400
  name: toolName,
396
401
  input: {},
402
+ rawXml,
397
403
  });
398
404
  }
399
405
 
@@ -411,6 +417,9 @@ export function parseAccumulatedIntoBlocks(
411
417
  let resultsMatch: RegExpExecArray | null;
412
418
  while ((resultsMatch = FUNCTION_RESULTS_BLOCK_REGEX.exec(processedText)) !== null) {
413
419
  const innerContent = resultsMatch[2] ?? '';
420
+ // Verbatim document text the harness placed — carried for exact replay
421
+ // on the prefill path (membrane#36).
422
+ const rawXml = resultsMatch[0];
414
423
  const blockResults: ContentBlock[] = [];
415
424
 
416
425
  // Parse result tags
@@ -426,6 +435,7 @@ export function parseAccumulatedIntoBlocks(
426
435
  toolUseId,
427
436
  content,
428
437
  isError: false,
438
+ rawXml,
429
439
  });
430
440
  }
431
441
 
@@ -442,6 +452,7 @@ export function parseAccumulatedIntoBlocks(
442
452
  toolUseId,
443
453
  content,
444
454
  isError: true,
455
+ rawXml,
445
456
  });
446
457
  }
447
458