llm-sse 0.4.1 → 0.4.3
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 +19 -0
- package/README.md +19 -0
- package/fixtures/README.md +16 -0
- package/fixtures/anthropic-weather-tool.sse +39 -0
- package/fixtures/expected/anthropic-weather-tool.events.json +17 -0
- package/fixtures/expected/anthropic-weather-tool.message.json +13 -0
- package/fixtures/expected/gemini-weather-tool.events.json +11 -0
- package/fixtures/expected/gemini-weather-tool.message.json +12 -0
- package/fixtures/expected/openai-weather-tool.events.json +17 -0
- package/fixtures/expected/openai-weather-tool.message.json +13 -0
- package/fixtures/gemini-weather-tool.sse +6 -0
- package/fixtures/openai-weather-tool.sse +14 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres
|
|
5
5
|
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.4.3] - 2026-06-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Added a public cross-provider stream fixture corpus for OpenAI, Anthropic and
|
|
12
|
+
Gemini weather-tool calls.
|
|
13
|
+
- Added tests that parse the corpus and compare normalized `StreamEvent[]` and
|
|
14
|
+
collected message outputs, including byte-split stream boundaries.
|
|
15
|
+
- Published the `fixtures/` directory in the npm package for downstream parser
|
|
16
|
+
and agent-loop tests.
|
|
17
|
+
|
|
18
|
+
## [0.4.2] - 2026-06-04
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Updated vulnerable development tooling and added CodeQL, OpenSSF Scorecard,
|
|
23
|
+
pinned GitHub Actions, least-privilege workflow permissions, Dependabot config
|
|
24
|
+
and a Scorecard README badge.
|
|
25
|
+
|
|
7
26
|
## [0.4.1] - 2026-06-04
|
|
8
27
|
|
|
9
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/llm-sse)
|
|
4
4
|
[](https://www.npmjs.com/package/llm-sse)
|
|
5
5
|
[](https://github.com/slegarraga/llm-sse/actions/workflows/ci.yml)
|
|
6
|
+
[](https://scorecard.dev/viewer/?uri=github.com/slegarraga/llm-sse)
|
|
6
7
|
[](./LICENSE)
|
|
7
8
|
[](./package.json)
|
|
8
9
|
|
|
9
10
|
> Parse streaming responses from OpenAI, Anthropic, Gemini and OpenAI-compatible providers into one unified event format. Text, reasoning and tool-call deltas, handled. **Zero dependencies.**
|
|
10
11
|
|
|
12
|
+
Security posture is tracked in [docs/security-posture.md](./docs/security-posture.md),
|
|
13
|
+
including CodeQL, OpenSSF Scorecard, Dependabot and branch rules.
|
|
14
|
+
|
|
11
15
|
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.
|
|
12
16
|
|
|
13
17
|
```ts
|
|
@@ -32,6 +36,7 @@ for await (const event of parseOpenAIStream(res.body)) {
|
|
|
32
36
|
- **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.
|
|
33
37
|
- **Tool calls just accumulate.** Streamed JSON argument fragments carry an `index`; concatenate by index (or let `collectStream` do it) to get the full call.
|
|
34
38
|
- **Correct SSE framing.** Robust to chunk boundaries splitting a line or event mid-way, CRLF, multi-line `data:` fields, comments and keep-alives.
|
|
39
|
+
- **Fixture-backed provider coverage.** Public OpenAI, Anthropic and Gemini `.sse` fixtures exercise text, reasoning, tool-call arguments and finish reasons.
|
|
35
40
|
- **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.
|
|
36
41
|
- **Zero dependencies**, ESM + CJS, fully typed.
|
|
37
42
|
|
|
@@ -102,6 +107,20 @@ The underlying SSE parser, exported for advanced use: yields the `data` payload
|
|
|
102
107
|
|
|
103
108
|
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.
|
|
104
109
|
|
|
110
|
+
## Fixture corpus
|
|
111
|
+
|
|
112
|
+
The package includes a small public fixture corpus under [`fixtures/`](./fixtures):
|
|
113
|
+
|
|
114
|
+
- `openai-weather-tool.sse`
|
|
115
|
+
- `anthropic-weather-tool.sse`
|
|
116
|
+
- `gemini-weather-tool.sse`
|
|
117
|
+
- expected normalized events and collected messages under `fixtures/expected/`
|
|
118
|
+
|
|
119
|
+
Each fixture describes the same semantic turn: reasoning, visible text, a
|
|
120
|
+
`get_weather` tool call, JSON arguments and provider-specific finish reason.
|
|
121
|
+
The tests parse the fixtures directly, including byte-split stream boundaries,
|
|
122
|
+
so contributors can change parsers with a stable cross-provider contract.
|
|
123
|
+
|
|
105
124
|
## Related
|
|
106
125
|
|
|
107
126
|
- [`tool-schema`](https://www.npmjs.com/package/tool-schema) — convert a JSON Schema into OpenAI / Anthropic / Gemini / MCP tool schemas.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Stream Fixture Corpus
|
|
2
|
+
|
|
3
|
+
This corpus contains small, deterministic Server-Sent Events streams for the
|
|
4
|
+
same semantic turn across providers:
|
|
5
|
+
|
|
6
|
+
- OpenAI / OpenAI-compatible chat completions
|
|
7
|
+
- Anthropic Messages streaming
|
|
8
|
+
- Gemini `streamGenerateContent?alt=sse`
|
|
9
|
+
|
|
10
|
+
Each `.sse` file is paired with expected normalized `StreamEvent[]` and
|
|
11
|
+
`CollectedMessage` JSON. The fixtures are safe for public CI: they contain no
|
|
12
|
+
API keys, no user data and no live provider responses.
|
|
13
|
+
|
|
14
|
+
Use them when changing parsers, comparing providers or writing downstream tests
|
|
15
|
+
that need stable examples of text, reasoning, tool-call arguments and finish
|
|
16
|
+
reasons.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
event: message_start
|
|
2
|
+
data: {"type":"message_start","message":{"id":"msg_fixture_anthropic_weather","type":"message","role":"assistant","content":[]}}
|
|
3
|
+
|
|
4
|
+
event: content_block_start
|
|
5
|
+
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}
|
|
6
|
+
|
|
7
|
+
event: content_block_delta
|
|
8
|
+
data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Need weather lookup. "}}
|
|
9
|
+
|
|
10
|
+
event: content_block_stop
|
|
11
|
+
data: {"type":"content_block_stop","index":0}
|
|
12
|
+
|
|
13
|
+
event: content_block_start
|
|
14
|
+
data: {"type":"content_block_start","index":1,"content_block":{"type":"text","text":""}}
|
|
15
|
+
|
|
16
|
+
event: content_block_delta
|
|
17
|
+
data: {"type":"content_block_delta","index":1,"delta":{"type":"text_delta","text":"Let me check Santiago. "}}
|
|
18
|
+
|
|
19
|
+
event: content_block_stop
|
|
20
|
+
data: {"type":"content_block_stop","index":1}
|
|
21
|
+
|
|
22
|
+
event: content_block_start
|
|
23
|
+
data: {"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_weather_1","name":"get_weather","input":{}}}
|
|
24
|
+
|
|
25
|
+
event: content_block_delta
|
|
26
|
+
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\"city\":"}}
|
|
27
|
+
|
|
28
|
+
event: content_block_delta
|
|
29
|
+
data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"\"Santiago\",\"units\":\"metric\"}"}}
|
|
30
|
+
|
|
31
|
+
event: content_block_stop
|
|
32
|
+
data: {"type":"content_block_stop","index":2}
|
|
33
|
+
|
|
34
|
+
event: message_delta
|
|
35
|
+
data: {"type":"message_delta","delta":{"stop_reason":"tool_use"}}
|
|
36
|
+
|
|
37
|
+
event: message_stop
|
|
38
|
+
data: {"type":"message_stop"}
|
|
39
|
+
|
|
@@ -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": "toolu_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": "tool_use" }
|
|
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": "toolu_weather_1",
|
|
8
|
+
"name": "get_weather",
|
|
9
|
+
"arguments": "{\"city\":\"Santiago\",\"units\":\"metric\"}"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"finishReason": "tool_use"
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "type": "reasoning", "text": "Need weather lookup. " },
|
|
3
|
+
{ "type": "text", "text": "Let me check Santiago. " },
|
|
4
|
+
{ "type": "tool_call_start", "index": 0, "name": "get_weather" },
|
|
5
|
+
{
|
|
6
|
+
"type": "tool_call_delta",
|
|
7
|
+
"index": 0,
|
|
8
|
+
"argumentsDelta": "{\"city\":\"Santiago\",\"units\":\"metric\"}"
|
|
9
|
+
},
|
|
10
|
+
{ "type": "finish", "reason": "STOP" }
|
|
11
|
+
]
|
|
@@ -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": 0,
|
|
7
|
+
"id": "call_weather_1",
|
|
8
|
+
"name": "get_weather"
|
|
9
|
+
},
|
|
10
|
+
{ "type": "tool_call_delta", "index": 0, "argumentsDelta": "{\"city\":" },
|
|
11
|
+
{
|
|
12
|
+
"type": "tool_call_delta",
|
|
13
|
+
"index": 0,
|
|
14
|
+
"argumentsDelta": "\"Santiago\",\"units\":\"metric\"}"
|
|
15
|
+
},
|
|
16
|
+
{ "type": "finish", "reason": "tool_calls" }
|
|
17
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"text": "Let me check Santiago. ",
|
|
3
|
+
"reasoning": "Need weather lookup. ",
|
|
4
|
+
"toolCalls": [
|
|
5
|
+
{
|
|
6
|
+
"index": 0,
|
|
7
|
+
"id": "call_weather_1",
|
|
8
|
+
"name": "get_weather",
|
|
9
|
+
"arguments": "{\"city\":\"Santiago\",\"units\":\"metric\"}"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"finishReason": "tool_calls"
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
data: {"candidates":[{"content":{"role":"model","parts":[{"text":"Need weather lookup. ","thought":true}]}}]}
|
|
2
|
+
|
|
3
|
+
data: {"candidates":[{"content":{"role":"model","parts":[{"text":"Let me check Santiago. "}]}}]}
|
|
4
|
+
|
|
5
|
+
data: {"candidates":[{"content":{"role":"model","parts":[{"functionCall":{"name":"get_weather","args":{"city":"Santiago","units":"metric"}}}]},"finishReason":"STOP"}]}
|
|
6
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
data: {"id":"chatcmpl_fixture_openai_weather","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"Need weather lookup. "}}]}
|
|
2
|
+
|
|
3
|
+
data: {"id":"chatcmpl_fixture_openai_weather","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Let me check Santiago. "}}]}
|
|
4
|
+
|
|
5
|
+
data: {"id":"chatcmpl_fixture_openai_weather","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_weather_1","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}
|
|
6
|
+
|
|
7
|
+
data: {"id":"chatcmpl_fixture_openai_weather","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]}}]}
|
|
8
|
+
|
|
9
|
+
data: {"id":"chatcmpl_fixture_openai_weather","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Santiago\",\"units\":\"metric\"}"}}]}}]}
|
|
10
|
+
|
|
11
|
+
data: {"id":"chatcmpl_fixture_openai_weather","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
|
|
12
|
+
|
|
13
|
+
data: [DONE]
|
|
14
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-sse",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
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.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openai",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"dist",
|
|
50
|
+
"fixtures",
|
|
50
51
|
"README.md",
|
|
51
52
|
"LICENSE",
|
|
52
53
|
"CHANGELOG.md"
|
|
@@ -74,6 +75,6 @@
|
|
|
74
75
|
"tsup": "^8.3.5",
|
|
75
76
|
"typescript": "^5.7.2",
|
|
76
77
|
"typescript-eslint": "^8.60.0",
|
|
77
|
-
"vitest": "^
|
|
78
|
+
"vitest": "^4.1.8"
|
|
78
79
|
}
|
|
79
80
|
}
|