llm-sse 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres
5
5
  to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.0] - 2026-06-03
8
+
9
+ ### Added
10
+
11
+ - `toAssistantMessage(collected)` — turn a collected stream into a standard
12
+ OpenAI-shape assistant message (the canonical hub format), so a streamed
13
+ response composes directly with `llm-messages`' `toAnthropic` / `toGemini`.
14
+ Synthesizes ids for tool calls that lack one.
15
+
7
16
  ## [0.2.0] - 2026-06-03
8
17
 
9
18
  ### Added
@@ -27,5 +36,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
27
36
  and `Uint8Array` or string sources.
28
37
  - Zero runtime dependencies; ESM + CJS builds with type declarations.
29
38
 
39
+ [0.3.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.3.0
30
40
  [0.2.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.2.0
31
41
  [0.1.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.1.0
package/README.md CHANGED
@@ -66,12 +66,26 @@ type StreamEvent =
66
66
  Drains an event stream into a single message:
67
67
 
68
68
  ```ts
69
- const { text, toolCalls, finishReason } = await collectStream(
69
+ const { text, reasoning, toolCalls, finishReason } = await collectStream(
70
70
  parseAnthropicStream(res.body),
71
71
  );
72
72
  // toolCalls: { index, id?, name?, arguments }[] — arguments is the joined JSON string
73
73
  ```
74
74
 
75
+ ### `toAssistantMessage(collected)`
76
+
77
+ Turn a collected stream into a standard OpenAI-shape assistant message — the format `llm-messages` treats as canonical — so a streamed response composes straight back into your history or into a different provider:
78
+
79
+ ```ts
80
+ import { collectStream, toAssistantMessage } from 'llm-sse';
81
+ import { toAnthropic } from 'llm-messages';
82
+
83
+ const message = toAssistantMessage(
84
+ await collectStream(parseOpenAIStream(res.body)),
85
+ );
86
+ const claudeBody = toAnthropic([...history, message]); // continue on Claude
87
+ ```
88
+
75
89
  ### `sseData(source)`
76
90
 
77
91
  The underlying SSE parser, exported for advanced use: yields the `data` payload of each event as a string.
package/dist/index.cjs CHANGED
@@ -25,7 +25,8 @@ __export(index_exports, {
25
25
  parseGeminiStream: () => parseGeminiStream,
26
26
  parseOpenAIStream: () => parseOpenAIStream,
27
27
  parseStream: () => parseStream,
28
- sseData: () => sseData
28
+ sseData: () => sseData,
29
+ toAssistantMessage: () => toAssistantMessage
29
30
  });
30
31
  module.exports = __toCommonJS(index_exports);
31
32
 
@@ -301,6 +302,25 @@ async function collectStream(events) {
301
302
  finishReason
302
303
  };
303
304
  }
305
+
306
+ // src/message.ts
307
+ function toAssistantMessage(collected) {
308
+ const message = {
309
+ role: "assistant",
310
+ content: collected.text.length > 0 ? collected.text : null
311
+ };
312
+ if (collected.toolCalls.length > 0) {
313
+ message.tool_calls = collected.toolCalls.map((call, position) => ({
314
+ id: call.id ?? `call_${position}`,
315
+ type: "function",
316
+ function: {
317
+ name: call.name ?? "",
318
+ arguments: call.arguments.length > 0 ? call.arguments : "{}"
319
+ }
320
+ }));
321
+ }
322
+ return message;
323
+ }
304
324
  // Annotate the CommonJS export names for ESM import in node:
305
325
  0 && (module.exports = {
306
326
  collectStream,
@@ -308,6 +328,7 @@ async function collectStream(events) {
308
328
  parseGeminiStream,
309
329
  parseOpenAIStream,
310
330
  parseStream,
311
- sseData
331
+ sseData,
332
+ toAssistantMessage
312
333
  });
313
334
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/sse.ts","../src/parse.ts","../src/collect.ts"],"sourcesContent":["export {\n parseStream,\n parseOpenAIStream,\n parseAnthropicStream,\n parseGeminiStream,\n} from './parse.ts';\nexport { collectStream } from './collect.ts';\nexport { sseData } from './sse.ts';\nexport type {\n Provider,\n StreamEvent,\n CollectedMessage,\n CollectedToolCall,\n ChunkSource,\n} from './types.ts';\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one Anthropic Messages stream event into normalized events.\n *\n * Anthropic uses typed events: `content_block_start` opens a text or `tool_use`\n * block at an `index`, `content_block_delta` carries `text_delta` /\n * `input_json_delta` fragments, and `message_delta` carries the `stop_reason`.\n */\nexport function mapAnthropic(event: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n\n switch (event?.type) {\n case 'content_block_start': {\n const block = event.content_block;\n if (block?.type === 'tool_use') {\n events.push({\n type: 'tool_call_start',\n index: event.index ?? 0,\n id: block.id,\n name: block.name,\n });\n }\n break;\n }\n case 'content_block_delta': {\n const delta = event.delta;\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n events.push({ type: 'text', text: delta.text });\n } else if (\n delta?.type === 'thinking_delta' &&\n typeof delta.thinking === 'string'\n ) {\n events.push({ type: 'reasoning', text: delta.thinking });\n } else if (\n delta?.type === 'input_json_delta' &&\n typeof delta.partial_json === 'string'\n ) {\n events.push({\n type: 'tool_call_delta',\n index: event.index ?? 0,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (reason) {\n events.push({ type: 'finish', reason });\n }\n break;\n }\n case 'error': {\n events.push({ type: 'error', error: event.error ?? event });\n break;\n }\n }\n\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/** Per-stream state for Gemini, which does not number its tool calls. */\nexport interface GeminiState {\n toolIndex: number;\n}\n\n/**\n * Map one Gemini `GenerateContentResponse` chunk into normalized events.\n *\n * Gemini streams `candidates[0].content.parts[]`: a part is either `text` or a\n * complete `functionCall` (`{ name, args }`) — it does not fragment arguments,\n * so the whole `args` object is emitted as a single tool-call delta. Calls are\n * numbered in the order they appear via `state`.\n */\nexport function mapGemini(chunk: any, state: GeminiState): StreamEvent[] {\n const events: StreamEvent[] = [];\n const candidate = chunk?.candidates?.[0];\n if (!candidate) {\n return events;\n }\n\n const parts = candidate.content?.parts;\n if (Array.isArray(parts)) {\n for (const part of parts) {\n if (typeof part.text === 'string' && part.text.length > 0) {\n // Gemini flags a thinking part with `thought: true`.\n events.push({\n type: part.thought === true ? 'reasoning' : 'text',\n text: part.text,\n });\n }\n if (part.functionCall) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: part.functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(part.functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (candidate.finishReason) {\n events.push({ type: 'finish', reason: candidate.finishReason });\n }\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one OpenAI `chat.completion.chunk` into normalized events.\n *\n * OpenAI streams a `choices[0].delta`: `content` carries text, and\n * `tool_calls[]` carry an `index`, an `id` + `function.name` on the first\n * fragment, then `function.arguments` fragments thereafter.\n */\nexport function mapOpenAI(chunk: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n const choice = chunk?.choices?.[0];\n if (!choice) {\n return events;\n }\n\n const delta = choice.delta;\n if (delta) {\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n events.push({ type: 'text', text: delta.content });\n }\n if (Array.isArray(delta.tool_calls)) {\n for (const call of delta.tool_calls) {\n const index = typeof call.index === 'number' ? call.index : 0;\n if (call.id !== undefined || call.function?.name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id: call.id,\n name: call.function?.name,\n });\n }\n const args = call.function?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({ type: 'tool_call_delta', index, argumentsDelta: args });\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\n return events;\n}\n","import type { ChunkSource } from './types.ts';\n\n/**\n * Decode a mixed byte/string chunk source into text. `Uint8Array` chunks are\n * decoded with a streaming `TextDecoder` so a multibyte UTF-8 character split\n * across two chunks is reassembled correctly.\n */\nasync function* decodeChunks(source: ChunkSource): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n for await (const chunk of source) {\n if (typeof chunk === 'string') {\n yield chunk;\n } else {\n const text = decoder.decode(chunk, { stream: true });\n if (text) {\n yield text;\n }\n }\n }\n const tail = decoder.decode();\n if (tail) {\n yield tail;\n }\n}\n\n/**\n * Parse a Server-Sent Events stream and yield the `data` payload of each event.\n *\n * Robust to the realities of streamed HTTP: events and lines split across\n * chunk boundaries are buffered until complete, multi-line `data:` fields are\n * joined with `\\n` (per the SSE spec), and comments (`:`) and other fields\n * (`event:`, `id:`, `retry:`) are ignored — the payload's own `type` field is\n * what the provider parsers key on.\n */\nexport async function* sseData(source: ChunkSource): AsyncGenerator<string> {\n let buffer = '';\n let dataLines: string[] = [];\n\n for await (const text of decodeChunks(source)) {\n buffer += text;\n\n let newline: number;\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '');\n buffer = buffer.slice(newline + 1);\n\n if (line === '') {\n // Blank line terminates an event.\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n dataLines = [];\n }\n continue;\n }\n if (line[0] === ':') {\n continue; // comment\n }\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.replace(/\\r$/, '');\n if (last.startsWith('data:')) {\n dataLines.push(last.slice(5).replace(/^ /, ''));\n }\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n }\n}\n","import { mapAnthropic } from './providers/anthropic.ts';\nimport { mapGemini } from './providers/gemini.ts';\nimport { mapOpenAI } from './providers/openai.ts';\nimport { sseData } from './sse.ts';\nimport type { ChunkSource, Provider, StreamEvent } from './types.ts';\n\n/** Shared SSE-to-events driver for the stateless providers. */\nasync function* parseWith(\n source: ChunkSource,\n map: (payload: any) => StreamEvent[],\n): AsyncGenerator<StreamEvent> {\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue; // ignore keep-alive / non-JSON data lines\n }\n for (const event of map(payload)) {\n yield event;\n }\n }\n}\n\n/** Parse an OpenAI Chat Completions stream into normalized events. */\nexport function parseOpenAIStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapOpenAI);\n}\n\n/** Parse an Anthropic Messages stream into normalized events. */\nexport function parseAnthropicStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapAnthropic);\n}\n\n/** Parse a Gemini `streamGenerateContent` (SSE) stream into normalized events. */\nexport async function* parseGeminiStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n const state = { toolIndex: 0 };\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n for (const event of mapGemini(payload, state)) {\n yield event;\n }\n }\n}\n\n/** Parse a provider stream into normalized events, dispatching on `provider`. */\nexport function parseStream(\n source: ChunkSource,\n provider: Provider,\n): AsyncGenerator<StreamEvent> {\n switch (provider) {\n case 'openai':\n return parseOpenAIStream(source);\n case 'anthropic':\n return parseAnthropicStream(source);\n case 'gemini':\n return parseGeminiStream(source);\n }\n}\n","import type {\n CollectedMessage,\n CollectedToolCall,\n StreamEvent,\n} from './types.ts';\n\n/**\n * Drain a normalized event stream into a single assistant message: all text\n * concatenated, tool calls accumulated by `index` (arguments joined into one\n * JSON string), and the final stop reason.\n *\n * `error` events are not accumulated here — iterate the events directly if you\n * need to react to them mid-stream.\n *\n * @example\n * ```ts\n * const { text, toolCalls } = await collectStream(parseOpenAIStream(res.body));\n * ```\n */\nexport async function collectStream(\n events: AsyncIterable<StreamEvent>,\n): Promise<CollectedMessage> {\n let text = '';\n let reasoning = '';\n let finishReason: string | undefined;\n const byIndex = new Map<number, CollectedToolCall>();\n const order: number[] = [];\n\n const ensure = (index: number): CollectedToolCall => {\n let call = byIndex.get(index);\n if (!call) {\n call = { index, arguments: '' };\n byIndex.set(index, call);\n order.push(index);\n }\n return call;\n };\n\n for await (const event of events) {\n switch (event.type) {\n case 'text':\n text += event.text;\n break;\n case 'reasoning':\n reasoning += event.text;\n break;\n case 'tool_call_start': {\n const call = ensure(event.index);\n if (event.id !== undefined) {\n call.id = event.id;\n }\n if (event.name !== undefined) {\n call.name = event.name;\n }\n break;\n }\n case 'tool_call_delta':\n ensure(event.index).arguments += event.argumentsDelta;\n break;\n case 'finish':\n finishReason = event.reason;\n break;\n case 'error':\n break;\n }\n }\n\n return {\n text,\n reasoning,\n toolCalls: order.map((index) => byIndex.get(index)!),\n finishReason,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/sse.ts","../src/parse.ts","../src/collect.ts","../src/message.ts"],"sourcesContent":["export {\n parseStream,\n parseOpenAIStream,\n parseAnthropicStream,\n parseGeminiStream,\n} from './parse.ts';\nexport { collectStream } from './collect.ts';\nexport { toAssistantMessage } from './message.ts';\nexport type { AssistantMessage, AssistantToolCall } from './message.ts';\nexport { sseData } from './sse.ts';\nexport type {\n Provider,\n StreamEvent,\n CollectedMessage,\n CollectedToolCall,\n ChunkSource,\n} from './types.ts';\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one Anthropic Messages stream event into normalized events.\n *\n * Anthropic uses typed events: `content_block_start` opens a text or `tool_use`\n * block at an `index`, `content_block_delta` carries `text_delta` /\n * `input_json_delta` fragments, and `message_delta` carries the `stop_reason`.\n */\nexport function mapAnthropic(event: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n\n switch (event?.type) {\n case 'content_block_start': {\n const block = event.content_block;\n if (block?.type === 'tool_use') {\n events.push({\n type: 'tool_call_start',\n index: event.index ?? 0,\n id: block.id,\n name: block.name,\n });\n }\n break;\n }\n case 'content_block_delta': {\n const delta = event.delta;\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n events.push({ type: 'text', text: delta.text });\n } else if (\n delta?.type === 'thinking_delta' &&\n typeof delta.thinking === 'string'\n ) {\n events.push({ type: 'reasoning', text: delta.thinking });\n } else if (\n delta?.type === 'input_json_delta' &&\n typeof delta.partial_json === 'string'\n ) {\n events.push({\n type: 'tool_call_delta',\n index: event.index ?? 0,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (reason) {\n events.push({ type: 'finish', reason });\n }\n break;\n }\n case 'error': {\n events.push({ type: 'error', error: event.error ?? event });\n break;\n }\n }\n\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/** Per-stream state for Gemini, which does not number its tool calls. */\nexport interface GeminiState {\n toolIndex: number;\n}\n\n/**\n * Map one Gemini `GenerateContentResponse` chunk into normalized events.\n *\n * Gemini streams `candidates[0].content.parts[]`: a part is either `text` or a\n * complete `functionCall` (`{ name, args }`) — it does not fragment arguments,\n * so the whole `args` object is emitted as a single tool-call delta. Calls are\n * numbered in the order they appear via `state`.\n */\nexport function mapGemini(chunk: any, state: GeminiState): StreamEvent[] {\n const events: StreamEvent[] = [];\n const candidate = chunk?.candidates?.[0];\n if (!candidate) {\n return events;\n }\n\n const parts = candidate.content?.parts;\n if (Array.isArray(parts)) {\n for (const part of parts) {\n if (typeof part.text === 'string' && part.text.length > 0) {\n // Gemini flags a thinking part with `thought: true`.\n events.push({\n type: part.thought === true ? 'reasoning' : 'text',\n text: part.text,\n });\n }\n if (part.functionCall) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: part.functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(part.functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (candidate.finishReason) {\n events.push({ type: 'finish', reason: candidate.finishReason });\n }\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one OpenAI `chat.completion.chunk` into normalized events.\n *\n * OpenAI streams a `choices[0].delta`: `content` carries text, and\n * `tool_calls[]` carry an `index`, an `id` + `function.name` on the first\n * fragment, then `function.arguments` fragments thereafter.\n */\nexport function mapOpenAI(chunk: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n const choice = chunk?.choices?.[0];\n if (!choice) {\n return events;\n }\n\n const delta = choice.delta;\n if (delta) {\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n events.push({ type: 'text', text: delta.content });\n }\n if (Array.isArray(delta.tool_calls)) {\n for (const call of delta.tool_calls) {\n const index = typeof call.index === 'number' ? call.index : 0;\n if (call.id !== undefined || call.function?.name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id: call.id,\n name: call.function?.name,\n });\n }\n const args = call.function?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({ type: 'tool_call_delta', index, argumentsDelta: args });\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\n return events;\n}\n","import type { ChunkSource } from './types.ts';\n\n/**\n * Decode a mixed byte/string chunk source into text. `Uint8Array` chunks are\n * decoded with a streaming `TextDecoder` so a multibyte UTF-8 character split\n * across two chunks is reassembled correctly.\n */\nasync function* decodeChunks(source: ChunkSource): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n for await (const chunk of source) {\n if (typeof chunk === 'string') {\n yield chunk;\n } else {\n const text = decoder.decode(chunk, { stream: true });\n if (text) {\n yield text;\n }\n }\n }\n const tail = decoder.decode();\n if (tail) {\n yield tail;\n }\n}\n\n/**\n * Parse a Server-Sent Events stream and yield the `data` payload of each event.\n *\n * Robust to the realities of streamed HTTP: events and lines split across\n * chunk boundaries are buffered until complete, multi-line `data:` fields are\n * joined with `\\n` (per the SSE spec), and comments (`:`) and other fields\n * (`event:`, `id:`, `retry:`) are ignored — the payload's own `type` field is\n * what the provider parsers key on.\n */\nexport async function* sseData(source: ChunkSource): AsyncGenerator<string> {\n let buffer = '';\n let dataLines: string[] = [];\n\n for await (const text of decodeChunks(source)) {\n buffer += text;\n\n let newline: number;\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '');\n buffer = buffer.slice(newline + 1);\n\n if (line === '') {\n // Blank line terminates an event.\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n dataLines = [];\n }\n continue;\n }\n if (line[0] === ':') {\n continue; // comment\n }\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.replace(/\\r$/, '');\n if (last.startsWith('data:')) {\n dataLines.push(last.slice(5).replace(/^ /, ''));\n }\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n }\n}\n","import { mapAnthropic } from './providers/anthropic.ts';\nimport { mapGemini } from './providers/gemini.ts';\nimport { mapOpenAI } from './providers/openai.ts';\nimport { sseData } from './sse.ts';\nimport type { ChunkSource, Provider, StreamEvent } from './types.ts';\n\n/** Shared SSE-to-events driver for the stateless providers. */\nasync function* parseWith(\n source: ChunkSource,\n map: (payload: any) => StreamEvent[],\n): AsyncGenerator<StreamEvent> {\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue; // ignore keep-alive / non-JSON data lines\n }\n for (const event of map(payload)) {\n yield event;\n }\n }\n}\n\n/** Parse an OpenAI Chat Completions stream into normalized events. */\nexport function parseOpenAIStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapOpenAI);\n}\n\n/** Parse an Anthropic Messages stream into normalized events. */\nexport function parseAnthropicStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapAnthropic);\n}\n\n/** Parse a Gemini `streamGenerateContent` (SSE) stream into normalized events. */\nexport async function* parseGeminiStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n const state = { toolIndex: 0 };\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n for (const event of mapGemini(payload, state)) {\n yield event;\n }\n }\n}\n\n/** Parse a provider stream into normalized events, dispatching on `provider`. */\nexport function parseStream(\n source: ChunkSource,\n provider: Provider,\n): AsyncGenerator<StreamEvent> {\n switch (provider) {\n case 'openai':\n return parseOpenAIStream(source);\n case 'anthropic':\n return parseAnthropicStream(source);\n case 'gemini':\n return parseGeminiStream(source);\n }\n}\n","import type {\n CollectedMessage,\n CollectedToolCall,\n StreamEvent,\n} from './types.ts';\n\n/**\n * Drain a normalized event stream into a single assistant message: all text\n * concatenated, tool calls accumulated by `index` (arguments joined into one\n * JSON string), and the final stop reason.\n *\n * `error` events are not accumulated here — iterate the events directly if you\n * need to react to them mid-stream.\n *\n * @example\n * ```ts\n * const { text, toolCalls } = await collectStream(parseOpenAIStream(res.body));\n * ```\n */\nexport async function collectStream(\n events: AsyncIterable<StreamEvent>,\n): Promise<CollectedMessage> {\n let text = '';\n let reasoning = '';\n let finishReason: string | undefined;\n const byIndex = new Map<number, CollectedToolCall>();\n const order: number[] = [];\n\n const ensure = (index: number): CollectedToolCall => {\n let call = byIndex.get(index);\n if (!call) {\n call = { index, arguments: '' };\n byIndex.set(index, call);\n order.push(index);\n }\n return call;\n };\n\n for await (const event of events) {\n switch (event.type) {\n case 'text':\n text += event.text;\n break;\n case 'reasoning':\n reasoning += event.text;\n break;\n case 'tool_call_start': {\n const call = ensure(event.index);\n if (event.id !== undefined) {\n call.id = event.id;\n }\n if (event.name !== undefined) {\n call.name = event.name;\n }\n break;\n }\n case 'tool_call_delta':\n ensure(event.index).arguments += event.argumentsDelta;\n break;\n case 'finish':\n finishReason = event.reason;\n break;\n case 'error':\n break;\n }\n }\n\n return {\n text,\n reasoning,\n toolCalls: order.map((index) => byIndex.get(index)!),\n finishReason,\n };\n}\n","import type { CollectedMessage } from './types.ts';\n\n/**\n * An assistant message in OpenAI Chat Completions shape — the canonical \"hub\"\n * format. Append it to your message history to continue the conversation, or\n * pass it to `llm-messages` to port it to Anthropic or Gemini.\n */\nexport interface AssistantMessage {\n role: 'assistant';\n /** The assistant text, or `null` when the turn was only tool calls. */\n content: string | null;\n /** Present only when the turn produced tool calls. */\n tool_calls?: AssistantToolCall[];\n}\n\nexport interface AssistantToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\n\n/**\n * Turn a {@link CollectedMessage} (from {@link collectStream}) into a standard\n * assistant message you can put back into a conversation.\n *\n * Output is the OpenAI Chat Completions shape, which is the format `llm-messages`\n * treats as canonical — so this composes directly with its `toAnthropic` /\n * `toGemini` converters. Tool calls without an id (e.g. from Gemini) get a\n * stable synthetic `call_<n>` id. Reasoning is intentionally omitted: it is not\n * part of the portable assistant message.\n *\n * @example\n * ```ts\n * const collected = await collectStream(parseOpenAIStream(res.body));\n * const message = toAssistantMessage(collected);\n * history.push(message); // or: toAnthropic([...history, message])\n * ```\n */\nexport function toAssistantMessage(\n collected: CollectedMessage,\n): AssistantMessage {\n const message: AssistantMessage = {\n role: 'assistant',\n content: collected.text.length > 0 ? collected.text : null,\n };\n\n if (collected.toolCalls.length > 0) {\n message.tool_calls = collected.toolCalls.map((call, position) => ({\n id: call.id ?? `call_${position}`,\n type: 'function',\n function: {\n name: call.name ?? '',\n arguments: call.arguments.length > 0 ? call.arguments : '{}',\n },\n }));\n }\n\n return message;\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -96,6 +96,45 @@ declare function parseStream(source: ChunkSource, provider: Provider): AsyncGene
96
96
  */
97
97
  declare function collectStream(events: AsyncIterable<StreamEvent>): Promise<CollectedMessage>;
98
98
 
99
+ /**
100
+ * An assistant message in OpenAI Chat Completions shape — the canonical "hub"
101
+ * format. Append it to your message history to continue the conversation, or
102
+ * pass it to `llm-messages` to port it to Anthropic or Gemini.
103
+ */
104
+ interface AssistantMessage {
105
+ role: 'assistant';
106
+ /** The assistant text, or `null` when the turn was only tool calls. */
107
+ content: string | null;
108
+ /** Present only when the turn produced tool calls. */
109
+ tool_calls?: AssistantToolCall[];
110
+ }
111
+ interface AssistantToolCall {
112
+ id: string;
113
+ type: 'function';
114
+ function: {
115
+ name: string;
116
+ arguments: string;
117
+ };
118
+ }
119
+ /**
120
+ * Turn a {@link CollectedMessage} (from {@link collectStream}) into a standard
121
+ * assistant message you can put back into a conversation.
122
+ *
123
+ * Output is the OpenAI Chat Completions shape, which is the format `llm-messages`
124
+ * treats as canonical — so this composes directly with its `toAnthropic` /
125
+ * `toGemini` converters. Tool calls without an id (e.g. from Gemini) get a
126
+ * stable synthetic `call_<n>` id. Reasoning is intentionally omitted: it is not
127
+ * part of the portable assistant message.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const collected = await collectStream(parseOpenAIStream(res.body));
132
+ * const message = toAssistantMessage(collected);
133
+ * history.push(message); // or: toAnthropic([...history, message])
134
+ * ```
135
+ */
136
+ declare function toAssistantMessage(collected: CollectedMessage): AssistantMessage;
137
+
99
138
  /**
100
139
  * Parse a Server-Sent Events stream and yield the `data` payload of each event.
101
140
  *
@@ -107,4 +146,4 @@ declare function collectStream(events: AsyncIterable<StreamEvent>): Promise<Coll
107
146
  */
108
147
  declare function sseData(source: ChunkSource): AsyncGenerator<string>;
109
148
 
110
- export { type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIStream, parseStream, sseData };
149
+ export { type AssistantMessage, type AssistantToolCall, type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIStream, parseStream, sseData, toAssistantMessage };
package/dist/index.d.ts CHANGED
@@ -96,6 +96,45 @@ declare function parseStream(source: ChunkSource, provider: Provider): AsyncGene
96
96
  */
97
97
  declare function collectStream(events: AsyncIterable<StreamEvent>): Promise<CollectedMessage>;
98
98
 
99
+ /**
100
+ * An assistant message in OpenAI Chat Completions shape — the canonical "hub"
101
+ * format. Append it to your message history to continue the conversation, or
102
+ * pass it to `llm-messages` to port it to Anthropic or Gemini.
103
+ */
104
+ interface AssistantMessage {
105
+ role: 'assistant';
106
+ /** The assistant text, or `null` when the turn was only tool calls. */
107
+ content: string | null;
108
+ /** Present only when the turn produced tool calls. */
109
+ tool_calls?: AssistantToolCall[];
110
+ }
111
+ interface AssistantToolCall {
112
+ id: string;
113
+ type: 'function';
114
+ function: {
115
+ name: string;
116
+ arguments: string;
117
+ };
118
+ }
119
+ /**
120
+ * Turn a {@link CollectedMessage} (from {@link collectStream}) into a standard
121
+ * assistant message you can put back into a conversation.
122
+ *
123
+ * Output is the OpenAI Chat Completions shape, which is the format `llm-messages`
124
+ * treats as canonical — so this composes directly with its `toAnthropic` /
125
+ * `toGemini` converters. Tool calls without an id (e.g. from Gemini) get a
126
+ * stable synthetic `call_<n>` id. Reasoning is intentionally omitted: it is not
127
+ * part of the portable assistant message.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const collected = await collectStream(parseOpenAIStream(res.body));
132
+ * const message = toAssistantMessage(collected);
133
+ * history.push(message); // or: toAnthropic([...history, message])
134
+ * ```
135
+ */
136
+ declare function toAssistantMessage(collected: CollectedMessage): AssistantMessage;
137
+
99
138
  /**
100
139
  * Parse a Server-Sent Events stream and yield the `data` payload of each event.
101
140
  *
@@ -107,4 +146,4 @@ declare function collectStream(events: AsyncIterable<StreamEvent>): Promise<Coll
107
146
  */
108
147
  declare function sseData(source: ChunkSource): AsyncGenerator<string>;
109
148
 
110
- export { type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIStream, parseStream, sseData };
149
+ export { type AssistantMessage, type AssistantToolCall, type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIStream, parseStream, sseData, toAssistantMessage };
package/dist/index.js CHANGED
@@ -270,12 +270,32 @@ async function collectStream(events) {
270
270
  finishReason
271
271
  };
272
272
  }
273
+
274
+ // src/message.ts
275
+ function toAssistantMessage(collected) {
276
+ const message = {
277
+ role: "assistant",
278
+ content: collected.text.length > 0 ? collected.text : null
279
+ };
280
+ if (collected.toolCalls.length > 0) {
281
+ message.tool_calls = collected.toolCalls.map((call, position) => ({
282
+ id: call.id ?? `call_${position}`,
283
+ type: "function",
284
+ function: {
285
+ name: call.name ?? "",
286
+ arguments: call.arguments.length > 0 ? call.arguments : "{}"
287
+ }
288
+ }));
289
+ }
290
+ return message;
291
+ }
273
292
  export {
274
293
  collectStream,
275
294
  parseAnthropicStream,
276
295
  parseGeminiStream,
277
296
  parseOpenAIStream,
278
297
  parseStream,
279
- sseData
298
+ sseData,
299
+ toAssistantMessage
280
300
  };
281
301
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/sse.ts","../src/parse.ts","../src/collect.ts"],"sourcesContent":["import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one Anthropic Messages stream event into normalized events.\n *\n * Anthropic uses typed events: `content_block_start` opens a text or `tool_use`\n * block at an `index`, `content_block_delta` carries `text_delta` /\n * `input_json_delta` fragments, and `message_delta` carries the `stop_reason`.\n */\nexport function mapAnthropic(event: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n\n switch (event?.type) {\n case 'content_block_start': {\n const block = event.content_block;\n if (block?.type === 'tool_use') {\n events.push({\n type: 'tool_call_start',\n index: event.index ?? 0,\n id: block.id,\n name: block.name,\n });\n }\n break;\n }\n case 'content_block_delta': {\n const delta = event.delta;\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n events.push({ type: 'text', text: delta.text });\n } else if (\n delta?.type === 'thinking_delta' &&\n typeof delta.thinking === 'string'\n ) {\n events.push({ type: 'reasoning', text: delta.thinking });\n } else if (\n delta?.type === 'input_json_delta' &&\n typeof delta.partial_json === 'string'\n ) {\n events.push({\n type: 'tool_call_delta',\n index: event.index ?? 0,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (reason) {\n events.push({ type: 'finish', reason });\n }\n break;\n }\n case 'error': {\n events.push({ type: 'error', error: event.error ?? event });\n break;\n }\n }\n\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/** Per-stream state for Gemini, which does not number its tool calls. */\nexport interface GeminiState {\n toolIndex: number;\n}\n\n/**\n * Map one Gemini `GenerateContentResponse` chunk into normalized events.\n *\n * Gemini streams `candidates[0].content.parts[]`: a part is either `text` or a\n * complete `functionCall` (`{ name, args }`) — it does not fragment arguments,\n * so the whole `args` object is emitted as a single tool-call delta. Calls are\n * numbered in the order they appear via `state`.\n */\nexport function mapGemini(chunk: any, state: GeminiState): StreamEvent[] {\n const events: StreamEvent[] = [];\n const candidate = chunk?.candidates?.[0];\n if (!candidate) {\n return events;\n }\n\n const parts = candidate.content?.parts;\n if (Array.isArray(parts)) {\n for (const part of parts) {\n if (typeof part.text === 'string' && part.text.length > 0) {\n // Gemini flags a thinking part with `thought: true`.\n events.push({\n type: part.thought === true ? 'reasoning' : 'text',\n text: part.text,\n });\n }\n if (part.functionCall) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: part.functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(part.functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (candidate.finishReason) {\n events.push({ type: 'finish', reason: candidate.finishReason });\n }\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one OpenAI `chat.completion.chunk` into normalized events.\n *\n * OpenAI streams a `choices[0].delta`: `content` carries text, and\n * `tool_calls[]` carry an `index`, an `id` + `function.name` on the first\n * fragment, then `function.arguments` fragments thereafter.\n */\nexport function mapOpenAI(chunk: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n const choice = chunk?.choices?.[0];\n if (!choice) {\n return events;\n }\n\n const delta = choice.delta;\n if (delta) {\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n events.push({ type: 'text', text: delta.content });\n }\n if (Array.isArray(delta.tool_calls)) {\n for (const call of delta.tool_calls) {\n const index = typeof call.index === 'number' ? call.index : 0;\n if (call.id !== undefined || call.function?.name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id: call.id,\n name: call.function?.name,\n });\n }\n const args = call.function?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({ type: 'tool_call_delta', index, argumentsDelta: args });\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\n return events;\n}\n","import type { ChunkSource } from './types.ts';\n\n/**\n * Decode a mixed byte/string chunk source into text. `Uint8Array` chunks are\n * decoded with a streaming `TextDecoder` so a multibyte UTF-8 character split\n * across two chunks is reassembled correctly.\n */\nasync function* decodeChunks(source: ChunkSource): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n for await (const chunk of source) {\n if (typeof chunk === 'string') {\n yield chunk;\n } else {\n const text = decoder.decode(chunk, { stream: true });\n if (text) {\n yield text;\n }\n }\n }\n const tail = decoder.decode();\n if (tail) {\n yield tail;\n }\n}\n\n/**\n * Parse a Server-Sent Events stream and yield the `data` payload of each event.\n *\n * Robust to the realities of streamed HTTP: events and lines split across\n * chunk boundaries are buffered until complete, multi-line `data:` fields are\n * joined with `\\n` (per the SSE spec), and comments (`:`) and other fields\n * (`event:`, `id:`, `retry:`) are ignored — the payload's own `type` field is\n * what the provider parsers key on.\n */\nexport async function* sseData(source: ChunkSource): AsyncGenerator<string> {\n let buffer = '';\n let dataLines: string[] = [];\n\n for await (const text of decodeChunks(source)) {\n buffer += text;\n\n let newline: number;\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '');\n buffer = buffer.slice(newline + 1);\n\n if (line === '') {\n // Blank line terminates an event.\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n dataLines = [];\n }\n continue;\n }\n if (line[0] === ':') {\n continue; // comment\n }\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.replace(/\\r$/, '');\n if (last.startsWith('data:')) {\n dataLines.push(last.slice(5).replace(/^ /, ''));\n }\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n }\n}\n","import { mapAnthropic } from './providers/anthropic.ts';\nimport { mapGemini } from './providers/gemini.ts';\nimport { mapOpenAI } from './providers/openai.ts';\nimport { sseData } from './sse.ts';\nimport type { ChunkSource, Provider, StreamEvent } from './types.ts';\n\n/** Shared SSE-to-events driver for the stateless providers. */\nasync function* parseWith(\n source: ChunkSource,\n map: (payload: any) => StreamEvent[],\n): AsyncGenerator<StreamEvent> {\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue; // ignore keep-alive / non-JSON data lines\n }\n for (const event of map(payload)) {\n yield event;\n }\n }\n}\n\n/** Parse an OpenAI Chat Completions stream into normalized events. */\nexport function parseOpenAIStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapOpenAI);\n}\n\n/** Parse an Anthropic Messages stream into normalized events. */\nexport function parseAnthropicStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapAnthropic);\n}\n\n/** Parse a Gemini `streamGenerateContent` (SSE) stream into normalized events. */\nexport async function* parseGeminiStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n const state = { toolIndex: 0 };\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n for (const event of mapGemini(payload, state)) {\n yield event;\n }\n }\n}\n\n/** Parse a provider stream into normalized events, dispatching on `provider`. */\nexport function parseStream(\n source: ChunkSource,\n provider: Provider,\n): AsyncGenerator<StreamEvent> {\n switch (provider) {\n case 'openai':\n return parseOpenAIStream(source);\n case 'anthropic':\n return parseAnthropicStream(source);\n case 'gemini':\n return parseGeminiStream(source);\n }\n}\n","import type {\n CollectedMessage,\n CollectedToolCall,\n StreamEvent,\n} from './types.ts';\n\n/**\n * Drain a normalized event stream into a single assistant message: all text\n * concatenated, tool calls accumulated by `index` (arguments joined into one\n * JSON string), and the final stop reason.\n *\n * `error` events are not accumulated here — iterate the events directly if you\n * need to react to them mid-stream.\n *\n * @example\n * ```ts\n * const { text, toolCalls } = await collectStream(parseOpenAIStream(res.body));\n * ```\n */\nexport async function collectStream(\n events: AsyncIterable<StreamEvent>,\n): Promise<CollectedMessage> {\n let text = '';\n let reasoning = '';\n let finishReason: string | undefined;\n const byIndex = new Map<number, CollectedToolCall>();\n const order: number[] = [];\n\n const ensure = (index: number): CollectedToolCall => {\n let call = byIndex.get(index);\n if (!call) {\n call = { index, arguments: '' };\n byIndex.set(index, call);\n order.push(index);\n }\n return call;\n };\n\n for await (const event of events) {\n switch (event.type) {\n case 'text':\n text += event.text;\n break;\n case 'reasoning':\n reasoning += event.text;\n break;\n case 'tool_call_start': {\n const call = ensure(event.index);\n if (event.id !== undefined) {\n call.id = event.id;\n }\n if (event.name !== undefined) {\n call.name = event.name;\n }\n break;\n }\n case 'tool_call_delta':\n ensure(event.index).arguments += event.argumentsDelta;\n break;\n case 'finish':\n finishReason = event.reason;\n break;\n case 'error':\n break;\n }\n }\n\n return {\n text,\n reasoning,\n toolCalls: order.map((index) => byIndex.get(index)!),\n finishReason,\n };\n}\n"],"mappings":";AASO,SAAS,aAAa,OAA2B;AACtD,QAAM,SAAwB,CAAC;AAE/B,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,uBAAuB;AAC1B,YAAM,QAAQ,MAAM;AACpB,UAAI,OAAO,SAAS,YAAY;AAC9B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,OAAO,MAAM,SAAS;AAAA,UACtB,IAAI,MAAM;AAAA,UACV,MAAM,MAAM;AAAA,QACd,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAAA,IACA,KAAK,uBAAuB;AAC1B,YAAM,QAAQ,MAAM;AACpB,UAAI,OAAO,SAAS,gBAAgB,OAAO,MAAM,SAAS,UAAU;AAClE,eAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC;AAAA,MAChD,WACE,OAAO,SAAS,oBAChB,OAAO,MAAM,aAAa,UAC1B;AACA,eAAO,KAAK,EAAE,MAAM,aAAa,MAAM,MAAM,SAAS,CAAC;AAAA,MACzD,WACE,OAAO,SAAS,sBAChB,OAAO,MAAM,iBAAiB,UAC9B;AACA,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,OAAO,MAAM,SAAS;AAAA,UACtB,gBAAgB,MAAM;AAAA,QACxB,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAAA,IACA,KAAK,iBAAiB;AACpB,YAAM,SAAS,MAAM,OAAO;AAC5B,UAAI,QAAQ;AACV,eAAO,KAAK,EAAE,MAAM,UAAU,OAAO,CAAC;AAAA,MACxC;AACA;AAAA,IACF;AAAA,IACA,KAAK,SAAS;AACZ,aAAO,KAAK,EAAE,MAAM,SAAS,OAAO,MAAM,SAAS,MAAM,CAAC;AAC1D;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;AC7CO,SAAS,UAAU,OAAY,OAAmC;AACvE,QAAM,SAAwB,CAAC;AAC/B,QAAM,YAAY,OAAO,aAAa,CAAC;AACvC,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,UAAU,SAAS;AACjC,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,QAAQ,OAAO;AACxB,UAAI,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,GAAG;AAEzD,eAAO,KAAK;AAAA,UACV,MAAM,KAAK,YAAY,OAAO,cAAc;AAAA,UAC5C,MAAM,KAAK;AAAA,QACb,CAAC;AAAA,MACH;AACA,UAAI,KAAK,cAAc;AACrB,cAAM,QAAQ,MAAM;AACpB,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN;AAAA,UACA,MAAM,KAAK,aAAa;AAAA,QAC1B,CAAC;AACD,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB,KAAK,UAAU,KAAK,aAAa,QAAQ,CAAC,CAAC;AAAA,QAC7D,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,cAAc;AAC1B,WAAO,KAAK,EAAE,MAAM,UAAU,QAAQ,UAAU,aAAa,CAAC;AAAA,EAChE;AACA,SAAO;AACT;;;AC3CO,SAAS,UAAU,OAA2B;AACnD,QAAM,SAAwB,CAAC;AAC/B,QAAM,SAAS,OAAO,UAAU,CAAC;AACjC,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,OAAO;AACrB,MAAI,OAAO;AACT,QAAI,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,SAAS,GAAG;AACjE,aAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,MAAM,QAAQ,CAAC;AAAA,IACnD;AACA,QAAI,MAAM,QAAQ,MAAM,UAAU,GAAG;AACnC,iBAAW,QAAQ,MAAM,YAAY;AACnC,cAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAC5D,YAAI,KAAK,OAAO,UAAa,KAAK,UAAU,SAAS,QAAW;AAC9D,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN;AAAA,YACA,IAAI,KAAK;AAAA,YACT,MAAM,KAAK,UAAU;AAAA,UACvB,CAAC;AAAA,QACH;AACA,cAAM,OAAO,KAAK,UAAU;AAC5B,YAAI,OAAO,SAAS,YAAY,KAAK,SAAS,GAAG;AAC/C,iBAAO,KAAK,EAAE,MAAM,mBAAmB,OAAO,gBAAgB,KAAK,CAAC;AAAA,QACtE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,eAAe;AACxB,WAAO,KAAK,EAAE,MAAM,UAAU,QAAQ,OAAO,cAAc,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;;;ACrCA,gBAAgB,aAAa,QAA6C;AACxE,QAAM,UAAU,IAAI,YAAY;AAChC,mBAAiB,SAAS,QAAQ;AAChC,QAAI,OAAO,UAAU,UAAU;AAC7B,YAAM;AAAA,IACR,OAAO;AACL,YAAM,OAAO,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AACnD,UAAI,MAAM;AACR,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,QAAQ,OAAO;AAC5B,MAAI,MAAM;AACR,UAAM;AAAA,EACR;AACF;AAWA,gBAAuB,QAAQ,QAA6C;AAC1E,MAAI,SAAS;AACb,MAAI,YAAsB,CAAC;AAE3B,mBAAiB,QAAQ,aAAa,MAAM,GAAG;AAC7C,cAAU;AAEV,QAAI;AACJ,YAAQ,UAAU,OAAO,QAAQ,IAAI,OAAO,IAAI;AAC9C,YAAM,OAAO,OAAO,MAAM,GAAG,OAAO,EAAE,QAAQ,OAAO,EAAE;AACvD,eAAS,OAAO,MAAM,UAAU,CAAC;AAEjC,UAAI,SAAS,IAAI;AAEf,YAAI,UAAU,SAAS,GAAG;AACxB,gBAAM,UAAU,KAAK,IAAI;AACzB,sBAAY,CAAC;AAAA,QACf;AACA;AAAA,MACF;AACA,UAAI,KAAK,CAAC,MAAM,KAAK;AACnB;AAAA,MACF;AACA,UAAI,KAAK,WAAW,OAAO,GAAG;AAC5B,kBAAU,KAAK,KAAK,MAAM,CAAC,EAAE,QAAQ,MAAM,EAAE,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,OAAO,OAAO,QAAQ,OAAO,EAAE;AACrC,MAAI,KAAK,WAAW,OAAO,GAAG;AAC5B,cAAU,KAAK,KAAK,MAAM,CAAC,EAAE,QAAQ,MAAM,EAAE,CAAC;AAAA,EAChD;AACA,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,UAAU,KAAK,IAAI;AAAA,EAC3B;AACF;;;AChEA,gBAAgB,UACd,QACA,KAC6B;AAC7B,mBAAiB,QAAQ,QAAQ,MAAM,GAAG;AACxC,QAAI,SAAS,UAAU;AACrB;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,IAAI;AAAA,IAC3B,QAAQ;AACN;AAAA,IACF;AACA,eAAW,SAAS,IAAI,OAAO,GAAG;AAChC,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAGO,SAAS,kBACd,QAC6B;AAC7B,SAAO,UAAU,QAAQ,SAAS;AACpC;AAGO,SAAS,qBACd,QAC6B;AAC7B,SAAO,UAAU,QAAQ,YAAY;AACvC;AAGA,gBAAuB,kBACrB,QAC6B;AAC7B,QAAM,QAAQ,EAAE,WAAW,EAAE;AAC7B,mBAAiB,QAAQ,QAAQ,MAAM,GAAG;AACxC,QAAI,SAAS,UAAU;AACrB;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,IAAI;AAAA,IAC3B,QAAQ;AACN;AAAA,IACF;AACA,eAAW,SAAS,UAAU,SAAS,KAAK,GAAG;AAC7C,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAGO,SAAS,YACd,QACA,UAC6B;AAC7B,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,kBAAkB,MAAM;AAAA,IACjC,KAAK;AACH,aAAO,qBAAqB,MAAM;AAAA,IACpC,KAAK;AACH,aAAO,kBAAkB,MAAM;AAAA,EACnC;AACF;;;ACxDA,eAAsB,cACpB,QAC2B;AAC3B,MAAI,OAAO;AACX,MAAI,YAAY;AAChB,MAAI;AACJ,QAAM,UAAU,oBAAI,IAA+B;AACnD,QAAM,QAAkB,CAAC;AAEzB,QAAM,SAAS,CAAC,UAAqC;AACnD,QAAI,OAAO,QAAQ,IAAI,KAAK;AAC5B,QAAI,CAAC,MAAM;AACT,aAAO,EAAE,OAAO,WAAW,GAAG;AAC9B,cAAQ,IAAI,OAAO,IAAI;AACvB,YAAM,KAAK,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAEA,mBAAiB,SAAS,QAAQ;AAChC,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,gBAAQ,MAAM;AACd;AAAA,MACF,KAAK;AACH,qBAAa,MAAM;AACnB;AAAA,MACF,KAAK,mBAAmB;AACtB,cAAM,OAAO,OAAO,MAAM,KAAK;AAC/B,YAAI,MAAM,OAAO,QAAW;AAC1B,eAAK,KAAK,MAAM;AAAA,QAClB;AACA,YAAI,MAAM,SAAS,QAAW;AAC5B,eAAK,OAAO,MAAM;AAAA,QACpB;AACA;AAAA,MACF;AAAA,MACA,KAAK;AACH,eAAO,MAAM,KAAK,EAAE,aAAa,MAAM;AACvC;AAAA,MACF,KAAK;AACH,uBAAe,MAAM;AACrB;AAAA,MACF,KAAK;AACH;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,MAAM,IAAI,CAAC,UAAU,QAAQ,IAAI,KAAK,CAAE;AAAA,IACnD;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/sse.ts","../src/parse.ts","../src/collect.ts","../src/message.ts"],"sourcesContent":["import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one Anthropic Messages stream event into normalized events.\n *\n * Anthropic uses typed events: `content_block_start` opens a text or `tool_use`\n * block at an `index`, `content_block_delta` carries `text_delta` /\n * `input_json_delta` fragments, and `message_delta` carries the `stop_reason`.\n */\nexport function mapAnthropic(event: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n\n switch (event?.type) {\n case 'content_block_start': {\n const block = event.content_block;\n if (block?.type === 'tool_use') {\n events.push({\n type: 'tool_call_start',\n index: event.index ?? 0,\n id: block.id,\n name: block.name,\n });\n }\n break;\n }\n case 'content_block_delta': {\n const delta = event.delta;\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n events.push({ type: 'text', text: delta.text });\n } else if (\n delta?.type === 'thinking_delta' &&\n typeof delta.thinking === 'string'\n ) {\n events.push({ type: 'reasoning', text: delta.thinking });\n } else if (\n delta?.type === 'input_json_delta' &&\n typeof delta.partial_json === 'string'\n ) {\n events.push({\n type: 'tool_call_delta',\n index: event.index ?? 0,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (reason) {\n events.push({ type: 'finish', reason });\n }\n break;\n }\n case 'error': {\n events.push({ type: 'error', error: event.error ?? event });\n break;\n }\n }\n\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/** Per-stream state for Gemini, which does not number its tool calls. */\nexport interface GeminiState {\n toolIndex: number;\n}\n\n/**\n * Map one Gemini `GenerateContentResponse` chunk into normalized events.\n *\n * Gemini streams `candidates[0].content.parts[]`: a part is either `text` or a\n * complete `functionCall` (`{ name, args }`) — it does not fragment arguments,\n * so the whole `args` object is emitted as a single tool-call delta. Calls are\n * numbered in the order they appear via `state`.\n */\nexport function mapGemini(chunk: any, state: GeminiState): StreamEvent[] {\n const events: StreamEvent[] = [];\n const candidate = chunk?.candidates?.[0];\n if (!candidate) {\n return events;\n }\n\n const parts = candidate.content?.parts;\n if (Array.isArray(parts)) {\n for (const part of parts) {\n if (typeof part.text === 'string' && part.text.length > 0) {\n // Gemini flags a thinking part with `thought: true`.\n events.push({\n type: part.thought === true ? 'reasoning' : 'text',\n text: part.text,\n });\n }\n if (part.functionCall) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: part.functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(part.functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (candidate.finishReason) {\n events.push({ type: 'finish', reason: candidate.finishReason });\n }\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one OpenAI `chat.completion.chunk` into normalized events.\n *\n * OpenAI streams a `choices[0].delta`: `content` carries text, and\n * `tool_calls[]` carry an `index`, an `id` + `function.name` on the first\n * fragment, then `function.arguments` fragments thereafter.\n */\nexport function mapOpenAI(chunk: any): StreamEvent[] {\n const events: StreamEvent[] = [];\n const choice = chunk?.choices?.[0];\n if (!choice) {\n return events;\n }\n\n const delta = choice.delta;\n if (delta) {\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n events.push({ type: 'text', text: delta.content });\n }\n if (Array.isArray(delta.tool_calls)) {\n for (const call of delta.tool_calls) {\n const index = typeof call.index === 'number' ? call.index : 0;\n if (call.id !== undefined || call.function?.name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id: call.id,\n name: call.function?.name,\n });\n }\n const args = call.function?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({ type: 'tool_call_delta', index, argumentsDelta: args });\n }\n }\n }\n }\n\n if (choice.finish_reason) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\n return events;\n}\n","import type { ChunkSource } from './types.ts';\n\n/**\n * Decode a mixed byte/string chunk source into text. `Uint8Array` chunks are\n * decoded with a streaming `TextDecoder` so a multibyte UTF-8 character split\n * across two chunks is reassembled correctly.\n */\nasync function* decodeChunks(source: ChunkSource): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n for await (const chunk of source) {\n if (typeof chunk === 'string') {\n yield chunk;\n } else {\n const text = decoder.decode(chunk, { stream: true });\n if (text) {\n yield text;\n }\n }\n }\n const tail = decoder.decode();\n if (tail) {\n yield tail;\n }\n}\n\n/**\n * Parse a Server-Sent Events stream and yield the `data` payload of each event.\n *\n * Robust to the realities of streamed HTTP: events and lines split across\n * chunk boundaries are buffered until complete, multi-line `data:` fields are\n * joined with `\\n` (per the SSE spec), and comments (`:`) and other fields\n * (`event:`, `id:`, `retry:`) are ignored — the payload's own `type` field is\n * what the provider parsers key on.\n */\nexport async function* sseData(source: ChunkSource): AsyncGenerator<string> {\n let buffer = '';\n let dataLines: string[] = [];\n\n for await (const text of decodeChunks(source)) {\n buffer += text;\n\n let newline: number;\n while ((newline = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newline).replace(/\\r$/, '');\n buffer = buffer.slice(newline + 1);\n\n if (line === '') {\n // Blank line terminates an event.\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n dataLines = [];\n }\n continue;\n }\n if (line[0] === ':') {\n continue; // comment\n }\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).replace(/^ /, ''));\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.replace(/\\r$/, '');\n if (last.startsWith('data:')) {\n dataLines.push(last.slice(5).replace(/^ /, ''));\n }\n if (dataLines.length > 0) {\n yield dataLines.join('\\n');\n }\n}\n","import { mapAnthropic } from './providers/anthropic.ts';\nimport { mapGemini } from './providers/gemini.ts';\nimport { mapOpenAI } from './providers/openai.ts';\nimport { sseData } from './sse.ts';\nimport type { ChunkSource, Provider, StreamEvent } from './types.ts';\n\n/** Shared SSE-to-events driver for the stateless providers. */\nasync function* parseWith(\n source: ChunkSource,\n map: (payload: any) => StreamEvent[],\n): AsyncGenerator<StreamEvent> {\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue; // ignore keep-alive / non-JSON data lines\n }\n for (const event of map(payload)) {\n yield event;\n }\n }\n}\n\n/** Parse an OpenAI Chat Completions stream into normalized events. */\nexport function parseOpenAIStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapOpenAI);\n}\n\n/** Parse an Anthropic Messages stream into normalized events. */\nexport function parseAnthropicStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapAnthropic);\n}\n\n/** Parse a Gemini `streamGenerateContent` (SSE) stream into normalized events. */\nexport async function* parseGeminiStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n const state = { toolIndex: 0 };\n for await (const data of sseData(source)) {\n if (data === '[DONE]') {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch {\n continue;\n }\n for (const event of mapGemini(payload, state)) {\n yield event;\n }\n }\n}\n\n/** Parse a provider stream into normalized events, dispatching on `provider`. */\nexport function parseStream(\n source: ChunkSource,\n provider: Provider,\n): AsyncGenerator<StreamEvent> {\n switch (provider) {\n case 'openai':\n return parseOpenAIStream(source);\n case 'anthropic':\n return parseAnthropicStream(source);\n case 'gemini':\n return parseGeminiStream(source);\n }\n}\n","import type {\n CollectedMessage,\n CollectedToolCall,\n StreamEvent,\n} from './types.ts';\n\n/**\n * Drain a normalized event stream into a single assistant message: all text\n * concatenated, tool calls accumulated by `index` (arguments joined into one\n * JSON string), and the final stop reason.\n *\n * `error` events are not accumulated here — iterate the events directly if you\n * need to react to them mid-stream.\n *\n * @example\n * ```ts\n * const { text, toolCalls } = await collectStream(parseOpenAIStream(res.body));\n * ```\n */\nexport async function collectStream(\n events: AsyncIterable<StreamEvent>,\n): Promise<CollectedMessage> {\n let text = '';\n let reasoning = '';\n let finishReason: string | undefined;\n const byIndex = new Map<number, CollectedToolCall>();\n const order: number[] = [];\n\n const ensure = (index: number): CollectedToolCall => {\n let call = byIndex.get(index);\n if (!call) {\n call = { index, arguments: '' };\n byIndex.set(index, call);\n order.push(index);\n }\n return call;\n };\n\n for await (const event of events) {\n switch (event.type) {\n case 'text':\n text += event.text;\n break;\n case 'reasoning':\n reasoning += event.text;\n break;\n case 'tool_call_start': {\n const call = ensure(event.index);\n if (event.id !== undefined) {\n call.id = event.id;\n }\n if (event.name !== undefined) {\n call.name = event.name;\n }\n break;\n }\n case 'tool_call_delta':\n ensure(event.index).arguments += event.argumentsDelta;\n break;\n case 'finish':\n finishReason = event.reason;\n break;\n case 'error':\n break;\n }\n }\n\n return {\n text,\n reasoning,\n toolCalls: order.map((index) => byIndex.get(index)!),\n finishReason,\n };\n}\n","import type { CollectedMessage } from './types.ts';\n\n/**\n * An assistant message in OpenAI Chat Completions shape — the canonical \"hub\"\n * format. Append it to your message history to continue the conversation, or\n * pass it to `llm-messages` to port it to Anthropic or Gemini.\n */\nexport interface AssistantMessage {\n role: 'assistant';\n /** The assistant text, or `null` when the turn was only tool calls. */\n content: string | null;\n /** Present only when the turn produced tool calls. */\n tool_calls?: AssistantToolCall[];\n}\n\nexport interface AssistantToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\n\n/**\n * Turn a {@link CollectedMessage} (from {@link collectStream}) into a standard\n * assistant message you can put back into a conversation.\n *\n * Output is the OpenAI Chat Completions shape, which is the format `llm-messages`\n * treats as canonical — so this composes directly with its `toAnthropic` /\n * `toGemini` converters. Tool calls without an id (e.g. from Gemini) get a\n * stable synthetic `call_<n>` id. Reasoning is intentionally omitted: it is not\n * part of the portable assistant message.\n *\n * @example\n * ```ts\n * const collected = await collectStream(parseOpenAIStream(res.body));\n * const message = toAssistantMessage(collected);\n * history.push(message); // or: toAnthropic([...history, message])\n * ```\n */\nexport function toAssistantMessage(\n collected: CollectedMessage,\n): AssistantMessage {\n const message: AssistantMessage = {\n role: 'assistant',\n content: collected.text.length > 0 ? collected.text : null,\n };\n\n if (collected.toolCalls.length > 0) {\n message.tool_calls = collected.toolCalls.map((call, position) => ({\n id: call.id ?? `call_${position}`,\n type: 'function',\n function: {\n name: call.name ?? '',\n arguments: call.arguments.length > 0 ? call.arguments : '{}',\n },\n }));\n }\n\n return message;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-sse",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Parse streaming SSE responses from OpenAI, Anthropic and Gemini into one unified event format. Text, reasoning and tool-call deltas. Zero dependencies.",
5
5
  "keywords": [
6
6
  "openai",