@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.
Files changed (48) hide show
  1. package/README.md +14 -0
  2. package/dist/auth/device.d.ts +3 -2
  3. package/dist/auth/device.d.ts.map +1 -1
  4. package/dist/auth/device.js +5 -2
  5. package/dist/auth/device.js.map +1 -1
  6. package/dist/auth/flows.d.ts +5 -1
  7. package/dist/auth/flows.d.ts.map +1 -1
  8. package/dist/auth/flows.js +237 -0
  9. package/dist/auth/flows.js.map +1 -1
  10. package/dist/auth/oauth.d.ts +4 -2
  11. package/dist/auth/oauth.d.ts.map +1 -1
  12. package/dist/auth/oauth.js +4 -3
  13. package/dist/auth/oauth.js.map +1 -1
  14. package/dist/auth/with-token.js +5 -3
  15. package/dist/auth/with-token.js.map +1 -1
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +3 -0
  18. package/dist/errors.js.map +1 -1
  19. package/dist/presets.d.ts +53 -0
  20. package/dist/presets.d.ts.map +1 -1
  21. package/dist/presets.js +100 -0
  22. package/dist/presets.js.map +1 -1
  23. package/dist/providers/factory.d.ts +16 -1
  24. package/dist/providers/factory.d.ts.map +1 -1
  25. package/dist/providers/factory.js +176 -18
  26. package/dist/providers/factory.js.map +1 -1
  27. package/dist/providers/gemini.d.ts +23 -5
  28. package/dist/providers/gemini.d.ts.map +1 -1
  29. package/dist/providers/gemini.js +247 -119
  30. package/dist/providers/gemini.js.map +1 -1
  31. package/dist/providers/openai.d.ts.map +1 -1
  32. package/dist/providers/openai.js +4 -3
  33. package/dist/providers/openai.js.map +1 -1
  34. package/dist/schema.d.ts +27 -9
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +197 -62
  37. package/dist/schema.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/auth/device.ts +8 -4
  40. package/src/auth/flows.ts +288 -1
  41. package/src/auth/oauth.ts +8 -3
  42. package/src/auth/with-token.ts +5 -3
  43. package/src/errors.ts +3 -0
  44. package/src/presets.ts +100 -0
  45. package/src/providers/factory.ts +233 -20
  46. package/src/providers/gemini.ts +280 -124
  47. package/src/providers/openai.ts +5 -3
  48. 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 (!node || typeof node !== "object" || Array.isArray(node)) return node;
166
- const { pattern: _pattern, ...schema } = node as Record<string, unknown>;
167
- for (const key of SCHEMA_SLOTS) {
168
- const value = schema[key];
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[key] = Array.isArray(value) ? value.map(withoutPatterns) : withoutPatterns(value);
185
+ schema[slot] = Array.isArray(value)
186
+ ? value.map((child) => visit(child, slot))
187
+ : visit(value, slot);
171
188
  }
172
189
  }
173
- for (const key of SCHEMA_LISTS) {
174
- const value = schema[key];
175
- if (Array.isArray(value)) schema[key] = value.map(withoutPatterns);
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 key of SCHEMA_MAPS) {
178
- const value = schema[key];
179
- if (value && typeof value === "object" && !Array.isArray(value)) {
180
- schema[key] = Object.fromEntries(
181
- Object.entries(value).map(([name, child]) => [name, withoutPatterns(child)]),
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
- * Sanitize a JSON Schema for Google Gemini's REST API.
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
- * Gemini's functionDeclarations schema parser only accepts a strict OpenAPI 3.0 subset.
192
- * It rejects:
193
- * - Nullable type arrays (`type: ["string", "null"]`), requiring `{ type: "string", nullable: true }`.
194
- * - Union types with null (`anyOf: [{ type: "string" }, { type: "null" }]`), requiring `{ type: "string", nullable: true }`.
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 || typeof node !== "object" || Array.isArray(node)) {
202
- return { type: "object", properties: {} };
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 schema = { ...(node as Record<string, unknown>) };
240
+ const cut = cutRefLoops(root, "#", false);
241
+ return isSchemaObject(cut) ? cut : root;
242
+ }
205
243
 
206
- delete schema.$schema;
207
- delete schema.$id;
208
- delete schema.$ref;
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.additionalProperties === true) {
214
- delete schema.additionalProperties;
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
- if ("const" in schema && !("enum" in schema)) {
218
- schema.enum = [String(schema.const)];
219
- delete schema.const;
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
- const union = schema.anyOf ?? schema.oneOf;
223
- if (Array.isArray(union)) {
224
- const nonNullBranches = union.filter(
225
- (b) => typeof b === "object" && b !== null && (b as Record<string, unknown>).type !== "null",
226
- );
227
- const hasNull = union.some(
228
- (b) => typeof b === "object" && b !== null && (b as Record<string, unknown>).type === "null",
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
- if (Array.isArray(schema.type)) {
238
- const types = schema.type as string[];
239
- const hasNull = types.includes("null");
240
- const nonNull = types.filter((t) => t !== "null");
241
- schema.type = nonNull[0] ?? "string";
242
- if (hasNull) schema.nullable = true;
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
- if (Array.isArray(schema.enum)) {
246
- schema.enum = schema.enum.map((val) => String(val));
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 (schema.type === "array" && !schema.items) {
250
- schema.items = { type: "string" };
251
- } else if (schema.items && typeof schema.items === "object") {
252
- schema.items = toGeminiToolSchema(schema.items);
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 (schema.properties && typeof schema.properties === "object") {
256
- const props = schema.properties as Record<string, unknown>;
257
- const sanitizedProps: Record<string, unknown> = {};
258
- for (const [key, val] of Object.entries(props)) {
259
- sanitizedProps[key] = toGeminiToolSchema(val);
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
- schema.properties = sanitizedProps;
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
- return schema;
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
  /**