@ai-sdk/anthropic 4.0.0-beta.39 → 4.0.0-beta.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/anthropic",
3
- "version": "4.0.0-beta.39",
3
+ "version": "4.0.0-beta.40",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -60,6 +60,7 @@ import {
60
60
  import { convertToAnthropicPrompt } from './convert-to-anthropic-prompt';
61
61
  import { CacheControlValidator } from './get-cache-control';
62
62
  import { mapAnthropicStopReason } from './map-anthropic-stop-reason';
63
+ import { sanitizeJsonSchema } from './sanitize-json-schema';
63
64
 
64
65
  function createCitationSource(
65
66
  citation: Citation,
@@ -468,7 +469,7 @@ export class AnthropicLanguageModel implements LanguageModelV4 {
468
469
  responseFormat.schema != null && {
469
470
  format: {
470
471
  type: 'json_schema',
471
- schema: responseFormat.schema,
472
+ schema: sanitizeJsonSchema(responseFormat.schema),
472
473
  },
473
474
  }),
474
475
  },
@@ -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
+ }