@f5-sales-demo/pi-ai 19.51.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/CHANGELOG.md +1997 -0
- package/README.md +1160 -0
- package/package.json +135 -0
- package/src/api-registry.ts +95 -0
- package/src/auth-storage.ts +2694 -0
- package/src/cli.ts +493 -0
- package/src/index.ts +42 -0
- package/src/model-cache.ts +97 -0
- package/src/model-manager.ts +349 -0
- package/src/model-thinking.ts +561 -0
- package/src/models.json +49439 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +81 -0
- package/src/provider-models/descriptors.ts +285 -0
- package/src/provider-models/google.ts +90 -0
- package/src/provider-models/index.ts +4 -0
- package/src/provider-models/openai-compat.ts +2074 -0
- package/src/provider-models/special.ts +106 -0
- package/src/providers/amazon-bedrock.ts +706 -0
- package/src/providers/anthropic.ts +1682 -0
- package/src/providers/azure-openai-responses.ts +391 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2218 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +381 -0
- package/src/providers/google-gemini-cli.ts +1133 -0
- package/src/providers/google-shared.ts +354 -0
- package/src/providers/google-vertex.ts +436 -0
- package/src/providers/google.ts +381 -0
- package/src/providers/kimi.ts +151 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +158 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +2345 -0
- package/src/providers/openai-completions-compat.ts +159 -0
- package/src/providers/openai-completions.ts +1290 -0
- package/src/providers/openai-responses-shared.ts +452 -0
- package/src/providers/openai-responses.ts +519 -0
- package/src/providers/register-builtins.ts +329 -0
- package/src/providers/synthetic.ts +154 -0
- package/src/providers/transform-messages.ts +234 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +728 -0
- package/src/types.ts +546 -0
- package/src/usage/claude.ts +337 -0
- package/src/usage/gemini.ts +248 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +200 -0
- package/src/usage/kimi.ts +286 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +387 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +130 -0
- package/src/utils/abort.ts +36 -0
- package/src/utils/anthropic-auth.ts +293 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +5 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +209 -0
- package/src/utils/http-inspector.ts +165 -0
- package/src/utils/idle-iterator.ts +176 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +134 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +59 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/github-copilot.ts +358 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +275 -0
- package/src/utils/oauth/google-gemini-cli.ts +334 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +512 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +251 -0
- package/src/utils/oauth/litellm.ts +81 -0
- package/src/utils/oauth/lm-studio.ts +40 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +59 -0
- package/src/utils/oauth/nanogpt.ts +51 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +190 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +200 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +60 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +59 -0
- package/src/utils/oauth/types.ts +89 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/xiaomi.ts +88 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +51 -0
- package/src/utils/overflow.ts +134 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +93 -0
- package/src/utils/schema/CONSTRAINTS.md +160 -0
- package/src/utils/schema/adapt.ts +20 -0
- package/src/utils/schema/compatibility.ts +397 -0
- package/src/utils/schema/dereference.ts +93 -0
- package/src/utils/schema/equality.ts +93 -0
- package/src/utils/schema/fields.ts +147 -0
- package/src/utils/schema/index.ts +9 -0
- package/src/utils/schema/normalize-cca.ts +479 -0
- package/src/utils/schema/sanitize-google.ts +212 -0
- package/src/utils/schema/strict-mode.ts +385 -0
- package/src/utils/schema/types.ts +5 -0
- package/src/utils/tool-choice.ts +81 -0
- package/src/utils/validation.ts +664 -0
- package/src/utils.ts +147 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { $flag } from "@f5xc-salesdemos/pi-utils";
|
|
2
|
+
import { type TUnsafe, Type } from "@sinclair/typebox";
|
|
3
|
+
import { areJsonValuesEqual } from "./equality";
|
|
4
|
+
import { COMBINATOR_KEYS, NON_STRUCTURAL_SCHEMA_KEYS } from "./fields";
|
|
5
|
+
import { isJsonObject } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a string enum schema compatible with Google's API and other providers
|
|
9
|
+
* that don't support anyOf/const patterns.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const OperationSchema = StringEnum(["add", "subtract", "multiply", "divide"], {
|
|
13
|
+
* description: "The operation to perform"
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* type Operation = Static<typeof OperationSchema>; // "add" | "subtract" | "multiply" | "divide"
|
|
17
|
+
*/
|
|
18
|
+
export function StringEnum<const T extends readonly string[]>(
|
|
19
|
+
values: T,
|
|
20
|
+
options?: { description?: string; default?: T[number] },
|
|
21
|
+
): TUnsafe<T[number]> {
|
|
22
|
+
return Type.Unsafe<T[number]>({
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: values as unknown as string[],
|
|
25
|
+
...(options?.description && { description: options.description }),
|
|
26
|
+
...(options?.default && { default: options.default }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const NO_STRICT = $flag("PI_NO_STRICT");
|
|
31
|
+
|
|
32
|
+
const strictSchemaCache = new WeakMap<Record<string, unknown>, { schema: Record<string, unknown>; strict: boolean }>();
|
|
33
|
+
function hasUnrepresentableStrictObjectMap(schema: Record<string, unknown>, seen?: WeakSet<object>): boolean {
|
|
34
|
+
if (!seen) seen = new WeakSet();
|
|
35
|
+
if (seen.has(schema)) return false;
|
|
36
|
+
seen.add(schema);
|
|
37
|
+
|
|
38
|
+
const hasPatternProperties =
|
|
39
|
+
isJsonObject(schema.patternProperties) && Object.keys(schema.patternProperties).length > 0;
|
|
40
|
+
const additionalPropertiesValue = schema.additionalProperties;
|
|
41
|
+
const hasSchemaAdditionalProperties = additionalPropertiesValue === true || isJsonObject(additionalPropertiesValue);
|
|
42
|
+
if (hasPatternProperties || hasSchemaAdditionalProperties) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (isJsonObject(schema.properties)) {
|
|
47
|
+
for (const propertySchema of Object.values(schema.properties)) {
|
|
48
|
+
if (isJsonObject(propertySchema) && hasUnrepresentableStrictObjectMap(propertySchema, seen)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isJsonObject(schema.items)) {
|
|
55
|
+
if (hasUnrepresentableStrictObjectMap(schema.items, seen)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
} else if (Array.isArray(schema.items)) {
|
|
59
|
+
for (const itemSchema of schema.items) {
|
|
60
|
+
if (isJsonObject(itemSchema) && hasUnrepresentableStrictObjectMap(itemSchema, seen)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const key of COMBINATOR_KEYS) {
|
|
67
|
+
const variants = schema[key];
|
|
68
|
+
if (!Array.isArray(variants)) continue;
|
|
69
|
+
for (const variant of variants) {
|
|
70
|
+
if (isJsonObject(variant) && hasUnrepresentableStrictObjectMap(variant, seen)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const defsKey of ["$defs", "definitions"] as const) {
|
|
77
|
+
const defs = schema[defsKey];
|
|
78
|
+
if (!isJsonObject(defs)) continue;
|
|
79
|
+
for (const defSchema of Object.values(defs)) {
|
|
80
|
+
if (isJsonObject(defSchema) && hasUnrepresentableStrictObjectMap(defSchema, seen)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
export function sanitizeSchemaForStrictMode(
|
|
89
|
+
schema: Record<string, unknown>,
|
|
90
|
+
seen?: WeakSet<object>,
|
|
91
|
+
cache?: WeakMap<Record<string, unknown>, Record<string, unknown>>,
|
|
92
|
+
): Record<string, unknown> {
|
|
93
|
+
if (!seen) seen = new WeakSet();
|
|
94
|
+
if (!cache) cache = new WeakMap();
|
|
95
|
+
const cached = cache.get(schema);
|
|
96
|
+
if (cached) return cached;
|
|
97
|
+
if (seen.has(schema)) return {};
|
|
98
|
+
seen.add(schema);
|
|
99
|
+
const typeValue = schema.type;
|
|
100
|
+
if (Array.isArray(typeValue)) {
|
|
101
|
+
const typeVariants = typeValue.filter((entry): entry is string => typeof entry === "string");
|
|
102
|
+
const schemaWithoutType = { ...schema };
|
|
103
|
+
delete schemaWithoutType.type;
|
|
104
|
+
|
|
105
|
+
const sanitizedWithoutType = sanitizeSchemaForStrictMode(schemaWithoutType, seen, cache);
|
|
106
|
+
if (typeVariants.length === 0) {
|
|
107
|
+
cache.set(schema, sanitizedWithoutType);
|
|
108
|
+
seen.delete(schema);
|
|
109
|
+
return sanitizedWithoutType;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const variants = typeVariants.map(variantType => {
|
|
113
|
+
const variantSchema: Record<string, unknown> = { ...sanitizedWithoutType, type: variantType };
|
|
114
|
+
if (variantType !== "object") {
|
|
115
|
+
delete variantSchema.properties;
|
|
116
|
+
delete variantSchema.required;
|
|
117
|
+
delete variantSchema.additionalProperties;
|
|
118
|
+
}
|
|
119
|
+
if (variantType !== "array") {
|
|
120
|
+
delete variantSchema.items;
|
|
121
|
+
}
|
|
122
|
+
return sanitizeSchemaForStrictMode(variantSchema, seen, cache);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (variants.length === 1) {
|
|
126
|
+
cache.set(schema, variants[0] as Record<string, unknown>);
|
|
127
|
+
seen.delete(schema);
|
|
128
|
+
return variants[0] as Record<string, unknown>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const result = {
|
|
132
|
+
anyOf: variants,
|
|
133
|
+
};
|
|
134
|
+
cache.set(schema, result);
|
|
135
|
+
seen.delete(schema);
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const sanitized: Record<string, unknown> = {};
|
|
140
|
+
cache.set(schema, sanitized);
|
|
141
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
142
|
+
if (NON_STRUCTURAL_SCHEMA_KEYS.has(key) || key === "type" || key === "const" || key === "nullable") {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (key === "properties" && isJsonObject(value)) {
|
|
147
|
+
const properties = Object.fromEntries(
|
|
148
|
+
Object.entries(value).map(([propertyName, propertySchema]) => [
|
|
149
|
+
propertyName,
|
|
150
|
+
isJsonObject(propertySchema) ? sanitizeSchemaForStrictMode(propertySchema, seen, cache) : propertySchema,
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
sanitized.properties = properties;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (key === "items") {
|
|
158
|
+
if (isJsonObject(value)) {
|
|
159
|
+
sanitized.items = sanitizeSchemaForStrictMode(value, seen, cache);
|
|
160
|
+
} else if (Array.isArray(value)) {
|
|
161
|
+
sanitized.items = value.map(entry =>
|
|
162
|
+
isJsonObject(entry) ? sanitizeSchemaForStrictMode(entry, seen, cache) : entry,
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
sanitized.items = value;
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (COMBINATOR_KEYS.includes(key as (typeof COMBINATOR_KEYS)[number]) && Array.isArray(value)) {
|
|
171
|
+
sanitized[key] = value.map(entry =>
|
|
172
|
+
isJsonObject(entry) ? sanitizeSchemaForStrictMode(entry, seen, cache) : entry,
|
|
173
|
+
);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if ((key === "$defs" || key === "definitions") && isJsonObject(value)) {
|
|
178
|
+
sanitized[key] = Object.fromEntries(
|
|
179
|
+
Object.entries(value).map(([definitionName, definitionSchema]) => [
|
|
180
|
+
definitionName,
|
|
181
|
+
isJsonObject(definitionSchema)
|
|
182
|
+
? sanitizeSchemaForStrictMode(definitionSchema, seen, cache)
|
|
183
|
+
: definitionSchema,
|
|
184
|
+
]),
|
|
185
|
+
);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (key === "additionalProperties") {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
sanitized[key] = value;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (Object.hasOwn(schema, "const")) {
|
|
197
|
+
const constVal = schema.const;
|
|
198
|
+
const existingEnum = Array.isArray(sanitized.enum) ? sanitized.enum : [];
|
|
199
|
+
if (!existingEnum.some(v => areJsonValuesEqual(v, constVal))) {
|
|
200
|
+
existingEnum.push(constVal);
|
|
201
|
+
}
|
|
202
|
+
sanitized.enum = existingEnum;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof typeValue === "string") {
|
|
206
|
+
sanitized.type = typeValue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (sanitized.type === undefined && isJsonObject(sanitized.properties)) {
|
|
210
|
+
sanitized.type = "object";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (sanitized.type === undefined && sanitized.items !== undefined) {
|
|
214
|
+
sanitized.type = "array";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (sanitized.type === undefined && Array.isArray(sanitized.enum)) {
|
|
218
|
+
const enumTypes = new Set(
|
|
219
|
+
sanitized.enum
|
|
220
|
+
.map(v =>
|
|
221
|
+
v === null
|
|
222
|
+
? "null"
|
|
223
|
+
: typeof v === "string"
|
|
224
|
+
? "string"
|
|
225
|
+
: typeof v === "number"
|
|
226
|
+
? "number"
|
|
227
|
+
: typeof v === "boolean"
|
|
228
|
+
? "boolean"
|
|
229
|
+
: undefined,
|
|
230
|
+
)
|
|
231
|
+
.filter((t): t is "null" | "string" | "number" | "boolean" => t !== undefined),
|
|
232
|
+
);
|
|
233
|
+
if (enumTypes.size === 1) {
|
|
234
|
+
sanitized.type = [...enumTypes][0];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (schema.nullable === true) {
|
|
239
|
+
const { nullable: _, ...withoutNullable } = sanitized;
|
|
240
|
+
seen.delete(schema);
|
|
241
|
+
return { anyOf: [withoutNullable, { type: "null" }] };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
seen.delete(schema);
|
|
245
|
+
return sanitized;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Recursively enforces JSON Schema constraints required by OpenAI/Codex strict mode:
|
|
250
|
+
* - `additionalProperties: false` on every object node
|
|
251
|
+
* - every key in `properties` present in `required`
|
|
252
|
+
*
|
|
253
|
+
* Properties absent from the original `required` array were TypeBox-optional.
|
|
254
|
+
* They are made nullable (`anyOf: [T, { type: "null" }]`) so the model can
|
|
255
|
+
* signal omission by outputting null rather than omitting the key entirely.
|
|
256
|
+
*
|
|
257
|
+
* @throws {Error} When a schema node has no `type`, array-based combinator
|
|
258
|
+
* (`anyOf`/`allOf`/`oneOf`), object-based combinator (`not`), or `$ref` —
|
|
259
|
+
* i.e. the node is not representable in strict mode. Prefer
|
|
260
|
+
* {@link tryEnforceStrictSchema} which catches this and degrades gracefully.
|
|
261
|
+
*/
|
|
262
|
+
export function enforceStrictSchema(
|
|
263
|
+
schema: Record<string, unknown>,
|
|
264
|
+
seen?: WeakSet<object>,
|
|
265
|
+
cache?: WeakMap<Record<string, unknown>, Record<string, unknown>>,
|
|
266
|
+
): Record<string, unknown> {
|
|
267
|
+
if (!seen) seen = new WeakSet();
|
|
268
|
+
if (!cache) cache = new WeakMap();
|
|
269
|
+
if (seen.has(schema)) {
|
|
270
|
+
throw new Error("Schema contains a circular object graph — cannot enforce strict mode");
|
|
271
|
+
}
|
|
272
|
+
const cached = cache.get(schema);
|
|
273
|
+
if (cached) {
|
|
274
|
+
return cached;
|
|
275
|
+
}
|
|
276
|
+
seen.add(schema);
|
|
277
|
+
const result = { ...schema };
|
|
278
|
+
cache.set(schema, result);
|
|
279
|
+
const isObjectType = result.type === "object";
|
|
280
|
+
if (isObjectType) {
|
|
281
|
+
result.additionalProperties = false;
|
|
282
|
+
const propertiesValue = result.properties;
|
|
283
|
+
const props =
|
|
284
|
+
propertiesValue != null && typeof propertiesValue === "object" && !Array.isArray(propertiesValue)
|
|
285
|
+
? (propertiesValue as Record<string, unknown>)
|
|
286
|
+
: {};
|
|
287
|
+
const originalRequired = new Set(
|
|
288
|
+
Array.isArray(result.required)
|
|
289
|
+
? result.required.filter((value): value is string => typeof value === "string")
|
|
290
|
+
: [],
|
|
291
|
+
);
|
|
292
|
+
const strictProperties = Object.fromEntries(
|
|
293
|
+
Object.entries(props).map(([key, value]) => {
|
|
294
|
+
const processed =
|
|
295
|
+
value != null && typeof value === "object" && !Array.isArray(value)
|
|
296
|
+
? enforceStrictSchema(value as Record<string, unknown>, seen, cache)
|
|
297
|
+
: value;
|
|
298
|
+
// Optional property — wrap as nullable so strict mode accepts it
|
|
299
|
+
if (!originalRequired.has(key)) {
|
|
300
|
+
// Don't double-wrap if already nullable
|
|
301
|
+
if (
|
|
302
|
+
isJsonObject(processed) &&
|
|
303
|
+
Array.isArray(processed.anyOf) &&
|
|
304
|
+
processed.anyOf.some(v => isJsonObject(v) && v.type === "null")
|
|
305
|
+
) {
|
|
306
|
+
return [key, processed];
|
|
307
|
+
}
|
|
308
|
+
return [key, { anyOf: [processed, { type: "null" }] }];
|
|
309
|
+
}
|
|
310
|
+
return [key, processed];
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
result.properties = strictProperties;
|
|
314
|
+
result.required = Object.keys(strictProperties);
|
|
315
|
+
}
|
|
316
|
+
if (result.items != null && typeof result.items === "object") {
|
|
317
|
+
if (Array.isArray(result.items)) {
|
|
318
|
+
result.items = result.items.map(entry =>
|
|
319
|
+
entry != null && typeof entry === "object" && !Array.isArray(entry)
|
|
320
|
+
? enforceStrictSchema(entry as Record<string, unknown>, seen, cache)
|
|
321
|
+
: entry,
|
|
322
|
+
);
|
|
323
|
+
} else {
|
|
324
|
+
result.items = enforceStrictSchema(result.items as Record<string, unknown>, seen, cache);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const key of COMBINATOR_KEYS) {
|
|
328
|
+
if (Array.isArray(result[key])) {
|
|
329
|
+
result[key] = (result[key] as unknown[]).map(entry =>
|
|
330
|
+
entry != null && typeof entry === "object" && !Array.isArray(entry)
|
|
331
|
+
? enforceStrictSchema(entry as Record<string, unknown>, seen, cache)
|
|
332
|
+
: entry,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
for (const defsKey of ["$defs", "definitions"] as const) {
|
|
337
|
+
if (result[defsKey] != null && typeof result[defsKey] === "object" && !Array.isArray(result[defsKey])) {
|
|
338
|
+
const defs = result[defsKey] as Record<string, unknown>;
|
|
339
|
+
result[defsKey] = Object.fromEntries(
|
|
340
|
+
Object.entries(defs).map(([name, def]) => [
|
|
341
|
+
name,
|
|
342
|
+
def != null && typeof def === "object" && !Array.isArray(def)
|
|
343
|
+
? enforceStrictSchema(def as Record<string, unknown>, seen, cache)
|
|
344
|
+
: def,
|
|
345
|
+
]),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Strict mode requires every schema node to declare a concrete type (or combinator/$ref).
|
|
350
|
+
// Schemas like `{}` (match anything) or `{items: {}}` are not representable in strict mode.
|
|
351
|
+
if (
|
|
352
|
+
result.type === undefined &&
|
|
353
|
+
result.$ref === undefined &&
|
|
354
|
+
!COMBINATOR_KEYS.some(key => Array.isArray(result[key])) &&
|
|
355
|
+
!isJsonObject(result.not)
|
|
356
|
+
) {
|
|
357
|
+
throw new Error("Schema node has no type, combinator, or $ref — cannot enforce strict mode");
|
|
358
|
+
}
|
|
359
|
+
seen.delete(schema);
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function tryEnforceStrictSchema(schema: Record<string, unknown>): {
|
|
364
|
+
schema: Record<string, unknown>;
|
|
365
|
+
strict: boolean;
|
|
366
|
+
} {
|
|
367
|
+
const cached = strictSchemaCache.get(schema);
|
|
368
|
+
if (cached) {
|
|
369
|
+
return cached;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
if (hasUnrepresentableStrictObjectMap(schema)) {
|
|
374
|
+
throw new Error("Schema uses dynamic object keys that are not representable in strict mode");
|
|
375
|
+
}
|
|
376
|
+
const sanitized = sanitizeSchemaForStrictMode(schema);
|
|
377
|
+
const result = { schema: enforceStrictSchema(sanitized), strict: true };
|
|
378
|
+
strictSchemaCache.set(schema, result);
|
|
379
|
+
return result;
|
|
380
|
+
} catch {
|
|
381
|
+
const result = { schema, strict: false };
|
|
382
|
+
strictSchemaCache.set(schema, result);
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for mapping unified ToolChoice to provider-specific formats.
|
|
3
|
+
*/
|
|
4
|
+
import type { ToolChoice } from "../types";
|
|
5
|
+
|
|
6
|
+
/** OpenAI Completions API tool choice format */
|
|
7
|
+
export type OpenAICompletionsToolChoice =
|
|
8
|
+
| "auto"
|
|
9
|
+
| "none"
|
|
10
|
+
| "required"
|
|
11
|
+
| { type: "function"; function: { name: string } }
|
|
12
|
+
| undefined;
|
|
13
|
+
|
|
14
|
+
/** OpenAI Responses API tool choice format (flat structure) */
|
|
15
|
+
export type OpenAIResponsesToolChoice = "auto" | "none" | "required" | { type: "function"; name: string } | undefined;
|
|
16
|
+
|
|
17
|
+
/** Anthropic-compatible tool choice format */
|
|
18
|
+
export type AnthropicToolChoice = "auto" | "none" | "any" | { type: "tool"; name: string } | undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract function name from unified ToolChoice.
|
|
22
|
+
*/
|
|
23
|
+
function extractFunctionName(choice: ToolChoice): string | undefined {
|
|
24
|
+
if (typeof choice === "string") return undefined;
|
|
25
|
+
if (choice.type === "tool" && "name" in choice) return choice.name;
|
|
26
|
+
if (choice.type === "function") {
|
|
27
|
+
if ("function" in choice && choice.function && typeof choice.function === "object") {
|
|
28
|
+
return (choice.function as { name?: string }).name;
|
|
29
|
+
}
|
|
30
|
+
if ("name" in choice) return choice.name;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Map unified ToolChoice to OpenAI Completions API format.
|
|
37
|
+
* - "any" → "required"
|
|
38
|
+
* - { type: "tool", name } → { type: "function", function: { name } }
|
|
39
|
+
*/
|
|
40
|
+
export function mapToOpenAICompletionsToolChoice(choice?: ToolChoice): OpenAICompletionsToolChoice {
|
|
41
|
+
if (!choice) return undefined;
|
|
42
|
+
if (typeof choice === "string") {
|
|
43
|
+
if (choice === "any") return "required";
|
|
44
|
+
if (choice === "auto" || choice === "none" || choice === "required") return choice;
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const name = extractFunctionName(choice);
|
|
48
|
+
return name ? { type: "function", function: { name } } : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Map unified ToolChoice to OpenAI Responses API format.
|
|
53
|
+
* - "any" → "required"
|
|
54
|
+
* - { type: "tool", name } → { type: "function", name } (flat structure)
|
|
55
|
+
*/
|
|
56
|
+
export function mapToOpenAIResponsesToolChoice(choice?: ToolChoice): OpenAIResponsesToolChoice {
|
|
57
|
+
if (!choice) return undefined;
|
|
58
|
+
if (typeof choice === "string") {
|
|
59
|
+
if (choice === "any") return "required";
|
|
60
|
+
if (choice === "auto" || choice === "none" || choice === "required") return choice;
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const name = extractFunctionName(choice);
|
|
64
|
+
return name ? { type: "function", name } : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Map unified ToolChoice to Anthropic-compatible format.
|
|
69
|
+
* - "required" → "any"
|
|
70
|
+
* - { type: "function", ... } → { type: "tool", name }
|
|
71
|
+
*/
|
|
72
|
+
export function mapToAnthropicToolChoice(choice?: ToolChoice): AnthropicToolChoice {
|
|
73
|
+
if (!choice) return undefined;
|
|
74
|
+
if (typeof choice === "string") {
|
|
75
|
+
if (choice === "required") return "any";
|
|
76
|
+
if (choice === "auto" || choice === "none" || choice === "any") return choice;
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const name = extractFunctionName(choice);
|
|
80
|
+
return name ? { type: "tool", name } : undefined;
|
|
81
|
+
}
|