@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.
- package/README.md +750 -0
- package/dist/__tests__/anthropic.test.d.ts +2 -0
- package/dist/__tests__/anthropic.test.d.ts.map +1 -0
- package/dist/__tests__/anthropic.test.js +183 -0
- package/dist/__tests__/anthropic.test.js.map +1 -0
- package/dist/__tests__/gemini.test.d.ts +2 -0
- package/dist/__tests__/gemini.test.d.ts.map +1 -0
- package/dist/__tests__/gemini.test.js +177 -0
- package/dist/__tests__/gemini.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +513 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/normalizer.test.d.ts +2 -0
- package/dist/__tests__/normalizer.test.d.ts.map +1 -0
- package/dist/__tests__/normalizer.test.js +349 -0
- package/dist/__tests__/normalizer.test.js.map +1 -0
- package/dist/__tests__/openai.test.d.ts +2 -0
- package/dist/__tests__/openai.test.d.ts.map +1 -0
- package/dist/__tests__/openai.test.js +473 -0
- package/dist/__tests__/openai.test.js.map +1 -0
- package/dist/__tests__/providers.test.d.ts +2 -0
- package/dist/__tests__/providers.test.d.ts.map +1 -0
- package/dist/__tests__/providers.test.js +187 -0
- package/dist/__tests__/providers.test.js.map +1 -0
- package/dist/__tests__/tool.test.d.ts +2 -0
- package/dist/__tests__/tool.test.d.ts.map +1 -0
- package/dist/__tests__/tool.test.js +212 -0
- package/dist/__tests__/tool.test.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/normalizer.d.ts +24 -0
- package/dist/normalizer.d.ts.map +1 -0
- package/dist/normalizer.js +263 -0
- package/dist/normalizer.js.map +1 -0
- package/dist/providers/anthropic.d.ts +7 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +109 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/cohere.d.ts +8 -0
- package/dist/providers/cohere.d.ts.map +1 -0
- package/dist/providers/cohere.js +63 -0
- package/dist/providers/cohere.js.map +1 -0
- package/dist/providers/gemini.d.ts +10 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +160 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +17 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +51 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/mcp.d.ts +8 -0
- package/dist/providers/mcp.d.ts.map +1 -0
- package/dist/providers/mcp.js +18 -0
- package/dist/providers/mcp.js.map +1 -0
- package/dist/providers/ollama.d.ts +8 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +63 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +309 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/tool.d.ts +19 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +120 -0
- package/dist/tool.js.map +1 -0
- package/dist/types.d.ts +143 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- 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
|
+
[](https://www.npmjs.com/package/schema-bridge)
|
|
6
|
+
[](https://www.npmjs.com/package/schema-bridge)
|
|
7
|
+
[](https://github.com/SiluPanda/schema-bridge/blob/master/LICENSE)
|
|
8
|
+
[](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
|