llm-messages 0.5.3 → 0.5.5

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,26 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.5] - 2026-07-23
10
+
11
+ ### Changed
12
+
13
+ - Documented the boundary between completed Responses API normalization in
14
+ `llm-messages` and incremental Responses streaming in `llm-sse`.
15
+ - Added public governance, support channels, maintainer responsibilities, and a
16
+ contributor-to-maintainer path.
17
+ - Replaced the closed Responses coverage roadmap link with a scoped
18
+ cross-package conformance issue.
19
+ - Included governance and support documentation in the published package.
20
+ - Development tooling overrides `esbuild` to a patched release so a clean
21
+ install passes `npm audit` while the runtime remains dependency-free.
22
+
23
+ ## [0.5.4] - 2026-06-29
24
+
25
+ ### Changed
26
+
27
+ - Documentation: add provider-SDK recipes (OpenAI, Anthropic, Vercel AI SDK), a "why not alternatives" section, complete cross-links to the sibling packages, and install / bundle-size badges. No code changes.
28
+
9
29
  ## [0.5.3] - 2026-06-29
10
30
 
11
31
  ### Fixed
@@ -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,101 @@
1
+ # Contributing to llm-messages
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
+ The published package supports Node 18+ at runtime. The local development
10
+ toolchain currently needs Node 20.19+ or Node 22.13+ because the lint, test and
11
+ build dependencies follow those active Node release lines. CI keeps the runtime
12
+ floor honest by installing the packed tarball in a separate Node 18 consumer
13
+ smoke.
14
+
15
+ ```sh
16
+ git clone https://github.com/slegarraga/llm-messages.git
17
+ cd llm-messages
18
+ npm install
19
+ ```
20
+
21
+ ## Development workflow
22
+
23
+ Every change should keep the full check suite green:
24
+
25
+ ```sh
26
+ npm run check # full validation suite
27
+ ```
28
+
29
+ That script runs the same checks individually listed here:
30
+
31
+ ```sh
32
+ npm run format:check # prettier --check
33
+ npm run typecheck # tsc --noEmit
34
+ npm run lint # eslint
35
+ npm test # vitest
36
+ npm run build # tsup (ESM + CJS + types)
37
+ npm run examples:check # smoke-test packaged ESM/CommonJS examples
38
+ npm run pack:check # npm pack --dry-run, with lifecycle output quieted
39
+ npm run pack:smoke # install the packed tarball and verify runtime/types usage
40
+ ```
41
+
42
+ Run `npm run test:watch` and `npm run format` while developing.
43
+
44
+ ## Release and staging review
45
+
46
+ Before staging release, fixture, package metadata or published-file changes,
47
+ review the complete worktree, including untracked files:
48
+
49
+ ```sh
50
+ git status --short -uall
51
+ git diff --stat
52
+ npm run check
53
+ npm pack --dry-run --foreground-scripts=false
54
+ ```
55
+
56
+ Confirm that new fixtures, examples and scripts are intentional, and that no
57
+ private source, test, script, config or generated files are included in the
58
+ package dry-run output. Do not publish from a dirty tree whose untracked files
59
+ have not been reviewed.
60
+
61
+ ## Pull requests
62
+
63
+ 1. Fork the repo and create a branch from `main` (e.g. `fix/gemini-id-matching`).
64
+ 2. Add or update tests. New behaviour without a test will not be merged.
65
+ 3. When adding conformance fixtures, update `docs/conformance-fixtures.md`
66
+ in the same change so the fixture inventory stays reviewable and the
67
+ offline harness remains deterministic.
68
+ 4. Make sure `format:check`, `typecheck`, `lint`, `test`, `build`,
69
+ `examples:check`, `pack:check` and `pack:smoke` all pass.
70
+ 5. Keep the public API surface small and documented with JSDoc.
71
+ 6. Open a pull request and fill in the template.
72
+
73
+ ## Commit messages
74
+
75
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/).
76
+ Examples:
77
+
78
+ ```
79
+ feat(gemini): support functionResponse arrays
80
+ fix(anthropic): merge consecutive tool-result turns
81
+ docs: clarify the canonical hub model
82
+ test: cover parallel tool calls
83
+ chore: bump dev dependencies
84
+ ```
85
+
86
+ The type drives the next version bump (`fix` -> patch, `feat` -> minor, a
87
+ `!` or `BREAKING CHANGE` footer -> major).
88
+
89
+ ## Reporting bugs
90
+
91
+ Open an issue with a minimal reproduction: the input conversation, the target
92
+ provider, what you expected, and what you got. A failing test case is the most
93
+ useful form a bug report can take.
94
+
95
+ ## Scope and philosophy
96
+
97
+ - Zero runtime dependencies. A dependency needs an exceptional justification.
98
+ - Conversions are total: they never throw on malformed input, they make a
99
+ deterministic choice and report it through `onWarning`.
100
+ - Provider behaviour is grounded in official documentation. When you add or
101
+ change a rule, link the source in the PR.
package/GOVERNANCE.md ADDED
@@ -0,0 +1,72 @@
1
+ # Governance
2
+
3
+ `llm-messages` is an independent open-source project and the conversation
4
+ boundary of the provider-portability suite. Governance favors correctness,
5
+ transparent tradeoffs, and a realistic path for contributors to take 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. Adding trusted reviewers and
13
+ maintainers is a project goal.
14
+
15
+ ## Decision process
16
+
17
+ - Small fixes and backward-compatible improvements are decided through
18
+ pull-request review.
19
+ - New canonical message fields, provider targets, warning codes, or breaking
20
+ changes should start with a public issue or discussion.
21
+ - Provider mappings must be grounded in primary documentation and reproducible
22
+ fixtures.
23
+ - Lossy behavior must stay visible through stable warnings rather than being
24
+ hidden for convenience.
25
+ - When reasonable contributors disagree, the maintainer records the tradeoff
26
+ and decision publicly.
27
+
28
+ ## Roles
29
+
30
+ ### Contributor
31
+
32
+ Anyone who reports a reproducible problem, improves documentation, adds a
33
+ reviewed fixture, or lands a code change.
34
+
35
+ ### Reviewer
36
+
37
+ A contributor with demonstrated understanding of one or more conversion
38
+ surfaces who regularly helps review changes or triage issues.
39
+
40
+ ### Maintainer
41
+
42
+ A trusted reviewer with sustained, constructive involvement who can merge
43
+ changes, manage issues, and prepare releases. Maintainers are expected to:
44
+
45
+ - uphold the code of conduct and security policy;
46
+ - require tests and primary-source evidence for provider behavior;
47
+ - preserve deterministic, offline CI and the zero-dependency runtime;
48
+ - disclose conflicts of interest;
49
+ - avoid merging their own substantial changes without another review when a
50
+ second maintainer is available.
51
+
52
+ Maintainer access is based on judgment, reliability, project need, and mutual
53
+ agreement. It is never exchanged for sponsorship, stars, or a fixed number of
54
+ pull requests.
55
+
56
+ ## Releases
57
+
58
+ Releases follow Semantic Versioning. The release workflow runs the full
59
+ validation suite, package dry run, installed ESM/CommonJS/type smoke tests, and
60
+ npm provenance publishing. Breaking changes require a migration note.
61
+
62
+ ## Security and conduct
63
+
64
+ Security reports follow [SECURITY.md](./SECURITY.md). Community behavior follows
65
+ [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). A maintainer involved in a conduct
66
+ or security concern should recuse themselves when another trusted reviewer is
67
+ available.
68
+
69
+ ## Governance changes
70
+
71
+ Governance changes use the normal public pull-request process and should explain
72
+ the problem they solve.
package/README.md CHANGED
@@ -6,11 +6,10 @@
6
6
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/slegarraga/llm-messages/badge)](https://scorecard.dev/viewer/?uri=github.com/slegarraga/llm-messages)
7
7
  [![license](https://img.shields.io/npm/l/llm-messages.svg)](./LICENSE)
8
8
  [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](./package.json)
9
+ [![install size](https://packagephobia.com/badge?p=llm-messages)](https://packagephobia.com/result?p=llm-messages)
10
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/llm-messages?label=min%2Bgzip)](https://bundlephobia.com/package/llm-messages)
9
11
 
10
- Convert chat conversations between **OpenAI**, **Anthropic** and **Gemini**
11
- message formats, and normalize provider responses into the same
12
- OpenAI-compatible assistant shape. Tool calls, system prompts, roles and
13
- response metadata handled correctly. Zero dependencies.
12
+ One library to write a conversation once and send it to OpenAI, Anthropic, or Gemini: handles system prompts, tool calls, role names, consecutive-turn merging, and response normalization. Zero dependencies.
14
13
 
15
14
  Switching an agent from one provider to another (or running fallback across providers) means rewriting the whole conversation, and the differences are subtle enough to break at runtime:
16
15
 
@@ -24,6 +23,17 @@ Switching an agent from one provider to another (or running fallback across prov
24
23
 
25
24
  `llm-messages` handles all of it. Write the conversation once, send it to any provider.
26
25
 
26
+ ## Project
27
+
28
+ - [Roadmap](./ROADMAP.md): conformance and portability priorities
29
+ - [Contributing](./CONTRIBUTING.md): development and fixture workflow
30
+ - [Governance](./GOVERNANCE.md): decisions and the path to reviewer or maintainer
31
+ - [Support](./SUPPORT.md): questions and reproducible bug reports
32
+ - [Security](./SECURITY.md): private vulnerability reporting
33
+ - [Conformance fixtures](./docs/conformance-fixtures.md): public compatibility contract
34
+ - [Changelog](./CHANGELOG.md): release history and migration notes
35
+ - [Code of Conduct](./CODE_OF_CONDUCT.md): community standards
36
+
27
37
  ## Install
28
38
 
29
39
  ```sh
@@ -57,6 +67,116 @@ const gemini = toGemini(messages);
57
67
  // contents: [{ role: 'user', parts: [{ text: "What's the weather in Paris?" }] }] }
58
68
  ```
59
69
 
70
+ ## Recipes
71
+
72
+ ### Convert an OpenAI conversation to Anthropic and call the SDK
73
+
74
+ ```ts
75
+ import Anthropic from '@anthropic-ai/sdk';
76
+ import { toAnthropic, responseFromAnthropic, type OpenAIMessage } from 'llm-messages';
77
+
78
+ const client = new Anthropic();
79
+
80
+ const conversation: OpenAIMessage[] = [
81
+ { role: 'system', content: 'You are a helpful assistant.' },
82
+ { role: 'user', content: 'What is 2 + 2?' },
83
+ ];
84
+
85
+ const { system, messages } = toAnthropic(conversation);
86
+
87
+ const raw = await client.messages.create({
88
+ model: 'claude-opus-4-8',
89
+ max_tokens: 256,
90
+ system,
91
+ messages,
92
+ });
93
+
94
+ // Normalize the response back to the OpenAI-compatible shape:
95
+ const { message, finishReason, usage } = responseFromAnthropic(raw);
96
+ const text = raw.content.find((b) => b.type === 'text')?.text ?? '';
97
+ // message.role === 'assistant', finishReason === 'stop', usage.outputTokens > 0
98
+ ```
99
+
100
+ ### Provider-fallback loop: one conversation, two providers
101
+
102
+ When a provider is rate-limited or unavailable, retry against the next one without
103
+ rewriting the conversation or the response-parsing logic:
104
+
105
+ ```ts
106
+ import OpenAI from 'openai';
107
+ import Anthropic from '@anthropic-ai/sdk';
108
+ import { toAnthropic, responseFromAnthropic, responseFromOpenAI, type OpenAIMessage } from 'llm-messages';
109
+
110
+ const conversation: OpenAIMessage[] = [
111
+ { role: 'system', content: 'You are a helpful assistant.' },
112
+ { role: 'user', content: 'Summarize the water cycle in one sentence.' },
113
+ ];
114
+
115
+ async function callWithFallback() {
116
+ // Try OpenAI first:
117
+ try {
118
+ const oai = new OpenAI();
119
+ const res = await oai.chat.completions.create({
120
+ model: 'gpt-4o-mini',
121
+ messages: conversation,
122
+ });
123
+ const raw = res.choices[0].message.content ?? '';
124
+ return responseFromOpenAI(res);
125
+ } catch {
126
+ // Fall back to Anthropic with the same conversation:
127
+ const ant = new Anthropic();
128
+ const { system, messages } = toAnthropic(conversation);
129
+ const res = await ant.messages.create({
130
+ model: 'claude-haiku-4-5-20251001',
131
+ max_tokens: 256,
132
+ system,
133
+ messages,
134
+ });
135
+ return responseFromAnthropic(res);
136
+ }
137
+ }
138
+
139
+ const { message, finishReason } = await callWithFallback();
140
+ // message.role === 'assistant' regardless of which provider answered
141
+ ```
142
+
143
+ ### Vercel AI SDK: keep your conversation portable
144
+
145
+ ```ts
146
+ import { generateText } from 'ai';
147
+ import { openai } from '@ai-sdk/openai';
148
+ import { toAnthropic, type OpenAIMessage } from 'llm-messages';
149
+
150
+ const conversation: OpenAIMessage[] = [
151
+ { role: 'system', content: 'You are a helpful assistant.' },
152
+ { role: 'user', content: 'Hello!' },
153
+ ];
154
+
155
+ // Use directly with Vercel AI SDK (already OpenAI-compatible):
156
+ const { text } = await generateText({ model: openai('gpt-4o-mini'), messages: conversation });
157
+
158
+ // Or convert the same conversation to send via Anthropic SDK:
159
+ const { system, messages } = toAnthropic(conversation);
160
+ ```
161
+
162
+ ## Why not X
163
+
164
+ **`ai` (Vercel AI SDK)** covers model routing and streaming well, but its
165
+ `CoreMessage` type differs from OpenAI's Chat Completions shape, and it does not
166
+ export converters to/from raw Anthropic or Gemini wire formats. `llm-messages` is
167
+ a complement: convert once so the conversation stays in one shape, then hand it to
168
+ whichever SDK or HTTP client you use.
169
+
170
+ **Writing the conversion yourself** is straightforward for text, but subtle for
171
+ tool calls: argument serialization differs per provider, consecutive same-role
172
+ turns are rejected by Anthropic and Gemini, Gemini matches results by name when
173
+ ids are absent, and Anthropic `tool_use_id` pairing has its own rules. The edge
174
+ cases accumulate. `llm-messages` has conformance fixtures that cover them.
175
+
176
+ **LangChain / LlamaIndex** solve orchestration. If you only need the message
177
+ conversion layer without the orchestration overhead, `llm-messages` is ~6 KB
178
+ min+gzip with zero runtime dependencies.
179
+
60
180
  ## API
61
181
 
62
182
  ### The canonical hub
@@ -168,6 +288,19 @@ for Gemini (which reports `STOP`) and Responses API bodies with `function_call`
168
288
  items. OpenAI Chat Completions, OpenAI Responses, Anthropic and Gemini tool
169
289
  calls without a non-empty string id get a deterministic one.
170
290
 
291
+ ### Streaming Responses API output
292
+
293
+ `llm-messages` normalizes completed response bodies and conversations. For
294
+ incremental SSE events, use
295
+ [`llm-sse`](https://github.com/slegarraga/llm-sse)'s
296
+ `parseOpenAIResponsesStream`, then `collectStream` and `toAssistantMessage`
297
+ before converting the completed history here. Keeping the streaming and message
298
+ boundaries separate lets both packages stay small, deterministic, and
299
+ zero-dependency.
300
+
301
+ The cross-package conformance example is tracked in
302
+ [issue #29](https://github.com/slegarraga/llm-messages/issues/29).
303
+
171
304
  ### Format cheatsheet
172
305
 
173
306
  | | OpenAI | Anthropic | Gemini |
@@ -246,21 +379,21 @@ local validation and production checks.
246
379
  Security posture is tracked in [docs/security-posture.md](./docs/security-posture.md),
247
380
  including CodeQL, OpenSSF Scorecard, Dependabot and branch rules.
248
381
 
249
- ## Provider portability suite
382
+ Questions, reproducible bugs, and maintainer responsibilities are documented in
383
+ [SUPPORT.md](./SUPPORT.md) and [GOVERNANCE.md](./GOVERNANCE.md).
384
+
385
+ ## Related
250
386
 
251
387
  `llm-messages` is the conversation boundary in a small provider-portability
252
- suite for OpenAI-compatible agent infrastructure:
253
-
254
- - [`tool-schema`](https://github.com/slegarraga/tool-schema) converts one JSON
255
- Schema into provider-specific tool/function schemas.
256
- - [`llm-sse`](https://github.com/slegarraga/llm-sse) parses streaming provider
257
- responses into unified events.
258
- - [`llm-errors`](https://github.com/slegarraga/llm-errors) normalizes provider
259
- errors, retry hints and fallback decisions.
260
- - [`json-from-llm`](https://github.com/slegarraga/json-from-llm) extracts JSON
261
- before it enters a tool or message pipeline.
262
- - [`llm-portability-demo`](https://github.com/slegarraga/llm-portability-demo)
263
- shows the whole flow offline, with no API key required.
388
+ suite for OpenAI-compatible agent infrastructure. The other packages in the suite:
389
+
390
+ - [`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
391
+ - [`tool-schema`](https://www.npmjs.com/package/tool-schema): convert a JSON Schema into a provider tool / function-calling schema for OpenAI, Anthropic, Gemini and MCP
392
+ - [`llm-sse`](https://www.npmjs.com/package/llm-sse): parse streaming SSE from LLM providers into typed, provider-agnostic events
393
+ - [`llm-errors`](https://www.npmjs.com/package/llm-errors): normalize provider errors (rate limits, retries, status) into one shape
394
+
395
+ The [`llm-portability-demo`](https://github.com/slegarraga/llm-portability-demo)
396
+ shows the whole flow offline, with no API key required.
264
397
 
265
398
  Read the
266
399
  [provider portability map](https://github.com/slegarraga/llm-portability-demo/blob/main/docs/provider-portability.md)
package/ROADMAP.md CHANGED
@@ -7,13 +7,15 @@ fallback behavior matter.
7
7
 
8
8
  ## Current priorities
9
9
 
10
- 1. **OpenAI Responses API coverage**
10
+ 1. **OpenAI Responses API boundary**
11
11
 
12
- Initial response normalization now maps Responses API `output_text` and
13
- `function_call` items back into the current OpenAI Chat Completions-compatible
14
- hub shape. Next: expand multimodal and streaming conformance fixtures.
12
+ Completed response normalization maps Responses API `output_text`,
13
+ `function_call`, refusal, and supported multimodal items into the current
14
+ OpenAI Chat Completions-compatible hub shape. Incremental SSE normalization
15
+ belongs in `llm-sse`. Next: prove the cross-package handoff from a streamed
16
+ function call into portable Anthropic and Gemini histories.
15
17
 
16
- Public issue: https://github.com/slegarraga/llm-messages/issues/6
18
+ Public issue: https://github.com/slegarraga/llm-messages/issues/29
17
19
 
18
20
  2. **Provider-backed fixture refreshes**
19
21
 
@@ -33,6 +35,11 @@ fallback behavior matter.
33
35
  Document the recommended flow for converting one conversation across
34
36
  providers after rate limits or retryable provider failures.
35
37
 
38
+ 5. **Reviewer and maintainer growth**
39
+
40
+ Make fixture ownership, review expectations, and the path to repository
41
+ responsibility explicit in [GOVERNANCE.md](./GOVERNANCE.md).
42
+
36
43
  ## API credit use
37
44
 
38
45
  If API credits are available, they will be used for conformance checks against
package/SUPPORT.md ADDED
@@ -0,0 +1,41 @@
1
+ # Support
2
+
3
+ `llm-messages` is maintained in public on a best-effort basis.
4
+
5
+ ## Questions and design discussions
6
+
7
+ Use
8
+ [GitHub Discussions](https://github.com/slegarraga/llm-messages/discussions)
9
+ for integration questions, proposed conversion behavior, and help choosing the
10
+ right API.
11
+
12
+ ## Bugs
13
+
14
+ Open a
15
+ [bug report](https://github.com/slegarraga/llm-messages/issues/new?template=bug_report.yml)
16
+ with:
17
+
18
+ - the source and target provider/API surfaces;
19
+ - a minimal, sanitized message or response body;
20
+ - the canonical output, warning, or finish reason you expected;
21
+ - the package and Node.js versions;
22
+ - a reproduction or failing conformance fixture when possible.
23
+
24
+ Do not include API keys, private prompts, personal data, or proprietary model
25
+ output.
26
+
27
+ ## Security
28
+
29
+ Do not open a public issue for a vulnerability. Follow
30
+ [SECURITY.md](./SECURITY.md) to use GitHub private vulnerability reporting or
31
+ the listed security email.
32
+
33
+ ## Scope
34
+
35
+ The project can help with message and completed-response normalization.
36
+ Incremental SSE event parsing belongs in
37
+ [`llm-sse`](https://github.com/slegarraga/llm-sse). Provider account access,
38
+ billing, rate limits, and general SDK support belong with the provider.
39
+
40
+ No response-time guarantee is offered, but well-scoped reproductions and pull
41
+ requests are prioritized.