@providerprotocol/ai 0.0.39 → 0.0.40

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.
Files changed (130) hide show
  1. package/README.md +269 -34
  2. package/dist/anthropic/index.d.ts +3 -3
  3. package/dist/anthropic/index.js +7 -5
  4. package/dist/anthropic/index.js.map +1 -1
  5. package/dist/cerebras/index.d.ts +3 -3
  6. package/dist/cerebras/index.js +7 -5
  7. package/dist/cerebras/index.js.map +1 -1
  8. package/dist/{chunk-WU4U6IHF.js → chunk-6QCV4WXF.js} +4 -13
  9. package/dist/chunk-6QCV4WXF.js.map +1 -0
  10. package/dist/{chunk-5XPRVUOK.js → chunk-AC3VHSZJ.js} +2 -2
  11. package/dist/{chunk-5XPRVUOK.js.map → chunk-AC3VHSZJ.js.map} +1 -1
  12. package/dist/{chunk-ZDYEDI2A.js → chunk-CWGTARDE.js} +2 -2
  13. package/dist/{chunk-KNBODIQU.js → chunk-DI47UY2H.js} +2 -2
  14. package/dist/{chunk-KNBODIQU.js.map → chunk-DI47UY2H.js.map} +1 -1
  15. package/dist/{chunk-IDZR4ROP.js → chunk-EHR3LIPS.js} +2 -2
  16. package/dist/{chunk-IDZR4ROP.js.map → chunk-EHR3LIPS.js.map} +1 -1
  17. package/dist/chunk-EY2LLDGY.js +94 -0
  18. package/dist/chunk-EY2LLDGY.js.map +1 -0
  19. package/dist/{chunk-MJI74VEJ.js → chunk-F5ENANMJ.js} +18 -2
  20. package/dist/chunk-F5ENANMJ.js.map +1 -0
  21. package/dist/chunk-IKJH5ZSJ.js +1 -0
  22. package/dist/chunk-IKJH5ZSJ.js.map +1 -0
  23. package/dist/{chunk-IIMTP3XC.js → chunk-KBI45OXI.js} +2 -2
  24. package/dist/{chunk-SAMIK4WZ.js → chunk-KVUOTFYZ.js} +2 -2
  25. package/dist/{chunk-U6M3MXNI.js → chunk-L6QWKFGE.js} +3 -2
  26. package/dist/chunk-L6QWKFGE.js.map +1 -0
  27. package/dist/{chunk-RDC5GYST.js → chunk-N4LAFGLX.js} +7 -7
  28. package/dist/{chunk-ZKNPQBIE.js → chunk-R3T2IYOU.js} +5 -3
  29. package/dist/{chunk-ZKNPQBIE.js.map → chunk-R3T2IYOU.js.map} +1 -1
  30. package/dist/chunk-U2G5PHHL.js +25 -0
  31. package/dist/chunk-U2G5PHHL.js.map +1 -0
  32. package/dist/{chunk-SBGZJVTJ.js → chunk-VQZPADW6.js} +100 -33
  33. package/dist/chunk-VQZPADW6.js.map +1 -0
  34. package/dist/{chunk-O32SBS6S.js → chunk-XTWBAL42.js} +2 -2
  35. package/dist/{chunk-O32SBS6S.js.map → chunk-XTWBAL42.js.map} +1 -1
  36. package/dist/{chunk-WNB5PSY6.js → chunk-ZMESKGUY.js} +2 -2
  37. package/dist/{chunk-7ULSRWDH.js → chunk-ZSZVWLGE.js} +2 -2
  38. package/dist/{embedding-iNQCeXfk.d.ts → embedding-ts1npsDg.d.ts} +1 -1
  39. package/dist/google/index.d.ts +38 -4
  40. package/dist/google/index.js +5 -4
  41. package/dist/google/index.js.map +1 -1
  42. package/dist/groq/index.d.ts +3 -3
  43. package/dist/groq/index.js +7 -5
  44. package/dist/groq/index.js.map +1 -1
  45. package/dist/http/index.d.ts +5 -5
  46. package/dist/http/index.js +19 -22
  47. package/dist/{image-stream-ARno6XlS.d.ts → image-stream-BPml2YZZ.d.ts} +1 -1
  48. package/dist/index.d.ts +8 -8
  49. package/dist/index.js +306 -112
  50. package/dist/index.js.map +1 -1
  51. package/dist/{llm-CZqlijjK.d.ts → llm-BWLaTzzY.d.ts} +75 -29
  52. package/dist/middleware/logging/index.d.ts +3 -3
  53. package/dist/middleware/logging/index.js +3 -0
  54. package/dist/middleware/logging/index.js.map +1 -1
  55. package/dist/middleware/parsed-object/index.d.ts +3 -3
  56. package/dist/middleware/parsed-object/index.js +5 -1
  57. package/dist/middleware/parsed-object/index.js.map +1 -1
  58. package/dist/middleware/persistence/index.d.ts +3 -3
  59. package/dist/middleware/persistence/index.js +3 -2
  60. package/dist/middleware/persistence/index.js.map +1 -1
  61. package/dist/middleware/pipeline/index.d.ts +195 -0
  62. package/dist/middleware/pipeline/index.js +61 -0
  63. package/dist/middleware/pipeline/index.js.map +1 -0
  64. package/dist/middleware/pubsub/index.d.ts +13 -11
  65. package/dist/middleware/pubsub/index.js +31 -5
  66. package/dist/middleware/pubsub/index.js.map +1 -1
  67. package/dist/middleware/pubsub/server/express/index.d.ts +3 -3
  68. package/dist/middleware/pubsub/server/express/index.js +2 -2
  69. package/dist/middleware/pubsub/server/fastify/index.d.ts +3 -3
  70. package/dist/middleware/pubsub/server/fastify/index.js +2 -2
  71. package/dist/middleware/pubsub/server/h3/index.d.ts +3 -3
  72. package/dist/middleware/pubsub/server/h3/index.js +2 -2
  73. package/dist/middleware/pubsub/server/index.d.ts +50 -9
  74. package/dist/middleware/pubsub/server/index.js +5 -5
  75. package/dist/middleware/pubsub/server/index.js.map +1 -1
  76. package/dist/middleware/pubsub/server/webapi/index.d.ts +3 -3
  77. package/dist/middleware/pubsub/server/webapi/index.js +2 -2
  78. package/dist/moonshot/index.d.ts +3 -3
  79. package/dist/moonshot/index.js +7 -5
  80. package/dist/moonshot/index.js.map +1 -1
  81. package/dist/ollama/index.d.ts +24 -4
  82. package/dist/ollama/index.js +5 -4
  83. package/dist/ollama/index.js.map +1 -1
  84. package/dist/openai/index.d.ts +65 -4
  85. package/dist/openai/index.js +7 -5
  86. package/dist/openai/index.js.map +1 -1
  87. package/dist/openrouter/index.d.ts +4 -4
  88. package/dist/openrouter/index.js +7 -5
  89. package/dist/openrouter/index.js.map +1 -1
  90. package/dist/proxy/index.d.ts +5 -5
  91. package/dist/proxy/index.js +16 -15
  92. package/dist/proxy/index.js.map +1 -1
  93. package/dist/proxy/server/express/index.d.ts +8 -9
  94. package/dist/proxy/server/express/index.js +4 -3
  95. package/dist/proxy/server/fastify/index.d.ts +8 -9
  96. package/dist/proxy/server/fastify/index.js +4 -3
  97. package/dist/proxy/server/h3/index.d.ts +8 -9
  98. package/dist/proxy/server/h3/index.js +4 -3
  99. package/dist/proxy/server/index.d.ts +5 -5
  100. package/dist/proxy/server/index.js +14 -13
  101. package/dist/proxy/server/webapi/index.d.ts +8 -9
  102. package/dist/proxy/server/webapi/index.js +4 -3
  103. package/dist/responses/index.d.ts +3 -3
  104. package/dist/responses/index.js +7 -5
  105. package/dist/responses/index.js.map +1 -1
  106. package/dist/retry-DVfdPLIB.d.ts +322 -0
  107. package/dist/{stream-DVVUIKpz.d.ts → stream-bBd_4Ipu.d.ts} +27 -4
  108. package/dist/{tool-D22EhP5F.d.ts → tool-BmAfKNBq.d.ts} +1 -1
  109. package/dist/{types-CyXF0J7C.d.ts → types-nTwlpyJE.d.ts} +13 -1
  110. package/dist/utils/index.d.ts +66 -2
  111. package/dist/utils/index.js +13 -0
  112. package/dist/xai/index.d.ts +3 -3
  113. package/dist/xai/index.js +7 -5
  114. package/dist/xai/index.js.map +1 -1
  115. package/package.json +6 -1
  116. package/dist/chunk-ARVM24K2.js +0 -128
  117. package/dist/chunk-ARVM24K2.js.map +0 -1
  118. package/dist/chunk-MJI74VEJ.js.map +0 -1
  119. package/dist/chunk-SBGZJVTJ.js.map +0 -1
  120. package/dist/chunk-U6M3MXNI.js.map +0 -1
  121. package/dist/chunk-WU4U6IHF.js.map +0 -1
  122. package/dist/chunk-Y5H7C5J4.js +0 -263
  123. package/dist/chunk-Y5H7C5J4.js.map +0 -1
  124. package/dist/retry-C1eJbEMV.d.ts +0 -531
  125. /package/dist/{chunk-ZDYEDI2A.js.map → chunk-CWGTARDE.js.map} +0 -0
  126. /package/dist/{chunk-IIMTP3XC.js.map → chunk-KBI45OXI.js.map} +0 -0
  127. /package/dist/{chunk-SAMIK4WZ.js.map → chunk-KVUOTFYZ.js.map} +0 -0
  128. /package/dist/{chunk-RDC5GYST.js.map → chunk-N4LAFGLX.js.map} +0 -0
  129. /package/dist/{chunk-WNB5PSY6.js.map → chunk-ZMESKGUY.js.map} +0 -0
  130. /package/dist/{chunk-7ULSRWDH.js.map → chunk-ZSZVWLGE.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/middleware/pipeline/index.ts"],"sourcesContent":["/**\n * @fileoverview Pipeline middleware for post-turn processing stages.\n *\n * Enables running async tasks (image generation, embedding, slug generation, etc.)\n * after the LLM completes, while streaming progress events to connected clients.\n *\n * @module middleware/pipeline\n */\n\nimport type { Middleware, MiddlewareContext } from '../../types/middleware.ts';\nimport type { Turn } from '../../types/turn.ts';\nimport type { StreamEvent, EventDelta } from '../../types/stream.ts';\n\n/**\n * Pipeline stage event delta with stage-specific data.\n */\nexport interface PipelineStageDelta extends EventDelta {\n /** Stage identifier */\n stage: string;\n /** Stage output payload */\n payload: unknown;\n}\n\n/**\n * Stream event for pipeline stage progress.\n * Extends StreamEvent with pipeline-specific delta containing stage and payload.\n */\nexport interface PipelineStageEvent extends StreamEvent {\n type: 'pipeline_stage';\n delta: PipelineStageDelta;\n}\n\n/**\n * Creates a pipeline stage stream event.\n *\n * @param stage - The stage identifier\n * @param payload - The stage output payload\n * @returns A pipeline stage event\n */\nexport function pipelineStageEvent(stage: string, payload: unknown): PipelineStageEvent {\n return {\n type: 'pipeline_stage',\n index: 0,\n delta: { stage, payload },\n };\n}\n\n/**\n * Type guard for PipelineStageEvent.\n *\n * @param event - Stream event to check\n * @returns True if the event is a PipelineStageEvent\n *\n * @example\n * ```typescript\n * for await (const event of stream) {\n * if (isPipelineStageEvent(event)) {\n * console.log(event.delta.stage, event.delta.payload);\n * }\n * }\n * ```\n */\nexport function isPipelineStageEvent(event: StreamEvent): event is PipelineStageEvent {\n return event.type === 'pipeline_stage';\n}\n\n/**\n * Emit function provided to pipeline stages.\n */\nexport type PipelineEmit = (data: unknown) => void;\n\n/**\n * A single pipeline stage that runs after turn completion.\n *\n * Stages can mutate the turn object to attach computed properties (like slugs,\n * image URLs, etc.) that will be available in the `.then()` callback. Use a\n * type assertion to add properties:\n *\n * @example\n * ```typescript\n * // Define extended turn type\n * interface ExtendedTurn { slug?: string; imageUrl?: string; }\n *\n * // In stage run function\n * run: (turn, emit) => {\n * const slug = generateSlug(turn.data!.title);\n * (turn as Turn<TData> & ExtendedTurn).slug = slug;\n * emit({ slug });\n * }\n *\n * // Access in .then() callback\n * model.stream(prompt).then(turn => {\n * const extended = turn as typeof turn & ExtendedTurn;\n * console.log(extended.slug); // Available!\n * });\n * ```\n *\n * @typeParam TData - Type of the turn's structured data\n */\nexport interface PipelineStage<TData = unknown> {\n /** Unique identifier for this stage (used in events) */\n type: string;\n\n /**\n * Execute this stage.\n *\n * @param turn - The completed turn (can mutate via type assertion to add properties)\n * @param emit - Function to emit progress events to subscribers\n */\n run: (turn: Turn<TData>, emit: PipelineEmit) => Promise<void> | void;\n}\n\n/**\n * Stage error details passed to onStageError callback.\n */\nexport interface PipelineStageError<TData = unknown> {\n /** The stage that failed */\n stage: PipelineStage<TData>;\n /** The error that occurred */\n error: Error;\n /** The turn being processed */\n turn: Turn<TData>;\n}\n\n/**\n * Pipeline middleware configuration.\n *\n * @typeParam TData - Type of the turn's structured data\n */\nexport interface PipelineConfig<TData = unknown> {\n /** Stages to run after turn completion, executed in order */\n stages: PipelineStage<TData>[];\n\n /** Run stages in parallel instead of sequential (default: false) */\n parallel?: boolean;\n\n /**\n * Continue running subsequent stages even if one fails (default: false).\n * In parallel mode, all stages run regardless; this only affects error propagation.\n */\n continueOnError?: boolean;\n\n /**\n * Called when a stage throws an error.\n * Useful for logging or cleanup. Does not prevent error propagation unless continueOnError is true.\n */\n onStageError?: (details: PipelineStageError<TData>) => void | Promise<void>;\n}\n\n/**\n * Creates pipeline middleware for post-turn processing stages.\n *\n * Pipeline middleware enables running async tasks (image generation, embedding,\n * slug generation, etc.) after the LLM completes, while streaming progress\n * events to connected clients.\n *\n * Events are emitted through the standard middleware pipeline via `ctx.emit()`,\n * so any middleware with `onStreamEvent` (like pubsub) will receive them.\n *\n * **Middleware Order**: Place pipeline AFTER pubsub in the array so that:\n * - `onStart`: pubsub runs first (sets up adapter)\n * - `onTurn`: pipeline runs first (emits events), pubsub runs second (cleans up)\n *\n * @typeParam TData - Type of the turn's structured data\n * @param config - Pipeline middleware configuration\n * @returns A middleware instance\n *\n * @example\n * ```typescript\n * import { llm } from '@providerprotocol/ai';\n * import { openai } from '@providerprotocol/ai/openai';\n * import { pubsubMiddleware, memoryAdapter } from '@providerprotocol/ai/middleware/pubsub';\n * import { pipelineMiddleware } from '@providerprotocol/ai/middleware/pipeline';\n *\n * const adapter = memoryAdapter();\n *\n * const model = llm({\n * model: openai('gpt-4o'),\n * structure: BlogPostSchema,\n * middleware: [\n * pubsubMiddleware({ adapter, streamId: postId }),\n * pipelineMiddleware<BlogPost>({\n * stages: [\n * {\n * type: 'slug',\n * run: async (turn, emit) => {\n * const slug = await generateSlug(turn.data!.title);\n * (turn as { slug?: string }).slug = slug;\n * emit({ slug });\n * },\n * },\n * {\n * type: 'embedding',\n * run: async (turn, emit) => {\n * await vectorize(turn.data!);\n * emit({ embedded: true });\n * },\n * },\n * ],\n * }),\n * ],\n * });\n *\n * model.stream(prompt).then(turn => {\n * const extended = turn as typeof turn & { slug?: string };\n * await db.posts.update({ _id: id }, {\n * ...turn.data,\n * slug: extended.slug,\n * });\n * });\n * ```\n */\nexport function pipelineMiddleware<TData = unknown>(config: PipelineConfig<TData>): Middleware {\n const { stages, parallel = false, continueOnError = false, onStageError } = config;\n\n const runStage = async (\n stage: PipelineStage<TData>,\n typedTurn: Turn<TData>,\n emit: PipelineEmit\n ): Promise<void> => {\n try {\n await stage.run(typedTurn, emit);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (onStageError) {\n await onStageError({ stage, error, turn: typedTurn });\n }\n if (!continueOnError) {\n throw error;\n }\n }\n };\n\n return {\n name: 'pipeline',\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n const createEmit = (stageType: string): PipelineEmit => {\n return (data: unknown) => {\n ctx.emit(pipelineStageEvent(stageType, data));\n };\n };\n\n const typedTurn = turn as Turn<TData>;\n\n if (parallel) {\n const results = await Promise.allSettled(\n stages.map((stage) => runStage(stage, typedTurn, createEmit(stage.type)))\n );\n // If not continuing on error, throw first rejection\n if (!continueOnError) {\n const firstRejection = results.find(\n (r): r is PromiseRejectedResult => r.status === 'rejected'\n );\n if (firstRejection) {\n throw firstRejection.reason;\n }\n }\n } else {\n for (const stage of stages) {\n await runStage(stage, typedTurn, createEmit(stage.type));\n }\n }\n },\n };\n}\n"],"mappings":";AAuCO,SAAS,mBAAmB,OAAe,SAAsC;AACtF,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO,EAAE,OAAO,QAAQ;AAAA,EAC1B;AACF;AAiBO,SAAS,qBAAqB,OAAiD;AACpF,SAAO,MAAM,SAAS;AACxB;AAoJO,SAAS,mBAAoC,QAA2C;AAC7F,QAAM,EAAE,QAAQ,WAAW,OAAO,kBAAkB,OAAO,aAAa,IAAI;AAE5E,QAAM,WAAW,OACf,OACA,WACA,SACkB;AAClB,QAAI;AACF,YAAM,MAAM,IAAI,WAAW,IAAI;AAAA,IACjC,SAAS,KAAK;AACZ,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,UAAI,cAAc;AAChB,cAAM,aAAa,EAAE,OAAO,OAAO,MAAM,UAAU,CAAC;AAAA,MACtD;AACA,UAAI,CAAC,iBAAiB;AACpB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,MAAM,OAAO,MAAY,KAAuC;AAC9D,YAAM,aAAa,CAAC,cAAoC;AACtD,eAAO,CAAC,SAAkB;AACxB,cAAI,KAAK,mBAAmB,WAAW,IAAI,CAAC;AAAA,QAC9C;AAAA,MACF;AAEA,YAAM,YAAY;AAElB,UAAI,UAAU;AACZ,cAAM,UAAU,MAAM,QAAQ;AAAA,UAC5B,OAAO,IAAI,CAAC,UAAU,SAAS,OAAO,WAAW,WAAW,MAAM,IAAI,CAAC,CAAC;AAAA,QAC1E;AAEA,YAAI,CAAC,iBAAiB;AACpB,gBAAM,iBAAiB,QAAQ;AAAA,YAC7B,CAAC,MAAkC,EAAE,WAAW;AAAA,UAClD;AACA,cAAI,gBAAgB;AAClB,kBAAM,eAAe;AAAA,UACvB;AAAA,QACF;AAAA,MACF,OAAO;AACL,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,SAAS,OAAO,WAAW,WAAW,MAAM,IAAI,CAAC;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -1,8 +1,8 @@
1
- import { M as Middleware } from '../../llm-CZqlijjK.js';
2
- import { M as MemoryAdapterOptions, P as PubSubAdapter, a as PubSubOptions } from '../../types-CyXF0J7C.js';
3
- export { C as CompletionCallback, F as FinalDataCallback, S as StoredStream, b as SubscriptionCallback, U as Unsubscribe } from '../../types-CyXF0J7C.js';
4
- import '../../stream-DVVUIKpz.js';
5
- import '../../tool-D22EhP5F.js';
1
+ import { M as Middleware } from '../../llm-BWLaTzzY.js';
2
+ import { M as MemoryAdapterOptions, P as PubSubAdapter, a as PubSubOptions } from '../../types-nTwlpyJE.js';
3
+ export { C as CompletionCallback, F as FinalDataCallback, S as StoredStream, b as SubscriptionCallback, U as Unsubscribe } from '../../types-nTwlpyJE.js';
4
+ import '../../stream-bBd_4Ipu.js';
5
+ import '../../tool-BmAfKNBq.js';
6
6
 
7
7
  /**
8
8
  * @fileoverview In-memory storage adapter for pub-sub middleware.
@@ -82,12 +82,14 @@ declare function getAdapter(state: Map<string, unknown>): PubSubAdapter | undefi
82
82
  * export default defineEventHandler(async (event) => {
83
83
  * const { input, conversationId } = await readBody(event);
84
84
  *
85
- * // Fire and forget - subscriber connects immediately, events flow when ready
86
- * const model = llm({
87
- * model: anthropic('claude-sonnet-4-20250514'),
88
- * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
89
- * });
90
- * model.stream(input).then(turn => saveToDatabase(turn));
85
+ * // Guard: prevent duplicate generations on reconnect
86
+ * if (!await adapter.exists(conversationId)) {
87
+ * const model = llm({
88
+ * model: anthropic('claude-sonnet-4-20250514'),
89
+ * middleware: [pubsubMiddleware({ adapter, streamId: conversationId })],
90
+ * });
91
+ * model.stream(input).then(turn => saveToDatabase(turn));
92
+ * }
91
93
  *
92
94
  * return h3.streamSubscriber(conversationId, adapter, event);
93
95
  * });
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  serializeTurn
3
- } from "../../chunk-7ULSRWDH.js";
3
+ } from "../../chunk-ZSZVWLGE.js";
4
4
  import "../../chunk-ETBFOLQN.js";
5
- import "../../chunk-WU4U6IHF.js";
5
+ import "../../chunk-6QCV4WXF.js";
6
+ import "../../chunk-U2G5PHHL.js";
6
7
  import "../../chunk-COS4ON4G.js";
7
8
 
8
9
  // src/middleware/pubsub/memory-adapter.ts
@@ -30,7 +31,8 @@ function memoryAdapter(options = {}) {
30
31
  createdAt: Date.now(),
31
32
  events: []
32
33
  },
33
- subscribers: /* @__PURE__ */ new Set()
34
+ subscribers: /* @__PURE__ */ new Set(),
35
+ cursorBase: 0
34
36
  };
35
37
  streams.set(streamId, entry);
36
38
  }
@@ -43,7 +45,7 @@ function memoryAdapter(options = {}) {
43
45
  async append(streamId, event) {
44
46
  const entry = getOrCreate(streamId);
45
47
  entry.stream.events.push(event);
46
- eventCursors.set(event, entry.stream.events.length - 1);
48
+ eventCursors.set(event, entry.cursorBase + entry.stream.events.length - 1);
47
49
  },
48
50
  async getEvents(streamId) {
49
51
  const entry = streams.get(streamId);
@@ -62,7 +64,7 @@ function memoryAdapter(options = {}) {
62
64
  if (!entry) {
63
65
  return;
64
66
  }
65
- const cursor = eventCursors.get(event) ?? entry.stream.events.length - 1;
67
+ const cursor = eventCursors.get(event) ?? entry.cursorBase + entry.stream.events.length - 1;
66
68
  for (const subscriber of entry.subscribers) {
67
69
  scheduleCallback(() => {
68
70
  subscriber.onEvent(event, cursor);
@@ -88,6 +90,18 @@ function memoryAdapter(options = {}) {
88
90
  }
89
91
  streams.delete(streamId);
90
92
  }
93
+ },
94
+ async clear(streamId) {
95
+ const entry = streams.get(streamId);
96
+ if (entry) {
97
+ entry.cursorBase += entry.stream.events.length;
98
+ entry.stream.events = [];
99
+ entry.finalData = void 0;
100
+ }
101
+ },
102
+ getCursorBase(streamId) {
103
+ const entry = streams.get(streamId);
104
+ return entry?.cursorBase ?? 0;
91
105
  }
92
106
  };
93
107
  }
@@ -175,6 +189,8 @@ function pubsubMiddleware(options = {}) {
175
189
  return;
176
190
  }
177
191
  if (streamEnded) {
192
+ await waitForAppends(id);
193
+ clearAppendState(id);
178
194
  adapter.setFinalData(id, serializeTurn(turn));
179
195
  await adapter.remove(id).catch(() => {
180
196
  });
@@ -194,6 +210,16 @@ function pubsubMiddleware(options = {}) {
194
210
  },
195
211
  async onAbort(_error, ctx) {
196
212
  await finalizeStreamByState(ctx.state);
213
+ },
214
+ async onRetry(_attempt, _error, ctx) {
215
+ const id = ctx.state.get(STATE_KEY_STREAM_ID);
216
+ if (!id) {
217
+ return;
218
+ }
219
+ await waitForAppends(id);
220
+ clearAppendState(id);
221
+ await adapter.clear(id);
222
+ ctx.state.delete(STATE_KEY_STREAM_ENDED);
197
223
  }
198
224
  };
199
225
  }
@@ -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 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 FinalDataCallback,\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 onFinalData?: FinalDataCallback;\n}\n\ninterface StreamEntry {\n stream: MutableStoredStream;\n subscribers: Set<Subscriber>;\n finalData?: unknown;\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, onFinalData): Unsubscribe {\n const entry = getOrCreate(streamId);\n const subscriber: Subscriber = { onEvent, onComplete, onFinalData };\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 setFinalData(streamId, data): void {\n const entry = streams.get(streamId);\n if (entry) {\n entry.finalData = data;\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 if (entry.finalData !== undefined && subscriber.onFinalData) {\n scheduleCallback(() => {\n subscriber.onFinalData!(entry.finalData);\n });\n }\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 { Turn } from '../../types/turn.ts';\nimport type { PubSubAdapter, PubSubOptions } from './types.ts';\nimport { memoryAdapter } from './memory-adapter.ts';\nimport { serializeTurn } from '../../providers/proxy/serialization.ts';\n\nexport type {\n PubSubAdapter,\n PubSubOptions,\n StoredStream,\n SubscriptionCallback,\n CompletionCallback,\n FinalDataCallback,\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';\nconst STATE_KEY_STREAM_ENDED = 'pubsub:streamEnded';\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 const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n // Wait for all appends to complete but don't finalize yet\n // onTurn will be called next with the Turn data\n await waitForAppends(id);\n clearAppendState(id);\n ctx.state.set(STATE_KEY_STREAM_ENDED, true);\n },\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n const streamEnded = ctx.state.get(STATE_KEY_STREAM_ENDED) as boolean | undefined;\n\n if (!id) {\n return;\n }\n\n // Only emit Turn if we were streaming (onStreamEnd was called)\n if (streamEnded) {\n // Set the final Turn data so subscribers receive it before completion\n adapter.setFinalData(id, serializeTurn(turn));\n // Now remove the stream (notifies subscribers with final data + completion)\n await adapter.remove(id).catch(() => {});\n } else {\n // streamId was set but .generate() was used instead of .stream()\n // Clean up the orphan stream entry and warn about misuse\n const exists = await adapter.exists(id);\n if (exists) {\n console.warn(\n `[pubsub] streamId \"${id}\" was configured but .generate() was used instead of .stream(). ` +\n `Pubsub middleware only works with streaming. Cleaning up orphan stream.`\n );\n await adapter.remove(id).catch(() => {});\n }\n }\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":"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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 FinalDataCallback,\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 onFinalData?: FinalDataCallback;\n}\n\ninterface StreamEntry {\n stream: MutableStoredStream;\n subscribers: Set<Subscriber>;\n finalData?: unknown;\n /** Cursor offset incremented on clear to ensure new events have higher cursors */\n cursorBase: number;\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 cursorBase: 0,\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 // Use cursorBase to ensure cursors increase monotonically across clears\n eventCursors.set(event, entry.cursorBase + 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, onFinalData): Unsubscribe {\n const entry = getOrCreate(streamId);\n const subscriber: Subscriber = { onEvent, onComplete, onFinalData };\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 // Cursor is stored in eventCursors with cursorBase already applied\n const cursor = eventCursors.get(event) ?? (entry.cursorBase + entry.stream.events.length - 1);\n for (const subscriber of entry.subscribers) {\n scheduleCallback(() => {\n subscriber.onEvent(event, cursor);\n });\n }\n },\n\n setFinalData(streamId, data): void {\n const entry = streams.get(streamId);\n if (entry) {\n entry.finalData = data;\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 if (entry.finalData !== undefined && subscriber.onFinalData) {\n scheduleCallback(() => {\n subscriber.onFinalData!(entry.finalData);\n });\n }\n scheduleCallback(subscriber.onComplete);\n }\n streams.delete(streamId);\n }\n },\n\n async clear(streamId): Promise<void> {\n const entry = streams.get(streamId);\n if (entry) {\n // Increment cursor base so new events have higher cursors than cleared events\n // This ensures subscribers don't skip events after a retry\n entry.cursorBase += entry.stream.events.length;\n entry.stream.events = [];\n entry.finalData = undefined;\n }\n },\n\n getCursorBase(streamId): number {\n const entry = streams.get(streamId);\n return entry?.cursorBase ?? 0;\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 { Turn } from '../../types/turn.ts';\nimport type { PubSubAdapter, PubSubOptions } from './types.ts';\nimport { memoryAdapter } from './memory-adapter.ts';\nimport { serializeTurn } from '../../providers/proxy/serialization.ts';\n\nexport type {\n PubSubAdapter,\n PubSubOptions,\n StoredStream,\n SubscriptionCallback,\n CompletionCallback,\n FinalDataCallback,\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';\nconst STATE_KEY_STREAM_ENDED = 'pubsub:streamEnded';\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 * // Guard: prevent duplicate generations on reconnect\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(turn));\n * }\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 const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n if (!id) {\n return;\n }\n // Wait for all stream-phase appends to complete\n await waitForAppends(id);\n // Clear append state to prevent memory leaks if onTurn is skipped or fails.\n // Other middleware may emit during onTurn - those get new append chains.\n clearAppendState(id);\n ctx.state.set(STATE_KEY_STREAM_ENDED, true);\n },\n\n async onTurn(turn: Turn, ctx: MiddlewareContext): Promise<void> {\n const id = ctx.state.get(STATE_KEY_STREAM_ID) as string | undefined;\n const streamEnded = ctx.state.get(STATE_KEY_STREAM_ENDED) as boolean | undefined;\n\n if (!id) {\n return;\n }\n\n // Only emit Turn if we were streaming (onStreamEnd was called)\n if (streamEnded) {\n // Wait for any late appends from other middleware that emitted during onTurn\n // (e.g., pipeline middleware emits events before pubsub's onTurn runs).\n // These create new append chains since onStreamEnd cleared the stream-phase chains.\n await waitForAppends(id);\n clearAppendState(id);\n // Set the final Turn data so subscribers receive it before completion\n adapter.setFinalData(id, serializeTurn(turn));\n // Now remove the stream (notifies subscribers with final data + completion)\n await adapter.remove(id).catch(() => {});\n } else {\n // streamId was set but .generate() was used instead of .stream()\n // Clean up the orphan stream entry and warn about misuse\n const exists = await adapter.exists(id);\n if (exists) {\n console.warn(\n `[pubsub] streamId \"${id}\" was configured but .generate() was used instead of .stream(). ` +\n `Pubsub middleware only works with streaming. Cleaning up orphan stream.`\n );\n await adapter.remove(id).catch(() => {});\n }\n }\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 async onRetry(_attempt: number, _error: Error, 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 // Wait for in-flight appends to complete before clearing to prevent\n // stale events from repopulating the buffer after clear (especially\n // with async adapters like Redis)\n await waitForAppends(id);\n\n // Clear pending append chains\n clearAppendState(id);\n\n // Clear buffered events from adapter so subscribers don't receive duplicates\n await adapter.clear(id);\n\n // Reset stream ended flag for new attempt\n ctx.state.delete(STATE_KEY_STREAM_ENDED);\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
- import { P as PubSubAdapter } from '../../../../types-CyXF0J7C.js';
2
- import '../../../../stream-DVVUIKpz.js';
3
- import '../../../../tool-D22EhP5F.js';
1
+ import { P as PubSubAdapter } from '../../../../types-nTwlpyJE.js';
2
+ import '../../../../stream-bBd_4Ipu.js';
3
+ import '../../../../tool-BmAfKNBq.js';
4
4
 
5
5
  /**
6
6
  * @fileoverview Express/Connect adapter for pub-sub stream resumption.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  express,
3
3
  streamSubscriber
4
- } from "../../../../chunk-SAMIK4WZ.js";
5
- import "../../../../chunk-U6M3MXNI.js";
4
+ } from "../../../../chunk-KVUOTFYZ.js";
5
+ import "../../../../chunk-L6QWKFGE.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  express,
@@ -1,6 +1,6 @@
1
- import { P as PubSubAdapter } from '../../../../types-CyXF0J7C.js';
2
- import '../../../../stream-DVVUIKpz.js';
3
- import '../../../../tool-D22EhP5F.js';
1
+ import { P as PubSubAdapter } from '../../../../types-nTwlpyJE.js';
2
+ import '../../../../stream-bBd_4Ipu.js';
3
+ import '../../../../tool-BmAfKNBq.js';
4
4
 
5
5
  /**
6
6
  * @fileoverview Fastify adapter for pub-sub stream resumption.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  fastify,
3
3
  streamSubscriber
4
- } from "../../../../chunk-ZDYEDI2A.js";
5
- import "../../../../chunk-U6M3MXNI.js";
4
+ } from "../../../../chunk-CWGTARDE.js";
5
+ import "../../../../chunk-L6QWKFGE.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  fastify,
@@ -1,6 +1,6 @@
1
- import { P as PubSubAdapter } from '../../../../types-CyXF0J7C.js';
2
- import '../../../../stream-DVVUIKpz.js';
3
- import '../../../../tool-D22EhP5F.js';
1
+ import { P as PubSubAdapter } from '../../../../types-nTwlpyJE.js';
2
+ import '../../../../stream-bBd_4Ipu.js';
3
+ import '../../../../tool-BmAfKNBq.js';
4
4
 
5
5
  /**
6
6
  * @fileoverview H3/Nitro/Nuxt adapter for pub-sub stream resumption.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  h3,
3
3
  streamSubscriber
4
- } from "../../../../chunk-IIMTP3XC.js";
5
- import "../../../../chunk-U6M3MXNI.js";
4
+ } from "../../../../chunk-KBI45OXI.js";
5
+ import "../../../../chunk-L6QWKFGE.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  h3,
@@ -6,21 +6,32 @@ 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-CyXF0J7C.js';
10
- import '../../../stream-DVVUIKpz.js';
11
- import '../../../tool-D22EhP5F.js';
9
+ export { P as PubSubAdapter } from '../../../types-nTwlpyJE.js';
10
+ import '../../../stream-bBd_4Ipu.js';
11
+ import '../../../tool-BmAfKNBq.js';
12
12
 
13
13
  /**
14
14
  * Server adapters namespace for pub-sub stream resumption.
15
15
  *
16
16
  * Contains framework-specific adapters for Web API, Express, Fastify, and H3.
17
+ * Always guard with `adapter.exists()` to prevent duplicate generations on reconnect.
17
18
  *
18
19
  * @example Web API (Next.js App Router, Bun, Deno)
19
20
  * ```typescript
20
21
  * import { webapi } from '@providerprotocol/ai/middleware/pubsub/server';
21
22
  *
22
23
  * export async function POST(req: Request) {
23
- * const { streamId } = await req.json();
24
+ * const { messages, streamId } = await req.json();
25
+ *
26
+ * // Guard: prevent duplicate generations on reconnect
27
+ * if (!await adapter.exists(streamId)) {
28
+ * const model = llm({
29
+ * model: anthropic('claude-sonnet-4-20250514'),
30
+ * middleware: [pubsubMiddleware({ adapter, streamId })],
31
+ * });
32
+ * model.stream(messages).then(turn => saveToDatabase(turn));
33
+ * }
34
+ *
24
35
  * return new Response(webapi.createSubscriberStream(streamId, adapter), {
25
36
  * headers: { 'Content-Type': 'text/event-stream' },
26
37
  * });
@@ -31,8 +42,18 @@ import '../../../tool-D22EhP5F.js';
31
42
  * ```typescript
32
43
  * import { express } from '@providerprotocol/ai/middleware/pubsub/server';
33
44
  *
34
- * app.post('/api/ai/reconnect', (req, res) => {
35
- * const { streamId } = req.body;
45
+ * app.post('/api/ai', async (req, res) => {
46
+ * const { messages, streamId } = req.body;
47
+ *
48
+ * // Guard: prevent duplicate generations on reconnect
49
+ * if (!await adapter.exists(streamId)) {
50
+ * const model = llm({
51
+ * model: anthropic('claude-sonnet-4-20250514'),
52
+ * middleware: [pubsubMiddleware({ adapter, streamId })],
53
+ * });
54
+ * model.stream(messages).then(turn => saveToDatabase(turn));
55
+ * }
56
+ *
36
57
  * express.streamSubscriber(streamId, adapter, res);
37
58
  * });
38
59
  * ```
@@ -41,8 +62,18 @@ import '../../../tool-D22EhP5F.js';
41
62
  * ```typescript
42
63
  * import { fastify } from '@providerprotocol/ai/middleware/pubsub/server';
43
64
  *
44
- * app.post('/api/ai/reconnect', (request, reply) => {
45
- * const { streamId } = request.body;
65
+ * app.post('/api/ai', async (request, reply) => {
66
+ * const { messages, streamId } = request.body;
67
+ *
68
+ * // Guard: prevent duplicate generations on reconnect
69
+ * if (!await adapter.exists(streamId)) {
70
+ * const model = llm({
71
+ * model: anthropic('claude-sonnet-4-20250514'),
72
+ * middleware: [pubsubMiddleware({ adapter, streamId })],
73
+ * });
74
+ * model.stream(messages).then(turn => saveToDatabase(turn));
75
+ * }
76
+ *
46
77
  * return fastify.streamSubscriber(streamId, adapter, reply);
47
78
  * });
48
79
  * ```
@@ -52,7 +83,17 @@ import '../../../tool-D22EhP5F.js';
52
83
  * import { h3 } from '@providerprotocol/ai/middleware/pubsub/server';
53
84
  *
54
85
  * export default defineEventHandler(async (event) => {
55
- * const { streamId } = await readBody(event);
86
+ * const { messages, streamId } = await readBody(event);
87
+ *
88
+ * // Guard: prevent duplicate generations on reconnect
89
+ * if (!await adapter.exists(streamId)) {
90
+ * const model = llm({
91
+ * model: anthropic('claude-sonnet-4-20250514'),
92
+ * middleware: [pubsubMiddleware({ adapter, streamId })],
93
+ * });
94
+ * model.stream(messages).then(turn => saveToDatabase(turn));
95
+ * }
96
+ *
56
97
  * return h3.streamSubscriber(streamId, adapter, event);
57
98
  * });
58
99
  * ```
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  webapi
3
- } from "../../../chunk-WNB5PSY6.js";
3
+ } from "../../../chunk-ZMESKGUY.js";
4
4
  import {
5
5
  express
6
- } from "../../../chunk-SAMIK4WZ.js";
6
+ } from "../../../chunk-KVUOTFYZ.js";
7
7
  import {
8
8
  h3
9
- } from "../../../chunk-IIMTP3XC.js";
9
+ } from "../../../chunk-KBI45OXI.js";
10
10
  import {
11
11
  fastify
12
- } from "../../../chunk-ZDYEDI2A.js";
13
- import "../../../chunk-U6M3MXNI.js";
12
+ } from "../../../chunk-CWGTARDE.js";
13
+ import "../../../chunk-L6QWKFGE.js";
14
14
  import "../../../chunk-ETBFOLQN.js";
15
15
 
16
16
  // src/middleware/pubsub/server/index.ts
@@ -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 *\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 { streamId } = await req.json();\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/reconnect', (req, res) => {\n * const { streamId } = req.body;\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/reconnect', (request, reply) => {\n * const { streamId } = request.body;\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 { streamId } = await readBody(event);\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":";;;;;;;;;;;;;;;;AAkEO,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 { 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,6 +1,6 @@
1
- import { P as PubSubAdapter } from '../../../../types-CyXF0J7C.js';
2
- import '../../../../stream-DVVUIKpz.js';
3
- import '../../../../tool-D22EhP5F.js';
1
+ import { P as PubSubAdapter } from '../../../../types-nTwlpyJE.js';
2
+ import '../../../../stream-bBd_4Ipu.js';
3
+ import '../../../../tool-BmAfKNBq.js';
4
4
 
5
5
  /**
6
6
  * @fileoverview Web API adapter for pub-sub stream resumption.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  createSubscriberStream,
3
3
  webapi
4
- } from "../../../../chunk-WNB5PSY6.js";
5
- import "../../../../chunk-U6M3MXNI.js";
4
+ } from "../../../../chunk-ZMESKGUY.js";
5
+ import "../../../../chunk-L6QWKFGE.js";
6
6
  import "../../../../chunk-ETBFOLQN.js";
7
7
  export {
8
8
  createSubscriberStream,
@@ -1,6 +1,6 @@
1
- import { e as Provider } from '../llm-CZqlijjK.js';
2
- import '../stream-DVVUIKpz.js';
3
- import '../tool-D22EhP5F.js';
1
+ import { e as Provider } from '../llm-BWLaTzzY.js';
2
+ import '../stream-bBd_4Ipu.js';
3
+ import '../tool-BmAfKNBq.js';
4
4
 
5
5
  /**
6
6
  * @fileoverview Moonshot AI Provider Type Definitions
@@ -6,7 +6,7 @@ import {
6
6
  } from "../chunk-TUTYMOBL.js";
7
7
  import {
8
8
  resolveApiKey
9
- } from "../chunk-ARVM24K2.js";
9
+ } from "../chunk-EY2LLDGY.js";
10
10
  import {
11
11
  createProvider
12
12
  } from "../chunk-JA3UZALR.js";
@@ -14,18 +14,20 @@ import {
14
14
  doFetch,
15
15
  doStreamFetch,
16
16
  normalizeHttpError
17
- } from "../chunk-SBGZJVTJ.js";
17
+ } from "../chunk-VQZPADW6.js";
18
18
  import {
19
19
  StreamEventType,
20
20
  objectDelta
21
- } from "../chunk-MJI74VEJ.js";
21
+ } from "../chunk-F5ENANMJ.js";
22
22
  import {
23
23
  AssistantMessage,
24
- generateId,
25
24
  isAssistantMessage,
26
25
  isToolResultMessage,
27
26
  isUserMessage
28
- } from "../chunk-WU4U6IHF.js";
27
+ } from "../chunk-6QCV4WXF.js";
28
+ import {
29
+ generateId
30
+ } from "../chunk-U2G5PHHL.js";
29
31
  import {
30
32
  toError
31
33
  } from "../chunk-GIDT7C6I.js";