llm-messages 0.5.0 → 0.5.2

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.
@@ -1,12 +1,26 @@
1
- # Conformance fixtures plan
1
+ # Conformance fixtures
2
2
 
3
3
  `llm-messages` keeps OpenAI Chat Completions-compatible messages as its hub
4
- shape. Provider-backed conformance fixtures are the next layer for proving that
5
- the conversions stay correct as OpenAI, Anthropic and Gemini payloads evolve.
4
+ shape. Committed offline conformance fixtures prove that conversions stay
5
+ stable. Provider-backed fixture refreshes are intentionally maintainer-run:
6
+ this guide defines how refreshed payloads become deterministic offline fixtures,
7
+ while the roadmap tracks automation for generating new provider payloads.
6
8
 
7
- The fixture workflow should be safe to run in public CI without secrets, while
8
- still allowing maintainers to refresh source payloads with API credits when
9
- needed.
9
+ The fixture workflow is safe to run in public CI without secrets, while still
10
+ allowing maintainers to refresh source payloads with API credits when needed.
11
+
12
+ Committed fixtures live in `test/fixtures/*.json` as repository test assets;
13
+ they are not included in the npm package tarball. The public Vitest harness in
14
+ `test/conformance.test.ts` discovers those files in sorted order and runs them
15
+ offline as part of `npm test`. It currently routes `source: "anthropic"`
16
+ fixtures through `fromAnthropic(...)`, `source: "gemini"` fixtures through
17
+ `fromGemini(...)`, `source: "openai"` fixtures through
18
+ `responseFromOpenAI(...)`, and `source: "openai-responses"` fixtures through
19
+ `responseFromOpenAIResponses(...)`. Response-level fixtures use
20
+ `source: "anthropic-response"` for `responseFromAnthropic(...)` and
21
+ `source: "gemini-response"` for `responseFromGemini(...)`; add explicit routing
22
+ before committing fixtures for another provider or API family. The harness fails
23
+ fast if no JSON fixtures are present.
10
24
 
11
25
  ## Fixture classes
12
26
 
@@ -20,6 +34,75 @@ needed.
20
34
  | Responses API shape | OpenAI Responses API output can be mapped or rejected with explicit warnings. |
21
35
  | Lossy conversion | Unsupported or ambiguous provider data emits stable warning codes. |
22
36
 
37
+ ## Current fixture inventory
38
+
39
+ Use this inventory as a release-review index for the committed offline fixtures;
40
+ the JSON files remain the source of truth for exact inputs and expected output.
41
+ Each fixture id below maps to `test/fixtures/<fixture>.json`.
42
+ Update this table in the same change that adds or removes a fixture.
43
+
44
+ | Fixture | Source | Contract | Warning codes |
45
+ | ------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
46
+ | `anthropic-response-tool-use-duplicate-provider-id` | `anthropic-response` | Anthropic response-level tool_use blocks with duplicate provider ids keep the first id and regenerate later duplicates. | `generated-id` |
47
+ | `anthropic-response-tool-use-generated-id` | `anthropic-response` | Anthropic response-level tool_use blocks without usable ids generate deterministic canonical tool call ids and warnings. | 2x `generated-id` |
48
+ | `anthropic-response-tool-use-id-reservation` | `anthropic-response` | Anthropic response-level id-less tool_use blocks skip generated ids reserved by later provider-supplied ids. | `generated-id` |
49
+ | `anthropic-response-tool-use-invalid-arguments` | `anthropic-response` | Response-level Anthropic tool_use blocks with non-object input fall back to empty canonical arguments and warn. | `invalid-json-arguments` |
50
+ | `anthropic-response-tool-use-malformed-name` | `anthropic-response` | Anthropic response-level tool_use blocks with malformed names use the stable fallback function name and warning. | `dropped-metadata` |
51
+ | `anthropic-response-unsupported-content-block` | `anthropic-response` | Anthropic response-level unsupported content blocks are dropped with explicit warnings while supported text remains. | `dropped-content` |
52
+ | `anthropic-tool-result-duplicate-provider-id` | `anthropic` | Anthropic tool_result blocks with repeated provider ids map to duplicate tool_use ids in occurrence order after regeneration. | `generated-id` |
53
+ | `anthropic-tool-result-generated-id` | `anthropic` | Anthropic tool_result blocks without usable tool_use_id generate deterministic canonical tool message ids and warnings. | `unmapped-tool-result` |
54
+ | `anthropic-tool-result-id-reservation` | `anthropic` | Anthropic tool_result references reserve provider-supplied ids before earlier id-less tool_use blocks generate canonical ids and warn when no prior tool_use matches. | `generated-id`, `unmapped-tool-result` |
55
+ | `anthropic-tool-result-unmapped-id` | `anthropic` | Anthropic tool_result ids that do not match prior tool_use blocks stay orphaned and warn. | `unmapped-tool-result` |
56
+ | `anthropic-tool-use-duplicate-provider-id` | `anthropic` | Anthropic tool_use blocks with duplicate provider ids keep the first id and regenerate later duplicates. | `generated-id` |
57
+ | `anthropic-tool-use-generated-id` | `anthropic` | Anthropic tool_use blocks without usable ids generate deterministic canonical tool call ids and warnings. | 2x `generated-id` |
58
+ | `anthropic-tool-use-invalid-arguments` | `anthropic` | Anthropic tool_use blocks with non-object input fall back to empty canonical arguments and warn. | `invalid-json-arguments` |
59
+ | `anthropic-tool-use-malformed-name` | `anthropic` | Anthropic tool_use blocks with malformed names use the stable fallback function name and warning. | `dropped-metadata` |
60
+ | `gemini-function-call-duplicate-provider-id` | `gemini` | Gemini functionCall blocks with duplicate provider ids keep the first id and regenerate later duplicates. | `generated-id` |
61
+ | `gemini-function-call-generated-id` | `gemini` | Gemini functionCall blocks without usable ids generate deterministic canonical tool call ids and warnings. | `generated-id` |
62
+ | `gemini-function-call-generated-id-reservation` | `gemini` | Gemini id-less functionCall blocks skip generated ids that are reserved by later provider-supplied ids. | `generated-id` |
63
+ | `gemini-function-call-invalid-arguments` | `gemini` | Gemini functionCall parts with non-object args fall back to empty canonical arguments and warn. | `invalid-json-arguments` |
64
+ | `gemini-function-call-malformed-name` | `gemini` | Gemini functionCall blocks with malformed names use the stable fallback function name and warning. | `dropped-metadata` |
65
+ | `gemini-function-response-duplicate-provider-id` | `gemini` | Gemini functionResponse blocks with repeated provider ids map to duplicate functionCall ids in occurrence order after regeneration. | `generated-id` |
66
+ | `gemini-function-response-duplicate-provider-id-omitted-name` | `gemini` | Gemini functionResponse blocks with repeated provider ids can omit names and still resolve in occurrence order after regeneration. | `generated-id` |
67
+ | `gemini-function-response-generated-id` | `gemini` | Gemini functionResponse blocks without a matching call or id generate deterministic canonical tool-message ids. | `unmapped-tool-result` |
68
+ | `gemini-function-response-id-precedence` | `gemini` | Gemini functionResponse.id is resolved before falling back to same-name pending function calls. | `dropped-metadata` |
69
+ | `gemini-function-response-non-string-id` | `gemini` | Gemini functionResponse ids that are not strings are ignored before matching by function name. | `dropped-metadata` |
70
+ | `gemini-function-response-null-response` | `gemini` | Gemini functionResponse null response payloads serialize into canonical tool-message content instead of collapsing to an omitted response. | none |
71
+ | `gemini-function-response-omitted-name-id-match` | `gemini` | Gemini functionResponse blocks with a matching id can omit name and still resolve to the pending function call. | none |
72
+ | `gemini-function-response-order` | `gemini` | Gemini user text around a functionResponse preserves canonical message ordering. | none |
73
+ | `gemini-function-response-scalar-response` | `gemini` | Gemini functionResponse scalar response payloads serialize into canonical tool-message content without crashing. | none |
74
+ | `gemini-function-response-unmapped-id` | `gemini` | Gemini functionResponse ids that do not match pending calls stay orphaned and do not consume same-name pending calls. | `unmapped-tool-result` |
75
+ | `gemini-response-function-call-duplicate-provider-id` | `gemini-response` | Gemini response-level functionCall blocks with duplicate provider ids keep the first id and regenerate later duplicates. | `generated-id` |
76
+ | `gemini-response-function-call-generated-id` | `gemini-response` | Gemini response-level functionCall blocks without usable ids generate deterministic canonical tool call ids and warnings. | `generated-id` |
77
+ | `gemini-response-function-call-id-reservation` | `gemini-response` | Gemini response-level id-less functionCall blocks skip generated ids reserved by later provider-supplied ids. | `generated-id` |
78
+ | `gemini-response-function-call-invalid-arguments` | `gemini-response` | Response-level Gemini functionCall parts with non-object args fall back to empty canonical arguments and warn. | `invalid-json-arguments` |
79
+ | `gemini-response-function-call-malformed-name` | `gemini-response` | Gemini response-level functionCall blocks with malformed names use the stable fallback function name and warning. | `dropped-metadata` |
80
+ | `gemini-response-unsupported-content-part` | `gemini-response` | Gemini response-level unsupported content parts are dropped with explicit warnings while supported text remains. | `dropped-content` |
81
+ | `openai-chat-duplicate-provider-id` | `openai` | OpenAI Chat Completions tool_calls with duplicate provider ids keep the first id and regenerate later duplicates. | `generated-id` |
82
+ | `openai-chat-generated-id-reservation` | `openai` | OpenAI Chat Completions id-less tool_calls skip generated ids that are reserved by later provider-supplied ids. | `generated-id` |
83
+ | `openai-chat-generated-id-warning` | `openai` | OpenAI Chat Completions tool_calls without ids generate deterministic canonical tool call ids and warnings. | `generated-id` |
84
+ | `openai-chat-invalid-argument-string` | `openai` | OpenAI Chat Completions tool_call argument strings that decode to non-objects fall back to empty canonical arguments. | `invalid-json-arguments` |
85
+ | `openai-chat-invalid-arguments` | `openai` | OpenAI Chat Completions tool_call arguments that are explicit non-object values fall back to empty canonical arguments. | `invalid-json-arguments` |
86
+ | `openai-chat-legacy-function-call` | `openai` | Legacy OpenAI Chat Completions function_call responses normalize to canonical tool_calls with a generated id. | `generated-id` |
87
+ | `openai-chat-legacy-function-call-invalid-arguments` | `openai` | Legacy OpenAI Chat Completions function_call argument strings that decode to non-objects fall back to empty canonical arguments. | `generated-id`, `invalid-json-arguments` |
88
+ | `openai-chat-legacy-function-call-malformed-name` | `openai` | Legacy OpenAI Chat Completions function_call responses with malformed names fall back before generating canonical ids. | `dropped-metadata`, `generated-id` |
89
+ | `openai-chat-malformed-function-name` | `openai` | OpenAI Chat Completions tool_calls with malformed function names use the stable fallback function name and warning. | `dropped-metadata` |
90
+ | `openai-chat-malformed-tool-call` | `openai` | OpenAI Chat Completions malformed tool_calls[] entries are dropped with explicit warnings while supported function calls remain. | `dropped-content` |
91
+ | `openai-chat-object-arguments` | `openai` | OpenAI Chat Completions tool_call argument objects serialize to canonical JSON strings without warning. | none |
92
+ | `openai-chat-refusal-content-part` | `openai` | OpenAI Chat Completions refusal content parts flatten into canonical assistant text without warning. | none |
93
+ | `openai-chat-unsupported-content-part` | `openai` | OpenAI Chat Completions unsupported response content parts are dropped with explicit warnings while supported text remains. | `dropped-content` |
94
+ | `openai-chat-unsupported-tool-call-type` | `openai` | OpenAI Chat Completions unsupported tool_calls[] types are dropped while supported function calls remain. | `dropped-content` |
95
+ | `openai-responses-call-id-precedence` | `openai-responses` | OpenAI Responses API function_call items with both call_id and id preserve call_id as the canonical tool-call id. | none |
96
+ | `openai-responses-duplicate-provider-id` | `openai-responses` | OpenAI Responses API function_call items with duplicate provider ids keep the first id and regenerate later duplicates. | `generated-id` |
97
+ | `openai-responses-generated-id-reservation` | `openai-responses` | OpenAI Responses API id-less function_call items skip generated ids that are reserved by later provider-supplied ids. | `generated-id` |
98
+ | `openai-responses-generated-id-warning` | `openai-responses` | OpenAI Responses API function_call items without call_id or id generate a deterministic tool call id and warning. | `generated-id` |
99
+ | `openai-responses-invalid-argument-string` | `openai-responses` | OpenAI Responses API function_call argument strings that decode to non-objects fall back to empty canonical arguments. | `invalid-json-arguments` |
100
+ | `openai-responses-invalid-arguments` | `openai-responses` | OpenAI Responses API function_call arguments that are explicit non-object values fall back to empty canonical arguments. | `invalid-json-arguments` |
101
+ | `openai-responses-malformed-function-name` | `openai-responses` | OpenAI Responses API function_call items with malformed names use the stable fallback function name and warning. | `dropped-metadata` |
102
+ | `openai-responses-refusal-content-part` | `openai-responses` | OpenAI Responses API refusal content parts flatten into canonical assistant text without warning. | none |
103
+ | `openai-responses-unsupported-content-part` | `openai-responses` | OpenAI Responses API unsupported message content parts are dropped with explicit warnings while supported text remains. | `dropped-content` |
104
+ | `openai-responses-unsupported-output-item` | `openai-responses` | OpenAI Responses API unsupported top-level output items are dropped with explicit warnings while supported message output remains. | `dropped-content` |
105
+
23
106
  ## Refresh flow
24
107
 
25
108
  1. Generate provider payloads locally with maintainer-controlled API keys.
@@ -29,7 +112,7 @@ needed.
29
112
  4. Commit only deterministic JSON fixtures and expected normalized output.
30
113
  5. Run public CI without any API keys.
31
114
 
32
- ## OpenAI API credit use
115
+ ## Provider API credit use
33
116
 
34
117
  Credits should be used only for fixture refreshes that improve public coverage:
35
118
 
@@ -45,12 +128,29 @@ that require secrets in public CI.
45
128
 
46
129
  Every committed fixture should include:
47
130
 
48
- - Provider name and source API family.
49
- - Input payload.
50
- - Expected canonical OpenAI-compatible message.
51
- - Expected provider output when round-tripping is supported.
52
- - Expected warning codes for lossy or unsupported conversions.
53
- - A short note explaining why the case matters.
131
+ - `name`: a stable, unique test title that matches the JSON filename without
132
+ `.json`.
133
+ - `description`: a short note explaining why the case matters.
134
+ - `source`: the source provider or API family. The current public harness
135
+ accepts `anthropic`, `anthropic-response`, `gemini`, `gemini-response`,
136
+ `openai` and `openai-responses`.
137
+ - `input`: the minimized provider payload. Anthropic fixtures must include a
138
+ `messages` array, Anthropic response fixtures must include a `content` array,
139
+ Gemini fixtures must include a `contents` array, Gemini response fixtures must
140
+ include a `candidates` array, OpenAI Chat Completions fixtures must include a
141
+ `choices` array, and OpenAI Responses fixtures must include an `output` array.
142
+ - `expectedOpenAI`: the expected canonical OpenAI-compatible messages.
143
+ - `expectedWarningCodes`: expected warning codes for lossy or unsupported
144
+ conversions. Values must match the package's exported `warningCodes` list.
145
+ - `expectedResponse`: required only for response-body fixtures
146
+ (`anthropic-response`, `gemini-response`, `openai`, `openai-responses`).
147
+ It records the normalized `finishReason` and `usage` so response fixtures
148
+ catch metadata regressions as well as message-shape regressions.
149
+
150
+ Future fixture classes may add provider round-trip expectations, but the current
151
+ public harness intentionally stays offline and asserts normalized output,
152
+ response metadata and warning codes. Duplicate fixture names and names that do
153
+ not match their filenames fail fast so test output remains unambiguous.
54
154
 
55
155
  ## Public CI rule
56
156
 
@@ -0,0 +1,34 @@
1
+ /* eslint-disable @typescript-eslint/no-require-imports */
2
+ // Run from the repo root after `npm run build`:
3
+ // node examples/commonjs.cjs
4
+ const assert = require('node:assert/strict');
5
+ const { normalizeResponse, toAnthropic, warningCodes } = require('../dist/index.cjs');
6
+
7
+ const anthropic = toAnthropic([
8
+ { role: 'system', content: 'You are concise.' },
9
+ { role: 'user', content: 'Ping' },
10
+ ]);
11
+
12
+ assert.deepEqual(anthropic, {
13
+ system: 'You are concise.',
14
+ messages: [{ role: 'user', content: 'Ping' }],
15
+ });
16
+
17
+ const normalized = normalizeResponse(
18
+ {
19
+ choices: [
20
+ {
21
+ finish_reason: 'stop',
22
+ message: { role: 'assistant', content: 'Pong' },
23
+ },
24
+ ],
25
+ usage: { prompt_tokens: 2, completion_tokens: 1 },
26
+ },
27
+ { from: 'openai' },
28
+ );
29
+
30
+ assert.equal(normalized.message.content, 'Pong');
31
+ assert.equal(normalized.finishReason, 'stop');
32
+ assert.equal(Object.isFrozen(warningCodes), true);
33
+
34
+ console.log('CommonJS smoke check passed.');
@@ -1,7 +1,10 @@
1
1
  // Run from the repo root after `npm run build`:
2
2
  // node examples/usage.mjs
3
+ import assert from 'node:assert/strict';
3
4
  import { toAnthropic, toGemini, convert } from '../dist/index.js';
4
5
 
6
+ const checkMode = process.argv.includes('--check');
7
+
5
8
  const messages = [
6
9
  { role: 'system', content: 'You are a weather assistant.' },
7
10
  { role: 'user', content: "What's the weather in Paris?" },
@@ -15,12 +18,24 @@ const messages = [
15
18
  { role: 'tool', tool_call_id: 'call_abc', content: '15C partly cloudy' },
16
19
  ];
17
20
 
21
+ const anthropic = toAnthropic(messages);
22
+ const gemini = toGemini(messages);
23
+ const converted = convert(anthropic, { from: 'anthropic', to: 'gemini' });
24
+
25
+ if (checkMode) {
26
+ assert.equal(anthropic.system, 'You are a weather assistant.');
27
+ assert.ok(Array.isArray(anthropic.messages));
28
+ assert.ok(Array.isArray(gemini.contents));
29
+ assert.ok(Array.isArray(converted.contents));
30
+ console.log('ESM usage smoke check passed.');
31
+ process.exit(0);
32
+ }
33
+
18
34
  console.log('--- OpenAI -> Anthropic ---');
19
- console.log(JSON.stringify(toAnthropic(messages), null, 2));
35
+ console.log(JSON.stringify(anthropic, null, 2));
20
36
 
21
37
  console.log('\n--- OpenAI -> Gemini ---');
22
- console.log(JSON.stringify(toGemini(messages), null, 2));
38
+ console.log(JSON.stringify(gemini, null, 2));
23
39
 
24
40
  console.log('\n--- Anthropic -> Gemini (via convert) ---');
25
- const anthropic = toAnthropic(messages);
26
- console.log(JSON.stringify(convert(anthropic, { from: 'anthropic', to: 'gemini' }), null, 2));
41
+ console.log(JSON.stringify(converted, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-messages",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",
@@ -58,23 +58,31 @@
58
58
  "examples",
59
59
  "README.md",
60
60
  "ROADMAP.md",
61
+ "SECURITY.md",
61
62
  "LICENSE",
62
63
  "CHANGELOG.md"
63
64
  ],
64
65
  "engines": {
65
66
  "node": ">=18"
66
67
  },
68
+ "packageManager": "npm@10.9.8",
67
69
  "sideEffects": false,
68
70
  "scripts": {
69
71
  "build": "tsup",
72
+ "check": "npm run format:check && npm run typecheck && npm run lint && npm test && npm run build && npm run examples:check && npm run pack:check && npm run pack:smoke",
73
+ "examples:check": "node examples/usage.mjs --check && node examples/commonjs.cjs",
74
+ "pack:check": "npm pack --dry-run --foreground-scripts=false",
75
+ "pack:smoke": "node scripts/pack-smoke.mjs",
70
76
  "typecheck": "tsc --noEmit",
71
77
  "test": "vitest run",
72
78
  "test:watch": "vitest",
73
79
  "lint": "eslint .",
74
80
  "format": "prettier --write .",
75
81
  "format:check": "prettier --check .",
76
- "prepublishOnly": "npm run build",
77
- "prepare": "npm run build"
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"
78
86
  },
79
87
  "devDependencies": {
80
88
  "@eslint/js": "^10.0.1",
@@ -82,7 +90,7 @@
82
90
  "eslint": "^10.4.1",
83
91
  "prettier": "^3.4.2",
84
92
  "tsup": "^8.3.5",
85
- "typescript": "^5.7.2",
93
+ "typescript": "^6.0.3",
86
94
  "typescript-eslint": "^8.60.0",
87
95
  "vitest": "^4.1.8"
88
96
  }