llm-sse 0.4.8 → 0.5.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 +39 -0
- package/CODE_OF_CONDUCT.md +57 -0
- package/CONTRIBUTING.md +65 -0
- package/GOVERNANCE.md +71 -0
- package/README.md +103 -13
- package/ROADMAP.md +64 -0
- package/SECURITY.md +24 -0
- package/SUPPORT.md +38 -0
- package/dist/index.cjs +78 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -8
- package/dist/index.d.ts +11 -8
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -1
- package/docs/security-posture.md +20 -0
- package/fixtures/expected/openai-responses-weather-tool.events.json +17 -0
- package/fixtures/expected/openai-responses-weather-tool.message.json +13 -0
- package/fixtures/openai-responses-weather-tool.sse +17 -0
- package/package.json +16 -3
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
collectStream: () => collectStream,
|
|
24
24
|
parseAnthropicStream: () => parseAnthropicStream,
|
|
25
25
|
parseGeminiStream: () => parseGeminiStream,
|
|
26
|
+
parseOpenAIResponsesStream: () => parseOpenAIResponsesStream,
|
|
26
27
|
parseOpenAIStream: () => parseOpenAIStream,
|
|
27
28
|
parseStream: () => parseStream,
|
|
28
29
|
sseData: () => sseData,
|
|
@@ -183,6 +184,77 @@ function mapOpenAI(chunk) {
|
|
|
183
184
|
return events;
|
|
184
185
|
}
|
|
185
186
|
|
|
187
|
+
// src/providers/openai-responses.ts
|
|
188
|
+
function mapOpenAIResponses(event) {
|
|
189
|
+
if (!event || typeof event !== "object") {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
switch (event.type) {
|
|
193
|
+
case "response.output_text.delta":
|
|
194
|
+
return stringDelta(event.delta, "text");
|
|
195
|
+
// The normalized model has one user-visible text channel. Preserve refusal
|
|
196
|
+
// text there so a provider-agnostic UI never drops the model's response.
|
|
197
|
+
case "response.refusal.delta":
|
|
198
|
+
return stringDelta(event.delta, "text");
|
|
199
|
+
case "response.reasoning_summary_text.delta":
|
|
200
|
+
case "response.reasoning_text.delta":
|
|
201
|
+
return stringDelta(event.delta, "reasoning");
|
|
202
|
+
case "response.output_item.added": {
|
|
203
|
+
const item = event.item;
|
|
204
|
+
if (!item || typeof item !== "object" || item.type !== "function_call") {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
const index = numericIndex(event.output_index);
|
|
208
|
+
const id = typeof item.call_id === "string" ? item.call_id : typeof item.id === "string" ? item.id : void 0;
|
|
209
|
+
const name = typeof item.name === "string" ? item.name : void 0;
|
|
210
|
+
return [{ type: "tool_call_start", index, id, name }];
|
|
211
|
+
}
|
|
212
|
+
case "response.function_call_arguments.delta":
|
|
213
|
+
if (typeof event.delta !== "string" || event.delta.length === 0) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
return [
|
|
217
|
+
{
|
|
218
|
+
type: "tool_call_delta",
|
|
219
|
+
index: numericIndex(event.output_index),
|
|
220
|
+
argumentsDelta: event.delta
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
case "response.completed":
|
|
224
|
+
return [{ type: "finish", reason: "completed" }];
|
|
225
|
+
case "response.incomplete": {
|
|
226
|
+
const reason = event.response?.incomplete_details?.reason;
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
type: "finish",
|
|
230
|
+
reason: typeof reason === "string" ? reason : "incomplete"
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
case "response.failed":
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
type: "error",
|
|
238
|
+
error: event.response?.error ?? {
|
|
239
|
+
type: "response_failed",
|
|
240
|
+
message: "The OpenAI response failed."
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{ type: "finish", reason: "failed" }
|
|
244
|
+
];
|
|
245
|
+
case "error":
|
|
246
|
+
return [{ type: "error", error: event }];
|
|
247
|
+
default:
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function numericIndex(value) {
|
|
252
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
253
|
+
}
|
|
254
|
+
function stringDelta(value, type) {
|
|
255
|
+
return typeof value === "string" && value.length > 0 ? [{ type, text: value }] : [];
|
|
256
|
+
}
|
|
257
|
+
|
|
186
258
|
// src/sse.ts
|
|
187
259
|
async function* decodeChunks(source) {
|
|
188
260
|
const decoder = new TextDecoder();
|
|
@@ -302,6 +374,9 @@ async function* parseWith(source, map) {
|
|
|
302
374
|
function parseOpenAIStream(source) {
|
|
303
375
|
return parseWith(source, mapOpenAI);
|
|
304
376
|
}
|
|
377
|
+
function parseOpenAIResponsesStream(source) {
|
|
378
|
+
return parseWith(source, mapOpenAIResponses);
|
|
379
|
+
}
|
|
305
380
|
function parseAnthropicStream(source) {
|
|
306
381
|
return parseWith(source, mapAnthropic);
|
|
307
382
|
}
|
|
@@ -330,6 +405,8 @@ function parseStream(source, provider) {
|
|
|
330
405
|
switch (provider) {
|
|
331
406
|
case "openai":
|
|
332
407
|
return parseOpenAIStream(source);
|
|
408
|
+
case "openai-responses":
|
|
409
|
+
return parseOpenAIResponsesStream(source);
|
|
333
410
|
case "anthropic":
|
|
334
411
|
return parseAnthropicStream(source);
|
|
335
412
|
case "gemini":
|
|
@@ -424,6 +501,7 @@ function toAssistantMessage(collected) {
|
|
|
424
501
|
collectStream,
|
|
425
502
|
parseAnthropicStream,
|
|
426
503
|
parseGeminiStream,
|
|
504
|
+
parseOpenAIResponsesStream,
|
|
427
505
|
parseOpenAIStream,
|
|
428
506
|
parseStream,
|
|
429
507
|
sseData,
|
package/dist/index.cjs.map
CHANGED
|
@@ -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","../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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_start',\n index,\n id: typeof block.id === 'string' ? block.id : undefined,\n name: typeof block.name === 'string' ? block.name : undefined,\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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (typeof reason === 'string' && reason.length > 0) {\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\nfunction blockIndex(index: unknown): number | undefined {\n return typeof index === 'number' && Number.isInteger(index) && index >= 0\n ? index\n : undefined;\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 (!part || typeof part !== 'object') {\n continue;\n }\n\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 const functionCall = part.functionCall;\n if (\n functionCall &&\n typeof functionCall === 'object' &&\n !Array.isArray(functionCall) &&\n typeof functionCall.name === 'string' &&\n functionCall.name.length > 0\n ) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (\n typeof candidate.finishReason === 'string' &&\n candidate.finishReason.length > 0\n ) {\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 `choices[].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 choices = Array.isArray(chunk?.choices) ? chunk.choices : [];\n if (choices.length === 0) {\n return events;\n }\n\n for (const choice of choices) {\n if (!choice || typeof choice !== 'object') {\n continue;\n }\n\n const delta = choice.delta;\n if (delta && typeof delta === 'object') {\n // Reasoning models on OpenAI-compatible endpoints (e.g. DeepSeek R1)\n // stream their thinking in `reasoning_content` (some use `reasoning`).\n const reasoning = delta.reasoning_content ?? delta.reasoning;\n if (typeof reasoning === 'string' && reasoning.length > 0) {\n events.push({ type: 'reasoning', text: reasoning });\n }\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 if (!call || typeof call !== 'object') {\n continue;\n }\n\n const index = typeof call.index === 'number' ? call.index : 0;\n const fn =\n call.function && typeof call.function === 'object'\n ? call.function\n : undefined;\n const id = typeof call.id === 'string' ? call.id : undefined;\n const name = typeof fn?.name === 'string' ? fn.name : undefined;\n\n if (id !== undefined || name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id,\n name,\n });\n }\n const args = fn?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: args,\n });\n }\n }\n }\n }\n\n if (\n typeof choice.finish_reason === 'string' &&\n choice.finish_reason.length > 0\n ) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\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 // The SSE spec strips one leading BOM. TextDecoder already does this for\n // byte chunks; this covers sources that yield strings.\n let atStart = true;\n\n const addLine = (line: string): void => {\n if (line[0] === ':') {\n return; // comment\n }\n\n const separator = line.indexOf(':');\n const field = separator === -1 ? line : line.slice(0, separator);\n let value = separator === -1 ? '' : line.slice(separator + 1);\n if (value.startsWith(' ')) {\n value = value.slice(1);\n }\n\n if (field === 'data') {\n dataLines.push(value);\n }\n };\n\n const finishEvent = (): string | undefined => {\n if (dataLines.length === 0) {\n return undefined;\n }\n const data = dataLines.join('\\n');\n dataLines = [];\n return data;\n };\n\n const processLine = (line: string): string | undefined => {\n if (line === '') {\n return finishEvent();\n }\n addLine(line);\n return undefined;\n };\n\n for await (const text of decodeChunks(source)) {\n if (atStart && text.length > 0) {\n atStart = false;\n buffer += text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n } else {\n buffer += text;\n }\n\n while (buffer.length > 0) {\n const cr = buffer.indexOf('\\r');\n const lf = buffer.indexOf('\\n');\n let newline: number;\n\n if (cr === -1) {\n newline = lf;\n } else if (lf === -1) {\n newline = cr;\n } else {\n newline = Math.min(cr, lf);\n }\n\n if (newline === -1) {\n break;\n }\n\n const newlineChar = buffer[newline];\n if (newlineChar === '\\r' && newline === buffer.length - 1) {\n break;\n }\n\n const line = buffer.slice(0, newline);\n const nextIsLf = newlineChar === '\\r' && buffer[newline + 1] === '\\n';\n buffer = buffer.slice(newline + (nextIsLf ? 2 : 1));\n\n const data = processLine(line);\n if (data !== undefined) {\n yield data;\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.endsWith('\\r') ? buffer.slice(0, -1) : buffer;\n if (last !== '') {\n addLine(last);\n }\n const data = finishEvent();\n if (data !== undefined) {\n yield data;\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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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\nfunction malformedJsonEvent(error: unknown): StreamEvent {\n return {\n type: 'error',\n error: {\n type: 'malformed_json',\n message: error instanceof Error ? error.message : String(error),\n },\n };\n}\n\nfunction isDone(data: string): boolean {\n return data.trim() === '[DONE]';\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":["data"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/providers/openai-responses.ts","../src/sse.ts","../src/parse.ts","../src/collect.ts","../src/message.ts"],"sourcesContent":["export {\n parseStream,\n parseOpenAIStream,\n parseOpenAIResponsesStream,\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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_start',\n index,\n id: typeof block.id === 'string' ? block.id : undefined,\n name: typeof block.name === 'string' ? block.name : undefined,\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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (typeof reason === 'string' && reason.length > 0) {\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\nfunction blockIndex(index: unknown): number | undefined {\n return typeof index === 'number' && Number.isInteger(index) && index >= 0\n ? index\n : undefined;\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 (!part || typeof part !== 'object') {\n continue;\n }\n\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 const functionCall = part.functionCall;\n if (\n functionCall &&\n typeof functionCall === 'object' &&\n !Array.isArray(functionCall) &&\n typeof functionCall.name === 'string' &&\n functionCall.name.length > 0\n ) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (\n typeof candidate.finishReason === 'string' &&\n candidate.finishReason.length > 0\n ) {\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 `choices[].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 choices = Array.isArray(chunk?.choices) ? chunk.choices : [];\n if (choices.length === 0) {\n return events;\n }\n\n for (const choice of choices) {\n if (!choice || typeof choice !== 'object') {\n continue;\n }\n\n const delta = choice.delta;\n if (delta && typeof delta === 'object') {\n // Reasoning models on OpenAI-compatible endpoints (e.g. DeepSeek R1)\n // stream their thinking in `reasoning_content` (some use `reasoning`).\n const reasoning = delta.reasoning_content ?? delta.reasoning;\n if (typeof reasoning === 'string' && reasoning.length > 0) {\n events.push({ type: 'reasoning', text: reasoning });\n }\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 if (!call || typeof call !== 'object') {\n continue;\n }\n\n const index = typeof call.index === 'number' ? call.index : 0;\n const fn =\n call.function && typeof call.function === 'object'\n ? call.function\n : undefined;\n const id = typeof call.id === 'string' ? call.id : undefined;\n const name = typeof fn?.name === 'string' ? fn.name : undefined;\n\n if (id !== undefined || name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id,\n name,\n });\n }\n const args = fn?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: args,\n });\n }\n }\n }\n }\n\n if (\n typeof choice.finish_reason === 'string' &&\n choice.finish_reason.length > 0\n ) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\n }\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one OpenAI Responses API streaming event into normalized events.\n *\n * Responses streams are typed events rather than Chat Completions\n * `choices[].delta` chunks. Text, reasoning summaries, function calls,\n * completion state and errors are normalized here while unrelated built-in\n * tool events pass through without producing an event.\n *\n * @see https://platform.openai.com/docs/api-reference/responses-streaming\n */\nexport function mapOpenAIResponses(event: any): StreamEvent[] {\n if (!event || typeof event !== 'object') {\n return [];\n }\n\n switch (event.type) {\n case 'response.output_text.delta':\n return stringDelta(event.delta, 'text');\n\n // The normalized model has one user-visible text channel. Preserve refusal\n // text there so a provider-agnostic UI never drops the model's response.\n case 'response.refusal.delta':\n return stringDelta(event.delta, 'text');\n\n case 'response.reasoning_summary_text.delta':\n case 'response.reasoning_text.delta':\n return stringDelta(event.delta, 'reasoning');\n\n case 'response.output_item.added': {\n const item = event.item;\n if (!item || typeof item !== 'object' || item.type !== 'function_call') {\n return [];\n }\n\n const index = numericIndex(event.output_index);\n const id =\n typeof item.call_id === 'string'\n ? item.call_id\n : typeof item.id === 'string'\n ? item.id\n : undefined;\n const name = typeof item.name === 'string' ? item.name : undefined;\n\n return [{ type: 'tool_call_start', index, id, name }];\n }\n\n case 'response.function_call_arguments.delta':\n if (typeof event.delta !== 'string' || event.delta.length === 0) {\n return [];\n }\n return [\n {\n type: 'tool_call_delta',\n index: numericIndex(event.output_index),\n argumentsDelta: event.delta,\n },\n ];\n\n case 'response.completed':\n return [{ type: 'finish', reason: 'completed' }];\n\n case 'response.incomplete': {\n const reason = event.response?.incomplete_details?.reason;\n return [\n {\n type: 'finish',\n reason: typeof reason === 'string' ? reason : 'incomplete',\n },\n ];\n }\n\n case 'response.failed':\n return [\n {\n type: 'error',\n error: event.response?.error ?? {\n type: 'response_failed',\n message: 'The OpenAI response failed.',\n },\n },\n { type: 'finish', reason: 'failed' },\n ];\n\n case 'error':\n return [{ type: 'error', error: event }];\n\n default:\n return [];\n }\n}\n\nfunction numericIndex(value: unknown): number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0\n ? value\n : 0;\n}\n\nfunction stringDelta(\n value: unknown,\n type: 'text' | 'reasoning',\n): StreamEvent[] {\n return typeof value === 'string' && value.length > 0\n ? [{ type, text: value }]\n : [];\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 // The SSE spec strips one leading BOM. TextDecoder already does this for\n // byte chunks; this covers sources that yield strings.\n let atStart = true;\n\n const addLine = (line: string): void => {\n if (line[0] === ':') {\n return; // comment\n }\n\n const separator = line.indexOf(':');\n const field = separator === -1 ? line : line.slice(0, separator);\n let value = separator === -1 ? '' : line.slice(separator + 1);\n if (value.startsWith(' ')) {\n value = value.slice(1);\n }\n\n if (field === 'data') {\n dataLines.push(value);\n }\n };\n\n const finishEvent = (): string | undefined => {\n if (dataLines.length === 0) {\n return undefined;\n }\n const data = dataLines.join('\\n');\n dataLines = [];\n return data;\n };\n\n const processLine = (line: string): string | undefined => {\n if (line === '') {\n return finishEvent();\n }\n addLine(line);\n return undefined;\n };\n\n for await (const text of decodeChunks(source)) {\n if (atStart && text.length > 0) {\n atStart = false;\n buffer += text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n } else {\n buffer += text;\n }\n\n while (buffer.length > 0) {\n const cr = buffer.indexOf('\\r');\n const lf = buffer.indexOf('\\n');\n let newline: number;\n\n if (cr === -1) {\n newline = lf;\n } else if (lf === -1) {\n newline = cr;\n } else {\n newline = Math.min(cr, lf);\n }\n\n if (newline === -1) {\n break;\n }\n\n const newlineChar = buffer[newline];\n if (newlineChar === '\\r' && newline === buffer.length - 1) {\n break;\n }\n\n const line = buffer.slice(0, newline);\n const nextIsLf = newlineChar === '\\r' && buffer[newline + 1] === '\\n';\n buffer = buffer.slice(newline + (nextIsLf ? 2 : 1));\n\n const data = processLine(line);\n if (data !== undefined) {\n yield data;\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.endsWith('\\r') ? buffer.slice(0, -1) : buffer;\n if (last !== '') {\n addLine(last);\n }\n const data = finishEvent();\n if (data !== undefined) {\n yield data;\n }\n}\n","import { mapAnthropic } from './providers/anthropic.ts';\nimport { mapGemini } from './providers/gemini.ts';\nimport { mapOpenAI } from './providers/openai.ts';\nimport { mapOpenAIResponses } from './providers/openai-responses.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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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 OpenAI Responses API stream into normalized events. */\nexport function parseOpenAIResponsesStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapOpenAIResponses);\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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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 'openai-responses':\n return parseOpenAIResponsesStream(source);\n case 'anthropic':\n return parseAnthropicStream(source);\n case 'gemini':\n return parseGeminiStream(source);\n }\n}\n\nfunction malformedJsonEvent(error: unknown): StreamEvent {\n return {\n type: 'error',\n error: {\n type: 'malformed_json',\n message: error instanceof Error ? error.message : String(error),\n },\n };\n}\n\nfunction isDone(data: string): boolean {\n return data.trim() === '[DONE]';\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":["data"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
/** The provider whose streaming format is being parsed. */
|
|
2
|
-
type Provider = 'openai' | 'anthropic' | 'gemini';
|
|
1
|
+
/** The provider/API whose streaming format is being parsed. */
|
|
2
|
+
type Provider = 'openai' | 'openai-responses' | 'anthropic' | 'gemini';
|
|
3
3
|
/**
|
|
4
4
|
* A single normalized event emitted while parsing a provider stream.
|
|
5
5
|
*
|
|
6
|
-
* The
|
|
7
|
-
* chunks,
|
|
8
|
-
* sends `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The providers stream very differently — OpenAI Chat Completions sends
|
|
7
|
+
* `choices[].delta` chunks, OpenAI Responses sends typed `response.*` events,
|
|
8
|
+
* Anthropic sends `content_block_*` / `message_*` events, and Gemini sends
|
|
9
|
+
* `candidates[].content.parts` — but they all reduce to the same handful of
|
|
10
|
+
* things: text arrived, reasoning arrived, a tool call started, tool-call
|
|
11
|
+
* arguments arrived, the turn finished, or the provider reported an error.
|
|
11
12
|
*/
|
|
12
13
|
type StreamEvent =
|
|
13
14
|
/** A chunk of assistant text. */
|
|
@@ -74,6 +75,8 @@ type ChunkSource = AsyncIterable<Uint8Array | string>;
|
|
|
74
75
|
|
|
75
76
|
/** Parse an OpenAI Chat Completions stream into normalized events. */
|
|
76
77
|
declare function parseOpenAIStream(source: ChunkSource): AsyncGenerator<StreamEvent>;
|
|
78
|
+
/** Parse an OpenAI Responses API stream into normalized events. */
|
|
79
|
+
declare function parseOpenAIResponsesStream(source: ChunkSource): AsyncGenerator<StreamEvent>;
|
|
77
80
|
/** Parse an Anthropic Messages stream into normalized events. */
|
|
78
81
|
declare function parseAnthropicStream(source: ChunkSource): AsyncGenerator<StreamEvent>;
|
|
79
82
|
/** Parse a Gemini `streamGenerateContent` (SSE) stream into normalized events. */
|
|
@@ -146,4 +149,4 @@ declare function toAssistantMessage(collected: CollectedMessage): AssistantMessa
|
|
|
146
149
|
*/
|
|
147
150
|
declare function sseData(source: ChunkSource): AsyncGenerator<string>;
|
|
148
151
|
|
|
149
|
-
export { type AssistantMessage, type AssistantToolCall, type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIStream, parseStream, sseData, toAssistantMessage };
|
|
152
|
+
export { type AssistantMessage, type AssistantToolCall, type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIResponsesStream, parseOpenAIStream, parseStream, sseData, toAssistantMessage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
/** The provider whose streaming format is being parsed. */
|
|
2
|
-
type Provider = 'openai' | 'anthropic' | 'gemini';
|
|
1
|
+
/** The provider/API whose streaming format is being parsed. */
|
|
2
|
+
type Provider = 'openai' | 'openai-responses' | 'anthropic' | 'gemini';
|
|
3
3
|
/**
|
|
4
4
|
* A single normalized event emitted while parsing a provider stream.
|
|
5
5
|
*
|
|
6
|
-
* The
|
|
7
|
-
* chunks,
|
|
8
|
-
* sends `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The providers stream very differently — OpenAI Chat Completions sends
|
|
7
|
+
* `choices[].delta` chunks, OpenAI Responses sends typed `response.*` events,
|
|
8
|
+
* Anthropic sends `content_block_*` / `message_*` events, and Gemini sends
|
|
9
|
+
* `candidates[].content.parts` — but they all reduce to the same handful of
|
|
10
|
+
* things: text arrived, reasoning arrived, a tool call started, tool-call
|
|
11
|
+
* arguments arrived, the turn finished, or the provider reported an error.
|
|
11
12
|
*/
|
|
12
13
|
type StreamEvent =
|
|
13
14
|
/** A chunk of assistant text. */
|
|
@@ -74,6 +75,8 @@ type ChunkSource = AsyncIterable<Uint8Array | string>;
|
|
|
74
75
|
|
|
75
76
|
/** Parse an OpenAI Chat Completions stream into normalized events. */
|
|
76
77
|
declare function parseOpenAIStream(source: ChunkSource): AsyncGenerator<StreamEvent>;
|
|
78
|
+
/** Parse an OpenAI Responses API stream into normalized events. */
|
|
79
|
+
declare function parseOpenAIResponsesStream(source: ChunkSource): AsyncGenerator<StreamEvent>;
|
|
77
80
|
/** Parse an Anthropic Messages stream into normalized events. */
|
|
78
81
|
declare function parseAnthropicStream(source: ChunkSource): AsyncGenerator<StreamEvent>;
|
|
79
82
|
/** Parse a Gemini `streamGenerateContent` (SSE) stream into normalized events. */
|
|
@@ -146,4 +149,4 @@ declare function toAssistantMessage(collected: CollectedMessage): AssistantMessa
|
|
|
146
149
|
*/
|
|
147
150
|
declare function sseData(source: ChunkSource): AsyncGenerator<string>;
|
|
148
151
|
|
|
149
|
-
export { type AssistantMessage, type AssistantToolCall, type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIStream, parseStream, sseData, toAssistantMessage };
|
|
152
|
+
export { type AssistantMessage, type AssistantToolCall, type ChunkSource, type CollectedMessage, type CollectedToolCall, type Provider, type StreamEvent, collectStream, parseAnthropicStream, parseGeminiStream, parseOpenAIResponsesStream, parseOpenAIStream, parseStream, sseData, toAssistantMessage };
|
package/dist/index.js
CHANGED
|
@@ -151,6 +151,77 @@ function mapOpenAI(chunk) {
|
|
|
151
151
|
return events;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// src/providers/openai-responses.ts
|
|
155
|
+
function mapOpenAIResponses(event) {
|
|
156
|
+
if (!event || typeof event !== "object") {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
switch (event.type) {
|
|
160
|
+
case "response.output_text.delta":
|
|
161
|
+
return stringDelta(event.delta, "text");
|
|
162
|
+
// The normalized model has one user-visible text channel. Preserve refusal
|
|
163
|
+
// text there so a provider-agnostic UI never drops the model's response.
|
|
164
|
+
case "response.refusal.delta":
|
|
165
|
+
return stringDelta(event.delta, "text");
|
|
166
|
+
case "response.reasoning_summary_text.delta":
|
|
167
|
+
case "response.reasoning_text.delta":
|
|
168
|
+
return stringDelta(event.delta, "reasoning");
|
|
169
|
+
case "response.output_item.added": {
|
|
170
|
+
const item = event.item;
|
|
171
|
+
if (!item || typeof item !== "object" || item.type !== "function_call") {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
const index = numericIndex(event.output_index);
|
|
175
|
+
const id = typeof item.call_id === "string" ? item.call_id : typeof item.id === "string" ? item.id : void 0;
|
|
176
|
+
const name = typeof item.name === "string" ? item.name : void 0;
|
|
177
|
+
return [{ type: "tool_call_start", index, id, name }];
|
|
178
|
+
}
|
|
179
|
+
case "response.function_call_arguments.delta":
|
|
180
|
+
if (typeof event.delta !== "string" || event.delta.length === 0) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return [
|
|
184
|
+
{
|
|
185
|
+
type: "tool_call_delta",
|
|
186
|
+
index: numericIndex(event.output_index),
|
|
187
|
+
argumentsDelta: event.delta
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
case "response.completed":
|
|
191
|
+
return [{ type: "finish", reason: "completed" }];
|
|
192
|
+
case "response.incomplete": {
|
|
193
|
+
const reason = event.response?.incomplete_details?.reason;
|
|
194
|
+
return [
|
|
195
|
+
{
|
|
196
|
+
type: "finish",
|
|
197
|
+
reason: typeof reason === "string" ? reason : "incomplete"
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
case "response.failed":
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
type: "error",
|
|
205
|
+
error: event.response?.error ?? {
|
|
206
|
+
type: "response_failed",
|
|
207
|
+
message: "The OpenAI response failed."
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{ type: "finish", reason: "failed" }
|
|
211
|
+
];
|
|
212
|
+
case "error":
|
|
213
|
+
return [{ type: "error", error: event }];
|
|
214
|
+
default:
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function numericIndex(value) {
|
|
219
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : 0;
|
|
220
|
+
}
|
|
221
|
+
function stringDelta(value, type) {
|
|
222
|
+
return typeof value === "string" && value.length > 0 ? [{ type, text: value }] : [];
|
|
223
|
+
}
|
|
224
|
+
|
|
154
225
|
// src/sse.ts
|
|
155
226
|
async function* decodeChunks(source) {
|
|
156
227
|
const decoder = new TextDecoder();
|
|
@@ -270,6 +341,9 @@ async function* parseWith(source, map) {
|
|
|
270
341
|
function parseOpenAIStream(source) {
|
|
271
342
|
return parseWith(source, mapOpenAI);
|
|
272
343
|
}
|
|
344
|
+
function parseOpenAIResponsesStream(source) {
|
|
345
|
+
return parseWith(source, mapOpenAIResponses);
|
|
346
|
+
}
|
|
273
347
|
function parseAnthropicStream(source) {
|
|
274
348
|
return parseWith(source, mapAnthropic);
|
|
275
349
|
}
|
|
@@ -298,6 +372,8 @@ function parseStream(source, provider) {
|
|
|
298
372
|
switch (provider) {
|
|
299
373
|
case "openai":
|
|
300
374
|
return parseOpenAIStream(source);
|
|
375
|
+
case "openai-responses":
|
|
376
|
+
return parseOpenAIResponsesStream(source);
|
|
301
377
|
case "anthropic":
|
|
302
378
|
return parseAnthropicStream(source);
|
|
303
379
|
case "gemini":
|
|
@@ -391,6 +467,7 @@ export {
|
|
|
391
467
|
collectStream,
|
|
392
468
|
parseAnthropicStream,
|
|
393
469
|
parseGeminiStream,
|
|
470
|
+
parseOpenAIResponsesStream,
|
|
394
471
|
parseOpenAIStream,
|
|
395
472
|
parseStream,
|
|
396
473
|
sseData,
|
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","../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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_start',\n index,\n id: typeof block.id === 'string' ? block.id : undefined,\n name: typeof block.name === 'string' ? block.name : undefined,\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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (typeof reason === 'string' && reason.length > 0) {\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\nfunction blockIndex(index: unknown): number | undefined {\n return typeof index === 'number' && Number.isInteger(index) && index >= 0\n ? index\n : undefined;\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 (!part || typeof part !== 'object') {\n continue;\n }\n\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 const functionCall = part.functionCall;\n if (\n functionCall &&\n typeof functionCall === 'object' &&\n !Array.isArray(functionCall) &&\n typeof functionCall.name === 'string' &&\n functionCall.name.length > 0\n ) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (\n typeof candidate.finishReason === 'string' &&\n candidate.finishReason.length > 0\n ) {\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 `choices[].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 choices = Array.isArray(chunk?.choices) ? chunk.choices : [];\n if (choices.length === 0) {\n return events;\n }\n\n for (const choice of choices) {\n if (!choice || typeof choice !== 'object') {\n continue;\n }\n\n const delta = choice.delta;\n if (delta && typeof delta === 'object') {\n // Reasoning models on OpenAI-compatible endpoints (e.g. DeepSeek R1)\n // stream their thinking in `reasoning_content` (some use `reasoning`).\n const reasoning = delta.reasoning_content ?? delta.reasoning;\n if (typeof reasoning === 'string' && reasoning.length > 0) {\n events.push({ type: 'reasoning', text: reasoning });\n }\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 if (!call || typeof call !== 'object') {\n continue;\n }\n\n const index = typeof call.index === 'number' ? call.index : 0;\n const fn =\n call.function && typeof call.function === 'object'\n ? call.function\n : undefined;\n const id = typeof call.id === 'string' ? call.id : undefined;\n const name = typeof fn?.name === 'string' ? fn.name : undefined;\n\n if (id !== undefined || name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id,\n name,\n });\n }\n const args = fn?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: args,\n });\n }\n }\n }\n }\n\n if (\n typeof choice.finish_reason === 'string' &&\n choice.finish_reason.length > 0\n ) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\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 // The SSE spec strips one leading BOM. TextDecoder already does this for\n // byte chunks; this covers sources that yield strings.\n let atStart = true;\n\n const addLine = (line: string): void => {\n if (line[0] === ':') {\n return; // comment\n }\n\n const separator = line.indexOf(':');\n const field = separator === -1 ? line : line.slice(0, separator);\n let value = separator === -1 ? '' : line.slice(separator + 1);\n if (value.startsWith(' ')) {\n value = value.slice(1);\n }\n\n if (field === 'data') {\n dataLines.push(value);\n }\n };\n\n const finishEvent = (): string | undefined => {\n if (dataLines.length === 0) {\n return undefined;\n }\n const data = dataLines.join('\\n');\n dataLines = [];\n return data;\n };\n\n const processLine = (line: string): string | undefined => {\n if (line === '') {\n return finishEvent();\n }\n addLine(line);\n return undefined;\n };\n\n for await (const text of decodeChunks(source)) {\n if (atStart && text.length > 0) {\n atStart = false;\n buffer += text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n } else {\n buffer += text;\n }\n\n while (buffer.length > 0) {\n const cr = buffer.indexOf('\\r');\n const lf = buffer.indexOf('\\n');\n let newline: number;\n\n if (cr === -1) {\n newline = lf;\n } else if (lf === -1) {\n newline = cr;\n } else {\n newline = Math.min(cr, lf);\n }\n\n if (newline === -1) {\n break;\n }\n\n const newlineChar = buffer[newline];\n if (newlineChar === '\\r' && newline === buffer.length - 1) {\n break;\n }\n\n const line = buffer.slice(0, newline);\n const nextIsLf = newlineChar === '\\r' && buffer[newline + 1] === '\\n';\n buffer = buffer.slice(newline + (nextIsLf ? 2 : 1));\n\n const data = processLine(line);\n if (data !== undefined) {\n yield data;\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.endsWith('\\r') ? buffer.slice(0, -1) : buffer;\n if (last !== '') {\n addLine(last);\n }\n const data = finishEvent();\n if (data !== undefined) {\n yield data;\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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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\nfunction malformedJsonEvent(error: unknown): StreamEvent {\n return {\n type: 'error',\n error: {\n type: 'malformed_json',\n message: error instanceof Error ? error.message : String(error),\n },\n };\n}\n\nfunction isDone(data: string): boolean {\n return data.trim() === '[DONE]';\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":["data"]}
|
|
1
|
+
{"version":3,"sources":["../src/providers/anthropic.ts","../src/providers/gemini.ts","../src/providers/openai.ts","../src/providers/openai-responses.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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_start',\n index,\n id: typeof block.id === 'string' ? block.id : undefined,\n name: typeof block.name === 'string' ? block.name : undefined,\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 const index = blockIndex(event.index);\n if (index === undefined) {\n break;\n }\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: delta.partial_json,\n });\n }\n break;\n }\n case 'message_delta': {\n const reason = event.delta?.stop_reason;\n if (typeof reason === 'string' && reason.length > 0) {\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\nfunction blockIndex(index: unknown): number | undefined {\n return typeof index === 'number' && Number.isInteger(index) && index >= 0\n ? index\n : undefined;\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 (!part || typeof part !== 'object') {\n continue;\n }\n\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 const functionCall = part.functionCall;\n if (\n functionCall &&\n typeof functionCall === 'object' &&\n !Array.isArray(functionCall) &&\n typeof functionCall.name === 'string' &&\n functionCall.name.length > 0\n ) {\n const index = state.toolIndex++;\n events.push({\n type: 'tool_call_start',\n index,\n name: functionCall.name,\n });\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: JSON.stringify(functionCall.args ?? {}),\n });\n }\n }\n }\n\n if (\n typeof candidate.finishReason === 'string' &&\n candidate.finishReason.length > 0\n ) {\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 `choices[].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 choices = Array.isArray(chunk?.choices) ? chunk.choices : [];\n if (choices.length === 0) {\n return events;\n }\n\n for (const choice of choices) {\n if (!choice || typeof choice !== 'object') {\n continue;\n }\n\n const delta = choice.delta;\n if (delta && typeof delta === 'object') {\n // Reasoning models on OpenAI-compatible endpoints (e.g. DeepSeek R1)\n // stream their thinking in `reasoning_content` (some use `reasoning`).\n const reasoning = delta.reasoning_content ?? delta.reasoning;\n if (typeof reasoning === 'string' && reasoning.length > 0) {\n events.push({ type: 'reasoning', text: reasoning });\n }\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 if (!call || typeof call !== 'object') {\n continue;\n }\n\n const index = typeof call.index === 'number' ? call.index : 0;\n const fn =\n call.function && typeof call.function === 'object'\n ? call.function\n : undefined;\n const id = typeof call.id === 'string' ? call.id : undefined;\n const name = typeof fn?.name === 'string' ? fn.name : undefined;\n\n if (id !== undefined || name !== undefined) {\n events.push({\n type: 'tool_call_start',\n index,\n id,\n name,\n });\n }\n const args = fn?.arguments;\n if (typeof args === 'string' && args.length > 0) {\n events.push({\n type: 'tool_call_delta',\n index,\n argumentsDelta: args,\n });\n }\n }\n }\n }\n\n if (\n typeof choice.finish_reason === 'string' &&\n choice.finish_reason.length > 0\n ) {\n events.push({ type: 'finish', reason: choice.finish_reason });\n }\n }\n return events;\n}\n","import type { StreamEvent } from '../types.ts';\n\n/**\n * Map one OpenAI Responses API streaming event into normalized events.\n *\n * Responses streams are typed events rather than Chat Completions\n * `choices[].delta` chunks. Text, reasoning summaries, function calls,\n * completion state and errors are normalized here while unrelated built-in\n * tool events pass through without producing an event.\n *\n * @see https://platform.openai.com/docs/api-reference/responses-streaming\n */\nexport function mapOpenAIResponses(event: any): StreamEvent[] {\n if (!event || typeof event !== 'object') {\n return [];\n }\n\n switch (event.type) {\n case 'response.output_text.delta':\n return stringDelta(event.delta, 'text');\n\n // The normalized model has one user-visible text channel. Preserve refusal\n // text there so a provider-agnostic UI never drops the model's response.\n case 'response.refusal.delta':\n return stringDelta(event.delta, 'text');\n\n case 'response.reasoning_summary_text.delta':\n case 'response.reasoning_text.delta':\n return stringDelta(event.delta, 'reasoning');\n\n case 'response.output_item.added': {\n const item = event.item;\n if (!item || typeof item !== 'object' || item.type !== 'function_call') {\n return [];\n }\n\n const index = numericIndex(event.output_index);\n const id =\n typeof item.call_id === 'string'\n ? item.call_id\n : typeof item.id === 'string'\n ? item.id\n : undefined;\n const name = typeof item.name === 'string' ? item.name : undefined;\n\n return [{ type: 'tool_call_start', index, id, name }];\n }\n\n case 'response.function_call_arguments.delta':\n if (typeof event.delta !== 'string' || event.delta.length === 0) {\n return [];\n }\n return [\n {\n type: 'tool_call_delta',\n index: numericIndex(event.output_index),\n argumentsDelta: event.delta,\n },\n ];\n\n case 'response.completed':\n return [{ type: 'finish', reason: 'completed' }];\n\n case 'response.incomplete': {\n const reason = event.response?.incomplete_details?.reason;\n return [\n {\n type: 'finish',\n reason: typeof reason === 'string' ? reason : 'incomplete',\n },\n ];\n }\n\n case 'response.failed':\n return [\n {\n type: 'error',\n error: event.response?.error ?? {\n type: 'response_failed',\n message: 'The OpenAI response failed.',\n },\n },\n { type: 'finish', reason: 'failed' },\n ];\n\n case 'error':\n return [{ type: 'error', error: event }];\n\n default:\n return [];\n }\n}\n\nfunction numericIndex(value: unknown): number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0\n ? value\n : 0;\n}\n\nfunction stringDelta(\n value: unknown,\n type: 'text' | 'reasoning',\n): StreamEvent[] {\n return typeof value === 'string' && value.length > 0\n ? [{ type, text: value }]\n : [];\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 // The SSE spec strips one leading BOM. TextDecoder already does this for\n // byte chunks; this covers sources that yield strings.\n let atStart = true;\n\n const addLine = (line: string): void => {\n if (line[0] === ':') {\n return; // comment\n }\n\n const separator = line.indexOf(':');\n const field = separator === -1 ? line : line.slice(0, separator);\n let value = separator === -1 ? '' : line.slice(separator + 1);\n if (value.startsWith(' ')) {\n value = value.slice(1);\n }\n\n if (field === 'data') {\n dataLines.push(value);\n }\n };\n\n const finishEvent = (): string | undefined => {\n if (dataLines.length === 0) {\n return undefined;\n }\n const data = dataLines.join('\\n');\n dataLines = [];\n return data;\n };\n\n const processLine = (line: string): string | undefined => {\n if (line === '') {\n return finishEvent();\n }\n addLine(line);\n return undefined;\n };\n\n for await (const text of decodeChunks(source)) {\n if (atStart && text.length > 0) {\n atStart = false;\n buffer += text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n } else {\n buffer += text;\n }\n\n while (buffer.length > 0) {\n const cr = buffer.indexOf('\\r');\n const lf = buffer.indexOf('\\n');\n let newline: number;\n\n if (cr === -1) {\n newline = lf;\n } else if (lf === -1) {\n newline = cr;\n } else {\n newline = Math.min(cr, lf);\n }\n\n if (newline === -1) {\n break;\n }\n\n const newlineChar = buffer[newline];\n if (newlineChar === '\\r' && newline === buffer.length - 1) {\n break;\n }\n\n const line = buffer.slice(0, newline);\n const nextIsLf = newlineChar === '\\r' && buffer[newline + 1] === '\\n';\n buffer = buffer.slice(newline + (nextIsLf ? 2 : 1));\n\n const data = processLine(line);\n if (data !== undefined) {\n yield data;\n }\n }\n }\n\n // A final event may arrive without a trailing blank line.\n const last = buffer.endsWith('\\r') ? buffer.slice(0, -1) : buffer;\n if (last !== '') {\n addLine(last);\n }\n const data = finishEvent();\n if (data !== undefined) {\n yield data;\n }\n}\n","import { mapAnthropic } from './providers/anthropic.ts';\nimport { mapGemini } from './providers/gemini.ts';\nimport { mapOpenAI } from './providers/openai.ts';\nimport { mapOpenAIResponses } from './providers/openai-responses.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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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 OpenAI Responses API stream into normalized events. */\nexport function parseOpenAIResponsesStream(\n source: ChunkSource,\n): AsyncGenerator<StreamEvent> {\n return parseWith(source, mapOpenAIResponses);\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 (isDone(data)) {\n return;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(data);\n } catch (error) {\n const trimmed = data.trimStart();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n yield malformedJsonEvent(error);\n }\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 'openai-responses':\n return parseOpenAIResponsesStream(source);\n case 'anthropic':\n return parseAnthropicStream(source);\n case 'gemini':\n return parseGeminiStream(source);\n }\n}\n\nfunction malformedJsonEvent(error: unknown): StreamEvent {\n return {\n type: 'error',\n error: {\n type: 'malformed_json',\n message: error instanceof Error ? error.message : String(error),\n },\n };\n}\n\nfunction isDone(data: string): boolean {\n return data.trim() === '[DONE]';\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":["data"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Security posture
|
|
2
|
+
|
|
3
|
+
`llm-sse` is a zero-runtime-dependency package, but the repository still tracks
|
|
4
|
+
supply-chain controls as part of maintenance.
|
|
5
|
+
|
|
6
|
+
Current controls:
|
|
7
|
+
|
|
8
|
+
- MIT license and public security policy.
|
|
9
|
+
- GitHub vulnerability alerts and Dependabot security updates.
|
|
10
|
+
- Secret scanning and push protection.
|
|
11
|
+
- CI on Node 20 and 22.
|
|
12
|
+
- CodeQL analysis for JavaScript/TypeScript.
|
|
13
|
+
- OpenSSF Scorecard workflow with published results.
|
|
14
|
+
- Pinned GitHub Actions and least-privilege workflow permissions.
|
|
15
|
+
- Branch ruleset for `main` requiring CI and CodeQL checks before merge.
|
|
16
|
+
- CODEOWNERS for maintainer review visibility.
|
|
17
|
+
|
|
18
|
+
Security reports should not be opened as public issues. Use the process in
|
|
19
|
+
[SECURITY.md](../SECURITY.md) or contact
|
|
20
|
+
[sebastian@0a.cl](mailto:sebastian@0a.cl).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "type": "reasoning", "text": "Need weather lookup. " },
|
|
3
|
+
{ "type": "text", "text": "Let me check Santiago. " },
|
|
4
|
+
{
|
|
5
|
+
"type": "tool_call_start",
|
|
6
|
+
"index": 2,
|
|
7
|
+
"id": "call_weather_1",
|
|
8
|
+
"name": "get_weather"
|
|
9
|
+
},
|
|
10
|
+
{ "type": "tool_call_delta", "index": 2, "argumentsDelta": "{\"city\":" },
|
|
11
|
+
{
|
|
12
|
+
"type": "tool_call_delta",
|
|
13
|
+
"index": 2,
|
|
14
|
+
"argumentsDelta": "\"Santiago\",\"units\":\"metric\"}"
|
|
15
|
+
},
|
|
16
|
+
{ "type": "finish", "reason": "completed" }
|
|
17
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text": "Let me check Santiago. ",
|
|
3
|
+
"reasoning": "Need weather lookup. ",
|
|
4
|
+
"toolCalls": [
|
|
5
|
+
{
|
|
6
|
+
"index": 2,
|
|
7
|
+
"id": "call_weather_1",
|
|
8
|
+
"name": "get_weather",
|
|
9
|
+
"arguments": "{\"city\":\"Santiago\",\"units\":\"metric\"}"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"finishReason": "completed"
|
|
13
|
+
}
|