@tashiscool/stream 0.1.0

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/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # @llm-utils/stream
2
+
3
+ Streaming utilities for real-time LLM responses. Handle HTTP streaming, chunk aggregation, and JSONL parsing with ease.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @llm-utils/stream
9
+ # or
10
+ npm install @llm-utils/stream
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **HTTP Response Interception** - Transform streaming responses without breaking semantics
16
+ - **Chunk Aggregation** - Aggregate chunks from multiple sources with lifecycle handlers
17
+ - **JSONL Streaming** - Parse and serialize JSONL streams (server and client)
18
+ - **Type-Safe** - Full TypeScript support with generics
19
+
20
+ ## Usage
21
+
22
+ ### Intercept and Transform Streaming Responses
23
+
24
+ ```typescript
25
+ import { interceptResponseWrites } from '@llm-utils/stream';
26
+
27
+ app.post('/chat', async (req, res) => {
28
+ const llmStream = await callLLM(req.body);
29
+
30
+ interceptResponseWrites(res, {
31
+ transform: async (chunk) => {
32
+ // Transform each chunk before sending to client
33
+ const data = JSON.parse(chunk);
34
+ const enriched = { ...data, timestamp: Date.now() };
35
+ return JSON.stringify(enriched) + '\n';
36
+ },
37
+ onFinish: async () => {
38
+ // Cleanup after stream ends
39
+ await saveConversation();
40
+ }
41
+ });
42
+
43
+ llmStream.pipe(res);
44
+ });
45
+ ```
46
+
47
+ ### Aggregate Chunks from Multiple Sources
48
+
49
+ ```typescript
50
+ import { createChunkAggregator } from '@llm-utils/stream';
51
+
52
+ const aggregator = createChunkAggregator({
53
+ onBegin: async (message) => {
54
+ console.log(`Starting message from node: ${message.nodeId}`);
55
+ },
56
+ onItem: async (message, delta) => {
57
+ // Stream delta to client
58
+ sendToClient(delta);
59
+ },
60
+ onEnd: async (message) => {
61
+ // Save complete message
62
+ await saveMessage(message);
63
+ },
64
+ onError: async (message, error) => {
65
+ console.error(`Error in ${message.nodeId}:`, error);
66
+ }
67
+ });
68
+
69
+ // Ingest structured chunks
70
+ for await (const chunk of structuredStream) {
71
+ await aggregator.ingest(chunk);
72
+ }
73
+
74
+ // Finalize any incomplete messages
75
+ await aggregator.finalizeAll();
76
+ ```
77
+
78
+ ### JSONL Streaming
79
+
80
+ ```typescript
81
+ import { streamJsonLines, parseJsonLines } from '@llm-utils/stream';
82
+
83
+ // Server: Stream objects as JSONL
84
+ app.get('/events', (req, res) => {
85
+ streamJsonLines(res, async function* () {
86
+ for (const event of events) {
87
+ yield event;
88
+ }
89
+ });
90
+ });
91
+
92
+ // Client: Parse JSONL stream
93
+ const response = await fetch('/events');
94
+ for await (const event of parseJsonLines(response)) {
95
+ handleEvent(event);
96
+ }
97
+ ```
98
+
99
+ ## API Reference
100
+
101
+ ### `interceptResponseWrites(res, options)`
102
+
103
+ Intercepts Express response writes for transformation.
104
+
105
+ ```typescript
106
+ interface InterceptOptions {
107
+ transform: (text: string) => Promise<string | undefined>;
108
+ onFinish?: () => Promise<void>;
109
+ onError?: (error: Error) => void;
110
+ }
111
+ ```
112
+
113
+ ### `createChunkAggregator(handlers)`
114
+
115
+ Creates a stateful chunk aggregator for multi-source streaming.
116
+
117
+ ```typescript
118
+ interface StructuredChunk {
119
+ nodeId: string;
120
+ runIndex: number;
121
+ itemIndex: number;
122
+ type: 'begin' | 'item' | 'end' | 'error';
123
+ data?: string;
124
+ error?: string;
125
+ }
126
+
127
+ interface ChunkHandlers {
128
+ onBegin?: (message: AggregatedMessage) => Promise<void>;
129
+ onItem?: (message: AggregatedMessage, delta: string) => Promise<void>;
130
+ onEnd?: (message: AggregatedMessage) => Promise<void>;
131
+ onError?: (message: AggregatedMessage, error?: string) => Promise<void>;
132
+ }
133
+ ```
134
+
135
+ ### `streamJsonLines(res, generator)`
136
+
137
+ Streams objects as JSONL to an HTTP response.
138
+
139
+ ### `parseJsonLines(response)`
140
+
141
+ Parses a JSONL stream from a fetch Response.
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Chunk Aggregation
3
+ * Aggregate streaming chunks from multiple sources with lifecycle handlers
4
+ */
5
+ /**
6
+ * A structured chunk from a streaming source
7
+ */
8
+ export interface StructuredChunk {
9
+ /** Identifier for the source node */
10
+ nodeId: string;
11
+ /** Run index for parallel executions */
12
+ runIndex: number;
13
+ /** Item index within the run */
14
+ itemIndex: number;
15
+ /** Chunk lifecycle type */
16
+ type: 'begin' | 'item' | 'end' | 'error';
17
+ /** Content data (for 'item' type) */
18
+ data?: string;
19
+ /** Error message (for 'error' type) */
20
+ error?: string;
21
+ }
22
+ /**
23
+ * An aggregated message from multiple chunks
24
+ */
25
+ export interface AggregatedMessage {
26
+ /** Source node identifier */
27
+ nodeId: string;
28
+ /** Run index */
29
+ runIndex: number;
30
+ /** Item index */
31
+ itemIndex: number;
32
+ /** Accumulated content */
33
+ content: string;
34
+ /** Current status */
35
+ status: 'streaming' | 'complete' | 'error';
36
+ /** Error message if status is 'error' */
37
+ error?: string;
38
+ /** Timestamp when message started */
39
+ startedAt: Date;
40
+ /** Timestamp when message completed */
41
+ completedAt?: Date;
42
+ }
43
+ /**
44
+ * Handlers for chunk lifecycle events
45
+ */
46
+ export interface ChunkHandlers {
47
+ /** Called when a new message stream begins */
48
+ onBegin?: (message: AggregatedMessage) => Promise<void>;
49
+ /** Called for each content chunk */
50
+ onItem?: (message: AggregatedMessage, delta: string) => Promise<void>;
51
+ /** Called when a message stream ends successfully */
52
+ onEnd?: (message: AggregatedMessage) => Promise<void>;
53
+ /** Called when a message stream errors */
54
+ onError?: (message: AggregatedMessage, error?: string) => Promise<void>;
55
+ }
56
+ /**
57
+ * Chunk aggregator interface
58
+ */
59
+ export interface ChunkAggregator {
60
+ /** Ingest a structured chunk */
61
+ ingest: (chunk: StructuredChunk) => Promise<AggregatedMessage>;
62
+ /** Finalize all incomplete messages */
63
+ finalizeAll: () => Promise<void>;
64
+ /** Get all current messages */
65
+ getMessages: () => Map<string, AggregatedMessage>;
66
+ /** Get a specific message by key */
67
+ getMessage: (nodeId: string, runIndex: number, itemIndex: number) => AggregatedMessage | undefined;
68
+ /** Clear all messages */
69
+ clear: () => void;
70
+ }
71
+ /**
72
+ * Creates a stateful chunk aggregator for multi-source streaming.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const aggregator = createChunkAggregator({
77
+ * onBegin: async (message) => {
78
+ * console.log(`Starting: ${message.nodeId}`);
79
+ * },
80
+ * onItem: async (message, delta) => {
81
+ * sendToClient(delta);
82
+ * },
83
+ * onEnd: async (message) => {
84
+ * await saveMessage(message);
85
+ * }
86
+ * });
87
+ *
88
+ * for await (const chunk of stream) {
89
+ * await aggregator.ingest(chunk);
90
+ * }
91
+ * await aggregator.finalizeAll();
92
+ * ```
93
+ */
94
+ export declare function createChunkAggregator(handlers?: ChunkHandlers): ChunkAggregator;
95
+ //# sourceMappingURL=aggregator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregator.d.ts","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;IACzC,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;IAC3C,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,SAAS,EAAE,IAAI,CAAC;IAChB,uCAAuC;IACvC,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,oCAAoC;IACpC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,qDAAqD;IACrD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,0CAA0C;IAC1C,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC/D,uCAAuC;IACvC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,+BAA+B;IAC/B,WAAW,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAClD,oCAAoC;IACpC,UAAU,EAAE,CACV,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,KACd,iBAAiB,GAAG,SAAS,CAAC;IACnC,yBAAyB;IACzB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,GAAE,aAAkB,GAC3B,eAAe,CAqHjB"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Chunk Aggregation
3
+ * Aggregate streaming chunks from multiple sources with lifecycle handlers
4
+ */
5
+ /**
6
+ * Creates a stateful chunk aggregator for multi-source streaming.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const aggregator = createChunkAggregator({
11
+ * onBegin: async (message) => {
12
+ * console.log(`Starting: ${message.nodeId}`);
13
+ * },
14
+ * onItem: async (message, delta) => {
15
+ * sendToClient(delta);
16
+ * },
17
+ * onEnd: async (message) => {
18
+ * await saveMessage(message);
19
+ * }
20
+ * });
21
+ *
22
+ * for await (const chunk of stream) {
23
+ * await aggregator.ingest(chunk);
24
+ * }
25
+ * await aggregator.finalizeAll();
26
+ * ```
27
+ */
28
+ export function createChunkAggregator(handlers = {}) {
29
+ const messages = new Map();
30
+ function getKey(chunk) {
31
+ return `${chunk.nodeId}:${chunk.runIndex}:${chunk.itemIndex}`;
32
+ }
33
+ async function ingest(chunk) {
34
+ const key = getKey(chunk);
35
+ switch (chunk.type) {
36
+ case 'begin': {
37
+ const message = {
38
+ nodeId: chunk.nodeId,
39
+ runIndex: chunk.runIndex,
40
+ itemIndex: chunk.itemIndex,
41
+ content: '',
42
+ status: 'streaming',
43
+ startedAt: new Date(),
44
+ };
45
+ messages.set(key, message);
46
+ await handlers.onBegin?.(message);
47
+ return message;
48
+ }
49
+ case 'item': {
50
+ let message = messages.get(key);
51
+ // Auto-create message if 'begin' was missed
52
+ if (!message) {
53
+ message = {
54
+ nodeId: chunk.nodeId,
55
+ runIndex: chunk.runIndex,
56
+ itemIndex: chunk.itemIndex,
57
+ content: '',
58
+ status: 'streaming',
59
+ startedAt: new Date(),
60
+ };
61
+ messages.set(key, message);
62
+ await handlers.onBegin?.(message);
63
+ }
64
+ if (chunk.data) {
65
+ message.content += chunk.data;
66
+ await handlers.onItem?.(message, chunk.data);
67
+ }
68
+ return message;
69
+ }
70
+ case 'end': {
71
+ const message = messages.get(key);
72
+ if (message) {
73
+ message.status = 'complete';
74
+ message.completedAt = new Date();
75
+ await handlers.onEnd?.(message);
76
+ }
77
+ return message;
78
+ }
79
+ case 'error': {
80
+ let message = messages.get(key);
81
+ // Auto-create message if 'begin' was missed
82
+ if (!message) {
83
+ message = {
84
+ nodeId: chunk.nodeId,
85
+ runIndex: chunk.runIndex,
86
+ itemIndex: chunk.itemIndex,
87
+ content: '',
88
+ status: 'error',
89
+ startedAt: new Date(),
90
+ };
91
+ messages.set(key, message);
92
+ }
93
+ message.status = 'error';
94
+ message.error = chunk.error;
95
+ message.completedAt = new Date();
96
+ await handlers.onError?.(message, chunk.error);
97
+ return message;
98
+ }
99
+ }
100
+ }
101
+ async function finalizeAll() {
102
+ for (const message of messages.values()) {
103
+ if (message.status === 'streaming') {
104
+ message.status = 'complete';
105
+ message.completedAt = new Date();
106
+ await handlers.onEnd?.(message);
107
+ }
108
+ }
109
+ }
110
+ function getMessages() {
111
+ return new Map(messages);
112
+ }
113
+ function getMessage(nodeId, runIndex, itemIndex) {
114
+ return messages.get(`${nodeId}:${runIndex}:${itemIndex}`);
115
+ }
116
+ function clear() {
117
+ messages.clear();
118
+ }
119
+ return {
120
+ ingest,
121
+ finalizeAll,
122
+ getMessages,
123
+ getMessage,
124
+ clear,
125
+ };
126
+ }
127
+ //# sourceMappingURL=aggregator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @llm-utils/stream
3
+ * Streaming utilities for real-time LLM responses
4
+ */
5
+ export { interceptResponseWrites } from './interceptor.js';
6
+ export type { InterceptOptions, ChunkTransformer } from './interceptor.js';
7
+ export { createChunkAggregator } from './aggregator.js';
8
+ export type { StructuredChunk, AggregatedMessage, ChunkHandlers, ChunkAggregator, } from './aggregator.js';
9
+ export { streamJsonLines, parseJsonLines, toJsonLine, fromJsonLine } from './jsonl.js';
10
+ export type { JsonLineGenerator } from './jsonl.js';
11
+ export { mux, muxSimple, parseCursor, serializeCursor, createCursor, createReplayableStream, mapEvents, filterEvents, accumulateDeltas, } from './mux.js';
12
+ export type { StreamEvent, StreamCursor, MuxOptions, NamedStream, } from './mux.js';
13
+ export { sse, jsonl, raw, streamToResponse, textStreamResponse, parseSSEResponse, parseJSONLResponse, streamToNodeResponse, createAIEvent, toEventStream, } from './response.js';
14
+ export type { StreamProtocol, StreamResponseOptions, ProtocolEncoder, NodeServerResponse, } from './response.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvF,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EACL,GAAG,EACH,SAAS,EACT,WAAW,EACX,eAAe,EACf,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAClB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,GAAG,EACH,KAAK,EACL,GAAG,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,kBAAkB,GACnB,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @llm-utils/stream
3
+ * Streaming utilities for real-time LLM responses
4
+ */
5
+ // Response Interception
6
+ export { interceptResponseWrites } from './interceptor.js';
7
+ // Chunk Aggregation
8
+ export { createChunkAggregator } from './aggregator.js';
9
+ // JSONL Streaming
10
+ export { streamJsonLines, parseJsonLines, toJsonLine, fromJsonLine } from './jsonl.js';
11
+ // Stream Multiplexing
12
+ export { mux, muxSimple, parseCursor, serializeCursor, createCursor, createReplayableStream, mapEvents, filterEvents, accumulateDeltas, } from './mux.js';
13
+ // HTTP Response Utilities
14
+ export { sse, jsonl, raw, streamToResponse, textStreamResponse, parseSSEResponse, parseJSONLResponse, streamToNodeResponse, createAIEvent, toEventStream, } from './response.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAwB;AACxB,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,oBAAoB;AACpB,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAQxD,kBAAkB;AAClB,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGvF,sBAAsB;AACtB,OAAO,EACL,GAAG,EACH,SAAS,EACT,WAAW,EACX,eAAe,EACf,YAAY,EACZ,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,gBAAgB,GACjB,MAAM,UAAU,CAAC;AAQlB,0BAA0B;AAC1B,OAAO,EACL,GAAG,EACH,KAAK,EACL,GAAG,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,aAAa,GACd,MAAM,eAAe,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * HTTP Response Interception
3
+ * Transform streaming responses without breaking HTTP semantics
4
+ */
5
+ import type { ServerResponse } from 'node:http';
6
+ /**
7
+ * Transform function for stream chunks
8
+ * Return undefined to skip the chunk
9
+ */
10
+ export type ChunkTransformer = (text: string) => Promise<string | undefined>;
11
+ /**
12
+ * Options for response interception
13
+ */
14
+ export interface InterceptOptions {
15
+ /** Transform each chunk before sending */
16
+ transform: ChunkTransformer;
17
+ /** Called when stream finishes */
18
+ onFinish?: () => Promise<void>;
19
+ /** Called on error */
20
+ onError?: (error: Error) => void;
21
+ }
22
+ /**
23
+ * Intercepts HTTP response writes for transformation.
24
+ * Preserves Express/Node response API compatibility.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * interceptResponseWrites(res, {
29
+ * transform: async (chunk) => {
30
+ * const data = JSON.parse(chunk);
31
+ * return JSON.stringify({ ...data, timestamp: Date.now() }) + '\n';
32
+ * },
33
+ * onFinish: async () => {
34
+ * await saveConversation();
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ export declare function interceptResponseWrites<T extends ServerResponse>(res: T, options: InterceptOptions): T;
40
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,sBAAsB;IACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,cAAc,EAC9D,GAAG,EAAE,CAAC,EACN,OAAO,EAAE,gBAAgB,GACxB,CAAC,CAgGH"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * HTTP Response Interception
3
+ * Transform streaming responses without breaking HTTP semantics
4
+ */
5
+ /**
6
+ * Intercepts HTTP response writes for transformation.
7
+ * Preserves Express/Node response API compatibility.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * interceptResponseWrites(res, {
12
+ * transform: async (chunk) => {
13
+ * const data = JSON.parse(chunk);
14
+ * return JSON.stringify({ ...data, timestamp: Date.now() }) + '\n';
15
+ * },
16
+ * onFinish: async () => {
17
+ * await saveConversation();
18
+ * }
19
+ * });
20
+ * ```
21
+ */
22
+ export function interceptResponseWrites(res, options) {
23
+ const { transform, onFinish, onError } = options;
24
+ // Store original methods
25
+ const originalWrite = res.write.bind(res);
26
+ const originalEnd = res.end.bind(res);
27
+ // Buffer for incomplete chunks
28
+ let buffer = '';
29
+ // Override write
30
+ res.write = function (chunk, encodingOrCallback, callback) {
31
+ const encoding = typeof encodingOrCallback === 'string' ? encodingOrCallback : 'utf8';
32
+ const cb = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
33
+ const text = typeof chunk === 'string' ? chunk : chunk.toString(encoding);
34
+ buffer += text;
35
+ // Process complete lines
36
+ const lines = buffer.split('\n');
37
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
38
+ // Process each complete line
39
+ (async () => {
40
+ try {
41
+ for (const line of lines) {
42
+ if (line.trim()) {
43
+ const transformed = await transform(line);
44
+ if (transformed !== undefined) {
45
+ originalWrite(transformed + '\n', encoding);
46
+ }
47
+ }
48
+ }
49
+ cb?.(null);
50
+ }
51
+ catch (error) {
52
+ onError?.(error);
53
+ cb?.(error);
54
+ }
55
+ })();
56
+ return true;
57
+ };
58
+ // Override end
59
+ res.end = function (chunkOrCallback, encodingOrCallback, callback) {
60
+ const chunk = typeof chunkOrCallback === 'function' ? undefined : chunkOrCallback;
61
+ const cb = typeof chunkOrCallback === 'function'
62
+ ? chunkOrCallback
63
+ : typeof encodingOrCallback === 'function'
64
+ ? encodingOrCallback
65
+ : callback;
66
+ (async () => {
67
+ try {
68
+ // Process any remaining chunk
69
+ if (chunk) {
70
+ const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
71
+ buffer += text;
72
+ }
73
+ // Process remaining buffer
74
+ if (buffer.trim()) {
75
+ const transformed = await transform(buffer);
76
+ if (transformed !== undefined) {
77
+ originalWrite(transformed);
78
+ }
79
+ }
80
+ // Call finish handler
81
+ await onFinish?.();
82
+ // End the response
83
+ originalEnd(cb);
84
+ }
85
+ catch (error) {
86
+ onError?.(error);
87
+ originalEnd(cb);
88
+ }
89
+ })();
90
+ return res;
91
+ };
92
+ return res;
93
+ }
94
+ //# sourceMappingURL=interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * JSONL Streaming
3
+ * Parse and serialize JSONL (JSON Lines) streams
4
+ */
5
+ /**
6
+ * Generator function that yields objects to stream as JSONL
7
+ */
8
+ export type JsonLineGenerator<T> = () => AsyncIterable<T>;
9
+ /**
10
+ * A minimal Response-like interface for parsing JSONL
11
+ */
12
+ interface ReadableResponse {
13
+ body: ReadableStream<Uint8Array> | null;
14
+ }
15
+ /**
16
+ * A minimal ServerResponse-like interface for streaming JSONL
17
+ */
18
+ interface WritableResponse {
19
+ setHeader(name: string, value: string): void;
20
+ write(chunk: string): boolean;
21
+ end(): void;
22
+ }
23
+ /**
24
+ * Streams objects as JSONL (JSON Lines) to an HTTP response.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * app.get('/events', (req, res) => {
29
+ * streamJsonLines(res, async function* () {
30
+ * for (const event of events) {
31
+ * yield event;
32
+ * }
33
+ * });
34
+ * });
35
+ * ```
36
+ */
37
+ export declare function streamJsonLines<T>(res: WritableResponse, generator: JsonLineGenerator<T>): void;
38
+ /**
39
+ * Parses a JSONL stream from a fetch Response.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const response = await fetch('/events');
44
+ * for await (const event of parseJsonLines(response)) {
45
+ * handleEvent(event);
46
+ * }
47
+ * ```
48
+ */
49
+ export declare function parseJsonLines<T = unknown>(response: ReadableResponse): AsyncGenerator<T, void, unknown>;
50
+ /**
51
+ * Serializes an object to a JSONL line
52
+ */
53
+ export declare function toJsonLine<T>(obj: T): string;
54
+ /**
55
+ * Parses a single JSONL line
56
+ */
57
+ export declare function fromJsonLine<T>(line: string): T;
58
+ export {};
59
+ //# sourceMappingURL=jsonl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../src/jsonl.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,MAAM,aAAa,CAAC,CAAC,CAAC,CAAC;AAE1D;;GAEG;AACH,UAAU,gBAAgB;IACxB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CACzC;AAED;;GAEG;AACH,UAAU,gBAAgB;IACxB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,GAAG,IAAI,IAAI,CAAC;CACb;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC9B,IAAI,CAgBN;AAED;;;;;;;;;;GAUG;AACH,wBAAuB,cAAc,CAAC,CAAC,GAAG,OAAO,EAC/C,QAAQ,EAAE,gBAAgB,GACzB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAgDlC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAE/C"}