@providerprotocol/ai 0.0.35 → 0.0.36
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 +17 -13
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.js +1 -1
- package/dist/cerebras/index.d.ts +2 -2
- package/dist/cerebras/index.js +1 -1
- package/dist/{chunk-7DXVRILR.js → chunk-2YXFLRQ6.js} +2 -2
- package/dist/chunk-2YXFLRQ6.js.map +1 -0
- package/dist/{chunk-HB4ZIH3T.js → chunk-4RX4VQCB.js} +2 -2
- package/dist/chunk-4RX4VQCB.js.map +1 -0
- package/dist/{chunk-ZI67WIQS.js → chunk-5IWHCXKN.js} +2 -2
- package/dist/chunk-5IWHCXKN.js.map +1 -0
- package/dist/{chunk-VOEWHQUB.js → chunk-CRP6Y7NF.js} +2 -2
- package/dist/chunk-CRP6Y7NF.js.map +1 -0
- package/dist/{chunk-3GWM5GR3.js → chunk-EPB3GQNL.js} +30 -65
- package/dist/chunk-EPB3GQNL.js.map +1 -0
- package/dist/{chunk-6S222DHN.js → chunk-RJGTRQ47.js} +20 -1
- package/dist/chunk-RJGTRQ47.js.map +1 -0
- package/dist/{embedding-CW6SaOOz.d.ts → embedding-BXA72PlJ.d.ts} +1 -1
- package/dist/google/index.d.ts +2 -2
- package/dist/google/index.js +1 -1
- package/dist/groq/index.d.ts +2 -2
- package/dist/groq/index.js +1 -1
- package/dist/http/index.d.ts +3 -3
- package/dist/{image-stream-C0ciACM2.d.ts → image-stream-CCgwB7ve.d.ts} +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +1 -1
- package/dist/{llm-DwbUK7un.d.ts → llm-ByUFPcFH.d.ts} +1 -1
- package/dist/middleware/logging/index.d.ts +2 -2
- package/dist/middleware/parsed-object/index.d.ts +2 -2
- package/dist/middleware/parsed-object/index.js +1 -1
- package/dist/middleware/pubsub/index.d.ts +27 -34
- package/dist/middleware/pubsub/index.js +49 -119
- package/dist/middleware/pubsub/index.js.map +1 -1
- package/dist/middleware/pubsub/server/express/index.d.ts +24 -10
- package/dist/middleware/pubsub/server/express/index.js +2 -2
- package/dist/middleware/pubsub/server/fastify/index.d.ts +24 -10
- package/dist/middleware/pubsub/server/fastify/index.js +2 -2
- package/dist/middleware/pubsub/server/h3/index.d.ts +23 -9
- package/dist/middleware/pubsub/server/h3/index.js +2 -2
- package/dist/middleware/pubsub/server/index.d.ts +2 -2
- package/dist/middleware/pubsub/server/index.js +5 -5
- package/dist/middleware/pubsub/server/webapi/index.d.ts +23 -13
- package/dist/middleware/pubsub/server/webapi/index.js +2 -2
- package/dist/ollama/index.d.ts +2 -2
- package/dist/ollama/index.js +1 -1
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.js +1 -1
- package/dist/openrouter/index.d.ts +2 -2
- package/dist/openrouter/index.js +1 -1
- package/dist/proxy/index.d.ts +4 -4
- package/dist/proxy/index.js +1 -1
- package/dist/proxy/server/express/index.d.ts +4 -4
- package/dist/proxy/server/fastify/index.d.ts +4 -4
- package/dist/proxy/server/h3/index.d.ts +4 -4
- package/dist/proxy/server/index.d.ts +4 -4
- package/dist/proxy/server/webapi/index.d.ts +4 -4
- package/dist/responses/index.d.ts +2 -2
- package/dist/responses/index.js +1 -1
- package/dist/{retry-YayV42GV.d.ts → retry-BDMo4AVu.d.ts} +1 -1
- package/dist/{stream-CecfVCPO.d.ts → stream-S7nwQRqM.d.ts} +17 -6
- package/dist/types-CE4B7pno.d.ts +96 -0
- package/dist/xai/index.d.ts +2 -2
- package/dist/xai/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3GWM5GR3.js.map +0 -1
- package/dist/chunk-6S222DHN.js.map +0 -1
- package/dist/chunk-7DXVRILR.js.map +0 -1
- package/dist/chunk-HB4ZIH3T.js.map +0 -1
- package/dist/chunk-VOEWHQUB.js.map +0 -1
- package/dist/chunk-ZI67WIQS.js.map +0 -1
- package/dist/types-C8Gciizr.d.ts +0 -168
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { M as Middleware } from '../../llm-
|
|
2
|
-
import { M as MemoryAdapterOptions, P as PubSubAdapter, a as PubSubOptions } from '../../types-
|
|
3
|
-
export { S as StoredStream, b as SubscriptionCallback, U as Unsubscribe } from '../../types-
|
|
4
|
-
import '../../stream-
|
|
1
|
+
import { M as Middleware } from '../../llm-ByUFPcFH.js';
|
|
2
|
+
import { M as MemoryAdapterOptions, P as PubSubAdapter, a as PubSubOptions } from '../../types-CE4B7pno.js';
|
|
3
|
+
export { C as CompletionCallback, S as StoredStream, b as SubscriptionCallback, U as Unsubscribe } from '../../types-CE4B7pno.js';
|
|
4
|
+
import '../../stream-S7nwQRqM.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @fileoverview In-memory storage adapter for pub-sub middleware.
|
|
8
8
|
*
|
|
9
|
-
* Provides a simple Map-based implementation
|
|
10
|
-
*
|
|
9
|
+
* Provides a simple Map-based implementation for temporary stream
|
|
10
|
+
* storage during active generation.
|
|
11
11
|
*
|
|
12
12
|
* @module middleware/pubsub/memory-adapter
|
|
13
13
|
*/
|
|
@@ -15,8 +15,8 @@ import '../../stream-CecfVCPO.js';
|
|
|
15
15
|
/**
|
|
16
16
|
* Creates an in-memory storage adapter for pub-sub middleware.
|
|
17
17
|
*
|
|
18
|
-
* Stores streams in a Map
|
|
19
|
-
*
|
|
18
|
+
* Stores streams in a Map. Throws when maxStreams is exceeded.
|
|
19
|
+
* Streams are created lazily on first append or subscribe.
|
|
20
20
|
*
|
|
21
21
|
* @param options - Adapter configuration
|
|
22
22
|
* @returns A PubSubAdapter instance
|
|
@@ -25,9 +25,7 @@ import '../../stream-CecfVCPO.js';
|
|
|
25
25
|
* ```typescript
|
|
26
26
|
* import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
|
|
27
27
|
*
|
|
28
|
-
* const
|
|
29
|
-
* adapter: memoryAdapter({ maxStreams: 500 }),
|
|
30
|
-
* });
|
|
28
|
+
* const adapter = memoryAdapter({ maxStreams: 500 });
|
|
31
29
|
* ```
|
|
32
30
|
*/
|
|
33
31
|
declare function memoryAdapter(options?: MemoryAdapterOptions): PubSubAdapter;
|
|
@@ -64,9 +62,9 @@ declare function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefi
|
|
|
64
62
|
* - Creates stream entries for new requests
|
|
65
63
|
* - Buffers all stream events
|
|
66
64
|
* - Publishes events to subscribers
|
|
67
|
-
* -
|
|
65
|
+
* - On stream end: notifies subscribers, then removes from adapter
|
|
68
66
|
*
|
|
69
|
-
* Server routes handle reconnection logic using `
|
|
67
|
+
* Server routes handle reconnection logic using `streamSubscriber`.
|
|
70
68
|
*
|
|
71
69
|
* @param options - Middleware configuration
|
|
72
70
|
* @returns Middleware instance
|
|
@@ -75,28 +73,23 @@ declare function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefi
|
|
|
75
73
|
* ```typescript
|
|
76
74
|
* import { llm } from '@providerprotocol/ai';
|
|
77
75
|
* import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
78
|
-
* import { pubsubMiddleware } from '@providerprotocol/ai/middleware/pubsub';
|
|
79
|
-
* import {
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* const
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* middleware: [pubsubMiddleware({ adapter, streamId })],
|
|
91
|
-
* });
|
|
92
|
-
* consumeInBackground(model.stream(messages));
|
|
93
|
-
* }
|
|
94
|
-
*
|
|
95
|
-
* // Both new and reconnect: subscribe to events
|
|
96
|
-
* return new Response(createSubscriberStream(streamId, adapter), {
|
|
97
|
-
* headers: { 'Content-Type': 'text/event-stream' },
|
|
76
|
+
* import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
|
|
77
|
+
* import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';
|
|
78
|
+
*
|
|
79
|
+
* const adapter = memoryAdapter();
|
|
80
|
+
*
|
|
81
|
+
* export default defineEventHandler(async (event) => {
|
|
82
|
+
* const { input, conversationId } = await readBody(event);
|
|
83
|
+
*
|
|
84
|
+
* // Fire and forget - subscriber connects immediately, events flow when ready
|
|
85
|
+
* const model = llm({
|
|
86
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
87
|
+
* middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
|
|
98
88
|
* });
|
|
99
|
-
*
|
|
89
|
+
* model.stream(input).then(turn => saveToDatabase(turn));
|
|
90
|
+
*
|
|
91
|
+
* return h3.streamSubscriber(conversationId, adapter, event);
|
|
92
|
+
* });
|
|
100
93
|
* ```
|
|
101
94
|
*/
|
|
102
95
|
declare function pubsubMiddleware(options?: PubSubOptions): Middleware;
|
|
@@ -3,83 +3,51 @@ function memoryAdapter(options = {}) {
|
|
|
3
3
|
const { maxStreams = 1e3 } = options;
|
|
4
4
|
const streams = /* @__PURE__ */ new Map();
|
|
5
5
|
const eventCursors = /* @__PURE__ */ new WeakMap();
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (entry.stream.updatedAt < oldestTime) {
|
|
12
|
-
oldestTime = entry.stream.updatedAt;
|
|
13
|
-
oldest = id;
|
|
14
|
-
}
|
|
6
|
+
const scheduleCallback = (callback) => {
|
|
7
|
+
queueMicrotask(() => {
|
|
8
|
+
try {
|
|
9
|
+
callback();
|
|
10
|
+
} catch {
|
|
15
11
|
}
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const getOrCreate = (streamId) => {
|
|
15
|
+
let entry = streams.get(streamId);
|
|
16
|
+
if (!entry) {
|
|
17
|
+
if (streams.size >= maxStreams) {
|
|
18
|
+
throw new Error(`Maximum concurrent streams (${maxStreams}) exceeded`);
|
|
18
19
|
}
|
|
20
|
+
entry = {
|
|
21
|
+
stream: {
|
|
22
|
+
streamId,
|
|
23
|
+
createdAt: Date.now(),
|
|
24
|
+
events: []
|
|
25
|
+
},
|
|
26
|
+
subscribers: /* @__PURE__ */ new Set()
|
|
27
|
+
};
|
|
28
|
+
streams.set(streamId, entry);
|
|
19
29
|
}
|
|
30
|
+
return entry;
|
|
20
31
|
};
|
|
21
32
|
return {
|
|
22
33
|
async exists(streamId) {
|
|
23
34
|
return streams.has(streamId);
|
|
24
35
|
},
|
|
25
|
-
async create(streamId, metadata) {
|
|
26
|
-
evictOldest();
|
|
27
|
-
const now = Date.now();
|
|
28
|
-
const stream = {
|
|
29
|
-
streamId,
|
|
30
|
-
modelId: metadata.modelId,
|
|
31
|
-
provider: metadata.provider,
|
|
32
|
-
createdAt: now,
|
|
33
|
-
updatedAt: now,
|
|
34
|
-
completed: false,
|
|
35
|
-
events: []
|
|
36
|
-
};
|
|
37
|
-
streams.set(streamId, {
|
|
38
|
-
stream,
|
|
39
|
-
subscribers: /* @__PURE__ */ new Set()
|
|
40
|
-
});
|
|
41
|
-
},
|
|
42
36
|
async append(streamId, event) {
|
|
43
|
-
const entry =
|
|
44
|
-
if (!entry) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
37
|
+
const entry = getOrCreate(streamId);
|
|
47
38
|
entry.stream.events.push(event);
|
|
48
39
|
eventCursors.set(event, entry.stream.events.length - 1);
|
|
49
|
-
entry.stream.updatedAt = Date.now();
|
|
50
|
-
},
|
|
51
|
-
async markCompleted(streamId) {
|
|
52
|
-
const entry = streams.get(streamId);
|
|
53
|
-
if (!entry) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
entry.stream.completed = true;
|
|
57
|
-
entry.stream.updatedAt = Date.now();
|
|
58
|
-
},
|
|
59
|
-
async isCompleted(streamId) {
|
|
60
|
-
const entry = streams.get(streamId);
|
|
61
|
-
return entry?.stream.completed ?? false;
|
|
62
40
|
},
|
|
63
41
|
async getEvents(streamId) {
|
|
64
42
|
const entry = streams.get(streamId);
|
|
65
|
-
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
return [...entry.stream.events];
|
|
69
|
-
},
|
|
70
|
-
async getStream(streamId) {
|
|
71
|
-
const entry = streams.get(streamId);
|
|
72
|
-
return entry?.stream ?? null;
|
|
43
|
+
return entry ? [...entry.stream.events] : [];
|
|
73
44
|
},
|
|
74
|
-
subscribe(streamId,
|
|
75
|
-
const entry =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
entry.subscribers.add(callback);
|
|
45
|
+
subscribe(streamId, onEvent, onComplete) {
|
|
46
|
+
const entry = getOrCreate(streamId);
|
|
47
|
+
const subscriber = { onEvent, onComplete };
|
|
48
|
+
entry.subscribers.add(subscriber);
|
|
81
49
|
return () => {
|
|
82
|
-
entry.subscribers.delete(
|
|
50
|
+
entry.subscribers.delete(subscriber);
|
|
83
51
|
};
|
|
84
52
|
},
|
|
85
53
|
publish(streamId, event) {
|
|
@@ -88,23 +56,19 @@ function memoryAdapter(options = {}) {
|
|
|
88
56
|
return;
|
|
89
57
|
}
|
|
90
58
|
const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;
|
|
91
|
-
for (const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
}
|
|
59
|
+
for (const subscriber of entry.subscribers) {
|
|
60
|
+
scheduleCallback(() => {
|
|
61
|
+
subscriber.onEvent(event, cursor);
|
|
62
|
+
});
|
|
96
63
|
}
|
|
97
64
|
},
|
|
98
65
|
async remove(streamId) {
|
|
99
|
-
streams.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const cutoff = now - maxAge;
|
|
104
|
-
for (const [id, entry] of streams) {
|
|
105
|
-
if (entry.stream.updatedAt < cutoff) {
|
|
106
|
-
streams.delete(id);
|
|
66
|
+
const entry = streams.get(streamId);
|
|
67
|
+
if (entry) {
|
|
68
|
+
for (const subscriber of entry.subscribers) {
|
|
69
|
+
scheduleCallback(subscriber.onComplete);
|
|
107
70
|
}
|
|
71
|
+
streams.delete(streamId);
|
|
108
72
|
}
|
|
109
73
|
}
|
|
110
74
|
};
|
|
@@ -113,9 +77,6 @@ function memoryAdapter(options = {}) {
|
|
|
113
77
|
// src/middleware/pubsub/index.ts
|
|
114
78
|
var STATE_KEY_STREAM_ID = "pubsub:streamId";
|
|
115
79
|
var STATE_KEY_ADAPTER = "pubsub:adapter";
|
|
116
|
-
var DEFAULT_TTL = 6e5;
|
|
117
|
-
var CLEANUP_INTERVAL = 6e4;
|
|
118
|
-
var adapterCleanupTimes = /* @__PURE__ */ new WeakMap();
|
|
119
80
|
function getStreamId(state) {
|
|
120
81
|
return state.get(STATE_KEY_STREAM_ID);
|
|
121
82
|
}
|
|
@@ -125,8 +86,7 @@ function getAdapter(state) {
|
|
|
125
86
|
function pubsubMiddleware(options = {}) {
|
|
126
87
|
const {
|
|
127
88
|
adapter = memoryAdapter(),
|
|
128
|
-
streamId
|
|
129
|
-
ttl = DEFAULT_TTL
|
|
89
|
+
streamId
|
|
130
90
|
} = options;
|
|
131
91
|
const appendChains = /* @__PURE__ */ new Map();
|
|
132
92
|
const enqueueAppend = (id, event) => {
|
|
@@ -151,25 +111,15 @@ function pubsubMiddleware(options = {}) {
|
|
|
151
111
|
const clearAppendState = (id) => {
|
|
152
112
|
appendChains.delete(id);
|
|
153
113
|
};
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const lastCleanup = adapterCleanupTimes.get(adapter) ?? 0;
|
|
157
|
-
if (now - lastCleanup > CLEANUP_INTERVAL) {
|
|
158
|
-
adapterCleanupTimes.set(adapter, now);
|
|
159
|
-
adapter.cleanup(ttl).catch(() => {
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
const finalizeStream = async (ctx) => {
|
|
164
|
-
const id = ctx.state.get(STATE_KEY_STREAM_ID);
|
|
114
|
+
const finalizeStreamByState = async (state) => {
|
|
115
|
+
const id = state.get(STATE_KEY_STREAM_ID);
|
|
165
116
|
if (!id) {
|
|
166
117
|
return;
|
|
167
118
|
}
|
|
168
119
|
await waitForAppends(id);
|
|
169
|
-
await adapter.markCompleted(id).catch(() => {
|
|
170
|
-
});
|
|
171
120
|
clearAppendState(id);
|
|
172
|
-
|
|
121
|
+
await adapter.remove(id).catch(() => {
|
|
122
|
+
});
|
|
173
123
|
};
|
|
174
124
|
return {
|
|
175
125
|
name: "pubsub",
|
|
@@ -177,21 +127,9 @@ function pubsubMiddleware(options = {}) {
|
|
|
177
127
|
ctx.state.set(STATE_KEY_ADAPTER, adapter);
|
|
178
128
|
if (streamId) {
|
|
179
129
|
ctx.state.set(STATE_KEY_STREAM_ID, streamId);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (!streamId) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (!ctx.streaming) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
const exists = await adapter.exists(streamId);
|
|
190
|
-
if (!exists) {
|
|
191
|
-
await adapter.create(streamId, {
|
|
192
|
-
modelId: ctx.modelId,
|
|
193
|
-
provider: ctx.provider
|
|
194
|
-
});
|
|
130
|
+
adapter.subscribe(streamId, () => {
|
|
131
|
+
}, () => {
|
|
132
|
+
})();
|
|
195
133
|
}
|
|
196
134
|
},
|
|
197
135
|
onStreamEvent(event, ctx) {
|
|
@@ -203,21 +141,13 @@ function pubsubMiddleware(options = {}) {
|
|
|
203
141
|
return event;
|
|
204
142
|
},
|
|
205
143
|
async onStreamEnd(ctx) {
|
|
206
|
-
|
|
207
|
-
if (!id) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
await waitForAppends(id);
|
|
211
|
-
await adapter.markCompleted(id).catch(() => {
|
|
212
|
-
});
|
|
213
|
-
clearAppendState(id);
|
|
214
|
-
maybeCleanup();
|
|
144
|
+
await finalizeStreamByState(ctx.state);
|
|
215
145
|
},
|
|
216
146
|
async onError(_error, ctx) {
|
|
217
|
-
await
|
|
147
|
+
await finalizeStreamByState(ctx.state);
|
|
218
148
|
},
|
|
219
149
|
async onAbort(_error, ctx) {
|
|
220
|
-
await
|
|
150
|
+
await finalizeStreamByState(ctx.state);
|
|
221
151
|
}
|
|
222
152
|
};
|
|
223
153
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/middleware/pubsub/memory-adapter.ts","../../../src/middleware/pubsub/index.ts"],"sourcesContent":["/**\n * @fileoverview In-memory storage adapter for pub-sub middleware.\n *\n * Provides a simple Map-based implementation with LRU eviction\n * for temporary stream storage during active generation.\n *\n * @module middleware/pubsub/memory-adapter\n */\n\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type {\n PubSubAdapter,\n StoredStream,\n SubscriptionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\n\n/**\n * Internal mutable version of StoredStream for adapter operations.\n * Public API exposes readonly StoredStream.\n */\ninterface MutableStoredStream {\n streamId: string;\n modelId: string;\n provider: string;\n createdAt: number;\n updatedAt: number;\n completed: boolean;\n events: StreamEvent[];\n}\n\ninterface StreamEntry {\n stream: MutableStoredStream;\n subscribers: Set<SubscriptionCallback>;\n}\n\n/**\n * Creates an in-memory storage adapter for pub-sub middleware.\n *\n * Stores streams in a Map with LRU eviction when maxStreams is reached.\n * All methods return promises for interface compatibility with async backends.\n *\n * @param options - Adapter configuration\n * @returns A PubSubAdapter instance\n *\n * @example\n * ```typescript\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n *\n * const mw = pubsubMiddleware({\n * adapter: memoryAdapter({ maxStreams: 500 }),\n * });\n * ```\n */\nexport function memoryAdapter(options: MemoryAdapterOptions = {}): PubSubAdapter {\n const { maxStreams = 1000 } = options;\n\n const streams = new Map<string, StreamEntry>();\n const eventCursors = new WeakMap<StreamEvent, number>();\n\n const evictOldest = (): void => {\n if (streams.size >= maxStreams) {\n let oldest: string | null = null;\n let oldestTime = Infinity;\n\n for (const [id, entry] of streams) {\n if (entry.stream.updatedAt < oldestTime) {\n oldestTime = entry.stream.updatedAt;\n oldest = id;\n }\n }\n\n if (oldest) {\n streams.delete(oldest);\n }\n }\n };\n\n return {\n async exists(streamId): Promise<boolean> {\n return streams.has(streamId);\n },\n\n async create(streamId, metadata): Promise<void> {\n evictOldest();\n\n const now = Date.now();\n const stream: MutableStoredStream = {\n streamId,\n modelId: metadata.modelId,\n provider: metadata.provider,\n createdAt: now,\n updatedAt: now,\n completed: false,\n events: [],\n };\n\n streams.set(streamId, {\n stream,\n subscribers: new Set(),\n });\n },\n\n async append(streamId, event): Promise<void> {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n entry.stream.events.push(event);\n eventCursors.set(event, entry.stream.events.length - 1);\n entry.stream.updatedAt = Date.now();\n },\n\n async markCompleted(streamId): Promise<void> {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n entry.stream.completed = true;\n entry.stream.updatedAt = Date.now();\n },\n\n async isCompleted(streamId): Promise<boolean> {\n const entry = streams.get(streamId);\n return entry?.stream.completed ?? false;\n },\n\n async getEvents(streamId): Promise<StreamEvent[] | null> {\n const entry = streams.get(streamId);\n if (!entry) {\n return null;\n }\n\n return [...entry.stream.events];\n },\n\n async getStream(streamId): Promise<StoredStream | null> {\n const entry = streams.get(streamId);\n return entry?.stream ?? null;\n },\n\n subscribe(streamId, callback): Unsubscribe {\n const entry = streams.get(streamId);\n if (!entry) {\n return () => {};\n }\n\n entry.subscribers.add(callback);\n\n return () => {\n entry.subscribers.delete(callback);\n };\n },\n\n publish(streamId, event): void {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;\n for (const callback of entry.subscribers) {\n try {\n callback(event, cursor);\n } catch {\n // Subscriber errors should not affect other subscribers\n }\n }\n },\n\n async remove(streamId): Promise<void> {\n streams.delete(streamId);\n },\n\n async cleanup(maxAge): Promise<void> {\n const now = Date.now();\n const cutoff = now - maxAge;\n\n for (const [id, entry] of streams) {\n if (entry.stream.updatedAt < cutoff) {\n streams.delete(id);\n }\n }\n },\n };\n}\n","/**\n * @fileoverview Pub-sub middleware for stream resumption.\n *\n * Enables reconnecting clients to catch up on missed events during\n * active generation. The middleware buffers events and publishes them\n * to subscribers. Server routes handle reconnection logic using the\n * exported `createSubscriberStream` utility.\n *\n * @module middleware/pubsub\n */\n\nimport type {\n Middleware,\n MiddlewareContext,\n StreamContext,\n} from '../../types/middleware.ts';\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type { PubSubAdapter, PubSubOptions } from './types.ts';\nimport { memoryAdapter } from './memory-adapter.ts';\n\nexport type {\n PubSubAdapter,\n PubSubOptions,\n StoredStream,\n SubscriptionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\nexport { memoryAdapter } from './memory-adapter.ts';\n\nconst STATE_KEY_STREAM_ID = 'pubsub:streamId';\nconst STATE_KEY_ADAPTER = 'pubsub:adapter';\n\nconst DEFAULT_TTL = 600_000; // 10 minutes\nconst CLEANUP_INTERVAL = 60_000; // 1 minute\n\n/** Track last cleanup time per adapter to avoid shared state issues */\nconst adapterCleanupTimes = new WeakMap<PubSubAdapter, number>();\n\ninterface AppendChainState {\n chain: Promise<void>;\n}\n\n/**\n * Gets the stream ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Stream ID or undefined if not set\n */\nexport function getStreamId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_STREAM_ID) as string | undefined;\n}\n\n/**\n * Gets the adapter from middleware state.\n *\n * @param state - Middleware state map\n * @returns Adapter or undefined if not set\n */\nexport function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefined {\n return state.get(STATE_KEY_ADAPTER) as PubSubAdapter | undefined;\n}\n\n/**\n * Creates pub-sub middleware for stream buffering and publishing.\n *\n * The middleware:\n * - Creates stream entries for new requests\n * - Buffers all stream events\n * - Publishes events to subscribers\n * - Marks streams as completed\n *\n * Server routes handle reconnection logic using `createSubscriberStream`.\n *\n * @param options - Middleware configuration\n * @returns Middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n * import { pubsubMiddleware } from '@providerprotocol/ai/middleware/pubsub';\n * import { createSubscriberStream } from '@providerprotocol/ai/middleware/pubsub/server/webapi';\n *\n * // Server route handling both new requests and reconnections\n * export async function POST(req: Request) {\n * const { messages, streamId } = await req.json();\n * const exists = await adapter.exists(streamId);\n *\n * if (!exists) {\n * // Start background generation (fire and forget)\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * consumeInBackground(model.stream(messages));\n * }\n *\n * // Both new and reconnect: subscribe to events\n * return new Response(createSubscriberStream(streamId, adapter), {\n * headers: { 'Content-Type': 'text/event-stream' },\n * });\n * }\n * ```\n */\nexport function pubsubMiddleware(options: PubSubOptions = {}): Middleware {\n const {\n adapter = memoryAdapter(),\n streamId,\n ttl = DEFAULT_TTL,\n } = options;\n\n const appendChains = new Map<string, AppendChainState>();\n\n const enqueueAppend = (id: string, event: StreamEvent): void => {\n const state = appendChains.get(id) ?? { chain: Promise.resolve() };\n\n const task = state.chain\n .catch(() => {})\n .then(async () => {\n await adapter.append(id, event);\n adapter.publish(id, event);\n });\n\n state.chain = task.catch(() => {});\n appendChains.set(id, state);\n };\n\n const waitForAppends = async (id: string): Promise<void> => {\n const state = appendChains.get(id);\n if (!state) {\n return;\n }\n\n await state.chain.catch(() => {});\n };\n\n const clearAppendState = (id: string): void => {\n appendChains.delete(id);\n };\n\n const maybeCleanup = (): void => {\n const now = Date.now();\n const lastCleanup = adapterCleanupTimes.get(adapter) ?? 0;\n if (now - lastCleanup > CLEANUP_INTERVAL) {\n adapterCleanupTimes.set(adapter, now);\n adapter.cleanup(ttl).catch(() => {\n // Cleanup errors are non-fatal\n });\n }\n };\n\n const finalizeStream = async (ctx: MiddlewareContext): Promise<void> => {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n\n await waitForAppends(id);\n\n await adapter.markCompleted(id).catch(() => {\n // Completion errors are non-fatal\n });\n\n clearAppendState(id);\n\n maybeCleanup();\n };\n\n return {\n name: 'pubsub',\n\n onStart(ctx: MiddlewareContext): void {\n ctx.state.set(STATE_KEY_ADAPTER, adapter);\n\n if (streamId) {\n ctx.state.set(STATE_KEY_STREAM_ID, streamId);\n }\n },\n\n async onRequest(ctx: MiddlewareContext): Promise<void> {\n if (!streamId) {\n return;\n }\n if (!ctx.streaming) {\n return;\n }\n\n // Create stream entry if it doesn't exist\n const exists = await adapter.exists(streamId);\n if (!exists) {\n await adapter.create(streamId, {\n modelId: ctx.modelId,\n provider: ctx.provider,\n });\n }\n },\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return event;\n }\n\n // Buffer first, then broadcast - ensures event is persisted before\n // subscribers are notified, preventing replay gaps with async adapters\n enqueueAppend(id, event);\n\n return event;\n },\n\n async onStreamEnd(ctx: StreamContext): Promise<void> {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n\n await waitForAppends(id);\n\n await adapter.markCompleted(id).catch(() => {\n // Completion errors are non-fatal\n });\n\n clearAppendState(id);\n\n maybeCleanup();\n },\n\n async onError(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStream(ctx);\n },\n\n async onAbort(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStream(ctx);\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/middleware/pubsub/memory-adapter.ts","../../../src/middleware/pubsub/index.ts"],"sourcesContent":["/**\n * @fileoverview In-memory storage adapter for pub-sub middleware.\n *\n * Provides a simple Map-based implementation for temporary stream\n * storage during active generation.\n *\n * @module middleware/pubsub/memory-adapter\n */\n\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type {\n PubSubAdapter,\n StoredStream,\n SubscriptionCallback,\n CompletionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\n\ninterface MutableStoredStream {\n streamId: string;\n createdAt: number;\n events: StreamEvent[];\n}\n\ninterface Subscriber {\n onEvent: SubscriptionCallback;\n onComplete: CompletionCallback;\n}\n\ninterface StreamEntry {\n stream: MutableStoredStream;\n subscribers: Set<Subscriber>;\n}\n\n/**\n * Creates an in-memory storage adapter for pub-sub middleware.\n *\n * Stores streams in a Map. Throws when maxStreams is exceeded.\n * Streams are created lazily on first append or subscribe.\n *\n * @param options - Adapter configuration\n * @returns A PubSubAdapter instance\n *\n * @example\n * ```typescript\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n *\n * const adapter = memoryAdapter({ maxStreams: 500 });\n * ```\n */\nexport function memoryAdapter(options: MemoryAdapterOptions = {}): PubSubAdapter {\n const { maxStreams = 1000 } = options;\n\n const streams = new Map<string, StreamEntry>();\n const eventCursors = new WeakMap<StreamEvent, number>();\n\n const scheduleCallback = (callback: () => void): void => {\n queueMicrotask(() => {\n try {\n callback();\n } catch {\n // Subscriber errors should not affect other subscribers\n }\n });\n };\n\n const getOrCreate = (streamId: string): StreamEntry => {\n let entry = streams.get(streamId);\n if (!entry) {\n if (streams.size >= maxStreams) {\n throw new Error(`Maximum concurrent streams (${maxStreams}) exceeded`);\n }\n entry = {\n stream: {\n streamId,\n createdAt: Date.now(),\n events: [],\n },\n subscribers: new Set(),\n };\n streams.set(streamId, entry);\n }\n return entry;\n };\n\n return {\n async exists(streamId): Promise<boolean> {\n return streams.has(streamId);\n },\n\n async append(streamId, event): Promise<void> {\n const entry = getOrCreate(streamId);\n entry.stream.events.push(event);\n eventCursors.set(event, entry.stream.events.length - 1);\n },\n\n async getEvents(streamId): Promise<StreamEvent[]> {\n const entry = streams.get(streamId);\n return entry ? [...entry.stream.events] : [];\n },\n\n subscribe(streamId, onEvent, onComplete): Unsubscribe {\n const entry = getOrCreate(streamId);\n const subscriber: Subscriber = { onEvent, onComplete };\n entry.subscribers.add(subscriber);\n\n return () => {\n entry.subscribers.delete(subscriber);\n };\n },\n\n publish(streamId, event): void {\n const entry = streams.get(streamId);\n if (!entry) {\n return;\n }\n\n const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;\n for (const subscriber of entry.subscribers) {\n scheduleCallback(() => {\n subscriber.onEvent(event, cursor);\n });\n }\n },\n\n async remove(streamId): Promise<void> {\n const entry = streams.get(streamId);\n if (entry) {\n for (const subscriber of entry.subscribers) {\n scheduleCallback(subscriber.onComplete);\n }\n streams.delete(streamId);\n }\n },\n };\n}\n","/**\n * @fileoverview Pub-sub middleware for stream resumption.\n *\n * Enables reconnecting clients to catch up on missed events during\n * active generation. The middleware buffers events and publishes them\n * to subscribers. Server routes handle reconnection logic using the\n * exported `createSubscriberStream` utility.\n *\n * @module middleware/pubsub\n */\n\nimport type {\n Middleware,\n MiddlewareContext,\n StreamContext,\n} from '../../types/middleware.ts';\nimport type { StreamEvent } from '../../types/stream.ts';\nimport type { PubSubAdapter, PubSubOptions } from './types.ts';\nimport { memoryAdapter } from './memory-adapter.ts';\n\nexport type {\n PubSubAdapter,\n PubSubOptions,\n StoredStream,\n SubscriptionCallback,\n CompletionCallback,\n Unsubscribe,\n MemoryAdapterOptions,\n} from './types.ts';\nexport { memoryAdapter } from './memory-adapter.ts';\n\nconst STATE_KEY_STREAM_ID = 'pubsub:streamId';\nconst STATE_KEY_ADAPTER = 'pubsub:adapter';\n\ninterface AppendChainState {\n chain: Promise<void>;\n}\n\n/**\n * Gets the stream ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Stream ID or undefined if not set\n */\nexport function getStreamId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_STREAM_ID) as string | undefined;\n}\n\n/**\n * Gets the adapter from middleware state.\n *\n * @param state - Middleware state map\n * @returns Adapter or undefined if not set\n */\nexport function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefined {\n return state.get(STATE_KEY_ADAPTER) as PubSubAdapter | undefined;\n}\n\n/**\n * Creates pub-sub middleware for stream buffering and publishing.\n *\n * The middleware:\n * - Creates stream entries for new requests\n * - Buffers all stream events\n * - Publishes events to subscribers\n * - On stream end: notifies subscribers, then removes from adapter\n *\n * Server routes handle reconnection logic using `streamSubscriber`.\n *\n * @param options - Middleware configuration\n * @returns Middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { anthropic } from '@providerprotocol/ai/anthropic';\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * const adapter = memoryAdapter();\n *\n * export default defineEventHandler(async (event) => {\n * const { input, conversationId } = await readBody(event);\n *\n * // Fire and forget - subscriber connects immediately, events flow when ready\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],\n * });\n * model.stream(input).then(turn => saveToDatabase(turn));\n *\n * return h3.streamSubscriber(conversationId, adapter, event);\n * });\n * ```\n */\nexport function pubsubMiddleware(options: PubSubOptions = {}): Middleware {\n const {\n adapter = memoryAdapter(),\n streamId,\n } = options;\n\n const appendChains = new Map<string, AppendChainState>();\n\n const enqueueAppend = (id: string, event: StreamEvent): void => {\n const state = appendChains.get(id) ?? { chain: Promise.resolve() };\n\n const task = state.chain\n .catch(() => {})\n .then(async () => {\n await adapter.append(id, event);\n adapter.publish(id, event);\n });\n\n state.chain = task.catch(() => {});\n appendChains.set(id, state);\n };\n\n const waitForAppends = async (id: string): Promise<void> => {\n const state = appendChains.get(id);\n if (!state) {\n return;\n }\n\n await state.chain.catch(() => {});\n };\n\n const clearAppendState = (id: string): void => {\n appendChains.delete(id);\n };\n\n /**\n * Finalizes a stream by marking completion and removing from adapter.\n *\n * Called on any terminal state (complete, error, abort). After finalization,\n * the stream is removed from the adapter. Apps should use `.then()` to persist\n * completed conversations and serve them from their own storage on reconnect.\n */\n const finalizeStreamByState = async (state: Map<string, unknown>): Promise<void> => {\n const id = state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n\n await waitForAppends(id);\n clearAppendState(id);\n\n // Remove from adapter (notifies subscribers) - apps persist via .then()\n await adapter.remove(id).catch(() => {});\n };\n\n return {\n name: 'pubsub',\n\n onStart(ctx: MiddlewareContext): void {\n ctx.state.set(STATE_KEY_ADAPTER, adapter);\n\n if (streamId) {\n ctx.state.set(STATE_KEY_STREAM_ID, streamId);\n // Ensure stream exists immediately so exists() returns true\n // before first token arrives (prevents duplicate generations)\n adapter.subscribe(streamId, () => {}, () => {})();\n }\n },\n\n onStreamEvent(event: StreamEvent, ctx: StreamContext): StreamEvent {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return event;\n }\n\n enqueueAppend(id, event);\n\n return event;\n },\n\n async onStreamEnd(ctx: StreamContext): Promise<void> {\n await finalizeStreamByState(ctx.state);\n },\n\n async onError(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStreamByState(ctx.state);\n },\n\n async onAbort(_error: Error, ctx: MiddlewareContext): Promise<void> {\n await finalizeStreamByState(ctx.state);\n },\n };\n}\n"],"mappings":";AAmDO,SAAS,cAAc,UAAgC,CAAC,GAAkB;AAC/E,QAAM,EAAE,aAAa,IAAK,IAAI;AAE9B,QAAM,UAAU,oBAAI,IAAyB;AAC7C,QAAM,eAAe,oBAAI,QAA6B;AAEtD,QAAM,mBAAmB,CAAC,aAA+B;AACvD,mBAAe,MAAM;AACnB,UAAI;AACF,iBAAS;AAAA,MACX,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,cAAc,CAAC,aAAkC;AACrD,QAAI,QAAQ,QAAQ,IAAI,QAAQ;AAChC,QAAI,CAAC,OAAO;AACV,UAAI,QAAQ,QAAQ,YAAY;AAC9B,cAAM,IAAI,MAAM,+BAA+B,UAAU,YAAY;AAAA,MACvE;AACA,cAAQ;AAAA,QACN,QAAQ;AAAA,UACN;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,UACpB,QAAQ,CAAC;AAAA,QACX;AAAA,QACA,aAAa,oBAAI,IAAI;AAAA,MACvB;AACA,cAAQ,IAAI,UAAU,KAAK;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,UAA4B;AACvC,aAAO,QAAQ,IAAI,QAAQ;AAAA,IAC7B;AAAA,IAEA,MAAM,OAAO,UAAU,OAAsB;AAC3C,YAAM,QAAQ,YAAY,QAAQ;AAClC,YAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,mBAAa,IAAI,OAAO,MAAM,OAAO,OAAO,SAAS,CAAC;AAAA,IACxD;AAAA,IAEA,MAAM,UAAU,UAAkC;AAChD,YAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,aAAO,QAAQ,CAAC,GAAG,MAAM,OAAO,MAAM,IAAI,CAAC;AAAA,IAC7C;AAAA,IAEA,UAAU,UAAU,SAAS,YAAyB;AACpD,YAAM,QAAQ,YAAY,QAAQ;AAClC,YAAM,aAAyB,EAAE,SAAS,WAAW;AACrD,YAAM,YAAY,IAAI,UAAU;AAEhC,aAAO,MAAM;AACX,cAAM,YAAY,OAAO,UAAU;AAAA,MACrC;AAAA,IACF;AAAA,IAEA,QAAQ,UAAU,OAAa;AAC7B,YAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,UAAI,CAAC,OAAO;AACV;AAAA,MACF;AAEA,YAAM,SAAS,aAAa,IAAI,KAAK,KAAK,MAAM,OAAO,OAAO,SAAS;AACvE,iBAAW,cAAc,MAAM,aAAa;AAC1C,yBAAiB,MAAM;AACrB,qBAAW,QAAQ,OAAO,MAAM;AAAA,QAClC,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,UAAyB;AACpC,YAAM,QAAQ,QAAQ,IAAI,QAAQ;AAClC,UAAI,OAAO;AACT,mBAAW,cAAc,MAAM,aAAa;AAC1C,2BAAiB,WAAW,UAAU;AAAA,QACxC;AACA,gBAAQ,OAAO,QAAQ;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACF;;;ACzGA,IAAM,sBAAsB;AAC5B,IAAM,oBAAoB;AAYnB,SAAS,YAAY,OAAiD;AAC3E,SAAO,MAAM,IAAI,mBAAmB;AACtC;AAQO,SAAS,WAAW,OAAwD;AACjF,SAAO,MAAM,IAAI,iBAAiB;AACpC;AAuCO,SAAS,iBAAiB,UAAyB,CAAC,GAAe;AACxE,QAAM;AAAA,IACJ,UAAU,cAAc;AAAA,IACxB;AAAA,EACF,IAAI;AAEJ,QAAM,eAAe,oBAAI,IAA8B;AAEvD,QAAM,gBAAgB,CAAC,IAAY,UAA6B;AAC9D,UAAM,QAAQ,aAAa,IAAI,EAAE,KAAK,EAAE,OAAO,QAAQ,QAAQ,EAAE;AAEjE,UAAM,OAAO,MAAM,MAChB,MAAM,MAAM;AAAA,IAAC,CAAC,EACd,KAAK,YAAY;AAChB,YAAM,QAAQ,OAAO,IAAI,KAAK;AAC9B,cAAQ,QAAQ,IAAI,KAAK;AAAA,IAC3B,CAAC;AAEH,UAAM,QAAQ,KAAK,MAAM,MAAM;AAAA,IAAC,CAAC;AACjC,iBAAa,IAAI,IAAI,KAAK;AAAA,EAC5B;AAEA,QAAM,iBAAiB,OAAO,OAA8B;AAC1D,UAAM,QAAQ,aAAa,IAAI,EAAE;AACjC,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAEA,UAAM,MAAM,MAAM,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAClC;AAEA,QAAM,mBAAmB,CAAC,OAAqB;AAC7C,iBAAa,OAAO,EAAE;AAAA,EACxB;AASA,QAAM,wBAAwB,OAAO,UAA+C;AAClF,UAAM,KAAK,MAAM,IAAI,mBAAmB;AACxC,QAAI,CAAC,IAAI;AACP;AAAA,IACF;AAEA,UAAM,eAAe,EAAE;AACvB,qBAAiB,EAAE;AAGnB,UAAM,QAAQ,OAAO,EAAE,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzC;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,QAAQ,KAA8B;AACpC,UAAI,MAAM,IAAI,mBAAmB,OAAO;AAExC,UAAI,UAAU;AACZ,YAAI,MAAM,IAAI,qBAAqB,QAAQ;AAG3C,gBAAQ,UAAU,UAAU,MAAM;AAAA,QAAC,GAAG,MAAM;AAAA,QAAC,CAAC,EAAE;AAAA,MAClD;AAAA,IACF;AAAA,IAEA,cAAc,OAAoB,KAAiC;AACjE,YAAM,KAAK,IAAI,MAAM,IAAI,mBAAmB;AAC5C,UAAI,CAAC,IAAI;AACP,eAAO;AAAA,MACT;AAEA,oBAAc,IAAI,KAAK;AAEvB,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,YAAY,KAAmC;AACnD,YAAM,sBAAsB,IAAI,KAAK;AAAA,IACvC;AAAA,IAEA,MAAM,QAAQ,QAAe,KAAuC;AAClE,YAAM,sBAAsB,IAAI,KAAK;AAAA,IACvC;AAAA,IAEA,MAAM,QAAQ,QAAe,KAAuC;AAClE,YAAM,sBAAsB,IAAI,KAAK;AAAA,IACvC;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PubSubAdapter } from '../../../../types-
|
|
2
|
-
import '../../../../stream-
|
|
1
|
+
import { P as PubSubAdapter } from '../../../../types-CE4B7pno.js';
|
|
2
|
+
import '../../../../stream-S7nwQRqM.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @fileoverview Express/Connect adapter for pub-sub stream resumption.
|
|
@@ -22,10 +22,10 @@ interface ExpressResponse {
|
|
|
22
22
|
/**
|
|
23
23
|
* Stream buffered and live events to an Express response.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
* 1. Replays
|
|
27
|
-
* 2.
|
|
28
|
-
* 3.
|
|
25
|
+
* Handles reconnection for Express routes:
|
|
26
|
+
* 1. Replays buffered events from the adapter
|
|
27
|
+
* 2. Subscribes to live events until completion signal
|
|
28
|
+
* 3. Ends when stream completes or client disconnects
|
|
29
29
|
*
|
|
30
30
|
* @param streamId - The stream ID to subscribe to
|
|
31
31
|
* @param adapter - The pub-sub adapter instance
|
|
@@ -33,11 +33,25 @@ interface ExpressResponse {
|
|
|
33
33
|
*
|
|
34
34
|
* @example
|
|
35
35
|
* ```typescript
|
|
36
|
-
* import {
|
|
36
|
+
* import { llm } from '@providerprotocol/ai';
|
|
37
|
+
* import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
38
|
+
* import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
|
|
39
|
+
* import { express } from '@providerprotocol/ai/middleware/pubsub/server';
|
|
37
40
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
+
* const adapter = memoryAdapter();
|
|
42
|
+
*
|
|
43
|
+
* app.post('/api/chat', async (req, res) => {
|
|
44
|
+
* const { input, conversationId } = req.body;
|
|
45
|
+
*
|
|
46
|
+
* if (!await adapter.exists(conversationId)) {
|
|
47
|
+
* const model = llm({
|
|
48
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
49
|
+
* middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
|
|
50
|
+
* });
|
|
51
|
+
* model.stream(input).then(turn => saveToDatabase(conversationId, turn));
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* return express.streamSubscriber(conversationId, adapter, res);
|
|
41
55
|
* });
|
|
42
56
|
* ```
|
|
43
57
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
express,
|
|
3
3
|
streamSubscriber
|
|
4
|
-
} from "../../../../chunk-
|
|
5
|
-
import "../../../../chunk-
|
|
4
|
+
} from "../../../../chunk-5IWHCXKN.js";
|
|
5
|
+
import "../../../../chunk-EPB3GQNL.js";
|
|
6
6
|
import "../../../../chunk-ETBFOLQN.js";
|
|
7
7
|
export {
|
|
8
8
|
express,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PubSubAdapter } from '../../../../types-
|
|
2
|
-
import '../../../../stream-
|
|
1
|
+
import { P as PubSubAdapter } from '../../../../types-CE4B7pno.js';
|
|
2
|
+
import '../../../../stream-S7nwQRqM.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @fileoverview Fastify adapter for pub-sub stream resumption.
|
|
@@ -23,10 +23,10 @@ interface FastifyReply {
|
|
|
23
23
|
/**
|
|
24
24
|
* Stream buffered and live events to a Fastify reply.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
* 1. Replays
|
|
28
|
-
* 2.
|
|
29
|
-
* 3.
|
|
26
|
+
* Handles reconnection for Fastify routes:
|
|
27
|
+
* 1. Replays buffered events from the adapter
|
|
28
|
+
* 2. Subscribes to live events until completion signal
|
|
29
|
+
* 3. Ends when stream completes or client disconnects
|
|
30
30
|
*
|
|
31
31
|
* @param streamId - The stream ID to subscribe to
|
|
32
32
|
* @param adapter - The pub-sub adapter instance
|
|
@@ -34,11 +34,25 @@ interface FastifyReply {
|
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
36
36
|
* ```typescript
|
|
37
|
-
* import {
|
|
37
|
+
* import { llm } from '@providerprotocol/ai';
|
|
38
|
+
* import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
39
|
+
* import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
|
|
40
|
+
* import { fastify as pubsubFastify } from '@providerprotocol/ai/middleware/pubsub/server';
|
|
38
41
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
+
* const adapter = memoryAdapter();
|
|
43
|
+
*
|
|
44
|
+
* app.post('/api/chat', async (request, reply) => {
|
|
45
|
+
* const { input, conversationId } = request.body as { input: string; conversationId: string };
|
|
46
|
+
*
|
|
47
|
+
* if (!await adapter.exists(conversationId)) {
|
|
48
|
+
* const model = llm({
|
|
49
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
50
|
+
* middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
|
|
51
|
+
* });
|
|
52
|
+
* model.stream(input).then(turn => saveToDatabase(conversationId, turn));
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* return pubsubFastify.streamSubscriber(conversationId, adapter, reply);
|
|
42
56
|
* });
|
|
43
57
|
* ```
|
|
44
58
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fastify,
|
|
3
3
|
streamSubscriber
|
|
4
|
-
} from "../../../../chunk-
|
|
5
|
-
import "../../../../chunk-
|
|
4
|
+
} from "../../../../chunk-CRP6Y7NF.js";
|
|
5
|
+
import "../../../../chunk-EPB3GQNL.js";
|
|
6
6
|
import "../../../../chunk-ETBFOLQN.js";
|
|
7
7
|
export {
|
|
8
8
|
fastify,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PubSubAdapter } from '../../../../types-
|
|
2
|
-
import '../../../../stream-
|
|
1
|
+
import { P as PubSubAdapter } from '../../../../types-CE4B7pno.js';
|
|
2
|
+
import '../../../../stream-S7nwQRqM.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @fileoverview H3/Nitro/Nuxt adapter for pub-sub stream resumption.
|
|
@@ -26,10 +26,10 @@ interface H3Event {
|
|
|
26
26
|
/**
|
|
27
27
|
* Stream buffered and live events to an H3 event response.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
* 1. Replays
|
|
31
|
-
* 2.
|
|
32
|
-
* 3.
|
|
29
|
+
* Handles reconnection for H3/Nuxt routes:
|
|
30
|
+
* 1. Replays buffered events from the adapter
|
|
31
|
+
* 2. Subscribes to live events until completion signal
|
|
32
|
+
* 3. Ends when stream completes or client disconnects
|
|
33
33
|
*
|
|
34
34
|
* @param streamId - The stream ID to subscribe to
|
|
35
35
|
* @param adapter - The pub-sub adapter instance
|
|
@@ -37,11 +37,25 @@ interface H3Event {
|
|
|
37
37
|
*
|
|
38
38
|
* @example
|
|
39
39
|
* ```typescript
|
|
40
|
-
* import {
|
|
40
|
+
* import { llm } from '@providerprotocol/ai';
|
|
41
|
+
* import { anthropic } from '@providerprotocol/ai/anthropic';
|
|
42
|
+
* import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
|
|
43
|
+
* import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';
|
|
44
|
+
*
|
|
45
|
+
* const adapter = memoryAdapter();
|
|
41
46
|
*
|
|
42
47
|
* export default defineEventHandler(async (event) => {
|
|
43
|
-
* const {
|
|
44
|
-
*
|
|
48
|
+
* const { input, conversationId } = await readBody(event);
|
|
49
|
+
*
|
|
50
|
+
* if (!await adapter.exists(conversationId)) {
|
|
51
|
+
* const model = llm({
|
|
52
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
53
|
+
* middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
|
|
54
|
+
* });
|
|
55
|
+
* model.stream(input).then(turn => saveToDatabase(conversationId, turn));
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* return h3.streamSubscriber(conversationId, adapter, event);
|
|
45
59
|
* });
|
|
46
60
|
* ```
|
|
47
61
|
*/
|
|
@@ -6,8 +6,8 @@ import { streamSubscriber } from './express/index.js';
|
|
|
6
6
|
export { express } from './express/index.js';
|
|
7
7
|
import { createSubscriberStream } from './webapi/index.js';
|
|
8
8
|
export { webapi } from './webapi/index.js';
|
|
9
|
-
export { P as PubSubAdapter } from '../../../types-
|
|
10
|
-
import '../../../stream-
|
|
9
|
+
export { P as PubSubAdapter } from '../../../types-CE4B7pno.js';
|
|
10
|
+
import '../../../stream-S7nwQRqM.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Server adapters namespace for pub-sub stream resumption.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
webapi
|
|
3
|
-
} from "../../../chunk-
|
|
3
|
+
} from "../../../chunk-2YXFLRQ6.js";
|
|
4
4
|
import {
|
|
5
5
|
express
|
|
6
|
-
} from "../../../chunk-
|
|
6
|
+
} from "../../../chunk-5IWHCXKN.js";
|
|
7
7
|
import {
|
|
8
8
|
h3
|
|
9
|
-
} from "../../../chunk-
|
|
9
|
+
} from "../../../chunk-4RX4VQCB.js";
|
|
10
10
|
import {
|
|
11
11
|
fastify
|
|
12
|
-
} from "../../../chunk-
|
|
13
|
-
import "../../../chunk-
|
|
12
|
+
} from "../../../chunk-CRP6Y7NF.js";
|
|
13
|
+
import "../../../chunk-EPB3GQNL.js";
|
|
14
14
|
import "../../../chunk-ETBFOLQN.js";
|
|
15
15
|
|
|
16
16
|
// src/middleware/pubsub/server/index.ts
|