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 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
  [![npm version](https://img.shields.io/npm/v/llm-sse.svg)](https://www.npmjs.com/package/llm-sse)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/llm-sse.svg)](https://www.npmjs.com/package/llm-sse)
5
5
  [![CI](https://github.com/slegarraga/llm-sse/actions/workflows/ci.yml/badge.svg)](https://github.com/slegarraga/llm-sse/actions/workflows/ci.yml)
6
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/slegarraga/llm-sse/badge)](https://scorecard.dev/viewer/?uri=github.com/slegarraga/llm-sse)
6
7
  [![license](https://img.shields.io/npm/l/llm-sse.svg)](./LICENSE)
7
8
  [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](./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,12 @@
1
+ {
2
+ "text": "Let me check Santiago. ",
3
+ "reasoning": "Need weather lookup. ",
4
+ "toolCalls": [
5
+ {
6
+ "index": 0,
7
+ "name": "get_weather",
8
+ "arguments": "{\"city\":\"Santiago\",\"units\":\"metric\"}"
9
+ }
10
+ ],
11
+ "finishReason": "STOP"
12
+ }
@@ -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.1",
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": "^2.1.8"
78
+ "vitest": "^4.1.8"
78
79
  }
79
80
  }