@providerkit/core 0.24.0 → 0.25.1
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 +14 -0
- package/dist/auth/device.d.ts +3 -2
- package/dist/auth/device.d.ts.map +1 -1
- package/dist/auth/device.js +5 -2
- package/dist/auth/device.js.map +1 -1
- package/dist/auth/flows.d.ts +5 -1
- package/dist/auth/flows.d.ts.map +1 -1
- package/dist/auth/flows.js +237 -0
- package/dist/auth/flows.js.map +1 -1
- package/dist/auth/oauth.d.ts +4 -2
- package/dist/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js +4 -3
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/with-token.js +5 -3
- package/dist/auth/with-token.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -0
- package/dist/errors.js.map +1 -1
- package/dist/presets.d.ts +53 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +100 -0
- package/dist/presets.js.map +1 -1
- package/dist/providers/factory.d.ts +16 -1
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +176 -18
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/gemini.d.ts +23 -5
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +247 -119
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +4 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/schema.d.ts +27 -9
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +197 -62
- package/dist/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/device.ts +8 -4
- package/src/auth/flows.ts +288 -1
- package/src/auth/oauth.ts +8 -3
- package/src/auth/with-token.ts +5 -3
- package/src/errors.ts +3 -0
- package/src/presets.ts +100 -0
- package/src/providers/factory.ts +233 -20
- package/src/providers/gemini.ts +280 -124
- package/src/providers/openai.ts +5 -3
- package/src/schema.ts +200 -72
package/src/schema.ts
CHANGED
|
@@ -162,106 +162,234 @@ const SCHEMA_MAPS = ["properties", "patternProperties", "$defs", "definitions",
|
|
|
162
162
|
* validation the caller's tool runner repeats anyway; the 400 costs the turn.
|
|
163
163
|
*/
|
|
164
164
|
export function withoutPatterns(node: unknown): unknown {
|
|
165
|
-
if (!
|
|
166
|
-
const { pattern: _pattern, ...schema } = node
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
if (!isSchemaObject(node)) return node;
|
|
166
|
+
const { pattern: _pattern, ...schema } = node;
|
|
167
|
+
mapChildren(schema, withoutPatterns);
|
|
168
|
+
return schema;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isSchemaObject(node: unknown): node is Record<string, unknown> {
|
|
172
|
+
return typeof node === "object" && node !== null && !Array.isArray(node);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Replaces every child schema of `schema`, in place, with what `visit` returns for it. `slot`
|
|
176
|
+
* is the keyword it sits under and `name` is its property name, for the keywords that map
|
|
177
|
+
* names to schemas. */
|
|
178
|
+
function mapChildren(
|
|
179
|
+
schema: Record<string, unknown>,
|
|
180
|
+
visit: (child: unknown, slot: string, name?: string) => unknown,
|
|
181
|
+
): void {
|
|
182
|
+
for (const slot of SCHEMA_SLOTS) {
|
|
183
|
+
const value = schema[slot];
|
|
169
184
|
if (value !== undefined) {
|
|
170
|
-
schema[
|
|
185
|
+
schema[slot] = Array.isArray(value)
|
|
186
|
+
? value.map((child) => visit(child, slot))
|
|
187
|
+
: visit(value, slot);
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
|
-
for (const
|
|
174
|
-
const value = schema[
|
|
175
|
-
if (Array.isArray(value)) schema[
|
|
190
|
+
for (const slot of SCHEMA_LISTS) {
|
|
191
|
+
const value = schema[slot];
|
|
192
|
+
if (Array.isArray(value)) schema[slot] = value.map((child) => visit(child, slot));
|
|
176
193
|
}
|
|
177
|
-
for (const
|
|
178
|
-
const value = schema[
|
|
179
|
-
if (
|
|
180
|
-
schema[
|
|
181
|
-
Object.entries(value).map(([name, child]) => [name,
|
|
194
|
+
for (const slot of SCHEMA_MAPS) {
|
|
195
|
+
const value = schema[slot];
|
|
196
|
+
if (isSchemaObject(value)) {
|
|
197
|
+
schema[slot] = Object.fromEntries(
|
|
198
|
+
Object.entries(value).map(([name, child]) => [name, visit(child, slot, name)]),
|
|
182
199
|
);
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
|
-
return schema;
|
|
186
202
|
}
|
|
187
203
|
|
|
188
204
|
/**
|
|
189
|
-
*
|
|
205
|
+
* A JSON Schema for Gemini's `parametersJsonSchema` and `responseJsonSchema`, which take
|
|
206
|
+
* standard JSON Schema. It is sent as written except for the four shapes the API refuses.
|
|
207
|
+
* Measured on `gemini-3.8-flash` on 2026-10-21, in a tool and as a response schema alike;
|
|
208
|
+
* every other keyword tried (`$ref`, `$defs`, `oneOf`, `allOf`, `not`, `if`, `const`, numeric
|
|
209
|
+
* `enum`, `type: [..., "null"]`, `patternProperties`, `additionalProperties`, arrays without
|
|
210
|
+
* `items`) came back accepted:
|
|
211
|
+
*
|
|
212
|
+
* - `required` names a property that is not declared, at any depth: 400 "requires unspecified
|
|
213
|
+
* property". The names with no property are dropped.
|
|
214
|
+
* - Draft-04 `exclusiveMinimum: true` (or `false`): 400 "must be a number". It becomes the
|
|
215
|
+
* number form, `exclusiveMinimum: <minimum>`, and `minimum` goes.
|
|
216
|
+
* - A tuple, `items: [a, b]`: 400 "must be a boolean or an object". It becomes `prefixItems`,
|
|
217
|
+
* and `additionalItems` becomes `items`, as in 2020-12.
|
|
218
|
+
* - A recursive `$ref` that is not behind an optional property or an array that may be empty:
|
|
219
|
+
* 400 "ref loops are only supported if ...". The self-reference becomes `{}`.
|
|
190
220
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* - Numeric enums (`enum: [1, 2, 3]`), requiring string enums.
|
|
196
|
-
* - `const: "val"`, requiring `enum: ["val"]`.
|
|
197
|
-
* - Arrays without `items`, requiring an explicit items schema.
|
|
198
|
-
* - Unsupported keywords ($schema, definitions, $defs, patternProperties, additionalProperties: true).
|
|
221
|
+
* This replaced a sanitizer written for the older `parameters` field, which accepts a small
|
|
222
|
+
* OpenAPI subset. Kept on `parametersJsonSchema`, it turned a numeric enum into a string enum
|
|
223
|
+
* (the model then sent `"2"` for `2`), forced string items onto an array of anything, and
|
|
224
|
+
* dropped every `$ref`, while still sending the 400s above.
|
|
199
225
|
*/
|
|
200
226
|
export function toGeminiToolSchema(node: unknown): Record<string, unknown> {
|
|
201
|
-
if (!node
|
|
202
|
-
|
|
227
|
+
if (!isSchemaObject(node)) return { type: "object", properties: {} };
|
|
228
|
+
const root = geminiStandardForm(node);
|
|
229
|
+
for (const defs of ["$defs", "definitions"]) {
|
|
230
|
+
const entries = root[defs];
|
|
231
|
+
if (isSchemaObject(entries)) {
|
|
232
|
+
root[defs] = Object.fromEntries(
|
|
233
|
+
Object.entries(entries).map(([name, def]) => [
|
|
234
|
+
name,
|
|
235
|
+
cutRefLoops(def, `#/${defs}/${name}`, false),
|
|
236
|
+
]),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
203
239
|
}
|
|
204
|
-
const
|
|
240
|
+
const cut = cutRefLoops(root, "#", false);
|
|
241
|
+
return isSchemaObject(cut) ? cut : root;
|
|
242
|
+
}
|
|
205
243
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
delete schema.definitions;
|
|
210
|
-
delete schema.$defs;
|
|
211
|
-
delete schema.patternProperties;
|
|
244
|
+
function geminiStandardForm(node: Record<string, unknown>): Record<string, unknown> {
|
|
245
|
+
const schema = { ...node };
|
|
246
|
+
mapChildren(schema, (child) => (isSchemaObject(child) ? geminiStandardForm(child) : child));
|
|
212
247
|
|
|
213
|
-
if (schema.
|
|
214
|
-
|
|
248
|
+
if (Array.isArray(schema.required)) {
|
|
249
|
+
const declared = isSchemaObject(schema.properties) ? schema.properties : {};
|
|
250
|
+
schema.required = schema.required.filter(
|
|
251
|
+
(name) => typeof name === "string" && Object.hasOwn(declared, name),
|
|
252
|
+
);
|
|
215
253
|
}
|
|
216
254
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
255
|
+
for (const [flag, bound] of [
|
|
256
|
+
["exclusiveMinimum", "minimum"],
|
|
257
|
+
["exclusiveMaximum", "maximum"],
|
|
258
|
+
] as const) {
|
|
259
|
+
if (typeof schema[flag] !== "boolean") continue;
|
|
260
|
+
if (schema[flag] && typeof schema[bound] === "number") {
|
|
261
|
+
schema[flag] = schema[bound];
|
|
262
|
+
delete schema[bound];
|
|
263
|
+
} else {
|
|
264
|
+
delete schema[flag];
|
|
265
|
+
}
|
|
220
266
|
}
|
|
221
267
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
);
|
|
230
|
-
if (nonNullBranches.length === 1 && typeof nonNullBranches[0] === "object") {
|
|
231
|
-
const merged = toGeminiToolSchema(nonNullBranches[0]);
|
|
232
|
-
if (hasNull) merged.nullable = true;
|
|
233
|
-
return merged;
|
|
268
|
+
if (Array.isArray(schema.items)) {
|
|
269
|
+
schema.prefixItems = schema.items;
|
|
270
|
+
delete schema.items;
|
|
271
|
+
if ("additionalItems" in schema) {
|
|
272
|
+
const extra = schema.additionalItems;
|
|
273
|
+
schema.items = isSchemaObject(extra) ? geminiStandardForm(extra) : extra;
|
|
274
|
+
delete schema.additionalItems;
|
|
234
275
|
}
|
|
235
276
|
}
|
|
277
|
+
return schema;
|
|
278
|
+
}
|
|
236
279
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Gemini takes a recursive `$ref` only when the loop passes through a property that is not
|
|
282
|
+
* required or an `items` that may be empty. Any other reference to `target` becomes `{}`.
|
|
283
|
+
*
|
|
284
|
+
* ponytail: this cuts a definition's reference to ITSELF. Two definitions that refer to each
|
|
285
|
+
* other through required properties still get the 400; follow the ref graph if one shows up.
|
|
286
|
+
*/
|
|
287
|
+
function cutRefLoops(node: unknown, target: string, safe: boolean): unknown {
|
|
288
|
+
if (!isSchemaObject(node)) return node;
|
|
289
|
+
if (!safe && node.$ref === target) return {};
|
|
290
|
+
const schema = { ...node };
|
|
291
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
292
|
+
const mayBeEmpty = !(typeof schema.minItems === "number" && schema.minItems > 0);
|
|
293
|
+
mapChildren(schema, (child, slot, name) =>
|
|
294
|
+
cutRefLoops(
|
|
295
|
+
child,
|
|
296
|
+
target,
|
|
297
|
+
safe ||
|
|
298
|
+
(slot === "items" && mayBeEmpty) ||
|
|
299
|
+
(slot === "properties" && !required.includes(name)),
|
|
300
|
+
),
|
|
301
|
+
);
|
|
302
|
+
return schema;
|
|
303
|
+
}
|
|
244
304
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Sanitize a JSON Schema to the OpenAPI 3.0 subset Google's Cloud Code Assist
|
|
307
|
+
* (`antigravity`) accepts in its `parameters` field (`type`, `nullable`,
|
|
308
|
+
* `description`, `format`, `enum`, `properties`, `items`, `required`, `anyOf`).
|
|
309
|
+
* Mirrors cc-proxy's `clean_json_schema`, including the `{ type: "string" }`
|
|
310
|
+
* fallback when an array omits `items`.
|
|
311
|
+
*/
|
|
312
|
+
export function toAntigravityToolSchema(node: unknown): Record<string, unknown> {
|
|
313
|
+
if (!isSchemaObject(node)) return { type: "object" };
|
|
314
|
+
const out: Record<string, unknown> = {};
|
|
315
|
+
let nullable = node.nullable === true;
|
|
248
316
|
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
317
|
+
if (typeof node.type === "string") {
|
|
318
|
+
if (node.type === "null") nullable = true;
|
|
319
|
+
else out.type = node.type;
|
|
320
|
+
} else if (Array.isArray(node.type)) {
|
|
321
|
+
let picked: string | undefined;
|
|
322
|
+
for (const item of node.type) {
|
|
323
|
+
if (typeof item !== "string") continue;
|
|
324
|
+
if (item === "null") nullable = true;
|
|
325
|
+
else if (picked === undefined) picked = item;
|
|
326
|
+
}
|
|
327
|
+
if (picked !== undefined) out.type = picked;
|
|
253
328
|
}
|
|
254
|
-
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
329
|
+
if (nullable) out.nullable = true;
|
|
330
|
+
if (typeof node.description === "string" && node.description.length > 0) {
|
|
331
|
+
out.description = node.description;
|
|
332
|
+
}
|
|
333
|
+
if (node.format === "enum" || node.format === "date-time") {
|
|
334
|
+
out.format = node.format;
|
|
335
|
+
}
|
|
336
|
+
if (Array.isArray(node.enum)) {
|
|
337
|
+
const strings = node.enum.filter((v): v is string => typeof v === "string");
|
|
338
|
+
if (strings.length > 0) out.enum = strings;
|
|
339
|
+
} else if (typeof node.const === "string") {
|
|
340
|
+
out.enum = [node.const];
|
|
341
|
+
}
|
|
342
|
+
if (isSchemaObject(node.properties)) {
|
|
343
|
+
const cleanProps: Record<string, unknown> = {};
|
|
344
|
+
for (const [propName, propSchema] of Object.entries(node.properties)) {
|
|
345
|
+
cleanProps[propName] = toAntigravityToolSchema(propSchema);
|
|
260
346
|
}
|
|
261
|
-
|
|
347
|
+
if (Array.isArray(node.required)) {
|
|
348
|
+
const validReq = node.required.filter(
|
|
349
|
+
(k): k is string => typeof k === "string" && Object.hasOwn(cleanProps, k),
|
|
350
|
+
);
|
|
351
|
+
if (validReq.length > 0) out.required = validReq;
|
|
352
|
+
}
|
|
353
|
+
out.properties = cleanProps;
|
|
354
|
+
if (!("type" in out)) out.type = "object";
|
|
262
355
|
}
|
|
263
|
-
|
|
264
|
-
|
|
356
|
+
if ("items" in node && node.items !== undefined) {
|
|
357
|
+
out.items = Array.isArray(node.items)
|
|
358
|
+
? node.items.length > 0
|
|
359
|
+
? toAntigravityToolSchema(node.items[0])
|
|
360
|
+
: { type: "string" }
|
|
361
|
+
: toAntigravityToolSchema(node.items);
|
|
362
|
+
if (!("type" in out)) out.type = "array";
|
|
363
|
+
}
|
|
364
|
+
const union = Array.isArray(node.anyOf)
|
|
365
|
+
? node.anyOf
|
|
366
|
+
: Array.isArray(node.oneOf)
|
|
367
|
+
? node.oneOf
|
|
368
|
+
: undefined;
|
|
369
|
+
if (union) {
|
|
370
|
+
const branches: Record<string, unknown>[] = [];
|
|
371
|
+
for (const branch of union) {
|
|
372
|
+
if (isSchemaObject(branch) && branch.type === "null") {
|
|
373
|
+
out.nullable = true;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
branches.push(toAntigravityToolSchema(branch));
|
|
377
|
+
}
|
|
378
|
+
if (branches.length === 1) {
|
|
379
|
+
for (const [k, v] of Object.entries(branches[0]!)) {
|
|
380
|
+
if (!(k in out)) out[k] = v;
|
|
381
|
+
}
|
|
382
|
+
} else if (branches.length > 0) {
|
|
383
|
+
out.anyOf = branches;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (!("type" in out) && !("anyOf" in out)) {
|
|
387
|
+
out.type = "string";
|
|
388
|
+
}
|
|
389
|
+
if (out.type === "array" && !("items" in out)) {
|
|
390
|
+
out.items = { type: "string" };
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
265
393
|
}
|
|
266
394
|
|
|
267
395
|
/**
|