llm-sse 0.4.9 → 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 CHANGED
@@ -6,6 +6,29 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-07-23
10
+
11
+ ### Added
12
+
13
+ - `parseOpenAIResponsesStream(source)` and the `openai-responses` dispatcher
14
+ target for typed OpenAI Responses API streams.
15
+ - Normalization for Responses API text, reasoning summaries, function-call
16
+ arguments, refusal text, completion, incomplete responses, and errors.
17
+ - A public Responses API weather-tool fixture with normalized event and
18
+ collected-message expectations.
19
+ - Public roadmap, governance, support channels, and a documented path from
20
+ contributor to maintainer.
21
+
22
+ ### Changed
23
+
24
+ - Project documentation now links contribution, security, governance, support,
25
+ roadmap, and release resources from the README.
26
+ - The published tarball now includes those public project documents and is
27
+ installation-tested before release.
28
+ - Development tooling overrides `esbuild` to a patched release so a clean
29
+ install passes `npm audit` without changing the package's zero-dependency
30
+ runtime.
31
+
9
32
  ## [0.4.9] - 2026-06-29
10
33
 
11
34
  ### Changed
@@ -117,6 +140,16 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
117
140
  and `Uint8Array` or string sources.
118
141
  - Zero runtime dependencies; ESM + CJS builds with type declarations.
119
142
 
143
+ [0.5.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.5.0
144
+ [0.4.9]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.9
145
+ [0.4.8]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.8
146
+ [0.4.7]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.7
147
+ [0.4.6]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.6
148
+ [0.4.5]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.5
149
+ [0.4.4]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.4
150
+ [0.4.3]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.3
151
+ [0.4.2]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.2
152
+ [0.4.1]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.1
120
153
  [0.4.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.4.0
121
154
  [0.3.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.3.0
122
155
  [0.2.0]: https://github.com/slegarraga/llm-sse/releases/tag/v0.2.0
@@ -0,0 +1,57 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment include:
18
+
19
+ - Demonstrating empathy and kindness toward other people
20
+ - Being respectful of differing opinions, viewpoints, and experiences
21
+ - Giving and gracefully accepting constructive feedback
22
+ - Accepting responsibility and apologizing to those affected by our mistakes
23
+ - Focusing on what is best for the overall community
24
+
25
+ Examples of unacceptable behavior include:
26
+
27
+ - The use of sexualized language or imagery, and sexual attention or advances
28
+ - Trolling, insulting or derogatory comments, and personal or political attacks
29
+ - Public or private harassment
30
+ - Publishing others' private information without their explicit permission
31
+ - Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Enforcement Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying and enforcing our standards of
37
+ acceptable behavior and will take appropriate and fair corrective action in
38
+ response to any behavior that they deem inappropriate, threatening, offensive, or
39
+ harmful.
40
+
41
+ ## Scope
42
+
43
+ This Code of Conduct applies within all community spaces, and also applies when
44
+ an individual is officially representing the community in public spaces.
45
+
46
+ ## Enforcement
47
+
48
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
49
+ reported to the maintainers at **sebastian@0a.cl**. All complaints will be
50
+ reviewed and investigated promptly and fairly. All maintainers are obligated to
51
+ respect the privacy and security of the reporter of any incident.
52
+
53
+ ## Attribution
54
+
55
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
56
+ version 2.1, available at
57
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
@@ -0,0 +1,65 @@
1
+ # Contributing to llm-sse
2
+
3
+ Thanks for taking the time to contribute. This project aims to be a small,
4
+ dependable, zero-dependency building block, so the bar for changes is clarity
5
+ and correctness over breadth.
6
+
7
+ ## Getting started
8
+
9
+ ```sh
10
+ git clone https://github.com/slegarraga/llm-sse.git
11
+ cd llm-sse
12
+ npm install
13
+ ```
14
+
15
+ ## Development workflow
16
+
17
+ Every change should keep the full check suite green:
18
+
19
+ ```sh
20
+ npm run typecheck # tsc --noEmit
21
+ npm run lint # eslint
22
+ npm test # vitest
23
+ npm run build # tsup (ESM + CJS + types)
24
+ npm run format # prettier --write
25
+ ```
26
+
27
+ Run `npm run test:watch` while developing.
28
+
29
+ ## Pull requests
30
+
31
+ 1. Fork the repo and create a branch from `main` (e.g. `fix/gemini-tool-index`).
32
+ 2. Add or update tests. New behaviour without a test will not be merged.
33
+ 3. Make sure `typecheck`, `lint`, `test` and `build` all pass.
34
+ 4. Keep the public API surface small and documented with JSDoc.
35
+ 5. Open a pull request describing the provider stream shape you are handling.
36
+
37
+ ## Commit messages
38
+
39
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/).
40
+ Examples:
41
+
42
+ ```
43
+ feat(anthropic): handle thinking_delta blocks
44
+ fix(sse): join multi-line data fields per spec
45
+ docs: clarify the tool-call delta model
46
+ test: cover chunk boundaries splitting an event
47
+ chore: bump dev dependencies
48
+ ```
49
+
50
+ The type drives the next version bump (`fix` -> patch, `feat` -> minor, a
51
+ `!` or `BREAKING CHANGE` footer -> major).
52
+
53
+ ## Reporting bugs
54
+
55
+ Open an issue with a minimal reproduction: the raw stream bytes (or the `data:`
56
+ lines) from the provider, which provider produced them, and the `StreamEvent`s
57
+ you expected. A failing test case is the most useful form a bug report can take.
58
+
59
+ ## Scope and philosophy
60
+
61
+ - Zero runtime dependencies. A dependency needs an exceptional justification.
62
+ - Parsing is total: malformed or non-JSON `data:` lines are skipped, never
63
+ thrown, so one bad keep-alive can't break a stream.
64
+ - Event mapping is grounded in official provider documentation. When you add or
65
+ change a rule, link the source in the PR.
package/GOVERNANCE.md ADDED
@@ -0,0 +1,71 @@
1
+ # Governance
2
+
3
+ `llm-sse` is an independent open-source project. Governance is designed to keep
4
+ the parser small and dependable while making it possible for trusted
5
+ contributors to take real ownership.
6
+
7
+ ## Current maintainer
8
+
9
+ - [Sebastian Legarraga](https://github.com/slegarraga) — project direction,
10
+ releases, security response, and repository administration.
11
+
12
+ The project currently has a single maintainer. Expanding the maintainer group is
13
+ an explicit goal, not a prerequisite contributors must wait for.
14
+
15
+ ## How decisions are made
16
+
17
+ - Bug fixes and small, backward-compatible improvements are decided through
18
+ pull-request review.
19
+ - New provider formats, normalized event variants, and breaking changes should
20
+ start with a public issue or discussion.
21
+ - Decisions prioritize primary provider documentation, reproducible fixtures,
22
+ interoperability, security, and maintenance cost.
23
+ - If reasonable contributors disagree, the maintainer records the tradeoff and
24
+ decision publicly rather than moving the discussion to a private channel.
25
+
26
+ ## Roles
27
+
28
+ ### Contributor
29
+
30
+ Anyone who reports a reproducible issue, improves documentation, supplies a
31
+ sanitized fixture, or lands a code change.
32
+
33
+ ### Reviewer
34
+
35
+ A contributor who understands an area of the project and regularly helps review
36
+ changes or triage issues. Reviewers may be listed in the repository and invited
37
+ to relevant reviews.
38
+
39
+ ### Maintainer
40
+
41
+ A trusted reviewer with sustained, constructive involvement who can merge
42
+ changes, manage issues, and help prepare releases. Maintainers are expected to:
43
+
44
+ - uphold the code of conduct and security policy;
45
+ - require tests and primary-source evidence for provider behavior changes;
46
+ - disclose conflicts of interest;
47
+ - avoid merging their own substantial changes without another review when a
48
+ second maintainer is available;
49
+ - preserve the project's zero-dependency and compatibility commitments.
50
+
51
+ Maintainer access is offered based on demonstrated judgment, reliability,
52
+ project need, and mutual agreement. It is never exchanged for sponsorship,
53
+ stars, or a fixed number of pull requests.
54
+
55
+ ## Releases
56
+
57
+ Releases follow Semantic Versioning. The release workflow reruns type checking,
58
+ linting, tests, and the build before publishing with npm provenance. Breaking
59
+ changes require a migration note.
60
+
61
+ ## Security and conduct
62
+
63
+ Security reports follow [SECURITY.md](./SECURITY.md). Community behavior follows
64
+ [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). A maintainer involved in a conduct
65
+ or security concern should recuse themselves when another trusted reviewer is
66
+ available.
67
+
68
+ ## Changes to governance
69
+
70
+ Governance changes use the same public pull-request process as code changes and
71
+ should explain the problem they solve.
package/README.md CHANGED
@@ -9,16 +9,64 @@
9
9
  [![license](https://img.shields.io/npm/l/llm-sse.svg)](./LICENSE)
10
10
  [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](./package.json)
11
11
 
12
- > Zero-dependency SSE parser that turns OpenAI, Anthropic, Gemini and any OpenAI-compatible stream into one unified event shape: text deltas, reasoning, tool-call fragments and finish reasons, handled the same way regardless of provider.
12
+ > Zero-dependency SSE parser that turns OpenAI Responses, OpenAI Chat Completions, Anthropic, Gemini and OpenAI-compatible streams into one unified event shape: text deltas, reasoning, tool-call fragments and finish reasons, handled the same way regardless of provider.
13
13
 
14
14
  Security posture is tracked in [docs/security-posture.md](https://github.com/slegarraga/llm-sse/blob/main/docs/security-posture.md),
15
15
  including CodeQL, OpenSSF Scorecard, Dependabot and branch rules.
16
16
 
17
- Each provider streams differently. OpenAI sends `choices[].delta` chunks, Anthropic sends typed `content_block_*` / `message_*` events, Gemini sends `candidates[].content.parts`, and the SSE framing, tool-call argument fragments and stop reasons are all shaped differently. `llm-sse` turns any of them into the same small set of events, so your streaming UI or agent loop stays provider-agnostic.
17
+ Each API streams differently. OpenAI Responses sends typed `response.*` events,
18
+ OpenAI Chat Completions sends `choices[].delta` chunks, Anthropic sends typed
19
+ `content_block_*` / `message_*` events, and Gemini sends
20
+ `candidates[].content.parts`. `llm-sse` turns all of them into the same small
21
+ set of events, so your streaming UI or agent loop stays provider-agnostic.
22
+
23
+ ## Project
24
+
25
+ - [Roadmap](./ROADMAP.md): compatibility, conformance, and sustainability priorities
26
+ - [Contributing](./CONTRIBUTING.md): local setup, tests, and pull-request expectations
27
+ - [Governance](./GOVERNANCE.md): decision process and the path to reviewer or maintainer
28
+ - [Support](./SUPPORT.md): where to ask questions or report reproducible bugs
29
+ - [Security](./SECURITY.md): private vulnerability reporting and supported versions
30
+ - [Changelog](./CHANGELOG.md): release history and migration notes
31
+ - [Code of Conduct](./CODE_OF_CONDUCT.md): community standards
18
32
 
19
33
  ## Quickstart
20
34
 
21
- ### OpenAI
35
+ ### OpenAI Responses API
36
+
37
+ ```ts
38
+ import { parseOpenAIResponsesStream } from 'llm-sse';
39
+
40
+ const res = await fetch('https://api.openai.com/v1/responses', {
41
+ method: 'POST',
42
+ headers: {
43
+ Authorization: `Bearer ${key}`,
44
+ 'content-type': 'application/json',
45
+ },
46
+ body: JSON.stringify({
47
+ model: process.env.OPENAI_MODEL,
48
+ input: 'What is the weather in Santiago?',
49
+ tools,
50
+ stream: true,
51
+ }),
52
+ });
53
+
54
+ for await (const event of parseOpenAIResponsesStream(res.body)) {
55
+ if (event.type === 'text') process.stdout.write(event.text);
56
+ if (event.type === 'tool_call_delta') {
57
+ process.stderr.write(event.argumentsDelta);
58
+ }
59
+ }
60
+ ```
61
+
62
+ The parser follows the official
63
+ [Responses streaming event reference](https://platform.openai.com/docs/api-reference/responses-streaming),
64
+ including output text, reasoning summaries, function-call arguments,
65
+ refusal text, completion, incomplete responses, and errors. Refusal deltas use
66
+ the normalized `text` channel so provider-agnostic UIs do not drop the model's
67
+ response.
68
+
69
+ ### OpenAI Chat Completions
22
70
 
23
71
  ```ts
24
72
  import { parseOpenAIStream } from 'llm-sse';
@@ -29,7 +77,11 @@ const res = await fetch('https://api.openai.com/v1/chat/completions', {
29
77
  Authorization: `Bearer ${key}`,
30
78
  'content-type': 'application/json',
31
79
  },
32
- body: JSON.stringify({ model: 'gpt-4o', messages, stream: true }),
80
+ body: JSON.stringify({
81
+ model: process.env.OPENAI_MODEL,
82
+ messages,
83
+ stream: true,
84
+ }),
33
85
  });
34
86
 
35
87
  for await (const event of parseOpenAIStream(res.body)) {
@@ -112,16 +164,29 @@ for await (const event of parseOpenAIStream(res.body)) {
112
164
 
113
165
  ## Why
114
166
 
115
- - **One event shape, three providers.** `text`, `tool_call_start`, `tool_call_delta`, `finish`, `error`, the same whether the bytes came from OpenAI, Anthropic or Gemini.
167
+ - **One event shape, four streaming formats.** `text`, `reasoning`, `tool_call_start`, `tool_call_delta`, `finish`, and `error`, whether the bytes came from OpenAI Responses, OpenAI Chat Completions, Anthropic, or Gemini.
116
168
  - **Tool calls just accumulate.** Streamed JSON argument fragments carry an `index`; concatenate by index (or let `collectStream` do it) to get the full call.
117
169
  - **Correct SSE framing.** Robust to chunk boundaries splitting a line or event mid-way, CRLF, multi-line `data:` fields, comments and keep-alives.
118
170
  - **Fixture-backed provider coverage.** Public OpenAI, Anthropic and Gemini `.sse` fixtures exercise text, reasoning, tool-call arguments and finish reasons.
119
171
  - **Bytes or strings.** Feed it a `fetch()` `ReadableStream<Uint8Array>`, a Node stream, or an async iterable of strings. Multibyte UTF-8 split across chunks is handled.
120
172
  - **Zero dependencies**, ESM + CJS, fully typed.
121
173
 
174
+ | Streaming source | Parser | Text | Reasoning | Function calls | Finish and errors |
175
+ | ------------------------------ | ---------------------------- | :--: | :---------------: | :------------: | :---------------: |
176
+ | OpenAI Responses API | `parseOpenAIResponsesStream` | ✓ | ✓ | ✓ | ✓ |
177
+ | OpenAI Chat Completions | `parseOpenAIStream` | ✓ | compatible fields | ✓ | ✓ |
178
+ | Anthropic Messages | `parseAnthropicStream` | ✓ | ✓ | ✓ | ✓ |
179
+ | Gemini `streamGenerateContent` | `parseGeminiStream` | ✓ | ✓ | ✓ | ✓ |
180
+
122
181
  ## Why not the provider SDK?
123
182
 
124
- The official SDKs (openai, @anthropic-ai/sdk, @google/generative-ai) each ship their own streaming abstraction, so combining two providers means learning two APIs and writing two adapter paths in your agent or UI. `llm-sse` is a thin, zero-dependency layer that normalizes the wire format only. You keep your own fetch/retry/auth logic, and every provider becomes the same three event types. If you are already using a provider SDK exclusively and do not plan to switch, the SDK's streaming helpers may be sufficient; this library is most useful when you need provider portability, a minimal footprint, or control over the HTTP layer.
183
+ The official SDKs each ship their own streaming abstraction, so combining APIs
184
+ means learning multiple event models and writing multiple adapter paths in your
185
+ agent or UI. `llm-sse` is a thin, zero-dependency layer that normalizes the wire
186
+ format only. You keep your own fetch, retry, and authentication logic. If you
187
+ use one provider exclusively, its SDK helpers may be sufficient; this library is
188
+ most useful when you need provider portability, a minimal footprint, or control
189
+ over the HTTP layer.
125
190
 
126
191
  ## Install
127
192
 
@@ -131,6 +196,13 @@ npm install llm-sse
131
196
 
132
197
  ## API
133
198
 
199
+ ### `parseOpenAIResponsesStream(source)`
200
+
201
+ Parses typed OpenAI Responses API SSE events. It maps
202
+ `response.output_text.delta`, reasoning deltas, function-call item and argument
203
+ events, and terminal response states into normalized events. The Responses
204
+ `output_index` is used as the normalized tool-call index.
205
+
134
206
  ### `parseOpenAIStream(source)` · `parseAnthropicStream(source)` · `parseGeminiStream(source)`
135
207
 
136
208
  Each takes a `source` (`AsyncIterable<Uint8Array | string>`, `fetch().body` satisfies this) and returns an `AsyncGenerator<StreamEvent>`.
@@ -141,7 +213,8 @@ Each takes a `source` (`AsyncIterable<Uint8Array | string>`, `fetch().body` sati
141
213
 
142
214
  ### `parseStream(source, provider)`
143
215
 
144
- Same thing, dispatching on `provider` (`'openai' | 'anthropic' | 'gemini'`).
216
+ Same thing, dispatching on `provider`
217
+ (`'openai-responses' | 'openai' | 'anthropic' | 'gemini'`).
145
218
 
146
219
  ### `StreamEvent`
147
220
 
@@ -155,7 +228,9 @@ type StreamEvent =
155
228
  | { type: 'error'; error: unknown };
156
229
  ```
157
230
 
158
- > `reasoning` carries the model's thinking (Anthropic extended thinking `thinking_delta` and Gemini `thought` parts) separately from `text`, so you can render it in its own affordance or drop it.
231
+ > `reasoning` carries OpenAI reasoning text or summaries, Anthropic extended
232
+ > thinking, and Gemini thought parts separately from `text`, so you can render
233
+ > it in its own affordance or drop it.
159
234
 
160
235
  ### `collectStream(events)`
161
236
 
@@ -271,13 +346,19 @@ const result2 = await complete('openai', openaiResponse);
271
346
 
272
347
  ## Tool calls
273
348
 
274
- All three providers are normalized to the same pattern: a `tool_call_start` (with `index`, and `id` / `name` when available) followed by one or more `tool_call_delta`s whose `argumentsDelta` strings concatenate into the call's JSON arguments. OpenAI and Anthropic fragment the arguments; Gemini sends them whole in a single delta. `collectStream` joins them for you.
349
+ All supported APIs are normalized to the same pattern: a `tool_call_start`
350
+ (with `index`, and `id` / `name` when available) followed by one or more
351
+ `tool_call_delta`s whose `argumentsDelta` strings concatenate into the call's
352
+ JSON arguments. OpenAI Responses uses its stable `output_index`; Chat
353
+ Completions and Anthropic stream argument fragments; Gemini sends arguments
354
+ whole in a single delta. `collectStream` joins them for you.
275
355
 
276
356
  ## Fixture corpus
277
357
 
278
358
  The package includes a small public fixture corpus under [`fixtures/`](https://github.com/slegarraga/llm-sse/tree/main/fixtures):
279
359
 
280
360
  - `openai-weather-tool.sse`
361
+ - `openai-responses-weather-tool.sse`
281
362
  - `anthropic-weather-tool.sse`
282
363
  - `gemini-weather-tool.sse`
283
364
  - expected normalized events and collected messages under `fixtures/expected/`
@@ -287,6 +368,15 @@ Each fixture describes the same semantic turn: reasoning, visible text, a
287
368
  The tests parse the fixtures directly, including byte-split stream boundaries,
288
369
  so contributors can change parsers with a stable cross-provider contract.
289
370
 
371
+ ## Community
372
+
373
+ Contributions are welcome, especially sanitized provider fixtures, reproducible
374
+ edge cases, interoperability examples, and review help. Start with
375
+ [CONTRIBUTING.md](./CONTRIBUTING.md), use
376
+ [Discussions](https://github.com/slegarraga/llm-sse/discussions) for design
377
+ questions, and see [GOVERNANCE.md](./GOVERNANCE.md) for how sustained
378
+ contributors can become reviewers or maintainers.
379
+
290
380
  ## Related
291
381
 
292
382
  - [`json-from-llm`](https://www.npmjs.com/package/json-from-llm): extract valid JSON from an LLM response, even inside reasoning tags, fenced blocks or prose
package/ROADMAP.md ADDED
@@ -0,0 +1,64 @@
1
+ # Roadmap
2
+
3
+ `llm-sse` exists to make provider streaming predictable without requiring a
4
+ provider SDK or adding runtime dependencies. The roadmap prioritizes correctness,
5
+ interoperability, and evidence-backed support over adding providers by name.
6
+
7
+ ## Current priorities
8
+
9
+ ### 1. Streaming conformance
10
+
11
+ - Keep the public fixture corpus aligned with documented OpenAI Responses,
12
+ OpenAI Chat Completions, Anthropic Messages, and Gemini event shapes.
13
+ - Add regression fixtures for real provider edge cases after removing secrets
14
+ and user content.
15
+ - Preserve byte-boundary, line-ending, malformed-event, and multi-tool coverage.
16
+
17
+ ### 2. Provider portability
18
+
19
+ - Keep normalized text, reasoning, tool-call, finish, and error events stable.
20
+ - Document unavoidable information loss between provider formats.
21
+ - Improve composition with
22
+ [`llm-messages`](https://github.com/slegarraga/llm-messages),
23
+ [`tool-schema`](https://github.com/slegarraga/tool-schema), and
24
+ [`llm-errors`](https://github.com/slegarraga/llm-errors).
25
+
26
+ ### 3. Maintainer workflows
27
+
28
+ - Make provider-behavior changes reviewable through small fixtures and explicit
29
+ links to primary documentation.
30
+ - Automate compatibility reports, release notes, and routine issue triage while
31
+ keeping release decisions human-reviewed.
32
+ - Publish enough project context for contributors to reproduce and own an area
33
+ of the parser.
34
+
35
+ ### 4. Sustainable community
36
+
37
+ - Grow usage through real integrations and examples rather than synthetic stars
38
+ or low-value promotion.
39
+ - Welcome additional reviewers and maintainers through the path described in
40
+ [GOVERNANCE.md](./GOVERNANCE.md).
41
+ - Track breaking proposals in public issues before implementation.
42
+
43
+ ## API credit use
44
+
45
+ If the project receives sponsored API credits, they will be used for opt-in
46
+ conformance checks against official provider endpoints, fixture refreshes,
47
+ regression reproduction, and maintainer automation. Secrets and user prompts
48
+ will not be committed. Pull requests will continue to run deterministically
49
+ against sanitized offline fixtures.
50
+
51
+ ## Non-goals
52
+
53
+ - Replacing full provider SDKs.
54
+ - Managing authentication, retries, billing, or network requests.
55
+ - Claiming compatibility with an endpoint without a documented event format or
56
+ reproducible fixture.
57
+ - Adding runtime dependencies for convenience.
58
+
59
+ ## Propose an item
60
+
61
+ Open a [GitHub Discussion](https://github.com/slegarraga/llm-sse/discussions)
62
+ for design exploration or a
63
+ [feature request](https://github.com/slegarraga/llm-sse/issues/new?template=feature_request.yml)
64
+ for a scoped, testable change.
package/SECURITY.md ADDED
@@ -0,0 +1,24 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ The latest published `0.x` release receives security fixes.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please report security issues privately rather than opening a public issue.
10
+
11
+ - Use GitHub's [private vulnerability reporting](https://github.com/slegarraga/llm-sse/security/advisories/new), or
12
+ - Email **sebastian@0a.cl** with the details.
13
+
14
+ Include a description, a reproduction, and the impact. You can expect an initial
15
+ response within a few days. Once a fix is released, we are happy to credit you in
16
+ the advisory unless you prefer to remain anonymous.
17
+
18
+ ## Scope
19
+
20
+ `llm-sse` has zero runtime dependencies and performs only in-memory parsing
21
+ of streamed text: it does not make network requests, read or write files, or
22
+ execute code from its input. The most relevant risks are denial of service from
23
+ pathological input (for example, unbounded buffering on a stream with no event
24
+ delimiters). Reports along those lines are welcome.
package/SUPPORT.md ADDED
@@ -0,0 +1,38 @@
1
+ # Support
2
+
3
+ `llm-sse` is maintained in public on a best-effort basis.
4
+
5
+ ## Questions and design discussions
6
+
7
+ Use [GitHub Discussions](https://github.com/slegarraga/llm-sse/discussions) for
8
+ integration questions, design exploration, and help choosing the right parser.
9
+
10
+ ## Bugs
11
+
12
+ Open a
13
+ [bug report](https://github.com/slegarraga/llm-sse/issues/new?template=bug_report.yml)
14
+ with:
15
+
16
+ - the provider and API surface;
17
+ - a minimal, sanitized sequence of SSE `data:` payloads;
18
+ - the normalized events you expected and received;
19
+ - the `llm-sse`, Node.js, and runtime versions;
20
+ - a reproduction or failing test when possible.
21
+
22
+ Please do not include API keys, private prompts, personal data, or proprietary
23
+ model output.
24
+
25
+ ## Security
26
+
27
+ Do not open a public issue for a vulnerability. Follow
28
+ [SECURITY.md](./SECURITY.md) to use GitHub private vulnerability reporting or
29
+ the listed security email.
30
+
31
+ ## Scope
32
+
33
+ The project can help with parsing and normalization behavior. Provider account
34
+ access, billing, rate limits, model availability, and general SDK support belong
35
+ with the relevant provider.
36
+
37
+ No response-time guarantee is offered, but well-scoped reproductions and pull
38
+ requests are prioritized.
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,
@@ -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 three providers stream very differently — OpenAI sends `choices[].delta`
7
- * chunks, Anthropic sends typed `content_block_*` / `message_*` events, Gemini
8
- * sends `candidates[].content.parts` but they all reduce to the same handful
9
- * of things: text arrived, a tool call started, tool-call arguments arrived,
10
- * the turn finished, or the provider reported an error.
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 three providers stream very differently — OpenAI sends `choices[].delta`
7
- * chunks, Anthropic sends typed `content_block_*` / `message_*` events, Gemini
8
- * sends `candidates[].content.parts` but they all reduce to the same handful
9
- * of things: text arrived, a tool call started, tool-call arguments arrived,
10
- * the turn finished, or the provider reported an error.
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
+ }
@@ -0,0 +1,17 @@
1
+ event: response.reasoning_summary_text.delta
2
+ data: {"type":"response.reasoning_summary_text.delta","item_id":"rs_fixture_weather","output_index":0,"summary_index":0,"delta":"Need weather lookup. ","sequence_number":1}
3
+
4
+ event: response.output_text.delta
5
+ data: {"type":"response.output_text.delta","item_id":"msg_fixture_weather","output_index":1,"content_index":0,"delta":"Let me check Santiago. ","sequence_number":2}
6
+
7
+ event: response.output_item.added
8
+ data: {"type":"response.output_item.added","output_index":2,"item":{"id":"fc_fixture_weather","type":"function_call","call_id":"call_weather_1","name":"get_weather","arguments":"","status":"in_progress"},"sequence_number":3}
9
+
10
+ event: response.function_call_arguments.delta
11
+ data: {"type":"response.function_call_arguments.delta","item_id":"fc_fixture_weather","output_index":2,"delta":"{\"city\":","sequence_number":4}
12
+
13
+ event: response.function_call_arguments.delta
14
+ data: {"type":"response.function_call_arguments.delta","item_id":"fc_fixture_weather","output_index":2,"delta":"\"Santiago\",\"units\":\"metric\"}","sequence_number":5}
15
+
16
+ event: response.completed
17
+ data: {"type":"response.completed","response":{"id":"resp_fixture_weather","status":"completed"},"sequence_number":6}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "llm-sse",
3
- "version": "0.4.9",
4
- "description": "Parse streaming SSE responses from OpenAI, Anthropic, Gemini and OpenAI-compatible providers into one unified event format. Text, reasoning and tool-call deltas. Zero dependencies.",
3
+ "version": "0.5.0",
4
+ "description": "Parse OpenAI Responses and Chat Completions, Anthropic, Gemini and OpenAI-compatible SSE streams into one unified event format. Zero dependencies.",
5
5
  "keywords": [
6
6
  "openai",
7
7
  "anthropic",
@@ -48,7 +48,14 @@
48
48
  "files": [
49
49
  "dist",
50
50
  "fixtures",
51
+ "docs",
51
52
  "README.md",
53
+ "ROADMAP.md",
54
+ "GOVERNANCE.md",
55
+ "SUPPORT.md",
56
+ "CONTRIBUTING.md",
57
+ "CODE_OF_CONDUCT.md",
58
+ "SECURITY.md",
52
59
  "LICENSE",
53
60
  "CHANGELOG.md"
54
61
  ],
@@ -58,15 +65,21 @@
58
65
  "sideEffects": false,
59
66
  "scripts": {
60
67
  "build": "tsup",
68
+ "check": "npm run format:check && npm run typecheck && npm run lint && npm test && npm run build && npm run pack:check && npm run pack:smoke",
61
69
  "typecheck": "tsc --noEmit",
62
70
  "test": "vitest run",
63
71
  "test:watch": "vitest",
64
72
  "lint": "eslint .",
65
73
  "format": "prettier --write .",
66
74
  "format:check": "prettier --check .",
67
- "prepublishOnly": "npm run build",
75
+ "pack:check": "npm pack --dry-run --foreground-scripts=false",
76
+ "pack:smoke": "node scripts/pack-smoke.mjs",
77
+ "prepublishOnly": "npm run check",
68
78
  "prepare": "npm run build"
69
79
  },
80
+ "overrides": {
81
+ "esbuild": "0.28.1"
82
+ },
70
83
  "devDependencies": {
71
84
  "@eslint/js": "^10.0.1",
72
85
  "@types/node": "^26.0.1",