@animalabs/membrane 0.5.40 → 0.5.42
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/pseudo-prefill.d.ts +71 -0
- package/dist/formatters/pseudo-prefill.d.ts.map +1 -0
- package/dist/formatters/pseudo-prefill.js +410 -0
- package/dist/formatters/pseudo-prefill.js.map +1 -0
- package/dist/membrane.d.ts +5 -0
- package/dist/membrane.d.ts.map +1 -1
- package/dist/membrane.js +91 -44
- package/dist/membrane.js.map +1 -1
- package/dist/providers/bedrock.d.ts.map +1 -1
- package/dist/providers/bedrock.js +27 -8
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +11 -2
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +27 -16
- 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 +48 -24
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +6 -1
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +27 -16
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +37 -17
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/utils.d.ts +44 -0
- package/dist/providers/utils.d.ts.map +1 -0
- package/dist/providers/utils.js +100 -0
- package/dist/providers/utils.js.map +1 -0
- package/dist/transforms/prefill.d.ts.map +1 -1
- package/dist/transforms/prefill.js +44 -45
- package/dist/transforms/prefill.js.map +1 -1
- package/dist/types/request.d.ts +8 -0
- package/dist/types/request.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/membrane.ts +96 -46
- package/src/providers/bedrock.ts +25 -9
- package/src/providers/gemini.ts +9 -2
- package/src/providers/openai-compatible.ts +27 -18
- package/src/providers/openai-completions.ts +47 -25
- package/src/providers/openai-responses.ts +5 -1
- package/src/providers/openai.ts +27 -18
- package/src/providers/openrouter.ts +36 -19
- package/src/providers/utils.ts +109 -0
- package/src/types/request.ts +9 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safely parse a JSON string, returning an empty object on failure.
|
|
3
|
+
* Used for tool call arguments which may be malformed from streaming.
|
|
4
|
+
*/
|
|
5
|
+
export function safeParseJson(str: string | undefined): Record<string, unknown> {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(str || '{}');
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.warn('[membrane] Failed to parse tool arguments JSON:', e);
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a combined AbortSignal that fires on either the caller's signal
|
|
16
|
+
* or a timeout (whichever comes first).
|
|
17
|
+
*
|
|
18
|
+
* The returned `cleanup` function MUST be called in a `finally` block to
|
|
19
|
+
* clear the timeout and remove the event listener, preventing leaks.
|
|
20
|
+
*
|
|
21
|
+
* Timeout aborts with `DOMException('Request timed out', 'AbortError')`
|
|
22
|
+
* so it classifies identically to user-initiated aborts.
|
|
23
|
+
*/
|
|
24
|
+
export function createCombinedSignal(
|
|
25
|
+
signal?: AbortSignal,
|
|
26
|
+
timeoutMs?: number
|
|
27
|
+
): { signal?: AbortSignal; cleanup?: () => void } {
|
|
28
|
+
if (!signal && !timeoutMs) return {};
|
|
29
|
+
if (signal && !timeoutMs) return { signal };
|
|
30
|
+
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
33
|
+
|
|
34
|
+
if (timeoutMs) {
|
|
35
|
+
timeoutId = setTimeout(
|
|
36
|
+
() => controller.abort(new DOMException('Request timed out', 'AbortError')),
|
|
37
|
+
timeoutMs
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const onAbort = () => controller.abort(signal!.reason);
|
|
42
|
+
if (signal) {
|
|
43
|
+
if (signal.aborted) {
|
|
44
|
+
controller.abort(signal.reason);
|
|
45
|
+
} else {
|
|
46
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
signal: controller.signal,
|
|
52
|
+
cleanup: () => {
|
|
53
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
54
|
+
if (signal) signal.removeEventListener('abort', onAbort);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* SSE (Server-Sent Events) line parser that correctly handles events
|
|
61
|
+
* split across multiple TCP chunks.
|
|
62
|
+
*
|
|
63
|
+
* The naive approach of `chunk.split('\n').filter(l => l.startsWith('data: '))`
|
|
64
|
+
* silently drops events when an SSE line spans two chunks:
|
|
65
|
+
* Chunk 1: `data: {"choices":[{"delta":{"content":"don'` (no newline — incomplete)
|
|
66
|
+
* Chunk 2: `t do that"}}]}\n` (doesn't start with `data: `)
|
|
67
|
+
* Result: the entire event is lost, causing "skipped words" in output.
|
|
68
|
+
*
|
|
69
|
+
* This parser buffers partial lines and only yields complete `data: ...` lines.
|
|
70
|
+
*/
|
|
71
|
+
export class SSELineParser {
|
|
72
|
+
private buffer: string = '';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Feed a raw chunk from the stream reader and get back complete SSE data lines.
|
|
76
|
+
* Each returned string is the content after `data: ` (e.g. the JSON payload or `[DONE]`).
|
|
77
|
+
*/
|
|
78
|
+
feed(chunk: string): string[] {
|
|
79
|
+
this.buffer += chunk;
|
|
80
|
+
const results: string[] = [];
|
|
81
|
+
|
|
82
|
+
// Split on newlines, keeping the last (potentially incomplete) segment in the buffer
|
|
83
|
+
const lines = this.buffer.split('\n');
|
|
84
|
+
this.buffer = lines.pop() || '';
|
|
85
|
+
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
if (trimmed.startsWith('data: ')) {
|
|
89
|
+
results.push(trimmed.slice(6));
|
|
90
|
+
}
|
|
91
|
+
// Skip empty lines, comments (`:...`), and other SSE fields (event:, id:, retry:)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Flush any remaining buffered content (call when stream ends).
|
|
99
|
+
*/
|
|
100
|
+
flush(): string[] {
|
|
101
|
+
if (!this.buffer.trim()) return [];
|
|
102
|
+
const trimmed = this.buffer.trim();
|
|
103
|
+
this.buffer = '';
|
|
104
|
+
if (trimmed.startsWith('data: ')) {
|
|
105
|
+
return [trimmed.slice(6)];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
package/src/types/request.ts
CHANGED
|
@@ -161,6 +161,15 @@ export interface NormalizedRequest {
|
|
|
161
161
|
*/
|
|
162
162
|
assistantParticipant?: string;
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Control streaming behavior when calling membrane.stream().
|
|
166
|
+
* - true or undefined: use streaming (default)
|
|
167
|
+
* - false: force non-streaming — membrane.stream() will internally use
|
|
168
|
+
* complete() and synthesize streaming callbacks from the full response.
|
|
169
|
+
* Useful for working around provider streaming bugs.
|
|
170
|
+
*/
|
|
171
|
+
streaming?: boolean;
|
|
172
|
+
|
|
164
173
|
/** Provider-specific parameters (pass-through) */
|
|
165
174
|
providerParams?: Record<string, unknown>;
|
|
166
175
|
}
|