@ai-sdk/anthropic 4.0.0-beta.4 → 4.0.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +305 -4
- package/README.md +2 -0
- package/dist/index.d.ts +83 -58
- package/dist/index.js +2043 -1356
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +85 -58
- package/dist/internal/index.js +1804 -1342
- package/dist/internal/index.js.map +1 -1
- package/docs/05-anthropic.mdx +116 -13
- package/package.json +14 -15
- package/src/{anthropic-messages-api.ts → anthropic-api.ts} +14 -6
- package/src/anthropic-error.ts +1 -1
- package/src/anthropic-files.ts +95 -0
- package/src/{anthropic-messages-language-model.ts → anthropic-language-model.ts} +263 -78
- package/src/anthropic-message-metadata.ts +1 -4
- package/src/{anthropic-messages-options.ts → anthropic-options.ts} +68 -11
- package/src/anthropic-prepare-tools.ts +14 -7
- package/src/anthropic-provider.ts +42 -13
- package/src/{convert-anthropic-messages-usage.ts → convert-anthropic-usage.ts} +4 -4
- package/src/{convert-to-anthropic-messages-prompt.ts → convert-to-anthropic-prompt.ts} +190 -149
- package/src/forward-anthropic-container-id-from-last-step.ts +2 -2
- package/src/get-cache-control.ts +5 -2
- package/src/index.ts +1 -1
- package/src/internal/index.ts +11 -2
- package/src/map-anthropic-stop-reason.ts +1 -1
- package/src/sanitize-json-schema.ts +203 -0
- package/src/skills/anthropic-skills-api.ts +44 -0
- package/src/skills/anthropic-skills.ts +132 -0
- package/src/tool/bash_20241022.ts +2 -2
- package/src/tool/bash_20250124.ts +2 -2
- package/src/tool/code-execution_20250522.ts +2 -2
- package/src/tool/code-execution_20250825.ts +2 -2
- package/src/tool/code-execution_20260120.ts +2 -2
- package/src/tool/computer_20241022.ts +2 -2
- package/src/tool/computer_20250124.ts +2 -2
- package/src/tool/computer_20251124.ts +2 -2
- package/src/tool/memory_20250818.ts +2 -2
- package/src/tool/text-editor_20241022.ts +2 -2
- package/src/tool/text-editor_20250124.ts +2 -2
- package/src/tool/text-editor_20250429.ts +2 -2
- package/src/tool/text-editor_20250728.ts +6 -3
- package/src/tool/tool-search-bm25_20251119.ts +2 -2
- package/src/tool/tool-search-regex_20251119.ts +2 -2
- package/src/tool/web-fetch-20250910.ts +2 -2
- package/src/tool/web-fetch-20260209.ts +2 -2
- package/src/tool/web-search_20250305.ts +2 -2
- package/src/tool/web-search_20260209.ts +2 -2
- package/dist/index.d.mts +0 -1090
- package/dist/index.mjs +0 -5244
- package/dist/index.mjs.map +0 -1
- package/dist/internal/index.d.mts +0 -969
- package/dist/internal/index.mjs +0 -5136
- package/dist/internal/index.mjs.map +0 -1
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
import {
|
|
2
|
-
SharedV4Warning,
|
|
3
|
-
LanguageModelV4DataContent,
|
|
4
|
-
LanguageModelV4Message,
|
|
5
|
-
LanguageModelV4Prompt,
|
|
6
|
-
SharedV4ProviderMetadata,
|
|
7
2
|
UnsupportedFunctionalityError,
|
|
3
|
+
type SharedV4Warning,
|
|
4
|
+
type LanguageModelV4Message,
|
|
5
|
+
type LanguageModelV4Prompt,
|
|
6
|
+
type SharedV4ProviderMetadata,
|
|
8
7
|
} from '@ai-sdk/provider';
|
|
9
8
|
import {
|
|
10
9
|
convertBase64ToUint8Array,
|
|
11
10
|
convertToBase64,
|
|
11
|
+
getTopLevelMediaType,
|
|
12
12
|
parseProviderOptions,
|
|
13
|
+
resolveFullMediaType,
|
|
14
|
+
resolveProviderReference,
|
|
13
15
|
validateTypes,
|
|
14
16
|
isNonNullable,
|
|
15
|
-
ToolNameMapping,
|
|
17
|
+
type ToolNameMapping,
|
|
16
18
|
} from '@ai-sdk/provider-utils';
|
|
17
19
|
import {
|
|
18
|
-
AnthropicAssistantMessage,
|
|
19
|
-
AnthropicMessagesPrompt,
|
|
20
20
|
anthropicReasoningMetadataSchema,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
type AnthropicAssistantMessage,
|
|
22
|
+
type AnthropicPrompt,
|
|
23
|
+
type AnthropicToolResultContent,
|
|
24
|
+
type AnthropicUserMessage,
|
|
25
|
+
type AnthropicWebFetchToolResultContent,
|
|
26
|
+
} from './anthropic-api';
|
|
27
|
+
import { anthropicFilePartProviderOptions } from './anthropic-options';
|
|
26
28
|
import { CacheControlValidator } from './get-cache-control';
|
|
27
29
|
import { codeExecution_20250522OutputSchema } from './tool/code-execution_20250522';
|
|
28
30
|
import { codeExecution_20250825OutputSchema } from './tool/code-execution_20250825';
|
|
@@ -31,44 +33,14 @@ import { toolSearchRegex_20251119OutputSchema as toolSearchOutputSchema } from '
|
|
|
31
33
|
import { webFetch_20250910OutputSchema } from './tool/web-fetch-20250910';
|
|
32
34
|
import { webSearch_20250305OutputSchema } from './tool/web-search_20250305';
|
|
33
35
|
|
|
34
|
-
function
|
|
36
|
+
function convertBytesDataToString(data: Uint8Array | string): string {
|
|
35
37
|
if (typeof data === 'string') {
|
|
36
38
|
return new TextDecoder().decode(convertBase64ToUint8Array(data));
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
if (data instanceof Uint8Array) {
|
|
40
|
-
return new TextDecoder().decode(data);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (data instanceof URL) {
|
|
44
|
-
throw new UnsupportedFunctionalityError({
|
|
45
|
-
functionality: 'URL-based text documents are not supported for citations',
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
throw new UnsupportedFunctionalityError({
|
|
50
|
-
functionality: `unsupported data type for text documents: ${typeof data}`,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Checks if data is a URL (either a URL object or a URL string).
|
|
56
|
-
*/
|
|
57
|
-
function isUrlData(
|
|
58
|
-
data: LanguageModelV4DataContent,
|
|
59
|
-
): data is URL | (string & { __brand: 'url-string' }) {
|
|
60
|
-
return data instanceof URL || isUrlString(data);
|
|
40
|
+
return new TextDecoder().decode(data);
|
|
61
41
|
}
|
|
62
42
|
|
|
63
|
-
function
|
|
64
|
-
return typeof data === 'string' && /^https?:\/\//i.test(data);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function getUrlString(data: LanguageModelV4DataContent): string {
|
|
68
|
-
return data instanceof URL ? data.toString() : (data as string);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function convertToAnthropicMessagesPrompt({
|
|
43
|
+
export async function convertToAnthropicPrompt({
|
|
72
44
|
prompt,
|
|
73
45
|
sendReasoning,
|
|
74
46
|
warnings,
|
|
@@ -81,15 +53,15 @@ export async function convertToAnthropicMessagesPrompt({
|
|
|
81
53
|
cacheControlValidator?: CacheControlValidator;
|
|
82
54
|
toolNameMapping: ToolNameMapping;
|
|
83
55
|
}): Promise<{
|
|
84
|
-
prompt:
|
|
56
|
+
prompt: AnthropicPrompt;
|
|
85
57
|
betas: Set<string>;
|
|
86
58
|
}> {
|
|
87
59
|
const betas = new Set<string>();
|
|
88
60
|
const blocks = groupIntoBlocks(prompt);
|
|
89
61
|
const validator = cacheControlValidator || new CacheControlValidator();
|
|
90
62
|
|
|
91
|
-
let system:
|
|
92
|
-
const messages:
|
|
63
|
+
let system: AnthropicPrompt['system'] = undefined;
|
|
64
|
+
const messages: AnthropicPrompt['messages'] = [];
|
|
93
65
|
|
|
94
66
|
async function shouldEnableCitations(
|
|
95
67
|
providerMetadata: SharedV4ProviderMetadata | undefined,
|
|
@@ -183,86 +155,154 @@ export async function convertToAnthropicMessagesPrompt({
|
|
|
183
155
|
}
|
|
184
156
|
|
|
185
157
|
case 'file': {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
158
|
+
switch (part.data.type) {
|
|
159
|
+
case 'reference': {
|
|
160
|
+
const fileId = resolveProviderReference({
|
|
161
|
+
reference: part.data.reference,
|
|
162
|
+
provider: 'anthropic',
|
|
163
|
+
});
|
|
164
|
+
betas.add('files-api-2025-04-14');
|
|
165
|
+
|
|
166
|
+
if (getTopLevelMediaType(part.mediaType) === 'image') {
|
|
167
|
+
anthropicContent.push({
|
|
168
|
+
type: 'image',
|
|
169
|
+
source: { type: 'file', file_id: fileId },
|
|
170
|
+
cache_control: cacheControl,
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
anthropicContent.push({
|
|
174
|
+
type: 'document',
|
|
175
|
+
source: { type: 'file', file_id: fileId },
|
|
176
|
+
cache_control: cacheControl,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'text': {
|
|
182
|
+
const enableCitations = await shouldEnableCitations(
|
|
183
|
+
part.providerOptions,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const metadata = await getDocumentMetadata(
|
|
187
|
+
part.providerOptions,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
anthropicContent.push({
|
|
191
|
+
type: 'document',
|
|
192
|
+
source: {
|
|
193
|
+
type: 'text',
|
|
194
|
+
media_type: 'text/plain',
|
|
195
|
+
data: part.data.text,
|
|
196
|
+
},
|
|
197
|
+
title: metadata.title ?? part.filename,
|
|
198
|
+
...(metadata.context && {
|
|
199
|
+
context: metadata.context,
|
|
200
|
+
}),
|
|
201
|
+
...(enableCitations && {
|
|
202
|
+
citations: { enabled: true },
|
|
203
|
+
}),
|
|
204
|
+
cache_control: cacheControl,
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'url':
|
|
209
|
+
case 'data': {
|
|
210
|
+
const topLevel = getTopLevelMediaType(part.mediaType);
|
|
211
|
+
if (topLevel === 'image') {
|
|
212
|
+
anthropicContent.push({
|
|
213
|
+
type: 'image',
|
|
214
|
+
source:
|
|
215
|
+
part.data.type === 'url'
|
|
216
|
+
? {
|
|
217
|
+
type: 'url',
|
|
218
|
+
url: part.data.url.toString(),
|
|
219
|
+
}
|
|
220
|
+
: {
|
|
221
|
+
type: 'base64',
|
|
222
|
+
media_type: resolveFullMediaType({ part }),
|
|
223
|
+
data: convertToBase64(part.data.data),
|
|
224
|
+
},
|
|
225
|
+
cache_control: cacheControl,
|
|
226
|
+
});
|
|
227
|
+
} else if (
|
|
228
|
+
topLevel === 'application' &&
|
|
229
|
+
(part.data.type === 'url'
|
|
230
|
+
? part.mediaType === 'application/pdf'
|
|
231
|
+
: resolveFullMediaType({ part }) ===
|
|
232
|
+
'application/pdf')
|
|
233
|
+
) {
|
|
234
|
+
betas.add('pdfs-2024-09-25');
|
|
235
|
+
|
|
236
|
+
const enableCitations = await shouldEnableCitations(
|
|
237
|
+
part.providerOptions,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const metadata = await getDocumentMetadata(
|
|
241
|
+
part.providerOptions,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
anthropicContent.push({
|
|
245
|
+
type: 'document',
|
|
246
|
+
source:
|
|
247
|
+
part.data.type === 'url'
|
|
248
|
+
? {
|
|
249
|
+
type: 'url',
|
|
250
|
+
url: part.data.url.toString(),
|
|
251
|
+
}
|
|
252
|
+
: {
|
|
253
|
+
type: 'base64',
|
|
254
|
+
media_type: 'application/pdf',
|
|
255
|
+
data: convertToBase64(part.data.data),
|
|
256
|
+
},
|
|
257
|
+
title: metadata.title ?? part.filename,
|
|
258
|
+
...(metadata.context && {
|
|
259
|
+
context: metadata.context,
|
|
260
|
+
}),
|
|
261
|
+
...(enableCitations && {
|
|
262
|
+
citations: { enabled: true },
|
|
263
|
+
}),
|
|
264
|
+
cache_control: cacheControl,
|
|
265
|
+
});
|
|
266
|
+
} else if (part.mediaType === 'text/plain') {
|
|
267
|
+
const enableCitations = await shouldEnableCitations(
|
|
268
|
+
part.providerOptions,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const metadata = await getDocumentMetadata(
|
|
272
|
+
part.providerOptions,
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
anthropicContent.push({
|
|
276
|
+
type: 'document',
|
|
277
|
+
source:
|
|
278
|
+
part.data.type === 'url'
|
|
279
|
+
? {
|
|
280
|
+
type: 'url',
|
|
281
|
+
url: part.data.url.toString(),
|
|
282
|
+
}
|
|
283
|
+
: {
|
|
284
|
+
type: 'text',
|
|
285
|
+
media_type: 'text/plain',
|
|
286
|
+
data: convertBytesDataToString(
|
|
287
|
+
part.data.data,
|
|
288
|
+
),
|
|
289
|
+
},
|
|
290
|
+
title: metadata.title ?? part.filename,
|
|
291
|
+
...(metadata.context && {
|
|
292
|
+
context: metadata.context,
|
|
293
|
+
}),
|
|
294
|
+
...(enableCitations && {
|
|
295
|
+
citations: { enabled: true },
|
|
296
|
+
}),
|
|
297
|
+
cache_control: cacheControl,
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
throw new UnsupportedFunctionalityError({
|
|
301
|
+
functionality: `media type: ${part.mediaType}`,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
266
306
|
}
|
|
267
307
|
|
|
268
308
|
break;
|
|
@@ -309,26 +349,16 @@ export async function convertToAnthropicMessagesPrompt({
|
|
|
309
349
|
type: 'text' as const,
|
|
310
350
|
text: contentPart.text,
|
|
311
351
|
};
|
|
312
|
-
case 'image-data': {
|
|
313
|
-
return {
|
|
314
|
-
type: 'image' as const,
|
|
315
|
-
source: {
|
|
316
|
-
type: 'base64' as const,
|
|
317
|
-
media_type: contentPart.mediaType,
|
|
318
|
-
data: contentPart.data,
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
case 'image-url': {
|
|
323
|
-
return {
|
|
324
|
-
type: 'image' as const,
|
|
325
|
-
source: {
|
|
326
|
-
type: 'url' as const,
|
|
327
|
-
url: contentPart.url,
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
352
|
case 'file-url': {
|
|
353
|
+
if (contentPart.mediaType.startsWith('image/')) {
|
|
354
|
+
return {
|
|
355
|
+
type: 'image' as const,
|
|
356
|
+
source: {
|
|
357
|
+
type: 'url' as const,
|
|
358
|
+
url: contentPart.url,
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
332
362
|
return {
|
|
333
363
|
type: 'document' as const,
|
|
334
364
|
source: {
|
|
@@ -338,6 +368,16 @@ export async function convertToAnthropicMessagesPrompt({
|
|
|
338
368
|
};
|
|
339
369
|
}
|
|
340
370
|
case 'file-data': {
|
|
371
|
+
if (contentPart.mediaType.startsWith('image/')) {
|
|
372
|
+
return {
|
|
373
|
+
type: 'image' as const,
|
|
374
|
+
source: {
|
|
375
|
+
type: 'base64' as const,
|
|
376
|
+
media_type: contentPart.mediaType,
|
|
377
|
+
data: contentPart.data,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
341
381
|
if (contentPart.mediaType === 'application/pdf') {
|
|
342
382
|
betas.add('pdfs-2024-09-25');
|
|
343
383
|
return {
|
|
@@ -391,7 +431,8 @@ export async function convertToAnthropicMessagesPrompt({
|
|
|
391
431
|
contentValue = output.value;
|
|
392
432
|
break;
|
|
393
433
|
case 'execution-denied':
|
|
394
|
-
contentValue =
|
|
434
|
+
contentValue =
|
|
435
|
+
output.reason ?? 'Tool call execution denied.';
|
|
395
436
|
break;
|
|
396
437
|
case 'json':
|
|
397
438
|
case 'error-json':
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { JSONObject } from '@ai-sdk/provider';
|
|
2
|
-
import { AnthropicMessageMetadata } from './anthropic-message-metadata';
|
|
1
|
+
import type { JSONObject } from '@ai-sdk/provider';
|
|
2
|
+
import type { AnthropicMessageMetadata } from './anthropic-message-metadata';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Sets the Anthropic container ID in the provider options based on
|
package/src/get-cache-control.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
SharedV4Warning,
|
|
3
|
+
SharedV4ProviderMetadata,
|
|
4
|
+
} from '@ai-sdk/provider';
|
|
5
|
+
import type { AnthropicCacheControl } from './anthropic-api';
|
|
3
6
|
|
|
4
7
|
// Anthropic allows a maximum of 4 cache breakpoints per request
|
|
5
8
|
const MAX_CACHE_BREAKPOINTS = 4;
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type {
|
|
|
6
6
|
AnthropicLanguageModelOptions,
|
|
7
7
|
/** @deprecated Use `AnthropicLanguageModelOptions` instead. */
|
|
8
8
|
AnthropicLanguageModelOptions as AnthropicProviderOptions,
|
|
9
|
-
} from './anthropic-
|
|
9
|
+
} from './anthropic-options';
|
|
10
10
|
export type { AnthropicToolOptions } from './anthropic-prepare-tools';
|
|
11
11
|
export { anthropic, createAnthropic } from './anthropic-provider';
|
|
12
12
|
export type {
|
package/src/internal/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
AnthropicLanguageModel,
|
|
3
|
+
/** @deprecated Use `AnthropicLanguageModel` instead. */
|
|
4
|
+
AnthropicLanguageModel as AnthropicMessagesLanguageModel,
|
|
5
|
+
getModelCapabilities,
|
|
6
|
+
} from '../anthropic-language-model';
|
|
2
7
|
export { anthropicTools } from '../anthropic-tools';
|
|
3
|
-
export type {
|
|
8
|
+
export type {
|
|
9
|
+
AnthropicModelId,
|
|
10
|
+
/** @deprecated Use `AnthropicModelId` instead. */
|
|
11
|
+
AnthropicModelId as AnthropicMessagesModelId,
|
|
12
|
+
} from '../anthropic-options';
|
|
4
13
|
export { prepareTools } from '../anthropic-prepare-tools';
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { JSONSchema7, JSONSchema7Definition } from '@ai-sdk/provider';
|
|
2
|
+
|
|
3
|
+
const SUPPORTED_STRING_FORMATS = new Set([
|
|
4
|
+
'date-time',
|
|
5
|
+
'time',
|
|
6
|
+
'date',
|
|
7
|
+
'duration',
|
|
8
|
+
'email',
|
|
9
|
+
'hostname',
|
|
10
|
+
'uri',
|
|
11
|
+
'ipv4',
|
|
12
|
+
'ipv6',
|
|
13
|
+
'uuid',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const DESCRIPTION_CONSTRAINT_KEYS = [
|
|
17
|
+
'minimum',
|
|
18
|
+
'maximum',
|
|
19
|
+
'exclusiveMinimum',
|
|
20
|
+
'exclusiveMaximum',
|
|
21
|
+
'multipleOf',
|
|
22
|
+
'minLength',
|
|
23
|
+
'maxLength',
|
|
24
|
+
'pattern',
|
|
25
|
+
'minItems',
|
|
26
|
+
'maxItems',
|
|
27
|
+
'uniqueItems',
|
|
28
|
+
'minProperties',
|
|
29
|
+
'maxProperties',
|
|
30
|
+
'not',
|
|
31
|
+
] satisfies Array<keyof JSONSchema7>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Removes JSON Schema keywords that Anthropic rejects in
|
|
35
|
+
* `output_config.format.schema`.
|
|
36
|
+
*
|
|
37
|
+
* The full original schema is still used by AI SDK result validation. This
|
|
38
|
+
* only relaxes the schema sent to Anthropic's constrained decoder.
|
|
39
|
+
*/
|
|
40
|
+
export function sanitizeJsonSchema(schema: JSONSchema7): JSONSchema7 {
|
|
41
|
+
return sanitizeSchema(schema) as JSONSchema7;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sanitizeDefinition(
|
|
45
|
+
definition: JSONSchema7Definition,
|
|
46
|
+
): JSONSchema7Definition {
|
|
47
|
+
if (typeof definition === 'boolean' || !isPlainObject(definition)) {
|
|
48
|
+
return definition;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return sanitizeSchema(definition as JSONSchema7);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sanitizeSchema(schema: JSONSchema7): JSONSchema7 {
|
|
55
|
+
const result: JSONSchema7 = {};
|
|
56
|
+
const schemaWithDefs = schema as JSONSchema7 & {
|
|
57
|
+
$defs?: Record<string, JSONSchema7Definition>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (schema.$ref != null) {
|
|
61
|
+
return { $ref: schema.$ref };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (schema.$schema != null) {
|
|
65
|
+
result.$schema = schema.$schema;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (schema.$id != null) {
|
|
69
|
+
result.$id = schema.$id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (schema.title != null) {
|
|
73
|
+
result.title = schema.title;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (schema.description != null) {
|
|
77
|
+
result.description = schema.description;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (schema.default !== undefined) {
|
|
81
|
+
result.default = schema.default;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (schema.const !== undefined) {
|
|
85
|
+
result.const = schema.const;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (schema.enum != null) {
|
|
89
|
+
result.enum = schema.enum;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (schema.type != null) {
|
|
93
|
+
result.type = schema.type;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (schema.anyOf != null) {
|
|
97
|
+
result.anyOf = schema.anyOf.map(sanitizeDefinition);
|
|
98
|
+
} else if (schema.oneOf != null) {
|
|
99
|
+
result.anyOf = schema.oneOf.map(sanitizeDefinition);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (schema.allOf != null) {
|
|
103
|
+
result.allOf = schema.allOf.map(sanitizeDefinition);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (schema.definitions != null) {
|
|
107
|
+
result.definitions = Object.fromEntries(
|
|
108
|
+
Object.entries(schema.definitions).map(([name, definition]) => [
|
|
109
|
+
name,
|
|
110
|
+
sanitizeDefinition(definition),
|
|
111
|
+
]),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (schemaWithDefs.$defs != null) {
|
|
116
|
+
const resultWithDefs = result as JSONSchema7 & {
|
|
117
|
+
$defs?: Record<string, JSONSchema7Definition>;
|
|
118
|
+
};
|
|
119
|
+
resultWithDefs.$defs = Object.fromEntries(
|
|
120
|
+
Object.entries(schemaWithDefs.$defs).map(([name, definition]) => [
|
|
121
|
+
name,
|
|
122
|
+
sanitizeDefinition(definition),
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (schema.type === 'object' || schema.properties != null) {
|
|
128
|
+
if (schema.properties != null) {
|
|
129
|
+
result.properties = Object.fromEntries(
|
|
130
|
+
Object.entries(schema.properties).map(([name, definition]) => [
|
|
131
|
+
name,
|
|
132
|
+
sanitizeDefinition(definition),
|
|
133
|
+
]),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
result.additionalProperties = false;
|
|
138
|
+
|
|
139
|
+
if (schema.required != null) {
|
|
140
|
+
result.required = schema.required;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (schema.items != null) {
|
|
145
|
+
result.items = Array.isArray(schema.items)
|
|
146
|
+
? schema.items.map(sanitizeDefinition)
|
|
147
|
+
: sanitizeDefinition(schema.items);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
typeof schema.format === 'string' &&
|
|
152
|
+
SUPPORTED_STRING_FORMATS.has(schema.format)
|
|
153
|
+
) {
|
|
154
|
+
result.format = schema.format;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const constraintDescription = getConstraintDescription(schema);
|
|
158
|
+
if (constraintDescription != null) {
|
|
159
|
+
result.description =
|
|
160
|
+
result.description == null
|
|
161
|
+
? constraintDescription
|
|
162
|
+
: `${result.description}\n${constraintDescription}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getConstraintDescription(schema: JSONSchema7): string | undefined {
|
|
169
|
+
const descriptions = DESCRIPTION_CONSTRAINT_KEYS.flatMap(key => {
|
|
170
|
+
const value = schema[key];
|
|
171
|
+
|
|
172
|
+
if (value == null || value === false) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return `${formatConstraintName(key)}: ${formatConstraintValue(value)}`;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
typeof schema.format === 'string' &&
|
|
181
|
+
!SUPPORTED_STRING_FORMATS.has(schema.format)
|
|
182
|
+
) {
|
|
183
|
+
descriptions.push(`format: ${schema.format}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return descriptions.length === 0 ? undefined : `${descriptions.join('; ')}.`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function formatConstraintName(key: string): string {
|
|
190
|
+
return key.replace(/[A-Z]/g, match => ` ${match.toLowerCase()}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function formatConstraintValue(value: unknown): string {
|
|
194
|
+
if (typeof value === 'string') {
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return JSON.stringify(value);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
202
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
203
|
+
}
|