@providerprotocol/ai 0.0.40 → 0.0.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.
@@ -0,0 +1,49 @@
1
+ import {
2
+ runSubscriberStream
3
+ } from "./chunk-L6QWKFGE.js";
4
+
5
+ // src/middleware/pubsub/server/h3.ts
6
+ function createSubscriberSSEStream(streamId, adapter) {
7
+ const encoder = new TextEncoder();
8
+ const abortController = new AbortController();
9
+ let closed = false;
10
+ return new ReadableStream({
11
+ async start(controller) {
12
+ await runSubscriberStream(
13
+ streamId,
14
+ adapter,
15
+ {
16
+ write: (data) => {
17
+ if (closed) {
18
+ return;
19
+ }
20
+ controller.enqueue(encoder.encode(data));
21
+ },
22
+ end: () => {
23
+ if (closed) {
24
+ return;
25
+ }
26
+ closed = true;
27
+ try {
28
+ controller.close();
29
+ } catch {
30
+ }
31
+ }
32
+ },
33
+ { signal: abortController.signal }
34
+ );
35
+ },
36
+ cancel() {
37
+ abortController.abort();
38
+ }
39
+ });
40
+ }
41
+ var h3 = {
42
+ createSubscriberSSEStream
43
+ };
44
+
45
+ export {
46
+ createSubscriberSSEStream,
47
+ h3
48
+ };
49
+ //# sourceMappingURL=chunk-5CJCCJBJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/pubsub/server/h3.ts"],"sourcesContent":["/**\n * @fileoverview H3/Nitro/Nuxt adapter for pub-sub stream resumption.\n *\n * Provides utilities for H3-based servers (Nuxt, Nitro, or standalone H3)\n * to handle stream reconnections.\n *\n * @module middleware/pubsub/server/h3\n */\n\nimport type { PubSubAdapter } from '../types.ts';\nimport { runSubscriberStream } from './shared.ts';\n\n/**\n * Creates a ReadableStream that replays buffered events and subscribes to live events.\n *\n * Use with H3's `sendStream` for proper chunked streaming that works\n * correctly in production (Nitro builds, reverse proxies, compression):\n *\n * ```typescript\n * import { sendStream } from 'h3';\n * return sendStream(event, h3.createSubscriberSSEStream(streamId, adapter));\n * ```\n *\n * @param streamId - The stream ID to subscribe to\n * @param adapter - The pub-sub adapter instance\n * @returns A ReadableStream of SSE-formatted data\n *\n * @example\n * ```typescript\n * import { sendStream } from 'h3';\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 * if (!await adapter.exists(conversationId)) {\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(conversationId, turn));\n * }\n *\n * return sendStream(event, h3.createSubscriberSSEStream(conversationId, adapter));\n * });\n * ```\n */\nexport function createSubscriberSSEStream(\n streamId: string,\n adapter: PubSubAdapter,\n): ReadableStream<Uint8Array> {\n const encoder = new TextEncoder();\n const abortController = new AbortController();\n let closed = false;\n\n return new ReadableStream({\n async start(controller) {\n await runSubscriberStream(\n streamId,\n adapter,\n {\n write: (data: string) => {\n if (closed) {\n return;\n }\n controller.enqueue(encoder.encode(data));\n },\n end: () => {\n if (closed) {\n return;\n }\n closed = true;\n try {\n controller.close();\n } catch {\n // Ignore close errors after cancellation\n }\n },\n },\n { signal: abortController.signal }\n );\n },\n cancel() {\n abortController.abort();\n },\n });\n}\n\n/**\n * H3 adapter namespace for pub-sub server utilities.\n */\nexport const h3 = {\n createSubscriberSSEStream,\n};\n"],"mappings":";;;;;AAoDO,SAAS,0BACd,UACA,SAC4B;AAC5B,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,kBAAkB,IAAI,gBAAgB;AAC5C,MAAI,SAAS;AAEb,SAAO,IAAI,eAAe;AAAA,IACxB,MAAM,MAAM,YAAY;AACtB,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,UACE,OAAO,CAAC,SAAiB;AACvB,gBAAI,QAAQ;AACV;AAAA,YACF;AACA,uBAAW,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,UACzC;AAAA,UACA,KAAK,MAAM;AACT,gBAAI,QAAQ;AACV;AAAA,YACF;AACA,qBAAS;AACT,gBAAI;AACF,yBAAW,MAAM;AAAA,YACnB,QAAQ;AAAA,YAER;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,QAAQ,gBAAgB,OAAO;AAAA,MACnC;AAAA,IACF;AAAA,IACA,SAAS;AACP,sBAAgB,MAAM;AAAA,IACxB;AAAA,EACF,CAAC;AACH;AAKO,IAAM,KAAK;AAAA,EAChB;AACF;","names":[]}
@@ -93,14 +93,21 @@ function persistenceMiddleware(options) {
93
93
  }
94
94
  ctx.state.set(STATE_KEY_THREAD, thread);
95
95
  if (thread.messages.length > 0) {
96
- const existingIds = new Set(ctx.request.messages.map((message) => message.id));
97
- const missing = thread.messages.filter((message) => !existingIds.has(message.id));
98
- if (missing.length > 0) {
99
- ctx.request.messages.unshift(...missing);
100
- const currentIndex = ctx.state.get(TURN_START_INDEX_KEY);
101
- const nextIndex = (typeof currentIndex === "number" ? currentIndex : 0) + missing.length;
102
- ctx.state.set(TURN_START_INDEX_KEY, nextIndex);
96
+ const requestById = new Map(ctx.request.messages.map((message) => [message.id, message]));
97
+ const threadIds = new Set(thread.messages.map((message) => message.id));
98
+ const mergedMessages = [];
99
+ for (const message of thread.messages) {
100
+ mergedMessages.push(requestById.get(message.id) ?? message);
103
101
  }
102
+ for (const message of ctx.request.messages) {
103
+ if (!threadIds.has(message.id)) {
104
+ mergedMessages.push(message);
105
+ }
106
+ }
107
+ ctx.request.messages.splice(0, ctx.request.messages.length, ...mergedMessages);
108
+ const currentIndex = ctx.state.get(TURN_START_INDEX_KEY);
109
+ const nextIndex = (typeof currentIndex === "number" ? currentIndex : 0) + thread.messages.length;
110
+ ctx.state.set(TURN_START_INDEX_KEY, nextIndex);
104
111
  }
105
112
  },
106
113
  async onTurn(turn, ctx) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/middleware/persistence.ts"],"sourcesContent":["/**\n * @fileoverview Persistence middleware for thread storage.\n *\n * Loads a conversation thread before execution and saves it after completion.\n * Designed for LLM requests; other modalities are ignored.\n *\n * @module middleware/persistence\n */\n\nimport type { Middleware, MiddlewareContext } from '../types/middleware.ts';\nimport type { LLMRequest } from '../types/llm.ts';\nimport type { Turn } from '../types/turn.ts';\nimport type { ThreadJSON } from '../types/thread.ts';\nimport { Thread } from '../types/thread.ts';\nimport { toError } from '../utils/error.ts';\n\nconst STATE_KEY_THREAD = 'persistence:thread';\nconst STATE_KEY_ID = 'persistence:id';\nconst TURN_START_INDEX_KEY = 'llm:turnStartIndex';\n\nconst isLLMRequest = (request: MiddlewareContext['request']): request is LLMRequest => (\n 'messages' in request\n);\n\n/**\n * Load result for persistence adapters.\n */\nexport type PersistenceLoadResult = Thread | ThreadJSON | null | undefined;\n\n/**\n * Adapter configuration for persistence middleware.\n */\nexport interface PersistenceAdapterConfig {\n /**\n * Unique identifier for the conversation.\n */\n id: string;\n\n /**\n * Loads a thread for the provided ID.\n *\n * Return a Thread instance, ThreadJSON, or null/undefined for new threads.\n *\n * @param id - Conversation identifier\n */\n load(id: string): Promise<PersistenceLoadResult>;\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n save(id: string, thread: Thread, turn: Turn | undefined): Promise<void>;\n}\n\n/**\n * Persistence adapter implementation.\n *\n * Provides a thin wrapper around load/save callbacks.\n */\nexport class PersistenceAdapter {\n readonly id: string;\n\n private readonly loader: PersistenceAdapterConfig['load'];\n\n private readonly saver: PersistenceAdapterConfig['save'];\n\n /**\n * Creates a persistence adapter.\n *\n * @param config - Adapter configuration\n */\n constructor(config: PersistenceAdapterConfig) {\n this.id = config.id;\n this.loader = config.load;\n this.saver = config.save;\n }\n\n /**\n * Loads a thread for the provided ID.\n *\n * @param id - Conversation identifier\n */\n async load(id: string): Promise<PersistenceLoadResult> {\n return this.loader(id);\n }\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n async save(id: string, thread: Thread, turn: Turn | undefined): Promise<void> {\n await this.saver(id, thread, turn);\n }\n}\n\n/**\n * Options for persistence middleware.\n */\nexport interface PersistenceOptions {\n /**\n * Adapter instance for loading and saving threads.\n */\n adapter: PersistenceAdapter;\n}\n\n/**\n * Gets the loaded thread from middleware state.\n *\n * @param state - Middleware state map\n * @returns Thread instance or undefined if not set\n */\nexport function getThread(state: Map<string, unknown>): Thread | undefined {\n return state.get(STATE_KEY_THREAD) as Thread | undefined;\n}\n\n/**\n * Gets the conversation ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Conversation ID or undefined if not set\n */\nexport function getThreadId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_ID) as string | undefined;\n}\n\n/**\n * Creates persistence middleware for thread storage.\n *\n * Loads a thread before requests and saves it after completion. The middleware\n * prepends loaded messages that are not already present in the request so turn\n * slicing excludes persisted history without duplicating explicit history.\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 { persistenceMiddleware, PersistenceAdapter } from '@providerprotocol/ai/middleware/persistence';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * system: 'You are a helpful assistant.',\n * middleware: [\n * persistenceMiddleware({\n * adapter: new PersistenceAdapter({\n * id: 'conversation-id',\n * load: async (id) => loadThreadFromMemory(id),\n * save: async (id, thread) => saveThreadToMemory(id, thread),\n * }),\n * }),\n * ],\n * });\n * ```\n */\nexport function persistenceMiddleware(options: PersistenceOptions): Middleware {\n const { adapter } = options;\n\n if (!adapter?.id) {\n throw new Error('persistenceMiddleware requires an adapter with a non-empty id');\n }\n if (typeof adapter.load !== 'function' || typeof adapter.save !== 'function') {\n throw new Error('persistenceMiddleware requires an adapter with load and save functions');\n }\n\n return {\n name: 'persistence',\n\n async onRequest(ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm' || !isLLMRequest(ctx.request)) {\n return;\n }\n\n ctx.state.set(STATE_KEY_ID, adapter.id);\n\n let loaded: PersistenceLoadResult;\n try {\n loaded = await adapter.load(adapter.id);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to load thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n\n let thread: Thread;\n if (!loaded) {\n thread = new Thread();\n } else if (loaded instanceof Thread) {\n thread = loaded;\n } else {\n try {\n thread = Thread.fromJSON(loaded);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to deserialize thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n }\n\n ctx.state.set(STATE_KEY_THREAD, thread);\n\n if (thread.messages.length > 0) {\n const existingIds = new Set(ctx.request.messages.map((message) => message.id));\n const missing = thread.messages.filter((message) => !existingIds.has(message.id));\n if (missing.length > 0) {\n ctx.request.messages.unshift(...missing);\n const currentIndex = ctx.state.get(TURN_START_INDEX_KEY);\n const nextIndex = (typeof currentIndex === 'number' ? currentIndex : 0) + missing.length;\n ctx.state.set(TURN_START_INDEX_KEY, nextIndex);\n }\n }\n },\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm') {\n return;\n }\n\n const thread = getThread(ctx.state);\n if (!thread) {\n return;\n }\n\n if (isLLMRequest(ctx.request)) {\n const turnMessageIds = new Set(turn.messages.map((message) => message.id));\n const existingIds = new Set(thread.messages.map((message) => message.id));\n for (const message of ctx.request.messages) {\n if (turnMessageIds.has(message.id)) {\n continue;\n }\n if (!existingIds.has(message.id)) {\n thread.push(message);\n existingIds.add(message.id);\n }\n }\n }\n\n thread.append(turn);\n\n try {\n await adapter.save(adapter.id, thread, turn);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to save thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/middleware/persistence.ts"],"sourcesContent":["/**\n * @fileoverview Persistence middleware for thread storage.\n *\n * Loads a conversation thread before execution and saves it after completion.\n * Designed for LLM requests; other modalities are ignored.\n *\n * @module middleware/persistence\n */\n\nimport type { Middleware, MiddlewareContext } from '../types/middleware.ts';\nimport type { LLMRequest } from '../types/llm.ts';\nimport type { Turn } from '../types/turn.ts';\nimport type { ThreadJSON } from '../types/thread.ts';\nimport { Thread } from '../types/thread.ts';\nimport { toError } from '../utils/error.ts';\n\nconst STATE_KEY_THREAD = 'persistence:thread';\nconst STATE_KEY_ID = 'persistence:id';\nconst TURN_START_INDEX_KEY = 'llm:turnStartIndex';\n\nconst isLLMRequest = (request: MiddlewareContext['request']): request is LLMRequest => (\n 'messages' in request\n);\n\n/**\n * Load result for persistence adapters.\n */\nexport type PersistenceLoadResult = Thread | ThreadJSON | null | undefined;\n\n/**\n * Adapter configuration for persistence middleware.\n */\nexport interface PersistenceAdapterConfig {\n /**\n * Unique identifier for the conversation.\n */\n id: string;\n\n /**\n * Loads a thread for the provided ID.\n *\n * Return a Thread instance, ThreadJSON, or null/undefined for new threads.\n *\n * @param id - Conversation identifier\n */\n load(id: string): Promise<PersistenceLoadResult>;\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n save(id: string, thread: Thread, turn: Turn | undefined): Promise<void>;\n}\n\n/**\n * Persistence adapter implementation.\n *\n * Provides a thin wrapper around load/save callbacks.\n */\nexport class PersistenceAdapter {\n readonly id: string;\n\n private readonly loader: PersistenceAdapterConfig['load'];\n\n private readonly saver: PersistenceAdapterConfig['save'];\n\n /**\n * Creates a persistence adapter.\n *\n * @param config - Adapter configuration\n */\n constructor(config: PersistenceAdapterConfig) {\n this.id = config.id;\n this.loader = config.load;\n this.saver = config.save;\n }\n\n /**\n * Loads a thread for the provided ID.\n *\n * @param id - Conversation identifier\n */\n async load(id: string): Promise<PersistenceLoadResult> {\n return this.loader(id);\n }\n\n /**\n * Persists the thread after a turn completes.\n *\n * @param id - Conversation identifier\n * @param thread - Updated thread instance\n * @param turn - Completed turn (undefined if not available)\n */\n async save(id: string, thread: Thread, turn: Turn | undefined): Promise<void> {\n await this.saver(id, thread, turn);\n }\n}\n\n/**\n * Options for persistence middleware.\n */\nexport interface PersistenceOptions {\n /**\n * Adapter instance for loading and saving threads.\n */\n adapter: PersistenceAdapter;\n}\n\n/**\n * Gets the loaded thread from middleware state.\n *\n * @param state - Middleware state map\n * @returns Thread instance or undefined if not set\n */\nexport function getThread(state: Map<string, unknown>): Thread | undefined {\n return state.get(STATE_KEY_THREAD) as Thread | undefined;\n}\n\n/**\n * Gets the conversation ID from middleware state.\n *\n * @param state - Middleware state map\n * @returns Conversation ID or undefined if not set\n */\nexport function getThreadId(state: Map<string, unknown>): string | undefined {\n return state.get(STATE_KEY_ID) as string | undefined;\n}\n\n/**\n * Creates persistence middleware for thread storage.\n *\n * Loads a thread before requests and saves it after completion. The middleware\n * prepends loaded messages that are not already present in the request so turn\n * slicing excludes persisted history without duplicating explicit history.\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 { persistenceMiddleware, PersistenceAdapter } from '@providerprotocol/ai/middleware/persistence';\n *\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * system: 'You are a helpful assistant.',\n * middleware: [\n * persistenceMiddleware({\n * adapter: new PersistenceAdapter({\n * id: 'conversation-id',\n * load: async (id) => loadThreadFromMemory(id),\n * save: async (id, thread) => saveThreadToMemory(id, thread),\n * }),\n * }),\n * ],\n * });\n * ```\n */\nexport function persistenceMiddleware(options: PersistenceOptions): Middleware {\n const { adapter } = options;\n\n if (!adapter?.id) {\n throw new Error('persistenceMiddleware requires an adapter with a non-empty id');\n }\n if (typeof adapter.load !== 'function' || typeof adapter.save !== 'function') {\n throw new Error('persistenceMiddleware requires an adapter with load and save functions');\n }\n\n return {\n name: 'persistence',\n\n async onRequest(ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm' || !isLLMRequest(ctx.request)) {\n return;\n }\n\n ctx.state.set(STATE_KEY_ID, adapter.id);\n\n let loaded: PersistenceLoadResult;\n try {\n loaded = await adapter.load(adapter.id);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to load thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n\n let thread: Thread;\n if (!loaded) {\n thread = new Thread();\n } else if (loaded instanceof Thread) {\n thread = loaded;\n } else {\n try {\n thread = Thread.fromJSON(loaded);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to deserialize thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n }\n\n ctx.state.set(STATE_KEY_THREAD, thread);\n\n if (thread.messages.length > 0) {\n const requestById = new Map(ctx.request.messages.map((message) => [message.id, message]));\n const threadIds = new Set(thread.messages.map((message) => message.id));\n const mergedMessages: LLMRequest['messages'] = [];\n\n for (const message of thread.messages) {\n mergedMessages.push(requestById.get(message.id) ?? message);\n }\n\n for (const message of ctx.request.messages) {\n if (!threadIds.has(message.id)) {\n mergedMessages.push(message);\n }\n }\n\n ctx.request.messages.splice(0, ctx.request.messages.length, ...mergedMessages);\n\n const currentIndex = ctx.state.get(TURN_START_INDEX_KEY);\n const nextIndex = (typeof currentIndex === 'number' ? currentIndex : 0) + thread.messages.length;\n ctx.state.set(TURN_START_INDEX_KEY, nextIndex);\n }\n },\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n if (ctx.modality !== 'llm') {\n return;\n }\n\n const thread = getThread(ctx.state);\n if (!thread) {\n return;\n }\n\n if (isLLMRequest(ctx.request)) {\n const turnMessageIds = new Set(turn.messages.map((message) => message.id));\n const existingIds = new Set(thread.messages.map((message) => message.id));\n for (const message of ctx.request.messages) {\n if (turnMessageIds.has(message.id)) {\n continue;\n }\n if (!existingIds.has(message.id)) {\n thread.push(message);\n existingIds.add(message.id);\n }\n }\n }\n\n thread.append(turn);\n\n try {\n await adapter.save(adapter.id, thread, turn);\n } catch (error) {\n const err = toError(error);\n throw new Error(`Persistence adapter failed to save thread \"${adapter.id}\": ${err.message}`, {\n cause: err,\n });\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -12,32 +12,23 @@ import '../../../../tool-BmAfKNBq.js';
12
12
  */
13
13
 
14
14
  /**
15
- * H3 Event interface (minimal type to avoid dependency).
16
- */
17
- interface H3Event {
18
- node: {
19
- res: {
20
- setHeader(name: string, value: string): void;
21
- write(chunk: string): boolean;
22
- end(): void;
23
- on(event: 'close', listener: () => void): void;
24
- };
25
- };
26
- }
27
- /**
28
- * Stream buffered and live events to an H3 event response.
15
+ * Creates a ReadableStream that replays buffered events and subscribes to live events.
16
+ *
17
+ * Use with H3's `sendStream` for proper chunked streaming that works
18
+ * correctly in production (Nitro builds, reverse proxies, compression):
29
19
  *
30
- * Handles reconnection for H3/Nuxt routes:
31
- * 1. Replays buffered events from the adapter
32
- * 2. Subscribes to live events until completion signal
33
- * 3. Ends when stream completes or client disconnects
20
+ * ```typescript
21
+ * import { sendStream } from 'h3';
22
+ * return sendStream(event, h3.createSubscriberSSEStream(streamId, adapter));
23
+ * ```
34
24
  *
35
25
  * @param streamId - The stream ID to subscribe to
36
26
  * @param adapter - The pub-sub adapter instance
37
- * @param event - H3 event object
27
+ * @returns A ReadableStream of SSE-formatted data
38
28
  *
39
29
  * @example
40
30
  * ```typescript
31
+ * import { sendStream } from 'h3';
41
32
  * import { llm } from '@providerprotocol/ai';
42
33
  * import { anthropic } from '@providerprotocol/ai/anthropic';
43
34
  * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';
@@ -56,16 +47,16 @@ interface H3Event {
56
47
  * model.stream(input).then(turn => saveToDatabase(conversationId, turn));
57
48
  * }
58
49
  *
59
- * return h3.streamSubscriber(conversationId, adapter, event);
50
+ * return sendStream(event, h3.createSubscriberSSEStream(conversationId, adapter));
60
51
  * });
61
52
  * ```
62
53
  */
63
- declare function streamSubscriber(streamId: string, adapter: PubSubAdapter, event: H3Event): Promise<void>;
54
+ declare function createSubscriberSSEStream(streamId: string, adapter: PubSubAdapter): ReadableStream<Uint8Array>;
64
55
  /**
65
56
  * H3 adapter namespace for pub-sub server utilities.
66
57
  */
67
58
  declare const h3: {
68
- streamSubscriber: typeof streamSubscriber;
59
+ createSubscriberSSEStream: typeof createSubscriberSSEStream;
69
60
  };
70
61
 
71
- export { h3, streamSubscriber };
62
+ export { createSubscriberSSEStream, h3 };
@@ -1,11 +1,11 @@
1
1
  import {
2
- h3,
3
- streamSubscriber
4
- } from "../../../../chunk-KBI45OXI.js";
2
+ createSubscriberSSEStream,
3
+ h3
4
+ } from "../../../../chunk-5CJCCJBJ.js";
5
5
  import "../../../../chunk-L6QWKFGE.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
- h3,
9
- streamSubscriber
8
+ createSubscriberSSEStream,
9
+ h3
10
10
  };
11
11
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- import { streamSubscriber as streamSubscriber$2 } from './h3/index.js';
1
+ import { createSubscriberSSEStream } from './h3/index.js';
2
2
  export { h3 } from './h3/index.js';
3
3
  import { streamSubscriber as streamSubscriber$1 } from './fastify/index.js';
4
4
  export { fastify } from './fastify/index.js';
@@ -80,6 +80,7 @@ import '../../../tool-BmAfKNBq.js';
80
80
  *
81
81
  * @example H3/Nuxt
82
82
  * ```typescript
83
+ * import { sendStream } from 'h3';
83
84
  * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';
84
85
  *
85
86
  * export default defineEventHandler(async (event) => {
@@ -94,7 +95,7 @@ import '../../../tool-BmAfKNBq.js';
94
95
  * model.stream(messages).then(turn => saveToDatabase(turn));
95
96
  * }
96
97
  *
97
- * return h3.streamSubscriber(streamId, adapter, event);
98
+ * return sendStream(event, h3.createSubscriberSSEStream(streamId, adapter));
98
99
  * });
99
100
  * ```
100
101
  */
@@ -113,7 +114,7 @@ declare const server: {
113
114
  };
114
115
  /** H3/Nitro/Nuxt adapter */
115
116
  h3: {
116
- streamSubscriber: typeof streamSubscriber$2;
117
+ createSubscriberSSEStream: typeof createSubscriberSSEStream;
117
118
  };
118
119
  };
119
120
 
@@ -6,7 +6,7 @@ import {
6
6
  } from "../../../chunk-KVUOTFYZ.js";
7
7
  import {
8
8
  h3
9
- } from "../../../chunk-KBI45OXI.js";
9
+ } from "../../../chunk-5CJCCJBJ.js";
10
10
  import {
11
11
  fastify
12
12
  } from "../../../chunk-CWGTARDE.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/middleware/pubsub/server/index.ts"],"sourcesContent":["/**\n * @fileoverview Framework adapters for pub-sub stream resumption.\n *\n * Provides framework-specific adapters for handling stream reconnections\n * with various server frameworks. The Web API adapter works with modern\n * frameworks like Bun, Deno, Next.js App Router, and Cloudflare Workers.\n * Additional adapters provide native integration for Express, Fastify, and H3/Nuxt.\n *\n * @module middleware/pubsub/server\n */\n\nimport { express } from './express.ts';\nimport { fastify } from './fastify.ts';\nimport { h3 } from './h3.ts';\nimport { webapi } from './webapi.ts';\n\nexport { express, fastify, h3, webapi };\nexport type { PubSubAdapter } from '../types.ts';\n\n/**\n * Server adapters namespace for pub-sub stream resumption.\n *\n * Contains framework-specific adapters for Web API, Express, Fastify, and H3.\n * Always guard with `adapter.exists()` to prevent duplicate generations on reconnect.\n *\n * @example Web API (Next.js App Router, Bun, Deno)\n * ```typescript\n * import { webapi } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * export async function POST(req: Request) {\n * const { messages, streamId } = await req.json();\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * return new Response(webapi.createSubscriberStream(streamId, adapter), {\n * headers: { 'Content-Type': 'text/event-stream' },\n * });\n * }\n * ```\n *\n * @example Express\n * ```typescript\n * import { express } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * app.post('/api/ai', async (req, res) => {\n * const { messages, streamId } = req.body;\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * express.streamSubscriber(streamId, adapter, res);\n * });\n * ```\n *\n * @example Fastify\n * ```typescript\n * import { fastify } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * app.post('/api/ai', async (request, reply) => {\n * const { messages, streamId } = request.body;\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * return fastify.streamSubscriber(streamId, adapter, reply);\n * });\n * ```\n *\n * @example H3/Nuxt\n * ```typescript\n * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * export default defineEventHandler(async (event) => {\n * const { messages, streamId } = await readBody(event);\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * return h3.streamSubscriber(streamId, adapter, event);\n * });\n * ```\n */\nexport const server = {\n /** Web API adapter (Bun, Deno, Next.js, Workers) */\n webapi,\n /** Express/Connect adapter */\n express,\n /** Fastify adapter */\n fastify,\n /** H3/Nitro/Nuxt adapter */\n h3,\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA2GO,IAAM,SAAS;AAAA;AAAA,EAEpB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/middleware/pubsub/server/index.ts"],"sourcesContent":["/**\n * @fileoverview Framework adapters for pub-sub stream resumption.\n *\n * Provides framework-specific adapters for handling stream reconnections\n * with various server frameworks. The Web API adapter works with modern\n * frameworks like Bun, Deno, Next.js App Router, and Cloudflare Workers.\n * Additional adapters provide native integration for Express, Fastify, and H3/Nuxt.\n *\n * @module middleware/pubsub/server\n */\n\nimport { express } from './express.ts';\nimport { fastify } from './fastify.ts';\nimport { h3 } from './h3.ts';\nimport { webapi } from './webapi.ts';\n\nexport { express, fastify, h3, webapi };\nexport type { PubSubAdapter } from '../types.ts';\n\n/**\n * Server adapters namespace for pub-sub stream resumption.\n *\n * Contains framework-specific adapters for Web API, Express, Fastify, and H3.\n * Always guard with `adapter.exists()` to prevent duplicate generations on reconnect.\n *\n * @example Web API (Next.js App Router, Bun, Deno)\n * ```typescript\n * import { webapi } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * export async function POST(req: Request) {\n * const { messages, streamId } = await req.json();\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * return new Response(webapi.createSubscriberStream(streamId, adapter), {\n * headers: { 'Content-Type': 'text/event-stream' },\n * });\n * }\n * ```\n *\n * @example Express\n * ```typescript\n * import { express } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * app.post('/api/ai', async (req, res) => {\n * const { messages, streamId } = req.body;\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * express.streamSubscriber(streamId, adapter, res);\n * });\n * ```\n *\n * @example Fastify\n * ```typescript\n * import { fastify } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * app.post('/api/ai', async (request, reply) => {\n * const { messages, streamId } = request.body;\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * return fastify.streamSubscriber(streamId, adapter, reply);\n * });\n * ```\n *\n * @example H3/Nuxt\n * ```typescript\n * import { sendStream } from 'h3';\n * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';\n *\n * export default defineEventHandler(async (event) => {\n * const { messages, streamId } = await readBody(event);\n *\n * // Guard: prevent duplicate generations on reconnect\n * if (!await adapter.exists(streamId)) {\n * const model = llm({\n * model: anthropic('claude-sonnet-4-20250514'),\n * middleware: [pubsubMiddleware({ adapter, streamId })],\n * });\n * model.stream(messages).then(turn => saveToDatabase(turn));\n * }\n *\n * return sendStream(event, h3.createSubscriberSSEStream(streamId, adapter));\n * });\n * ```\n */\nexport const server = {\n /** Web API adapter (Bun, Deno, Next.js, Workers) */\n webapi,\n /** Express/Connect adapter */\n express,\n /** Fastify adapter */\n fastify,\n /** H3/Nitro/Nuxt adapter */\n h3,\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA4GO,IAAM,SAAS;AAAA;AAAA,EAEpB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@providerprotocol/ai",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "description": "UPP: Unified Provider Protocol for AI inference",
5
5
  "license": "MIT",
6
6
  "homepage": "https://providerprotocol.org",
@@ -165,10 +165,14 @@
165
165
  "scripts": {
166
166
  "build": "tsup",
167
167
  "prepublishOnly": "bun run build",
168
- "test": "bun test",
168
+ "test": "bun run test:unit",
169
169
  "lint": "bunx eslint . --max-warnings 0",
170
170
  "test:unit": "bun test tests/unit",
171
- "test:live": "bun test tests/live",
171
+ "test:live": "bun run test:live:canary",
172
+ "test:live:canary": "bun test tests/live/canary",
173
+ "test:live:nightly": "bun test tests/live/nightly",
174
+ "test:live:release": "bun test tests/live/release",
175
+ "test:all": "bun run test:unit && bun run test:live:canary",
172
176
  "typecheck": "tsc --noEmit",
173
177
  "docgen": "bun run scripts/docgen.ts",
174
178
  "docs:sort": "bun run scripts/sort-docs.ts",
@@ -1,31 +0,0 @@
1
- import {
2
- runSubscriberStream
3
- } from "./chunk-L6QWKFGE.js";
4
-
5
- // src/middleware/pubsub/server/h3.ts
6
- async function streamSubscriber(streamId, adapter, event) {
7
- const res = event.node.res;
8
- res.setHeader("Content-Type", "text/event-stream");
9
- res.setHeader("Cache-Control", "no-cache");
10
- res.setHeader("Connection", "keep-alive");
11
- const abortController = new AbortController();
12
- res.on("close", () => abortController.abort());
13
- await runSubscriberStream(
14
- streamId,
15
- adapter,
16
- {
17
- write: (data) => res.write(data),
18
- end: () => res.end()
19
- },
20
- { signal: abortController.signal }
21
- );
22
- }
23
- var h3 = {
24
- streamSubscriber
25
- };
26
-
27
- export {
28
- streamSubscriber,
29
- h3
30
- };
31
- //# sourceMappingURL=chunk-KBI45OXI.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/middleware/pubsub/server/h3.ts"],"sourcesContent":["/**\n * @fileoverview H3/Nitro/Nuxt adapter for pub-sub stream resumption.\n *\n * Provides utilities for H3-based servers (Nuxt, Nitro, or standalone H3)\n * to handle stream reconnections.\n *\n * @module middleware/pubsub/server/h3\n */\n\nimport type { PubSubAdapter } from '../types.ts';\nimport { runSubscriberStream } from './shared.ts';\n\n/**\n * H3 Event interface (minimal type to avoid dependency).\n */\ninterface H3Event {\n node: {\n res: {\n setHeader(name: string, value: string): void;\n write(chunk: string): boolean;\n end(): void;\n on(event: 'close', listener: () => void): void;\n };\n };\n}\n\n/**\n * Stream buffered and live events to an H3 event response.\n *\n * Handles reconnection for H3/Nuxt routes:\n * 1. Replays buffered events from the adapter\n * 2. Subscribes to live events until completion signal\n * 3. Ends when stream completes or client disconnects\n *\n * @param streamId - The stream ID to subscribe to\n * @param adapter - The pub-sub adapter instance\n * @param event - H3 event object\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 * if (!await adapter.exists(conversationId)) {\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(conversationId, turn));\n * }\n *\n * return h3.streamSubscriber(conversationId, adapter, event);\n * });\n * ```\n */\nexport async function streamSubscriber(\n streamId: string,\n adapter: PubSubAdapter,\n event: H3Event\n): Promise<void> {\n const res = event.node.res;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-cache');\n res.setHeader('Connection', 'keep-alive');\n\n const abortController = new AbortController();\n res.on('close', () => abortController.abort());\n\n await runSubscriberStream(\n streamId,\n adapter,\n {\n write: (data: string) => res.write(data),\n end: () => res.end(),\n },\n { signal: abortController.signal }\n );\n}\n\n/**\n * H3 adapter namespace for pub-sub server utilities.\n */\nexport const h3 = {\n streamSubscriber,\n};\n"],"mappings":";;;;;AA8DA,eAAsB,iBACpB,UACA,SACA,OACe;AACf,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,UAAU,gBAAgB,mBAAmB;AACjD,MAAI,UAAU,iBAAiB,UAAU;AACzC,MAAI,UAAU,cAAc,YAAY;AAExC,QAAM,kBAAkB,IAAI,gBAAgB;AAC5C,MAAI,GAAG,SAAS,MAAM,gBAAgB,MAAM,CAAC;AAE7C,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,MACE,OAAO,CAAC,SAAiB,IAAI,MAAM,IAAI;AAAA,MACvC,KAAK,MAAM,IAAI,IAAI;AAAA,IACrB;AAAA,IACA,EAAE,QAAQ,gBAAgB,OAAO;AAAA,EACnC;AACF;AAKO,IAAM,KAAK;AAAA,EAChB;AACF;","names":[]}