llm-messages 0.5.3 → 0.5.4

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +123 -17
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.4] - 2026-06-29
10
+
11
+ ### Changed
12
+
13
+ - 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.
14
+
9
15
  ## [0.5.3] - 2026-06-29
10
16
 
11
17
  ### Fixed
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
 
@@ -57,6 +56,116 @@ const gemini = toGemini(messages);
57
56
  // contents: [{ role: 'user', parts: [{ text: "What's the weather in Paris?" }] }] }
58
57
  ```
59
58
 
59
+ ## Recipes
60
+
61
+ ### Convert an OpenAI conversation to Anthropic and call the SDK
62
+
63
+ ```ts
64
+ import Anthropic from '@anthropic-ai/sdk';
65
+ import { toAnthropic, responseFromAnthropic, type OpenAIMessage } from 'llm-messages';
66
+
67
+ const client = new Anthropic();
68
+
69
+ const conversation: OpenAIMessage[] = [
70
+ { role: 'system', content: 'You are a helpful assistant.' },
71
+ { role: 'user', content: 'What is 2 + 2?' },
72
+ ];
73
+
74
+ const { system, messages } = toAnthropic(conversation);
75
+
76
+ const raw = await client.messages.create({
77
+ model: 'claude-opus-4-8',
78
+ max_tokens: 256,
79
+ system,
80
+ messages,
81
+ });
82
+
83
+ // Normalize the response back to the OpenAI-compatible shape:
84
+ const { message, finishReason, usage } = responseFromAnthropic(raw);
85
+ const text = raw.content.find((b) => b.type === 'text')?.text ?? '';
86
+ // message.role === 'assistant', finishReason === 'stop', usage.outputTokens > 0
87
+ ```
88
+
89
+ ### Provider-fallback loop: one conversation, two providers
90
+
91
+ When a provider is rate-limited or unavailable, retry against the next one without
92
+ rewriting the conversation or the response-parsing logic:
93
+
94
+ ```ts
95
+ import OpenAI from 'openai';
96
+ import Anthropic from '@anthropic-ai/sdk';
97
+ import { toAnthropic, responseFromAnthropic, responseFromOpenAI, type OpenAIMessage } from 'llm-messages';
98
+
99
+ const conversation: OpenAIMessage[] = [
100
+ { role: 'system', content: 'You are a helpful assistant.' },
101
+ { role: 'user', content: 'Summarize the water cycle in one sentence.' },
102
+ ];
103
+
104
+ async function callWithFallback() {
105
+ // Try OpenAI first:
106
+ try {
107
+ const oai = new OpenAI();
108
+ const res = await oai.chat.completions.create({
109
+ model: 'gpt-4o-mini',
110
+ messages: conversation,
111
+ });
112
+ const raw = res.choices[0].message.content ?? '';
113
+ return responseFromOpenAI(res);
114
+ } catch {
115
+ // Fall back to Anthropic with the same conversation:
116
+ const ant = new Anthropic();
117
+ const { system, messages } = toAnthropic(conversation);
118
+ const res = await ant.messages.create({
119
+ model: 'claude-haiku-4-5-20251001',
120
+ max_tokens: 256,
121
+ system,
122
+ messages,
123
+ });
124
+ return responseFromAnthropic(res);
125
+ }
126
+ }
127
+
128
+ const { message, finishReason } = await callWithFallback();
129
+ // message.role === 'assistant' regardless of which provider answered
130
+ ```
131
+
132
+ ### Vercel AI SDK: keep your conversation portable
133
+
134
+ ```ts
135
+ import { generateText } from 'ai';
136
+ import { openai } from '@ai-sdk/openai';
137
+ import { toAnthropic, type OpenAIMessage } from 'llm-messages';
138
+
139
+ const conversation: OpenAIMessage[] = [
140
+ { role: 'system', content: 'You are a helpful assistant.' },
141
+ { role: 'user', content: 'Hello!' },
142
+ ];
143
+
144
+ // Use directly with Vercel AI SDK (already OpenAI-compatible):
145
+ const { text } = await generateText({ model: openai('gpt-4o-mini'), messages: conversation });
146
+
147
+ // Or convert the same conversation to send via Anthropic SDK:
148
+ const { system, messages } = toAnthropic(conversation);
149
+ ```
150
+
151
+ ## Why not X
152
+
153
+ **`ai` (Vercel AI SDK)** covers model routing and streaming well, but its
154
+ `CoreMessage` type differs from OpenAI's Chat Completions shape, and it does not
155
+ export converters to/from raw Anthropic or Gemini wire formats. `llm-messages` is
156
+ a complement: convert once so the conversation stays in one shape, then hand it to
157
+ whichever SDK or HTTP client you use.
158
+
159
+ **Writing the conversion yourself** is straightforward for text, but subtle for
160
+ tool calls: argument serialization differs per provider, consecutive same-role
161
+ turns are rejected by Anthropic and Gemini, Gemini matches results by name when
162
+ ids are absent, and Anthropic `tool_use_id` pairing has its own rules. The edge
163
+ cases accumulate. `llm-messages` has conformance fixtures that cover them.
164
+
165
+ **LangChain / LlamaIndex** solve orchestration. If you only need the message
166
+ conversion layer without the orchestration overhead, `llm-messages` is ~6 KB
167
+ min+gzip with zero runtime dependencies.
168
+
60
169
  ## API
61
170
 
62
171
  ### The canonical hub
@@ -246,21 +355,18 @@ local validation and production checks.
246
355
  Security posture is tracked in [docs/security-posture.md](./docs/security-posture.md),
247
356
  including CodeQL, OpenSSF Scorecard, Dependabot and branch rules.
248
357
 
249
- ## Provider portability suite
358
+ ## Related
250
359
 
251
360
  `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.
361
+ suite for OpenAI-compatible agent infrastructure. The other packages in the suite:
362
+
363
+ - [`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
364
+ - [`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
365
+ - [`llm-sse`](https://www.npmjs.com/package/llm-sse): parse streaming SSE from LLM providers into typed, provider-agnostic events
366
+ - [`llm-errors`](https://www.npmjs.com/package/llm-errors): normalize provider errors (rate limits, retries, status) into one shape
367
+
368
+ The [`llm-portability-demo`](https://github.com/slegarraga/llm-portability-demo)
369
+ shows the whole flow offline, with no API key required.
264
370
 
265
371
  Read the
266
372
  [provider portability map](https://github.com/slegarraga/llm-portability-demo/blob/main/docs/provider-portability.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-messages",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Convert chat conversations and responses between OpenAI, Anthropic and Gemini. Tool calls, images, audio, documents and roles handled. Zero dependencies.",
5
5
  "keywords": [
6
6
  "openai",