@silupanda/schema-bridge 0.2.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.
Files changed (74) hide show
  1. package/README.md +750 -0
  2. package/dist/__tests__/anthropic.test.d.ts +2 -0
  3. package/dist/__tests__/anthropic.test.d.ts.map +1 -0
  4. package/dist/__tests__/anthropic.test.js +183 -0
  5. package/dist/__tests__/anthropic.test.js.map +1 -0
  6. package/dist/__tests__/gemini.test.d.ts +2 -0
  7. package/dist/__tests__/gemini.test.d.ts.map +1 -0
  8. package/dist/__tests__/gemini.test.js +177 -0
  9. package/dist/__tests__/gemini.test.js.map +1 -0
  10. package/dist/__tests__/integration.test.d.ts +2 -0
  11. package/dist/__tests__/integration.test.d.ts.map +1 -0
  12. package/dist/__tests__/integration.test.js +513 -0
  13. package/dist/__tests__/integration.test.js.map +1 -0
  14. package/dist/__tests__/normalizer.test.d.ts +2 -0
  15. package/dist/__tests__/normalizer.test.d.ts.map +1 -0
  16. package/dist/__tests__/normalizer.test.js +349 -0
  17. package/dist/__tests__/normalizer.test.js.map +1 -0
  18. package/dist/__tests__/openai.test.d.ts +2 -0
  19. package/dist/__tests__/openai.test.d.ts.map +1 -0
  20. package/dist/__tests__/openai.test.js +473 -0
  21. package/dist/__tests__/openai.test.js.map +1 -0
  22. package/dist/__tests__/providers.test.d.ts +2 -0
  23. package/dist/__tests__/providers.test.d.ts.map +1 -0
  24. package/dist/__tests__/providers.test.js +187 -0
  25. package/dist/__tests__/providers.test.js.map +1 -0
  26. package/dist/__tests__/tool.test.d.ts +2 -0
  27. package/dist/__tests__/tool.test.d.ts.map +1 -0
  28. package/dist/__tests__/tool.test.js +212 -0
  29. package/dist/__tests__/tool.test.js.map +1 -0
  30. package/dist/index.d.ts +45 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +81 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/normalizer.d.ts +24 -0
  35. package/dist/normalizer.d.ts.map +1 -0
  36. package/dist/normalizer.js +263 -0
  37. package/dist/normalizer.js.map +1 -0
  38. package/dist/providers/anthropic.d.ts +7 -0
  39. package/dist/providers/anthropic.d.ts.map +1 -0
  40. package/dist/providers/anthropic.js +109 -0
  41. package/dist/providers/anthropic.js.map +1 -0
  42. package/dist/providers/cohere.d.ts +8 -0
  43. package/dist/providers/cohere.d.ts.map +1 -0
  44. package/dist/providers/cohere.js +63 -0
  45. package/dist/providers/cohere.js.map +1 -0
  46. package/dist/providers/gemini.d.ts +10 -0
  47. package/dist/providers/gemini.d.ts.map +1 -0
  48. package/dist/providers/gemini.js +160 -0
  49. package/dist/providers/gemini.js.map +1 -0
  50. package/dist/providers/index.d.ts +17 -0
  51. package/dist/providers/index.d.ts.map +1 -0
  52. package/dist/providers/index.js +51 -0
  53. package/dist/providers/index.js.map +1 -0
  54. package/dist/providers/mcp.d.ts +8 -0
  55. package/dist/providers/mcp.d.ts.map +1 -0
  56. package/dist/providers/mcp.js +18 -0
  57. package/dist/providers/mcp.js.map +1 -0
  58. package/dist/providers/ollama.d.ts +8 -0
  59. package/dist/providers/ollama.d.ts.map +1 -0
  60. package/dist/providers/ollama.js +63 -0
  61. package/dist/providers/ollama.js.map +1 -0
  62. package/dist/providers/openai.d.ts +10 -0
  63. package/dist/providers/openai.d.ts.map +1 -0
  64. package/dist/providers/openai.js +309 -0
  65. package/dist/providers/openai.js.map +1 -0
  66. package/dist/tool.d.ts +19 -0
  67. package/dist/tool.d.ts.map +1 -0
  68. package/dist/tool.js +120 -0
  69. package/dist/tool.js.map +1 -0
  70. package/dist/types.d.ts +143 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +3 -0
  73. package/dist/types.js.map +1 -0
  74. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,750 @@
1
+ # schema-bridge
2
+
3
+ Write your JSON Schema once. Deploy it to every LLM provider.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/schema-bridge.svg)](https://www.npmjs.com/package/schema-bridge)
6
+ [![npm downloads](https://img.shields.io/npm/dt/schema-bridge.svg)](https://www.npmjs.com/package/schema-bridge)
7
+ [![license](https://img.shields.io/npm/l/schema-bridge.svg)](https://github.com/SiluPanda/schema-bridge/blob/master/LICENSE)
8
+ [![node](https://img.shields.io/node/v/schema-bridge.svg)](https://nodejs.org)
9
+
10
+ ---
11
+
12
+ ## Description
13
+
14
+ `schema-bridge` converts a single JSON Schema definition into provider-specific structured output configurations for **OpenAI**, **Anthropic**, **Google Gemini**, **Cohere**, **MCP**, **Ollama**, and **Vercel AI SDK**. Each LLM provider imposes different structural requirements, keyword restrictions, and wrapper formats on schemas used for structured output and tool definitions. `schema-bridge` handles every provider's quirks automatically so you can define your schema once and use it everywhere.
15
+
16
+ Every major LLM provider accepts JSON Schema to define structured output or tool parameters, but no two providers accept the same subset. OpenAI strict mode requires `additionalProperties: false` on every object and rejects keywords like `minimum`, `maximum`, and `format`. Anthropic wraps tool schemas in an `input_schema` key. Gemini uses `responseSchema` inside `generationConfig` and does not support `default`. MCP uses `inputSchema` with full JSON Schema support. Getting these conversions wrong results in hard 400 errors, not subtle bugs. `schema-bridge` eliminates this problem entirely.
17
+
18
+ The package has **zero runtime dependencies**. It accepts JSON Schema objects (draft-07, draft-2020-12) as input, normalizes them to a canonical internal representation, applies provider-specific transformations, and returns the result in the exact shape the provider's API expects. A `TransformationRecord` array documents every change made and flags lossy conversions.
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install schema-bridge
26
+ ```
27
+
28
+ **Requirements:** Node.js >= 18
29
+
30
+ ---
31
+
32
+ ## Quick Start
33
+
34
+ ```typescript
35
+ import { convert, convertTool, convertTools } from 'schema-bridge';
36
+
37
+ // Define your schema once
38
+ const schema = {
39
+ type: 'object',
40
+ properties: {
41
+ temperature: { type: 'number', description: 'Temperature in Fahrenheit' },
42
+ conditions: { type: 'string', description: 'Weather conditions' },
43
+ humidity: { type: 'number', description: 'Humidity percentage', minimum: 0, maximum: 100 },
44
+ },
45
+ required: ['temperature', 'conditions'],
46
+ };
47
+
48
+ // Convert for any provider
49
+ const openaiResult = convert(schema, 'openai');
50
+ // openaiResult.schema has additionalProperties: false injected,
51
+ // all properties required, optional fields made nullable,
52
+ // and unsupported keywords (minimum, maximum) removed.
53
+
54
+ const anthropicResult = convert(schema, 'anthropic');
55
+ // anthropicResult.schema preserves minimum, maximum, and optional fields as-is.
56
+
57
+ const mcpResult = convert(schema, 'mcp');
58
+ // mcpResult.schema passes through unchanged (MCP supports full JSON Schema).
59
+
60
+ // Convert a tool definition for a specific provider
61
+ const toolResult = convertTool(
62
+ {
63
+ name: 'get_weather',
64
+ description: 'Get current weather for a location',
65
+ schema,
66
+ },
67
+ 'openai',
68
+ );
69
+ // toolResult.tool is ready to pass directly to the OpenAI SDK:
70
+ // { type: "function", function: { name, description, parameters, strict } }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Features
76
+
77
+ - **Seven providers supported** -- OpenAI, Anthropic, Gemini, Cohere, MCP, Ollama, and Vercel AI SDK, each with its own provider adapter that encodes the provider's exact schema requirements.
78
+ - **Automatic `additionalProperties: false` injection** -- for OpenAI strict mode and Anthropic strict mode, applied recursively to every nested object.
79
+ - **Required field expansion** -- for OpenAI strict mode, all properties are added to the `required` array and optional fields are converted to nullable types.
80
+ - **Unsupported keyword removal** -- keywords like `minimum`, `maximum`, `format`, `pattern`, `default`, and `examples` are stripped per provider, with each removal recorded as a transformation.
81
+ - **Composition simplification** -- `allOf` schemas are merged into a single schema, `oneOf` is converted to `anyOf`, and `not` is removed for providers with limited support.
82
+ - **`$ref` resolution** -- `$ref` references are inlined for providers that require it, with recursive schema detection and configurable truncation depth.
83
+ - **Recursive schema handling** -- recursive `$ref` cycles are detected automatically and either preserved (for providers that support `$ref`/`$defs`) or truncated with a lossy transformation warning.
84
+ - **Transformation reports** -- every conversion returns a `TransformationRecord[]` documenting each change (path, type, message, lossiness) and a `warnings` array for lossy conversions.
85
+ - **Constraint promotion** -- optionally appends removed constraints to `description` fields so the LLM still sees them as natural language hints.
86
+ - **Tool definition conversion** -- `convertTool` and `convertTools` produce provider-specific tool envelopes ready to pass directly to each provider's SDK.
87
+ - **Schema normalization** -- `normalize` converts draft-07 schemas to canonical draft-2020-12 form (`definitions` to `$defs`, array-form `items` to `prefixItems`, `$schema` stripped).
88
+ - **Zero runtime dependencies** -- the core conversion logic has no external dependencies.
89
+ - **Immutable input** -- the original schema is never mutated; all transformations operate on deep clones.
90
+
91
+ ---
92
+
93
+ ## API Reference
94
+
95
+ ### `convert(schema, provider, options?)`
96
+
97
+ Convert a JSON Schema to a provider-specific structured output format.
98
+
99
+ **Parameters:**
100
+
101
+ | Parameter | Type | Description |
102
+ |-----------|------|-------------|
103
+ | `schema` | `JSONSchema` | A JSON Schema object. |
104
+ | `provider` | `Provider` | Target provider: `'openai'`, `'anthropic'`, `'gemini'`, `'cohere'`, `'mcp'`, `'ollama'`, or `'vercel-ai'`. |
105
+ | `options` | `ConvertOptions` | Optional. Conversion options (see below). |
106
+
107
+ **Returns:** `ProviderSchema`
108
+
109
+ ```typescript
110
+ interface ProviderSchema {
111
+ schema: JSONSchema; // The transformed schema
112
+ transformations: TransformationRecord[]; // Every change applied
113
+ warnings: string[]; // Lossy conversion warnings
114
+ }
115
+ ```
116
+
117
+ **Example:**
118
+
119
+ ```typescript
120
+ import { convert } from 'schema-bridge';
121
+
122
+ const schema = {
123
+ type: 'object',
124
+ properties: {
125
+ name: { type: 'string', minLength: 1 },
126
+ age: { type: 'number', minimum: 0 },
127
+ },
128
+ required: ['name'],
129
+ };
130
+
131
+ const result = convert(schema, 'openai');
132
+ console.log(result.schema);
133
+ // {
134
+ // type: 'object',
135
+ // properties: {
136
+ // name: { type: 'string' },
137
+ // age: { type: ['number', 'null'] },
138
+ // },
139
+ // required: ['name', 'age'],
140
+ // additionalProperties: false,
141
+ // }
142
+
143
+ console.log(result.warnings);
144
+ // ['Removed "minLength" at ...', 'Removed "minimum" at ...']
145
+ ```
146
+
147
+ ---
148
+
149
+ ### `convertTool(tool, provider, options?)`
150
+
151
+ Convert a tool definition to a provider-specific tool object.
152
+
153
+ **Parameters:**
154
+
155
+ | Parameter | Type | Description |
156
+ |-----------|------|-------------|
157
+ | `tool` | `ToolDefinitionInput` | Tool definition with `name`, `description`, `schema`, and optional `outputSchema`. |
158
+ | `provider` | `Provider` | Target provider name. |
159
+ | `options` | `ConvertOptions` | Optional. Conversion options. |
160
+
161
+ **Returns:** `{ tool: ToolDefinition; transformations: TransformationRecord[]; warnings: string[] }`
162
+
163
+ Each provider uses its own envelope format:
164
+
165
+ | Provider | Envelope Shape |
166
+ |----------|---------------|
167
+ | OpenAI | `{ type: "function", function: { name, description, parameters, strict } }` |
168
+ | Anthropic | `{ name, description, input_schema }` |
169
+ | Gemini | `{ name, description, parameters }` |
170
+ | Cohere | `{ type: "function", function: { name, description, parameters } }` |
171
+ | MCP | `{ name, description, inputSchema, outputSchema? }` |
172
+ | Ollama | `{ name, description, format }` |
173
+ | Vercel AI | `{ name, description, parameters }` |
174
+
175
+ **Example:**
176
+
177
+ ```typescript
178
+ import { convertTool } from 'schema-bridge';
179
+
180
+ const { tool } = convertTool(
181
+ {
182
+ name: 'search_web',
183
+ description: 'Search the web for information',
184
+ schema: {
185
+ type: 'object',
186
+ properties: {
187
+ query: { type: 'string', description: 'Search query' },
188
+ limit: { type: 'number', minimum: 1, maximum: 100 },
189
+ },
190
+ required: ['query'],
191
+ },
192
+ },
193
+ 'anthropic',
194
+ );
195
+
196
+ // tool is ready to pass to the Anthropic SDK:
197
+ // {
198
+ // name: 'search_web',
199
+ // description: 'Search the web for information',
200
+ // input_schema: { type: 'object', properties: { ... }, required: ['query'] }
201
+ // }
202
+ ```
203
+
204
+ ---
205
+
206
+ ### `convertTools(tools, provider, options?)`
207
+
208
+ Batch-convert multiple tool definitions to a provider-specific format.
209
+
210
+ **Parameters:**
211
+
212
+ | Parameter | Type | Description |
213
+ |-----------|------|-------------|
214
+ | `tools` | `ToolDefinitionInput[]` | Array of tool definitions. |
215
+ | `provider` | `Provider` | Target provider name. |
216
+ | `options` | `ConvertOptions` | Optional. Conversion options. |
217
+
218
+ **Returns:** `{ tools: ToolDefinition[]; transformations: TransformationRecord[][]; warnings: string[][] }`
219
+
220
+ For Gemini, all tools are wrapped in a single `{ functionDeclarations: [...] }` object, matching Gemini's expected format.
221
+
222
+ **Example:**
223
+
224
+ ```typescript
225
+ import { convertTools } from 'schema-bridge';
226
+
227
+ const tools = [
228
+ { name: 'get_weather', description: 'Get weather', schema: weatherSchema },
229
+ { name: 'search_web', description: 'Search the web', schema: searchSchema },
230
+ ];
231
+
232
+ const result = convertTools(tools, 'gemini');
233
+ // result.tools is a single-element array:
234
+ // [{ functionDeclarations: [{ name, description, parameters }, { name, description, parameters }] }]
235
+ ```
236
+
237
+ ---
238
+
239
+ ### `normalize(schema)`
240
+
241
+ Normalize a JSON Schema to canonical draft-2020-12 form. This is called internally by `convert`, but is also exported for direct use.
242
+
243
+ **Normalization steps:**
244
+
245
+ - Converts `definitions` to `$defs`
246
+ - Converts array-form `items` to `prefixItems`
247
+ - Strips the `$schema` property
248
+ - Recurses into all subschemas (`properties`, `items`, `anyOf`, `oneOf`, `allOf`, `not`, `additionalProperties`, `$defs`)
249
+
250
+ **Parameters:**
251
+
252
+ | Parameter | Type | Description |
253
+ |-----------|------|-------------|
254
+ | `schema` | `JSONSchema` | A JSON Schema object. |
255
+
256
+ **Returns:** `JSONSchema` -- a new normalized schema object (the input is not mutated).
257
+
258
+ ```typescript
259
+ import { normalize } from 'schema-bridge';
260
+
261
+ const draft07 = {
262
+ $schema: 'http://json-schema.org/draft-07/schema#',
263
+ type: 'object',
264
+ definitions: {
265
+ Address: { type: 'object', properties: { street: { type: 'string' } } },
266
+ },
267
+ };
268
+
269
+ const normalized = normalize(draft07);
270
+ // normalized.$schema is undefined
271
+ // normalized.$defs.Address exists
272
+ // normalized.definitions is undefined
273
+ ```
274
+
275
+ ---
276
+
277
+ ### `resolveRefs(schema, options?)`
278
+
279
+ Resolve all `$ref` references by inlining them. This is called internally by `convert`, but is also exported for direct use.
280
+
281
+ **Parameters:**
282
+
283
+ | Parameter | Type | Description |
284
+ |-----------|------|-------------|
285
+ | `schema` | `JSONSchema` | A JSON Schema object (should be normalized first). |
286
+ | `options.maxRecursionDepth` | `number` | Maximum depth for recursive `$ref` inlining (default: `5`). |
287
+ | `options.preserveRefs` | `boolean` | If `true`, keep recursive `$ref` and `$defs` intact instead of inlining. |
288
+
289
+ **Returns:** `{ schema: JSONSchema; transformations: TransformationRecord[] }`
290
+
291
+ ```typescript
292
+ import { resolveRefs } from 'schema-bridge';
293
+
294
+ const schema = {
295
+ type: 'object',
296
+ properties: { address: { $ref: '#/$defs/Address' } },
297
+ $defs: { Address: { type: 'object', properties: { city: { type: 'string' } } } },
298
+ };
299
+
300
+ const { schema: resolved, transformations } = resolveRefs(schema);
301
+ // resolved.properties.address.type === 'object'
302
+ // resolved.properties.address.properties.city.type === 'string'
303
+ // resolved.$defs is undefined (all refs inlined)
304
+ ```
305
+
306
+ ---
307
+
308
+ ### `supported()`
309
+
310
+ List all supported provider names.
311
+
312
+ **Returns:** `Provider[]` -- `['openai', 'anthropic', 'gemini', 'cohere', 'mcp', 'vercel-ai', 'ollama']`
313
+
314
+ ```typescript
315
+ import { supported } from 'schema-bridge';
316
+
317
+ console.log(supported());
318
+ // ['openai', 'anthropic', 'gemini', 'cohere', 'mcp', 'vercel-ai', 'ollama']
319
+ ```
320
+
321
+ ---
322
+
323
+ ### Provider-Specific Converters
324
+
325
+ For advanced use cases, individual provider converters are exported directly:
326
+
327
+ ```typescript
328
+ import {
329
+ convertToOpenAI,
330
+ convertToAnthropic,
331
+ convertToGemini,
332
+ convertToCohere,
333
+ convertToMCP,
334
+ convertToOllama,
335
+ wrapOpenAIResponseFormat,
336
+ wrapGeminiResponseFormat,
337
+ } from 'schema-bridge';
338
+ ```
339
+
340
+ #### `convertToOpenAI(schema, options?)`
341
+
342
+ Applies OpenAI strict mode transformations to a JSON Schema. Returns `ProviderSchema`.
343
+
344
+ #### `convertToAnthropic(schema, options?)`
345
+
346
+ Applies Anthropic transformations (removes `$comment`; optional `additionalProperties: false` in strict mode). Returns `ProviderSchema`.
347
+
348
+ #### `convertToGemini(schema, options?)`
349
+
350
+ Applies Gemini transformations (removes `default`, `examples`, `$comment`; simplifies composition). Returns `ProviderSchema`.
351
+
352
+ #### `convertToCohere(schema, options?)`
353
+
354
+ Applies Cohere transformations (removes `$comment`). Returns `ProviderSchema`.
355
+
356
+ #### `convertToMCP(schema, options?)`
357
+
358
+ Passes through with no modifications (MCP supports full JSON Schema). Returns `ProviderSchema`.
359
+
360
+ #### `convertToOllama(schema, options?)`
361
+
362
+ Applies Ollama transformations (removes `examples`, `$comment`). Returns `ProviderSchema`.
363
+
364
+ #### `wrapOpenAIResponseFormat(schema, name, strict?)`
365
+
366
+ Wraps a converted schema in OpenAI's `response_format` envelope.
367
+
368
+ ```typescript
369
+ import { convertToOpenAI, wrapOpenAIResponseFormat } from 'schema-bridge';
370
+
371
+ const { schema } = convertToOpenAI(mySchema);
372
+ const responseFormat = wrapOpenAIResponseFormat(schema, 'my_response', true);
373
+ // {
374
+ // type: 'json_schema',
375
+ // json_schema: { name: 'my_response', schema: { ... }, strict: true }
376
+ // }
377
+ ```
378
+
379
+ #### `wrapGeminiResponseFormat(schema)`
380
+
381
+ Wraps a converted schema in Gemini's `generationConfig` envelope.
382
+
383
+ ```typescript
384
+ import { convertToGemini, wrapGeminiResponseFormat } from 'schema-bridge';
385
+
386
+ const { schema } = convertToGemini(mySchema);
387
+ const config = wrapGeminiResponseFormat(schema);
388
+ // {
389
+ // generationConfig: { responseMimeType: 'application/json', responseSchema: { ... } }
390
+ // }
391
+ ```
392
+
393
+ ---
394
+
395
+ ### Tool Builder Functions
396
+
397
+ The underlying tool builder functions are also exported:
398
+
399
+ ```typescript
400
+ import { createTool, createTools } from 'schema-bridge';
401
+ ```
402
+
403
+ `createTool` and `createTools` are identical to `convertTool` and `convertTools`. Both pairs are available for naming preference.
404
+
405
+ ---
406
+
407
+ ## Configuration
408
+
409
+ ### `ConvertOptions`
410
+
411
+ All conversion functions accept an optional `ConvertOptions` object:
412
+
413
+ | Option | Type | Default | Description |
414
+ |--------|------|---------|-------------|
415
+ | `strict` | `boolean` | `true` for OpenAI, `false` for Anthropic | Enable strict mode. For OpenAI, injects `additionalProperties: false`, expands `required`, and removes unsupported keywords. For Anthropic, injects `additionalProperties: false` only. |
416
+ | `name` | `string` | `undefined` | Schema name, used in the OpenAI `response_format` envelope. |
417
+ | `description` | `string` | `undefined` | Schema description. |
418
+ | `promoteConstraintsToDescription` | `boolean` | `false` | When a keyword is removed (e.g., `minimum: 0` for OpenAI), append it to the field's `description` so the LLM still sees the constraint as a natural language hint. |
419
+ | `maxRecursionDepth` | `number` | `5` | Maximum depth for inlining recursive `$ref` references. Beyond this depth, recursive refs are truncated to `{ type: 'object' }` with a lossy transformation warning. |
420
+
421
+ ```typescript
422
+ const result = convert(schema, 'openai', {
423
+ strict: true,
424
+ promoteConstraintsToDescription: true,
425
+ maxRecursionDepth: 3,
426
+ });
427
+ ```
428
+
429
+ ---
430
+
431
+ ## Error Handling
432
+
433
+ ### Unsupported Provider
434
+
435
+ Passing a provider name that is not recognized throws an `Error`:
436
+
437
+ ```typescript
438
+ convert(schema, 'unknown-provider');
439
+ // Error: Unsupported provider: "unknown-provider". Supported providers: openai, anthropic, gemini, cohere, mcp, vercel-ai, ollama
440
+ ```
441
+
442
+ ### Invalid Tool Name
443
+
444
+ Tool names must match `^[a-zA-Z0-9_-]+$`. Invalid names throw a `TypeError`:
445
+
446
+ ```typescript
447
+ convertTool({ name: 'get weather', description: '...', schema }, 'openai');
448
+ // TypeError: Invalid tool name "get weather": must contain only alphanumeric characters, underscores, and hyphens
449
+ ```
450
+
451
+ ### Lossy Transformations
452
+
453
+ When a schema feature cannot be represented in the target provider, `schema-bridge` does not throw. Instead, it applies the transformation, marks it as `lossy: true` in the `TransformationRecord`, and adds a human-readable message to the `warnings` array. This allows you to audit exactly what was lost:
454
+
455
+ ```typescript
456
+ const result = convert(
457
+ {
458
+ type: 'object',
459
+ properties: {
460
+ score: { type: 'number', minimum: 0, maximum: 100 },
461
+ },
462
+ required: ['score'],
463
+ },
464
+ 'openai',
465
+ );
466
+
467
+ for (const t of result.transformations.filter(t => t.lossy)) {
468
+ console.log(`${t.path}: ${t.message}`);
469
+ }
470
+ // $.properties.score: Removed unsupported keyword "minimum" (value: 0)
471
+ // $.properties.score: Removed unsupported keyword "maximum" (value: 100)
472
+ ```
473
+
474
+ ### Recursive Schema Truncation
475
+
476
+ Recursive schemas that exceed `maxRecursionDepth` are truncated to `{ type: 'object' }` for providers that do not support `$ref`/`$defs`. This produces a `RECURSIVE_SCHEMA_TRUNCATED` transformation record with `lossy: true`.
477
+
478
+ ---
479
+
480
+ ## Advanced Usage
481
+
482
+ ### Multi-Provider Tool Deployment
483
+
484
+ Convert a set of tools for whichever provider the user selects at runtime:
485
+
486
+ ```typescript
487
+ import { convertTools } from 'schema-bridge';
488
+ import type { Provider } from 'schema-bridge';
489
+
490
+ const tools = [
491
+ {
492
+ name: 'get_weather',
493
+ description: 'Get current weather',
494
+ schema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
495
+ },
496
+ {
497
+ name: 'search_docs',
498
+ description: 'Search documentation',
499
+ schema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
500
+ },
501
+ ];
502
+
503
+ function getProviderTools(provider: Provider) {
504
+ return convertTools(tools, provider);
505
+ }
506
+
507
+ // At runtime, use whichever provider the user configured
508
+ const { tools: openaiTools } = getProviderTools('openai');
509
+ const { tools: anthropicTools } = getProviderTools('anthropic');
510
+ ```
511
+
512
+ ### MCP Server with Output Schema
513
+
514
+ MCP is the only provider that supports `outputSchema` on tool definitions:
515
+
516
+ ```typescript
517
+ import { convertTool } from 'schema-bridge';
518
+
519
+ const { tool } = convertTool(
520
+ {
521
+ name: 'calculate',
522
+ description: 'Perform a calculation',
523
+ schema: {
524
+ type: 'object',
525
+ properties: { expression: { type: 'string' } },
526
+ required: ['expression'],
527
+ },
528
+ outputSchema: {
529
+ type: 'object',
530
+ properties: { result: { type: 'number' } },
531
+ required: ['result'],
532
+ },
533
+ },
534
+ 'mcp',
535
+ );
536
+
537
+ // tool.inputSchema and tool.outputSchema are both present
538
+ ```
539
+
540
+ ### Promoting Constraints to Descriptions
541
+
542
+ When converting for providers that strip validation keywords, you can preserve them as natural language hints in the `description` field:
543
+
544
+ ```typescript
545
+ import { convert } from 'schema-bridge';
546
+
547
+ const result = convert(
548
+ {
549
+ type: 'object',
550
+ properties: {
551
+ age: { type: 'number', description: 'User age', minimum: 0, maximum: 150 },
552
+ },
553
+ required: ['age'],
554
+ },
555
+ 'openai',
556
+ { promoteConstraintsToDescription: true },
557
+ );
558
+
559
+ console.log(result.schema.properties.age.description);
560
+ // "User age (minimum: 0) (maximum: 150)"
561
+ ```
562
+
563
+ ### OpenAI Response Format Envelope
564
+
565
+ Use `wrapOpenAIResponseFormat` to produce the complete `response_format` object for OpenAI's structured output API:
566
+
567
+ ```typescript
568
+ import { convert, wrapOpenAIResponseFormat } from 'schema-bridge';
569
+
570
+ const { schema } = convert(mySchema, 'openai');
571
+ const responseFormat = wrapOpenAIResponseFormat(schema, 'extract_data');
572
+
573
+ // Pass directly to the OpenAI SDK:
574
+ // openai.chat.completions.create({ ..., response_format: responseFormat })
575
+ ```
576
+
577
+ ### Gemini Generation Config Envelope
578
+
579
+ Use `wrapGeminiResponseFormat` to produce the `generationConfig` object for Gemini's structured output:
580
+
581
+ ```typescript
582
+ import { convert, wrapGeminiResponseFormat } from 'schema-bridge';
583
+
584
+ const { schema } = convert(mySchema, 'gemini');
585
+ const config = wrapGeminiResponseFormat(schema);
586
+
587
+ // config.generationConfig.responseMimeType === 'application/json'
588
+ // config.generationConfig.responseSchema === schema
589
+ ```
590
+
591
+ ### Inspecting Transformation Reports
592
+
593
+ Every conversion returns a full audit trail:
594
+
595
+ ```typescript
596
+ import { convert } from 'schema-bridge';
597
+
598
+ const result = convert(complexSchema, 'openai');
599
+
600
+ for (const t of result.transformations) {
601
+ console.log(`[${t.type}] ${t.path}: ${t.message} (lossy: ${t.lossy})`);
602
+ }
603
+ // [ADDITIONAL_PROPERTIES_INJECTED] $: Set additionalProperties to false (was undefined) (lossy: false)
604
+ // [REQUIRED_EXPANDED] $.properties.nickname: Added "nickname" to required array and made nullable (lossy: false)
605
+ // [KEYWORD_REMOVED] $.properties.age: Removed unsupported keyword "minimum" (value: 0) (lossy: true)
606
+ // ...
607
+ ```
608
+
609
+ **Transformation types:**
610
+
611
+ | Type | Description |
612
+ |------|-------------|
613
+ | `REF_INLINED` | A `$ref` was replaced with the referenced schema definition. |
614
+ | `ADDITIONAL_PROPERTIES_INJECTED` | `additionalProperties: false` was set on an object. |
615
+ | `REQUIRED_EXPANDED` | A property was added to the `required` array and made nullable. |
616
+ | `FIELD_MADE_NULLABLE` | A field's type was changed to include `null`. |
617
+ | `KEYWORD_REMOVED` | An unsupported keyword was removed from the schema. |
618
+ | `COMPOSITION_SIMPLIFIED` | An `allOf` was merged, `oneOf` converted to `anyOf`, or `not` removed. |
619
+ | `DEFAULT_REMOVED` | A `default` value was removed. |
620
+ | `RECURSIVE_SCHEMA_TRUNCATED` | A recursive `$ref` was truncated at the configured depth limit. |
621
+
622
+ ---
623
+
624
+ ## Provider-Specific Behavior
625
+
626
+ ### OpenAI (strict mode, default)
627
+
628
+ | Transformation | Detail |
629
+ |---------------|--------|
630
+ | `additionalProperties: false` | Injected on every object at every nesting level. |
631
+ | Required expansion | All properties added to `required`; optional fields converted to nullable (`["string", "null"]` or `anyOf` with `{ type: "null" }`). |
632
+ | Keyword removal | `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`, `minLength`, `maxLength`, `minItems`, `maxItems`, `pattern`, `format`, `default`, `examples`, `$comment` are removed. |
633
+ | Composition | `allOf` merged into single schema. `oneOf` converted to `anyOf`. `not` removed (lossy). |
634
+ | `$ref` handling | Preserved for recursive schemas; non-recursive refs inlined. |
635
+ | Strict: false | When `strict: false`, no transformations are applied. |
636
+
637
+ ### Anthropic
638
+
639
+ | Transformation | Detail |
640
+ |---------------|--------|
641
+ | Keyword removal | `$comment` removed. All other keywords preserved. |
642
+ | Constraints | `minimum`, `maximum`, `pattern`, `format`, `default` -- all preserved. |
643
+ | Optional fields | Remain truly optional (not expanded to required). |
644
+ | Strict mode | `strict: true` injects `additionalProperties: false` on all objects. Does not expand `required`. |
645
+ | `$ref` handling | Preserved. |
646
+ | Composition | `anyOf`, `oneOf`, `allOf` all preserved. |
647
+
648
+ ### Gemini
649
+
650
+ | Transformation | Detail |
651
+ |---------------|--------|
652
+ | Keyword removal | `default`, `examples`, `$comment` removed. |
653
+ | Constraints | `minimum`, `maximum`, `pattern`, `format` preserved. |
654
+ | Composition | `allOf` merged. `not` removed (lossy). `anyOf` and `oneOf` preserved. |
655
+ | `$ref` handling | Preserved. |
656
+ | `additionalProperties` | Not modified. |
657
+
658
+ ### Cohere
659
+
660
+ | Transformation | Detail |
661
+ |---------------|--------|
662
+ | Keyword removal | `$comment` removed. |
663
+ | All other keywords | Preserved (constraints, composition, `$ref`). |
664
+
665
+ ### MCP
666
+
667
+ | Transformation | Detail |
668
+ |---------------|--------|
669
+ | Keyword removal | None. Full JSON Schema support. |
670
+ | `outputSchema` | Supported on tool definitions (unique to MCP). |
671
+ | `$ref` handling | Preserved. |
672
+
673
+ ### Ollama
674
+
675
+ | Transformation | Detail |
676
+ |---------------|--------|
677
+ | Keyword removal | `examples`, `$comment` removed. |
678
+ | All other keywords | Preserved. |
679
+ | Schema delivery | Passed directly as the `format` field value. |
680
+
681
+ ### Vercel AI SDK
682
+
683
+ Uses the same converter as MCP (standard JSON Schema passthrough, no keyword removal).
684
+
685
+ ---
686
+
687
+ ## TypeScript
688
+
689
+ `schema-bridge` is written in TypeScript and ships with full type declarations. All types are exported from the package root:
690
+
691
+ ```typescript
692
+ import type {
693
+ // Core types
694
+ JSONSchema,
695
+ Provider,
696
+ ConvertOptions,
697
+ ProviderSchema,
698
+ TransformationRecord,
699
+ TransformationType,
700
+
701
+ // Tool definition types
702
+ ToolDefinitionInput,
703
+ ToolDefinition,
704
+ OpenAIToolDefinition,
705
+ AnthropicToolDefinition,
706
+ GeminiToolDefinition,
707
+ CohereToolDefinition,
708
+ MCPToolDefinition,
709
+ OllamaToolDefinition,
710
+ GenericToolDefinition,
711
+ } from 'schema-bridge';
712
+ ```
713
+
714
+ ### `Provider`
715
+
716
+ ```typescript
717
+ type Provider = 'openai' | 'anthropic' | 'gemini' | 'cohere' | 'mcp' | 'vercel-ai' | 'ollama';
718
+ ```
719
+
720
+ ### `JSONSchema`
721
+
722
+ A recursive interface supporting draft-07 and draft-2020-12 keywords including `type`, `properties`, `required`, `items`, `prefixItems`, `additionalProperties`, `enum`, `const`, `anyOf`, `oneOf`, `allOf`, `not`, `$ref`, `$defs`, `definitions`, `description`, `title`, `default`, `format`, `pattern`, `minimum`, `maximum`, and more. Includes an index signature for extension keywords.
723
+
724
+ ### `TransformationRecord`
725
+
726
+ ```typescript
727
+ interface TransformationRecord {
728
+ type: TransformationType; // e.g., 'KEYWORD_REMOVED', 'ADDITIONAL_PROPERTIES_INJECTED'
729
+ path: string; // JSON path, e.g., '$.properties.age'
730
+ message: string; // Human-readable description
731
+ lossy: boolean; // Whether the transformation lost information
732
+ }
733
+ ```
734
+
735
+ ### `ToolDefinitionInput`
736
+
737
+ ```typescript
738
+ interface ToolDefinitionInput {
739
+ name: string; // Must match /^[a-zA-Z0-9_-]+$/
740
+ description: string;
741
+ schema: JSONSchema;
742
+ outputSchema?: JSONSchema; // Only used by MCP provider
743
+ }
744
+ ```
745
+
746
+ ---
747
+
748
+ ## License
749
+
750
+ MIT