@ai-sdk/anthropic 4.0.92 → 4.0.94

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.
@@ -0,0 +1,138 @@
1
+ import type { JSONSchema7 } from '@ai-sdk/provider';
2
+ import { isRecord } from '@ai-sdk/provider-utils';
3
+ import {
4
+ hasCircularSchemaReference,
5
+ resolveSchemaPointer,
6
+ } from './has-circular-schema-reference';
7
+
8
+ // Anthropic rejects schemas with more than 24 optional properties in total,
9
+ // and grammar compilation fails earlier as optional properties add up: from 13
10
+ // on Bedrock and the Anthropic API, from 11 on Vertex (compilation timeout).
11
+ const MAX_OPTIONAL_PROPERTIES = 10;
12
+
13
+ const SUBSCHEMA_KEYWORDS = [
14
+ 'additionalProperties',
15
+ 'contains',
16
+ 'else',
17
+ 'if',
18
+ 'items',
19
+ 'not',
20
+ 'then',
21
+ ];
22
+ const SUBSCHEMA_LIST_KEYWORDS = ['allOf', 'anyOf', 'oneOf', 'prefixItems'];
23
+ const SUBSCHEMA_MAP_KEYWORDS = [
24
+ 'dependentSchemas',
25
+ 'patternProperties',
26
+ 'properties',
27
+ ];
28
+
29
+ type SchemaStats = { optionalProperties: number; hasOpenObject: boolean };
30
+
31
+ /**
32
+ * Whether Anthropic's native structured outputs (`output_config.format`) can
33
+ * represent a schema without rejecting it or losing data.
34
+ *
35
+ * Rejected: self-referencing schemas, and schemas with too many optional
36
+ * properties. Lossy: open objects (maps, free-form objects), which native
37
+ * structured outputs close with `additionalProperties: false`, so the model
38
+ * can only return `{}` for them.
39
+ */
40
+ export function supportsNativeStructuredOutputSchema(
41
+ schema: JSONSchema7,
42
+ ): boolean {
43
+ if (hasCircularSchemaReference(schema)) {
44
+ return false;
45
+ }
46
+
47
+ const { optionalProperties, hasOpenObject } = getSchemaStats(
48
+ schema,
49
+ schema,
50
+ new Map(),
51
+ );
52
+
53
+ return !hasOpenObject && optionalProperties <= MAX_OPTIONAL_PROPERTIES;
54
+ }
55
+
56
+ function getSchemaStats(
57
+ node: unknown,
58
+ root: JSONSchema7,
59
+ refStats: Map<string, SchemaStats>,
60
+ ): SchemaStats {
61
+ const stats: SchemaStats = { optionalProperties: 0, hasOpenObject: false };
62
+ if (!isRecord(node)) {
63
+ return stats;
64
+ }
65
+
66
+ const add = (child: unknown) => {
67
+ const childStats = getSchemaStats(child, root, refStats);
68
+ stats.optionalProperties += childStats.optionalProperties;
69
+ stats.hasOpenObject ||= childStats.hasOpenObject;
70
+ };
71
+
72
+ // references are counted at every use; the schema has no cycles here:
73
+ if (typeof node.$ref === 'string' && node.$ref.startsWith('#')) {
74
+ let resolved = refStats.get(node.$ref);
75
+ if (resolved == null) {
76
+ resolved = getSchemaStats(
77
+ resolveSchemaPointer(root, node.$ref),
78
+ root,
79
+ refStats,
80
+ );
81
+ refStats.set(node.$ref, resolved);
82
+ }
83
+ stats.optionalProperties += resolved.optionalProperties;
84
+ stats.hasOpenObject ||= resolved.hasOpenObject;
85
+ }
86
+
87
+ if (isOpenObject(node)) {
88
+ stats.hasOpenObject = true;
89
+ }
90
+
91
+ if (isRecord(node.properties)) {
92
+ const required = new Set(Array.isArray(node.required) ? node.required : []);
93
+ stats.optionalProperties += Object.keys(node.properties).filter(
94
+ name => !required.has(name),
95
+ ).length;
96
+ }
97
+
98
+ for (const keyword of SUBSCHEMA_KEYWORDS) {
99
+ const value = node[keyword];
100
+ if (Array.isArray(value)) {
101
+ value.forEach(add);
102
+ } else {
103
+ add(value);
104
+ }
105
+ }
106
+ for (const keyword of SUBSCHEMA_LIST_KEYWORDS) {
107
+ const value = node[keyword];
108
+ if (Array.isArray(value)) {
109
+ value.forEach(add);
110
+ }
111
+ }
112
+ for (const keyword of SUBSCHEMA_MAP_KEYWORDS) {
113
+ const value = node[keyword];
114
+ if (isRecord(value)) {
115
+ Object.values(value).forEach(add);
116
+ }
117
+ }
118
+
119
+ return stats;
120
+ }
121
+
122
+ function isOpenObject(node: Record<string, unknown>): boolean {
123
+ const { additionalProperties, patternProperties, properties, type } = node;
124
+
125
+ if (additionalProperties === true || isRecord(additionalProperties)) {
126
+ return true;
127
+ }
128
+ if (
129
+ isRecord(patternProperties) &&
130
+ Object.keys(patternProperties).length > 0
131
+ ) {
132
+ return true;
133
+ }
134
+
135
+ const isObjectType =
136
+ type === 'object' || (Array.isArray(type) && type.includes('object'));
137
+ return isObjectType && properties == null && additionalProperties !== false;
138
+ }