@animalabs/membrane 0.5.54 → 0.5.63
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/native.d.ts.map +1 -1
- package/dist/formatters/native.js +11 -0
- package/dist/formatters/native.js.map +1 -1
- package/dist/formatters/normalize-tool-pairs.d.ts +4 -2
- package/dist/formatters/normalize-tool-pairs.d.ts.map +1 -1
- package/dist/formatters/normalize-tool-pairs.js +95 -22
- package/dist/formatters/normalize-tool-pairs.js.map +1 -1
- package/dist/formatters/types.d.ts +26 -0
- package/dist/formatters/types.d.ts.map +1 -1
- package/dist/membrane.d.ts +10 -0
- package/dist/membrane.d.ts.map +1 -1
- package/dist/membrane.js +118 -13
- package/dist/membrane.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +83 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +3 -0
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +57 -3
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +3 -0
- package/dist/providers/openai.js.map +1 -1
- package/dist/types/provider.d.ts +9 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/request.d.ts +10 -0
- package/dist/types/request.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/formatters/native.ts +10 -0
- package/src/formatters/normalize-tool-pairs.ts +100 -25
- package/src/formatters/types.ts +28 -1
- package/src/membrane.ts +129 -13
- package/src/providers/anthropic.ts +87 -3
- package/src/providers/openai-compatible.ts +4 -0
- package/src/providers/openai-completions.ts +58 -2
- package/src/providers/openai.ts +4 -0
- package/src/types/provider.ts +10 -0
- package/src/types/request.ts +12 -1
package/dist/types/provider.d.ts
CHANGED
|
@@ -126,6 +126,8 @@ export interface ProviderRequest {
|
|
|
126
126
|
presencePenalty?: number;
|
|
127
127
|
/** Frequency penalty */
|
|
128
128
|
frequencyPenalty?: number;
|
|
129
|
+
/** Repetition penalty (multiplicative, vLLM/HuggingFace style) */
|
|
130
|
+
repetitionPenalty?: number;
|
|
129
131
|
/** Stop sequences */
|
|
130
132
|
stopSequences?: string[];
|
|
131
133
|
/** Tools in provider format */
|
|
@@ -140,6 +142,13 @@ export interface ProviderRequestOptions {
|
|
|
140
142
|
idleTimeoutMs?: number;
|
|
141
143
|
/** Called with the raw API request body right before fetch */
|
|
142
144
|
onRequest?: (rawRequest: unknown) => void;
|
|
145
|
+
/**
|
|
146
|
+
* Wrap native thinking deltas in <thinking>...</thinking> tags on the
|
|
147
|
+
* onChunk stream. Used by the XML formatter path so its tag-based parser
|
|
148
|
+
* tracks thinking blocks; without this, native thinking content streams
|
|
149
|
+
* indistinguishably from visible text.
|
|
150
|
+
*/
|
|
151
|
+
wrapThinkingTags?: boolean;
|
|
143
152
|
}
|
|
144
153
|
export interface ProviderResponse {
|
|
145
154
|
/** Raw response content */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/types/provider.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/types/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/types/request.d.ts
CHANGED
|
@@ -18,10 +18,20 @@ export interface GenerationConfig {
|
|
|
18
18
|
presencePenalty?: number;
|
|
19
19
|
/** Frequency penalty (provider-specific) */
|
|
20
20
|
frequencyPenalty?: number;
|
|
21
|
+
/** Repetition penalty — multiplicative (vLLM/HuggingFace style, typically 1.0-1.2) */
|
|
22
|
+
repetitionPenalty?: number;
|
|
21
23
|
/** Enable thinking/reasoning mode */
|
|
22
24
|
thinking?: {
|
|
23
25
|
enabled: boolean;
|
|
24
26
|
budgetTokens?: number;
|
|
27
|
+
/** Thinking type for the API: 'enabled' (default, explicit budget) or 'adaptive' (model-managed) */
|
|
28
|
+
type?: 'enabled' | 'adaptive';
|
|
29
|
+
/**
|
|
30
|
+
* Controls how thinking content is returned: 'summarized' (readable summary)
|
|
31
|
+
* or 'omitted' (empty thinking field, signature only). Models like Fable 5 /
|
|
32
|
+
* Opus 4.7+ default to 'omitted' — set 'summarized' to receive thinking text.
|
|
33
|
+
*/
|
|
34
|
+
display?: 'summarized' | 'omitted';
|
|
25
35
|
};
|
|
26
36
|
/** Image generation config (Gemini) */
|
|
27
37
|
imageGeneration?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/types/request.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAMjD,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IAEd,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAElB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,qCAAqC;IACrC,QAAQ,CAAC,EAAE;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/types/request.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/formatters/native.ts
CHANGED
|
@@ -385,10 +385,20 @@ export class NativeFormatter implements PrefillFormatter {
|
|
|
385
385
|
is_error: block.isError,
|
|
386
386
|
});
|
|
387
387
|
} else if (block.type === 'thinking') {
|
|
388
|
+
// Round-trip thinking blocks verbatim, including the signature — the
|
|
389
|
+
// API validates it and (on display:'omitted' models) decrypts it to
|
|
390
|
+
// reconstruct the original reasoning. Signature-only blocks (empty
|
|
391
|
+
// thinking field) are valid and must be passed back unchanged.
|
|
388
392
|
result.push({
|
|
389
393
|
type: 'thinking',
|
|
390
394
|
thinking: block.thinking,
|
|
395
|
+
...((block as { signature?: string }).signature
|
|
396
|
+
? { signature: (block as { signature?: string }).signature }
|
|
397
|
+
: {}),
|
|
391
398
|
});
|
|
399
|
+
} else if (block.type === 'redacted_thinking') {
|
|
400
|
+
// Pass through verbatim (carries encrypted data field)
|
|
401
|
+
result.push({ ...(block as unknown as Record<string, unknown>) });
|
|
392
402
|
} else if (block.type === 'document' || block.type === 'audio') {
|
|
393
403
|
hasUnsupportedMedia = true;
|
|
394
404
|
}
|
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
* output is shipped, so producer-side bugs cannot leak the same 400 family
|
|
17
17
|
* (compression-bug 5/6/7/8/9, agent-framework #37, 2026-05-22 miner stall).
|
|
18
18
|
*
|
|
19
|
-
* Algorithm overview (
|
|
19
|
+
* Algorithm overview (eight phases): reclassify blocks by required role,
|
|
20
20
|
* reflow into role-correct envelopes, hoist matching tool_results across
|
|
21
21
|
* the assistant→user boundary, evict interlopers wedged between use and
|
|
22
22
|
* result, synthesize `[pending]` results for trailing orphans (or signal
|
|
23
|
-
* not-ready when the id is in the caller-supplied pending set),
|
|
23
|
+
* not-ready when the id is in the caller-supplied pending set), drop
|
|
24
|
+
* empty envelopes, prepend a synthetic `[continuing]` user envelope when
|
|
25
|
+
* the first envelope ended up assistant-role, validate.
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import type { ProviderMessage as LooseProviderMessage } from './types.js';
|
|
@@ -123,36 +125,108 @@ export function normalizeToolPairs(
|
|
|
123
125
|
// Phase 5.5: suppress cache_control on/after any envelope containing
|
|
124
126
|
// a synthetic block, so cache keys don't get invalidated when the
|
|
125
127
|
// real result arrives in a later round.
|
|
128
|
+
//
|
|
129
|
+
// The suppression itself happens here (we know which envelope is the
|
|
130
|
+
// first synthetic by its position in the current array), but the
|
|
131
|
+
// `cache_suppressed_for_synthetic` telemetry is deferred until after
|
|
132
|
+
// phase 6 (which can drop empty envelopes before the synthetic) and
|
|
133
|
+
// phase 7 (which can prepend a `[continuing]` envelope, shifting
|
|
134
|
+
// everything by +1). The event's `envelopeIndex` must refer to the
|
|
135
|
+
// final output array so consumers can index back into it reliably.
|
|
136
|
+
// We pin the envelope by reference and recompute the index after
|
|
137
|
+
// those phases settle.
|
|
126
138
|
// ---------------------------------------------------------------------
|
|
139
|
+
let pendingCacheSuppressionRef: Envelope | null = null;
|
|
127
140
|
if (orphanRes.firstSyntheticEnvelope !== null) {
|
|
128
|
-
|
|
141
|
+
const ref = envelopes[orphanRes.firstSyntheticEnvelope]!;
|
|
142
|
+
const suppressed = suppressCacheControlFrom(envelopes, orphanRes.firstSyntheticEnvelope);
|
|
143
|
+
if (suppressed) {
|
|
144
|
+
pendingCacheSuppressionRef = ref;
|
|
145
|
+
}
|
|
129
146
|
}
|
|
130
147
|
|
|
131
148
|
// ---------------------------------------------------------------------
|
|
132
149
|
// Phase 6: drop empty envelopes (can arise from phase 4 dropping or
|
|
133
|
-
// phase 3 hoisting)
|
|
134
|
-
//
|
|
135
|
-
//
|
|
150
|
+
// phase 3 hoisting). We deliberately do NOT merge consecutive
|
|
151
|
+
// same-role envelopes here — that's the formatter's job.
|
|
152
|
+
//
|
|
153
|
+
// The synthetic-bearing envelope (held by `pendingCacheSuppressionRef`)
|
|
154
|
+
// cannot be dropped here — phase 5 unshifts its synthetic block onto
|
|
155
|
+
// that envelope's content, so it's guaranteed non-empty.
|
|
136
156
|
// ---------------------------------------------------------------------
|
|
137
157
|
envelopes = envelopes.filter((e) => e.content.length > 0);
|
|
138
158
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
// ---------------------------------------------------------------------
|
|
160
|
+
// Phase 7: ensure first envelope is user-role.
|
|
161
|
+
//
|
|
162
|
+
// Anthropic requires `messages[0].role === 'user'`. The leading
|
|
163
|
+
// envelope can become assistant for two distinct reasons:
|
|
164
|
+
//
|
|
165
|
+
// (a) Re-roling artifact — a strict-role block (thinking, tool_use)
|
|
166
|
+
// lived under a user-role input message and phase 1+2 moved it
|
|
167
|
+
// to a new leading assistant envelope. `originalFirstRole`
|
|
168
|
+
// is `'user'`.
|
|
169
|
+
//
|
|
170
|
+
// (b) Producer bug — a context strategy genuinely selected an
|
|
171
|
+
// assistant message as the first message of its compiled view
|
|
172
|
+
// (the 2026-05-26 reviewer postmortem: PassthroughStrategy
|
|
173
|
+
// `selectFromEnd` cut on an assistant turn). `originalFirstRole`
|
|
174
|
+
// is `'assistant'`.
|
|
175
|
+
//
|
|
176
|
+
// Both cases get the same repair (prepend a `[continuing]` user
|
|
177
|
+
// envelope) because deletion would lose content in case (a) — the
|
|
178
|
+
// re-roled blocks are real conversation content the producer
|
|
179
|
+
// expected to ship. The synthetic costs a leading cache miss
|
|
180
|
+
// (deterministic literal, so idempotent across identical inputs)
|
|
181
|
+
// but preserves API correctness and producer simplicity. We emit
|
|
182
|
+
// a warn-level event so telemetry can distinguish the causes and
|
|
183
|
+
// alert on (b) without coupling control flow to attribution.
|
|
184
|
+
//
|
|
185
|
+
// Idempotency: the synthetic content is a fixed literal. Running
|
|
186
|
+
// normalize twice on the same input produces identical output the
|
|
187
|
+
// second time (envelope[0] is user, gate doesn't fire).
|
|
188
|
+
// ---------------------------------------------------------------------
|
|
189
|
+
if (envelopes.length > 0 && envelopes[0]!.role === 'assistant') {
|
|
190
|
+
// `input` is guaranteed non-empty here: rebuildEnvelopes only
|
|
191
|
+
// produces envelopes when iterating input messages, so a non-empty
|
|
192
|
+
// envelopes implies a non-empty input.
|
|
193
|
+
const originalFirstRole = input[0]!.role;
|
|
194
|
+
const leadingBlockTypes = envelopes[0]!.content.map((b) => b.type);
|
|
150
195
|
envelopes.unshift({ role: 'user', content: [{ type: 'text', text: '[continuing]' }] });
|
|
196
|
+
onEvent({ kind: 'leading_user_synthesized', originalFirstRole, leadingBlockTypes });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------
|
|
200
|
+
// Deferred phase 5.5 telemetry: emit `cache_suppressed_for_synthetic`
|
|
201
|
+
// now that index-mutating phases (6, 7) have settled. The envelope
|
|
202
|
+
// reference pinned in phase 5.5 survives both — phase 6 can't drop it
|
|
203
|
+
// (the synthetic block keeps it non-empty), and phase 7 either leaves
|
|
204
|
+
// it in place or shifts it by +1 via unshift.
|
|
205
|
+
// ---------------------------------------------------------------------
|
|
206
|
+
if (pendingCacheSuppressionRef !== null) {
|
|
207
|
+
const envelopeIndex = envelopes.indexOf(pendingCacheSuppressionRef);
|
|
208
|
+
// Assertion: indexOf must succeed. Phases 6 and 7 only filter/prepend;
|
|
209
|
+
// neither can remove an envelope holding a synthetic block.
|
|
210
|
+
if (envelopeIndex < 0) {
|
|
211
|
+
throw new MembraneNormalizerError(
|
|
212
|
+
`Phase 5.5 envelope reference vanished between phase 6 and phase 7 — ` +
|
|
213
|
+
`internal bug: synthetic-bearing envelope should be reachable after both phases.`,
|
|
214
|
+
input.map(cloneMsg),
|
|
215
|
+
envelopes.map(toProviderMessage),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
onEvent({ kind: 'cache_suppressed_for_synthetic', envelopeIndex });
|
|
151
219
|
}
|
|
152
220
|
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
221
|
+
// ---------------------------------------------------------------------
|
|
222
|
+
// Phase 8: validate. When `ready === false` we intentionally have
|
|
223
|
+
// unmatched tool_uses — but ONLY the ones in `pending` are allowed to
|
|
224
|
+
// remain unsynthesized. Any other gap is a bug in phase 5 and must
|
|
225
|
+
// throw. The first-message-must-be-user branch should be unreachable
|
|
226
|
+
// after phase 7; it remains as defense-in-depth against a future
|
|
227
|
+
// phase introducing a leading assistant envelope without firing
|
|
228
|
+
// phase 7.
|
|
229
|
+
// ---------------------------------------------------------------------
|
|
156
230
|
validate(envelopes, input, pending);
|
|
157
231
|
|
|
158
232
|
return { messages: envelopes.map(toProviderMessage), ready };
|
|
@@ -459,8 +533,7 @@ function resolveOrphans(
|
|
|
459
533
|
function suppressCacheControlFrom(
|
|
460
534
|
envelopes: Envelope[],
|
|
461
535
|
startIndex: number,
|
|
462
|
-
|
|
463
|
-
): void {
|
|
536
|
+
): boolean {
|
|
464
537
|
// Strip cache_control from blocks at-or-after startIndex. We must NOT
|
|
465
538
|
// mutate the caller's input blocks (envelopes share references with
|
|
466
539
|
// the input via rebuildEnvelopes), so clone-on-write: replace any
|
|
@@ -468,6 +541,10 @@ function suppressCacheControlFrom(
|
|
|
468
541
|
// The envelope's content array is replaced wholesale via .map; this
|
|
469
542
|
// is the only place in the normalizer that creates new block objects
|
|
470
543
|
// out of existing ones (synthetics aside).
|
|
544
|
+
//
|
|
545
|
+
// Returns whether any block was actually suppressed, so the caller
|
|
546
|
+
// can decide whether to emit telemetry. Emission is deferred until
|
|
547
|
+
// after phases 6 and 7 settle the final envelope ordering.
|
|
471
548
|
let suppressed = false;
|
|
472
549
|
for (let i = startIndex; i < envelopes.length; i++) {
|
|
473
550
|
const env = envelopes[i]!;
|
|
@@ -480,9 +557,7 @@ function suppressCacheControlFrom(
|
|
|
480
557
|
return rest as ProviderBlock;
|
|
481
558
|
});
|
|
482
559
|
}
|
|
483
|
-
|
|
484
|
-
onEvent({ kind: 'cache_suppressed_for_synthetic', envelopeIndex: startIndex });
|
|
485
|
-
}
|
|
560
|
+
return suppressed;
|
|
486
561
|
}
|
|
487
562
|
|
|
488
563
|
function validate(
|
package/src/formatters/types.ts
CHANGED
|
@@ -111,7 +111,34 @@ export type NormalizeEvent =
|
|
|
111
111
|
| { kind: 'synthetic_pending_result'; toolUseId: string; reason: 'trailing' | 'mid_stream' }
|
|
112
112
|
| { kind: 'orphan_tool_result_textified'; toolUseId: string }
|
|
113
113
|
| { kind: 'pending_in_flight'; toolUseId: string }
|
|
114
|
-
| { kind: 'cache_suppressed_for_synthetic'; envelopeIndex: number }
|
|
114
|
+
| { kind: 'cache_suppressed_for_synthetic'; envelopeIndex: number }
|
|
115
|
+
| {
|
|
116
|
+
/**
|
|
117
|
+
* Fires when the first envelope after re-roling is assistant and a
|
|
118
|
+
* synthetic `[continuing]` user envelope had to be prepended to
|
|
119
|
+
* satisfy Anthropic's `messages[0].role === 'user'` requirement.
|
|
120
|
+
*
|
|
121
|
+
* `originalFirstRole` distinguishes the two causes a consumer might
|
|
122
|
+
* want to alert on separately:
|
|
123
|
+
* - `'user'` → re-roling artifact (a strict-role block lived
|
|
124
|
+
* under a user-role message and was moved to a
|
|
125
|
+
* new assistant envelope). Usually benign.
|
|
126
|
+
* - `'assistant'`→ producer shipped an assistant-first messages
|
|
127
|
+
* list. Real producer bug worth investigating.
|
|
128
|
+
*
|
|
129
|
+
* (Empty input never reaches this event: `rebuildEnvelopes([])`
|
|
130
|
+
* returns `[]`, and the phase-7 gate requires a non-empty envelope
|
|
131
|
+
* list. So `input[0]` is always defined when this fires.)
|
|
132
|
+
*
|
|
133
|
+
* `leadingBlockTypes` is the block-type list of the now-second
|
|
134
|
+
* envelope (i.e. what came right after the synthesized user turn),
|
|
135
|
+
* useful for classifying re-roling causes (e.g. `['thinking']`
|
|
136
|
+
* vs. `['text', 'tool_use']`).
|
|
137
|
+
*/
|
|
138
|
+
kind: 'leading_user_synthesized';
|
|
139
|
+
originalFirstRole: 'user' | 'assistant';
|
|
140
|
+
leadingBlockTypes: string[];
|
|
141
|
+
};
|
|
115
142
|
|
|
116
143
|
// ============================================================================
|
|
117
144
|
// Build Result
|
package/src/membrane.ts
CHANGED
|
@@ -292,6 +292,12 @@ export class Membrane {
|
|
|
292
292
|
// These can't be handled by the text-based XML parser, so we capture and append them
|
|
293
293
|
const extraContentBlocks: ContentBlock[] = [];
|
|
294
294
|
|
|
295
|
+
// Native thinking blocks from the provider (with signatures). The parser
|
|
296
|
+
// derives signature-less thinking blocks from <thinking> text (via
|
|
297
|
+
// wrapThinkingTags); signatures from these are merged into those after
|
|
298
|
+
// parsing, and signature-only blocks are prepended.
|
|
299
|
+
const providerThinkingBlocks: ContentBlock[] = [];
|
|
300
|
+
|
|
295
301
|
// Transform initial request using the formatter
|
|
296
302
|
let { providerRequest, prefillResult } = this.transformRequest(request, formatter);
|
|
297
303
|
|
|
@@ -385,6 +391,10 @@ export class Membrane {
|
|
|
385
391
|
{
|
|
386
392
|
signal,
|
|
387
393
|
normalizedRequest: request,
|
|
394
|
+
// The tag-based parser tracks thinking via <thinking> tags — ask the
|
|
395
|
+
// provider to wrap native thinking deltas so they don't stream as
|
|
396
|
+
// visible text (see ProviderRequestOptions.wrapThinkingTags)
|
|
397
|
+
wrapThinkingTags: true,
|
|
388
398
|
onRequest: (req) => {
|
|
389
399
|
rawRequest = req;
|
|
390
400
|
onRequest?.(req);
|
|
@@ -410,6 +420,18 @@ export class Membrane {
|
|
|
410
420
|
data: (block as any).data,
|
|
411
421
|
mimeType: (block as any).mimeType,
|
|
412
422
|
} as ContentBlock);
|
|
423
|
+
} else if (block.type === 'thinking') {
|
|
424
|
+
// Native thinking block from the provider — carries the signature
|
|
425
|
+
// (encrypted full reasoning). Captured so consumers can persist and
|
|
426
|
+
// round-trip it for reasoning continuity. Includes signature-only
|
|
427
|
+
// blocks (display:'omitted' returns an empty thinking field).
|
|
428
|
+
providerThinkingBlocks.push({
|
|
429
|
+
type: 'thinking',
|
|
430
|
+
thinking: (block as any).thinking ?? '',
|
|
431
|
+
...((block as any).signature ? { signature: (block as any).signature } : {}),
|
|
432
|
+
} as ContentBlock);
|
|
433
|
+
} else if (block.type === 'redacted_thinking') {
|
|
434
|
+
providerThinkingBlocks.push({ ...(block as any) } as ContentBlock);
|
|
413
435
|
}
|
|
414
436
|
}
|
|
415
437
|
}
|
|
@@ -700,6 +722,33 @@ export class Membrane {
|
|
|
700
722
|
response.content.push(...extraContentBlocks);
|
|
701
723
|
}
|
|
702
724
|
|
|
725
|
+
// Merge provider thinking signatures into parser-derived thinking blocks
|
|
726
|
+
// (matched in stream order), and prepend any leftover provider blocks —
|
|
727
|
+
// signature-only thinking (display:'omitted') never appears in the text
|
|
728
|
+
// stream, so the parser produces no block for it. redacted_thinking
|
|
729
|
+
// blocks are always prepended verbatim.
|
|
730
|
+
if (providerThinkingBlocks.length > 0) {
|
|
731
|
+
const parsedThinking = response.content.filter(
|
|
732
|
+
(b) => b.type === 'thinking'
|
|
733
|
+
) as Array<{ type: 'thinking'; thinking: string; signature?: string }>;
|
|
734
|
+
|
|
735
|
+
const providerThinking = providerThinkingBlocks.filter((b) => b.type === 'thinking');
|
|
736
|
+
const redacted = providerThinkingBlocks.filter((b) => b.type === 'redacted_thinking');
|
|
737
|
+
|
|
738
|
+
const matched = Math.min(providerThinking.length, parsedThinking.length);
|
|
739
|
+
for (let i = 0; i < matched; i++) {
|
|
740
|
+
const sig = (providerThinking[i] as { signature?: string }).signature;
|
|
741
|
+
if (sig) {
|
|
742
|
+
parsedThinking[i]!.signature = sig;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const leftover = providerThinking.slice(matched);
|
|
747
|
+
if (leftover.length > 0 || redacted.length > 0) {
|
|
748
|
+
response.content.unshift(...leftover, ...redacted);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
703
752
|
return response;
|
|
704
753
|
} catch (error) {
|
|
705
754
|
// Check if this is an abort error
|
|
@@ -1005,6 +1054,19 @@ export class Membrane {
|
|
|
1005
1054
|
content: block.content,
|
|
1006
1055
|
is_error: block.isError,
|
|
1007
1056
|
});
|
|
1057
|
+
} else if (block.type === 'thinking') {
|
|
1058
|
+
// Round-trip thinking blocks verbatim including the signature — the
|
|
1059
|
+
// API validates it and (on display:'omitted' models) decrypts it to
|
|
1060
|
+
// reconstruct prior reasoning. Empty thinking + signature is valid.
|
|
1061
|
+
content.push({
|
|
1062
|
+
type: 'thinking',
|
|
1063
|
+
thinking: (block as { thinking?: string }).thinking ?? '',
|
|
1064
|
+
...((block as { signature?: string }).signature
|
|
1065
|
+
? { signature: (block as { signature?: string }).signature }
|
|
1066
|
+
: {}),
|
|
1067
|
+
});
|
|
1068
|
+
} else if (block.type === 'redacted_thinking') {
|
|
1069
|
+
content.push({ ...(block as unknown as Record<string, unknown>) });
|
|
1008
1070
|
} else if (block.type === 'image') {
|
|
1009
1071
|
if (block.source.type === 'base64') {
|
|
1010
1072
|
const imageBlock: Record<string, unknown> = {
|
|
@@ -1081,13 +1143,8 @@ export class Membrane {
|
|
|
1081
1143
|
);
|
|
1082
1144
|
}
|
|
1083
1145
|
|
|
1084
|
-
// Build thinking config for native extended thinking
|
|
1085
|
-
const thinking = request.config
|
|
1086
|
-
? {
|
|
1087
|
-
type: 'enabled' as const,
|
|
1088
|
-
budget_tokens: request.config.thinking.budgetTokens ?? 5000,
|
|
1089
|
-
}
|
|
1090
|
-
: undefined;
|
|
1146
|
+
// Build thinking config for native extended thinking (budget clamped to max_tokens)
|
|
1147
|
+
const thinking = this.buildThinkingParam(request.config);
|
|
1091
1148
|
|
|
1092
1149
|
// Anthropic requires temperature=1 when extended thinking is enabled
|
|
1093
1150
|
const temperature = thinking ? 1 : request.config.temperature;
|
|
@@ -1172,8 +1229,10 @@ export class Membrane {
|
|
|
1172
1229
|
* Used by transformRequest, buildContinuationRequest, and buildContinuationRequestWithImages.
|
|
1173
1230
|
*/
|
|
1174
1231
|
private getBaseProviderParams(config: NormalizedRequest['config']) {
|
|
1232
|
+
// Build thinking config for native extended thinking
|
|
1233
|
+
const thinking = this.buildThinkingParam(config);
|
|
1175
1234
|
// Anthropic requires temperature=1 when extended thinking is enabled
|
|
1176
|
-
const temperature =
|
|
1235
|
+
const temperature = thinking ? 1 : config.temperature;
|
|
1177
1236
|
return {
|
|
1178
1237
|
model: config.model,
|
|
1179
1238
|
maxTokens: config.maxTokens,
|
|
@@ -1182,9 +1241,41 @@ export class Membrane {
|
|
|
1182
1241
|
topK: config.topK,
|
|
1183
1242
|
presencePenalty: config.presencePenalty,
|
|
1184
1243
|
frequencyPenalty: config.frequencyPenalty,
|
|
1244
|
+
repetitionPenalty: config.repetitionPenalty,
|
|
1245
|
+
thinking,
|
|
1185
1246
|
};
|
|
1186
1247
|
}
|
|
1187
1248
|
|
|
1249
|
+
/**
|
|
1250
|
+
* Build the provider thinking parameter from config.
|
|
1251
|
+
*
|
|
1252
|
+
* For type 'enabled', the API requires max_tokens > budget_tokens and a
|
|
1253
|
+
* minimum budget of 1024 — a misconfigured budget (e.g., default 10000 with
|
|
1254
|
+
* max_tokens 4096) is clamped to fit. If no valid budget fits (max_tokens
|
|
1255
|
+
* too small), thinking is omitted entirely rather than sending a request
|
|
1256
|
+
* the API will reject.
|
|
1257
|
+
*/
|
|
1258
|
+
private buildThinkingParam(config: NormalizedRequest['config']):
|
|
1259
|
+
| { type: 'adaptive'; display?: 'summarized' | 'omitted' }
|
|
1260
|
+
| { type: 'enabled'; budget_tokens: number; display?: 'summarized' | 'omitted' }
|
|
1261
|
+
| undefined {
|
|
1262
|
+
if (!config.thinking?.enabled) return undefined;
|
|
1263
|
+
|
|
1264
|
+
const display = config.thinking.display;
|
|
1265
|
+
if ((config.thinking.type ?? 'enabled') === 'adaptive') {
|
|
1266
|
+
return { type: 'adaptive', ...(display ? { display } : {}) };
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const requested = config.thinking.budgetTokens ?? 5000;
|
|
1270
|
+
const maxTokens = typeof config.maxTokens === 'number' ? config.maxTokens : undefined;
|
|
1271
|
+
const budget = maxTokens !== undefined ? Math.min(requested, maxTokens - 1024) : requested;
|
|
1272
|
+
if (budget < 1024) {
|
|
1273
|
+
// Can't fit a valid thinking budget under max_tokens — skip thinking
|
|
1274
|
+
return undefined;
|
|
1275
|
+
}
|
|
1276
|
+
return { type: 'enabled', budget_tokens: budget, ...(display ? { display } : {}) };
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1188
1279
|
/**
|
|
1189
1280
|
* Transform a normalized request into provider format using the formatter
|
|
1190
1281
|
*/
|
|
@@ -1232,6 +1323,15 @@ export class Membrane {
|
|
|
1232
1323
|
},
|
|
1233
1324
|
};
|
|
1234
1325
|
|
|
1326
|
+
// The API rejects extended thinking combined with an assistant prefill.
|
|
1327
|
+
// Prefill-style builds (XML formatter) use the thinking config for the
|
|
1328
|
+
// literal `<thinking>` text prefix instead of the API feature — drop the
|
|
1329
|
+
// API param when the built request actually ends in an assistant prefill.
|
|
1330
|
+
// Chat-style builds (no prefill) keep it.
|
|
1331
|
+
if (buildResult.assistantPrefill && providerRequest.thinking) {
|
|
1332
|
+
delete providerRequest.thinking;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1235
1335
|
return { providerRequest, prefillResult: buildResult };
|
|
1236
1336
|
}
|
|
1237
1337
|
|
|
@@ -1243,6 +1343,8 @@ export class Membrane {
|
|
|
1243
1343
|
timeoutMs?: number;
|
|
1244
1344
|
idleTimeoutMs?: number;
|
|
1245
1345
|
onRequest?: (rawRequest: unknown) => void;
|
|
1346
|
+
/** See ProviderRequestOptions.wrapThinkingTags */
|
|
1347
|
+
wrapThinkingTags?: boolean;
|
|
1246
1348
|
/**
|
|
1247
1349
|
* The original NormalizedRequest, threaded through so the
|
|
1248
1350
|
* `beforeRequest` hook can see both shapes (normalized + provider).
|
|
@@ -1292,6 +1394,9 @@ export class Membrane {
|
|
|
1292
1394
|
|
|
1293
1395
|
return {
|
|
1294
1396
|
...this.getBaseProviderParams(originalRequest.config),
|
|
1397
|
+
// Continuations always end in an assistant prefill — the API rejects
|
|
1398
|
+
// extended thinking combined with prefill, so never send the param here
|
|
1399
|
+
thinking: undefined,
|
|
1295
1400
|
messages,
|
|
1296
1401
|
system: prefillResult.systemContent
|
|
1297
1402
|
? (Array.isArray(prefillResult.systemContent) && prefillResult.systemContent.length > 0
|
|
@@ -1362,6 +1467,9 @@ export class Membrane {
|
|
|
1362
1467
|
|
|
1363
1468
|
return {
|
|
1364
1469
|
...this.getBaseProviderParams(originalRequest.config),
|
|
1470
|
+
// Continuations always end in an assistant prefill — the API rejects
|
|
1471
|
+
// extended thinking combined with prefill, so never send the param here
|
|
1472
|
+
thinking: undefined,
|
|
1365
1473
|
messages,
|
|
1366
1474
|
system: prefillResult.systemContent
|
|
1367
1475
|
? (Array.isArray(prefillResult.systemContent) && prefillResult.systemContent.length > 0
|
|
@@ -1595,6 +1703,11 @@ export class Membrane {
|
|
|
1595
1703
|
return 'stop_sequence';
|
|
1596
1704
|
case 'tool_use':
|
|
1597
1705
|
return 'tool_use';
|
|
1706
|
+
case 'refusal':
|
|
1707
|
+
// Safety refusal (e.g., Fable 5 reasoning_extraction). Must survive
|
|
1708
|
+
// mapping — downstream consumers react to refusals (chapterx adds a
|
|
1709
|
+
// Discord reaction). Defaulting this to end_turn silently hid them.
|
|
1710
|
+
return 'refusal';
|
|
1598
1711
|
default:
|
|
1599
1712
|
return 'end_turn';
|
|
1600
1713
|
}
|
|
@@ -2483,13 +2596,16 @@ export class Membrane {
|
|
|
2483
2596
|
}
|
|
2484
2597
|
|
|
2485
2598
|
// Native tool names must match ^[a-zA-Z0-9_-]{1,128}$.
|
|
2486
|
-
//
|
|
2487
|
-
//
|
|
2488
|
-
//
|
|
2599
|
+
// Tool names use `--` namespacing, which is already API-valid; the only
|
|
2600
|
+
// character that ever needs escaping is a literal colon, encoded losslessly as
|
|
2601
|
+
// `__` and back. We deliberately do NOT escape underscores — they are valid,
|
|
2602
|
+
// and escaping them (the previous `_u`/`_c` scheme) garbled every
|
|
2603
|
+
// underscore-containing tool name in the request the model actually sees
|
|
2604
|
+
// (`send_message` → `send_umessage`), polluting its reasoning for no benefit.
|
|
2489
2605
|
function sanitizeToolName(name: string): string {
|
|
2490
|
-
return name.replace(
|
|
2606
|
+
return name.replace(/:/g, '__');
|
|
2491
2607
|
}
|
|
2492
2608
|
|
|
2493
2609
|
function unsanitizeToolName(name: string): string {
|
|
2494
|
-
return name.replace(/
|
|
2610
|
+
return name.replace(/__/g, ':');
|
|
2495
2611
|
}
|