llm-messages 0.5.2 → 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.
- package/CHANGELOG.md +12 -0
- package/README.md +124 -18
- package/package.json +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ 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
|
+
|
|
15
|
+
## [0.5.3] - 2026-06-29
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- The npm package page showed a broken "resource not found" downloads badge after the self-hosted badge JSON was removed. The README now uses shields.io's native `npm/dm` badge, which renders the live download count directly on npm.
|
|
20
|
+
|
|
9
21
|
## [0.5.2] - 2026-06-12
|
|
10
22
|
|
|
11
23
|
### Summary
|
package/README.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# llm-messages
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/llm-messages)
|
|
4
|
-
[](https://www.npmjs.com/package/llm-messages)
|
|
5
5
|
[](https://github.com/slegarraga/llm-messages/actions/workflows/ci.yml)
|
|
6
6
|
[](https://scorecard.dev/viewer/?uri=github.com/slegarraga/llm-messages)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
[](./package.json)
|
|
9
|
+
[](https://packagephobia.com/result?p=llm-messages)
|
|
10
|
+
[](https://bundlephobia.com/package/llm-messages)
|
|
9
11
|
|
|
10
|
-
|
|
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
|
-
##
|
|
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
|
-
- [`
|
|
255
|
-
|
|
256
|
-
- [`llm-sse`](https://
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
"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",
|
|
@@ -80,9 +80,7 @@
|
|
|
80
80
|
"format": "prettier --write .",
|
|
81
81
|
"format:check": "prettier --check .",
|
|
82
82
|
"prepublishOnly": "npm run check",
|
|
83
|
-
"prepare": "npm run build"
|
|
84
|
-
"badges:downloads": "node scripts/update-download-badge.mjs",
|
|
85
|
-
"badges:downloads:check": "node scripts/update-download-badge.mjs --check"
|
|
83
|
+
"prepare": "npm run build"
|
|
86
84
|
},
|
|
87
85
|
"devDependencies": {
|
|
88
86
|
"@eslint/js": "^10.0.1",
|