@animalabs/membrane 0.5.52 → 0.5.54
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 +0 -1
- package/dist/formatters/native.d.ts.map +1 -1
- package/dist/formatters/native.js +11 -25
- package/dist/formatters/native.js.map +1 -1
- package/dist/formatters/normalize-tool-pairs.d.ts +91 -0
- package/dist/formatters/normalize-tool-pairs.d.ts.map +1 -0
- package/dist/formatters/normalize-tool-pairs.js +536 -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/dist/membrane.d.ts.map +1 -1
- package/dist/membrane.js +23 -1
- package/dist/membrane.js.map +1 -1
- package/package.json +1 -1
- package/src/formatters/index.ts +9 -0
- package/src/formatters/native.ts +12 -28
- package/src/formatters/normalize-tool-pairs.ts +663 -0
- package/src/formatters/types.ts +39 -0
- package/src/membrane.ts +25 -2
package/src/formatters/types.ts
CHANGED
|
@@ -81,8 +81,38 @@ export interface BuildOptions {
|
|
|
81
81
|
* This enables per-message cache boundaries in the conversation.
|
|
82
82
|
*/
|
|
83
83
|
hasCacheMarker?: (message: NormalizedMessage, index: number) => boolean;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* IDs of tool_use blocks the caller knows are currently in-flight
|
|
87
|
+
* (e.g. a yielding stream that has emitted the tool_use but is still
|
|
88
|
+
* waiting on the result). If a trailing unmatched tool_use's id is in
|
|
89
|
+
* this set, the normalizer signals `ready: false` instead of injecting
|
|
90
|
+
* a synthetic `[pending]` result. Default: empty (always synthesize).
|
|
91
|
+
*/
|
|
92
|
+
pendingToolCallIds?: ReadonlySet<string>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Telemetry callback fired once per normalization action. Lets the
|
|
96
|
+
* framework count/log normalizations without coupling Membrane to a
|
|
97
|
+
* specific logger. See `NormalizeEvent` for the event shapes.
|
|
98
|
+
*/
|
|
99
|
+
onNormalize?: (event: NormalizeEvent) => void;
|
|
84
100
|
}
|
|
85
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Events emitted by the tool-pair normalizer. Surfaced through
|
|
104
|
+
* `BuildOptions.onNormalize`. Every normalization action emits one
|
|
105
|
+
* event; treat non-zero counts as a producer-side bug to investigate.
|
|
106
|
+
*/
|
|
107
|
+
export type NormalizeEvent =
|
|
108
|
+
| { kind: 'block_re_roled'; blockType: string; from: 'user' | 'assistant'; to: 'user' | 'assistant' }
|
|
109
|
+
| { kind: 'tool_result_hoisted'; toolUseId: string; fromEnvelope: number; toEnvelope: number }
|
|
110
|
+
| { kind: 'interloper_deferred'; blockType: string; fromEnvelope: number }
|
|
111
|
+
| { kind: 'synthetic_pending_result'; toolUseId: string; reason: 'trailing' | 'mid_stream' }
|
|
112
|
+
| { kind: 'orphan_tool_result_textified'; toolUseId: string }
|
|
113
|
+
| { kind: 'pending_in_flight'; toolUseId: string }
|
|
114
|
+
| { kind: 'cache_suppressed_for_synthetic'; envelopeIndex: number };
|
|
115
|
+
|
|
86
116
|
// ============================================================================
|
|
87
117
|
// Build Result
|
|
88
118
|
// ============================================================================
|
|
@@ -105,6 +135,15 @@ export interface BuildResult {
|
|
|
105
135
|
|
|
106
136
|
/** Number of cache control markers applied (for Anthropic prompt caching) */
|
|
107
137
|
cacheMarkersApplied?: number;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* `false` only when the tool-pair normalizer detected a trailing
|
|
141
|
+
* unmatched tool_use whose id is in `pendingToolCallIds` — i.e. the
|
|
142
|
+
* caller (yielding stream) is mid-cycle and the request should not be
|
|
143
|
+
* shipped yet. Callers that don't pass `pendingToolCallIds` will never
|
|
144
|
+
* see `false` here.
|
|
145
|
+
*/
|
|
146
|
+
ready?: boolean;
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
export interface ProviderMessage {
|
package/src/membrane.ts
CHANGED
|
@@ -52,6 +52,7 @@ import type {
|
|
|
52
52
|
} from './types/yielding-stream.js';
|
|
53
53
|
import type { PrefillFormatter, StreamParser } from './formatters/types.js';
|
|
54
54
|
import { AnthropicXmlFormatter } from './formatters/anthropic-xml.js';
|
|
55
|
+
import { normalizeToolPairs, mergeConsecutiveRoles } from './formatters/normalize-tool-pairs.js';
|
|
55
56
|
import { YieldingStreamImpl } from './yielding-stream.js';
|
|
56
57
|
import { calculateCost } from './utils/cost.js';
|
|
57
58
|
import { getDefaultPricing } from './registry/default-pricing.js';
|
|
@@ -1030,7 +1031,29 @@ export class Membrane {
|
|
|
1030
1031
|
|
|
1031
1032
|
providerMessages.push({ role, content });
|
|
1032
1033
|
}
|
|
1033
|
-
|
|
1034
|
+
|
|
1035
|
+
// Wire-boundary safety net: repair upstream-produced violations of
|
|
1036
|
+
// Anthropic's tool-cycle structural rules (orphan tool_use, mis-roled
|
|
1037
|
+
// blocks, consecutive same-role envelopes from upstream chunkers that
|
|
1038
|
+
// dropped a tool_result). Mirrors NativeFormatter.buildMessages — the
|
|
1039
|
+
// streaming-native path (runNativeToolsYielding) used to bypass this
|
|
1040
|
+
// and exposed every agent inference to the 400 family.
|
|
1041
|
+
//
|
|
1042
|
+
// Synthesized [pending] tool_results land in fresh user envelopes;
|
|
1043
|
+
// the normalizer also suppresses cache_control on those envelopes
|
|
1044
|
+
// so an in-flight gap can't poison the prompt cache. Merging after
|
|
1045
|
+
// normalize collapses any same-role neighbours the upstream may have
|
|
1046
|
+
// produced before they reach the API's alternating-role check.
|
|
1047
|
+
//
|
|
1048
|
+
// `pendingToolCallIds` is intentionally not threaded here: by the
|
|
1049
|
+
// time runNativeToolsYielding rebuilds the request between
|
|
1050
|
+
// tool-execution rounds, it has already appended the corresponding
|
|
1051
|
+
// tool_results to `messages`. Any unmatched tool_use that reaches
|
|
1052
|
+
// this splice is upstream stranding (the bug class this fix exists
|
|
1053
|
+
// to catch) — `[pending]` is exactly the right synthesis.
|
|
1054
|
+
const normalized = normalizeToolPairs(providerMessages);
|
|
1055
|
+
const mergedMessages = mergeConsecutiveRoles(normalized.messages);
|
|
1056
|
+
|
|
1034
1057
|
// Convert tools to provider format.
|
|
1035
1058
|
// Native tool names must match ^[a-zA-Z0-9_-]{1,128}$ — sanitize colons
|
|
1036
1059
|
// from the module:tool namespace convention. Reversed in parseProviderContent.
|
|
@@ -1073,7 +1096,7 @@ export class Membrane {
|
|
|
1073
1096
|
model: request.config.model,
|
|
1074
1097
|
maxTokens: request.config.maxTokens,
|
|
1075
1098
|
temperature,
|
|
1076
|
-
messages:
|
|
1099
|
+
messages: mergedMessages,
|
|
1077
1100
|
system,
|
|
1078
1101
|
tools,
|
|
1079
1102
|
thinking,
|