Consume Server-Sent Events and LLM token streams with `@ahoo-wang/fetcher-eventstream`: the `Response.prototype` helpers (`requiredJsonEventStream`), standalone converters, `TerminateDetector` for `[DONE]`-style sentinels, `jsonEventStreamResultExtractor` and `for await` iteration. Use for your own SSE endpoint, a non-OpenAI token stream or an SSE decorator endpoint. Do not load for GPT or OpenAI-compatible chat completions, streamed or not: fetcher-openai-client handles those.
Installs into .claude/skills of the current project.
Are you the author of Fetcher Llm Streaming?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ahoo-wang-fetcher-llm-streaming)
---
name: fetcher-llm-streaming
description: >
Consume Server-Sent Events and LLM token streams with `@ahoo-wang/fetcher-eventstream`: the `Response.prototype` helpers (`requiredJsonEventStream`), standalone converters, `TerminateDetector` for `[DONE]`-style sentinels, `jsonEventStreamResultExtractor` and `for await` iteration. Use for your own SSE endpoint, a non-OpenAI token stream or an SSE decorator endpoint. Do not load for GPT or OpenAI-compatible chat completions, streamed or not: fetcher-openai-client handles those.
---
# fetcher-llm-streaming
## Decisions
- **Prototype helpers vs converters**: `import '@ahoo-wang/fetcher-eventstream'` patches `Response.prototype` (`contentType`, `isEventStream`, `eventStream()`, `requiredEventStream()`, `jsonEventStream()`, `requiredJsonEventStream()`, skipping members that already exist) and polyfills `ReadableStream` async iteration. `toServerSentEventStream(response)` / `toJsonServerSentEventStream(stream, detector?)` avoid _calling_ the patched members, but importing anything from the package still runs the patch — there is no side-effect-free entry. A library that must not touch `Response.prototype` cannot depend on this package.
- **Nullable vs required**: `eventStream()` / `jsonEventStream()` return `null` for a non-SSE Content-Type; the `required*` variants throw `EventStreamConvertError` (with `.response`). A body that is already used or locked also throws `EventStreamConvertError` — a response converts once.
- **Decorator/`RequestOptions` endpoints**: `jsonEventStreamResultExtractor<T>(detector)` builds the extractor; set it per endpoint (`@post(path, { resultExtractor })`).
- **OpenAI chat completions** (`/chat/completions`, `[DONE]`, typed chunks) are done by `$fetcher-openai-client`; don't rebuild them here.
## Gotchas a capable model gets wrong
- There is **no default terminator**. Without a `TerminateDetector`, `data: [DONE]` reaches `JSON.parse` and iteration throws `SyntaxError`. This package exports no `[DONE]` detector — write `e => e.data === '[DONE]'` (`DoneDetector` lives in `@ahoo-wang/fetcher-openai`).
- The constant `JsonEventStreamResultExtractor` has **no** detector and takes no options; use the factory `jsonEventStreamResultExtractor(detector)` instead. Neither is on `ResultExtractors` from `@ahoo-wang/fetcher`.
- Items are `JsonServerSentEvent<T>`: the payload is `event.data` (not `event.token`); `id` is `''` when the server sent none; `event` defaults to `'message'`. The detector sees the raw string event before parsing, and the matching event is not yielded.
- With a detector, a stream that ends without the terminating event errors with `EventStreamIncompleteError` — a cut-off answer is not silently "complete". Mid-stream errors (`SyntaxError`, `EventStreamIncompleteError`, network errors) come from the `for await`, not from the call that returned the `Response`. `break` cancels the connection.
## Canonical patterns
```ts
import '@ahoo-wang/fetcher-eventstream';
import { fetcher } from '@ahoo-wang/fetcher';
import type { TerminateDetector } from '@ahoo-wang/fetcher-eventstream';
const untilDone: TerminateDetector = e => e.data === '[DONE]';
export async function generate(prompt: string): Promise<string> {
const response = await fetcher.post('/generate', { body: { prompt } });
let text = '';
for await (const event of response.requiredJsonEventStream<{
token: string;
}>(untilDone)) {
text += event.data.token;
}
return text;
}
```
Declared endpoint (see `$fetcher-decorator-service`):
```ts
import {
api,
autoGeneratedError,
body,
post,
} from '@ahoo-wang/fetcher-decorator';
import {
jsonEventStreamResultExtractor,
type JsonServerSentEventStream,
} from '@ahoo-wang/fetcher-eventstream';
interface Chunk {
token: string;
}
@api('/llm')
export class LlmApi {
@post('/generate', {
resultExtractor: jsonEventStreamResultExtractor<Chunk>(
e => e.data === '[DONE]',
),
})
generate(
@body() req: { prompt: string },
): Promise<JsonServerSentEventStream<Chunk>> {
throw autoGeneratedError(req);
}
}
```
## References
- `references/api.md`: every export with its signature, SSE parsing rules, error semantics, the transform pipeline and `SafeTransformer`, CommonJS. Load it for raw `ServerSentEvent` streams, custom transformers or exact error behaviour.
## Related Skills
- $fetcher-openai-client: OpenAI-compatible chat completions with `[DONE]` and typing handled.
- $fetcher-decorator-service: declaring streaming endpoints with decorators.
- $fetcher-integration: the Fetcher that produces the `Response`.