@gullabs/google 0.10.0 → 0.11.0

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 CHANGED
@@ -22,10 +22,8 @@ pnpm add @gullabs/google @gullabs/core @google/genai
22
22
  | `isGeminiCapacityError(err)` | Detects Gemini Flex shared-capacity errors for built-in fallback |
23
23
  | `geminiModelDescriptors`, `gemmaModelDescriptors`, `defaultGeminiRegistry` | Built-in model descriptors + pre-built registry |
24
24
  | `geminiPricingSource()`, `GEMINI_PRICING`, `TIER_FACTOR` | Built-in Gemini pricing snapshot and tier-factor map |
25
-
26
-
27
- | `GoogleFileStore` | Files API: upload + poll ACTIVE + delete |
28
- | `FileDeleteOptions` | `{ failClosed?, signal? }` — opt-in fail-closed delete (parity with `@gullabs/xai`) |
25
+ | `GoogleFileStore` | Files API: upload + poll ACTIVE + delete |
26
+ | `FileDeleteOptions` | `{ failClosed?, signal? }` — opt-in fail-closed delete (parity with `@gullabs/xai`) |
29
27
 
30
28
  ## File store delete modes
31
29
 
@@ -67,7 +65,7 @@ const result = await client.generate(
67
65
  - `output.jsonSchema` → `responseMimeType: 'application/json'` + verbatim `responseSchema` when native structured output is enabled; the engine returns parsed output and `outputParsed` without validating shape
68
66
  - `providerOptions.google.*` → typed provider-extension lane for admitted keys such as `cachedContent`, `safetySettings`, and exact tool declarations
69
67
  - Usage: `promptTokenCount`→`inputTokens`, `candidatesTokenCount`+`thoughtsTokenCount`→`outputTokens` (GROSS)
70
- - Errors: `401/403`→`invalid_auth`, `429`→`rate_limited`, `5xx`→`server`, timeouts, safety blocks
68
+ - Errors: `401` and a bare `403` default to `invalid_auth`; `429`→`rate_limited`; `5xx`→`server`; timeouts; Gemini safety blocks are a 200-path `content_filter` (`promptFeedback.blockReason` / no candidates), not an HTTP 403
71
69
 
72
70
  ## Strict model-config expectations
73
71
 
package/dist/index.cjs CHANGED
@@ -42,6 +42,49 @@ var GOOGLE_REASONING_EFFORT_BUDGET = {
42
42
  high: 24576
43
43
  };
44
44
 
45
+ // src/grounding.ts
46
+ function hostnameFrom(url) {
47
+ return url.hostname.startsWith("www.") ? url.hostname.slice(4) : url.hostname;
48
+ }
49
+ function toCitation(chunk) {
50
+ if (chunk === null || typeof chunk !== "object") return void 0;
51
+ const web = chunk["web"];
52
+ if (web === null || typeof web !== "object") return void 0;
53
+ const uri = web["uri"];
54
+ if (typeof uri !== "string" || uri.length === 0) return void 0;
55
+ let parsed;
56
+ try {
57
+ parsed = new URL(uri);
58
+ } catch {
59
+ return void 0;
60
+ }
61
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
62
+ if (parsed.hostname === "") return void 0;
63
+ const title = web["title"];
64
+ const hasTitle = typeof title === "string" && title.length > 0;
65
+ const sourceName = hasTitle ? title : hostnameFrom(parsed);
66
+ return {
67
+ url: uri,
68
+ ...hasTitle ? { title } : {},
69
+ sourceName
70
+ };
71
+ }
72
+ function normalizeGroundingCitations(groundingMetadata) {
73
+ if (groundingMetadata === null || typeof groundingMetadata !== "object") return [];
74
+ const chunks = groundingMetadata["groundingChunks"];
75
+ if (!Array.isArray(chunks)) return [];
76
+ const seen = /* @__PURE__ */ new Set();
77
+ const citations = [];
78
+ for (const chunk of chunks) {
79
+ const citation = toCitation(chunk);
80
+ if (citation === void 0) continue;
81
+ if (seen.has(citation.url)) continue;
82
+ seen.add(citation.url);
83
+ citations.push(citation);
84
+ }
85
+ return citations;
86
+ }
87
+
45
88
  // src/flex-fallback.ts
46
89
  var CAPACITY_PATTERNS = [
47
90
  /capacity/i,
@@ -389,6 +432,12 @@ function mapUsage(meta) {
389
432
  };
390
433
  return usage;
391
434
  }
435
+ function mapGoogleToolChoice(choice) {
436
+ if (choice === "auto") return { mode: "AUTO" };
437
+ if (choice === "required") return { mode: "ANY" };
438
+ if (choice === "none") return { mode: "NONE" };
439
+ return { mode: "ANY", allowedFunctionNames: [choice.name] };
440
+ }
392
441
  function mapPart(p) {
393
442
  switch (p.kind) {
394
443
  case "text":
@@ -416,6 +465,20 @@ function mapPart(p) {
416
465
  "Google Gemini expects FileUriPart with a Files API uri; got file-ref (provider file id). Upload via GoogleFileStore and pass the returned uri.",
417
466
  { kind: "bad_request", retryable: false, provider: "google" }
418
467
  );
468
+ case "tool-call":
469
+ return {
470
+ functionCall: {
471
+ name: p.toolName,
472
+ args: p.args
473
+ }
474
+ };
475
+ case "tool-result":
476
+ return {
477
+ functionResponse: {
478
+ name: p.toolName,
479
+ response: p.isError === true ? { error: p.result } : p.result
480
+ }
481
+ };
419
482
  default:
420
483
  return core.assertNever(p);
421
484
  }
@@ -559,6 +622,34 @@ function geminiAdapter(opts) {
559
622
  if (googleProviderConfig.tools !== void 0) {
560
623
  config.tools = googleProviderConfig.tools;
561
624
  }
625
+ if (req.tools !== void 0 && req.tools.length > 0) {
626
+ if (req.modelDescriptor?.capabilities?.functionCalling !== true) {
627
+ throw new core.LlmError(
628
+ `tools is not supported for google model "${model}" (capabilities.functionCalling is not true).`,
629
+ { kind: "bad_request", retryable: false, provider: "google" }
630
+ );
631
+ }
632
+ if (googleProviderConfig.tools !== void 0) {
633
+ throw new core.LlmError(
634
+ "tools cannot be combined with providerOptions.google.tools (googleSearch) in this iteration.",
635
+ { kind: "bad_request", retryable: false, provider: "google" }
636
+ );
637
+ }
638
+ config.tools = [
639
+ {
640
+ functionDeclarations: req.tools.map((tool) => ({
641
+ name: tool.name,
642
+ description: tool.description,
643
+ parameters: tool.inputJsonSchema
644
+ }))
645
+ }
646
+ ];
647
+ if (req.toolChoice !== void 0) {
648
+ config.toolConfig = {
649
+ functionCallingConfig: mapGoogleToolChoice(req.toolChoice)
650
+ };
651
+ }
652
+ }
562
653
  assertSamplingAllowed(config, model, req.modelDescriptor?.capabilities?.sampling);
563
654
  let tierTimeoutHandle;
564
655
  const clearTierTimeout = () => {
@@ -676,7 +767,15 @@ function geminiAdapter(opts) {
676
767
  const parts = candidate.content?.parts ?? [];
677
768
  const textParts = [];
678
769
  const thoughtParts = [];
770
+ const toolCalls = [];
679
771
  for (const part of parts) {
772
+ if (part.functionCall !== void 0 && typeof part.functionCall.name === "string") {
773
+ toolCalls.push({
774
+ toolCallId: part.functionCall.name,
775
+ toolName: part.functionCall.name,
776
+ args: part.functionCall.args ?? {}
777
+ });
778
+ }
680
779
  if (part.text !== void 0) {
681
780
  if (part.thought === true) {
682
781
  thoughtParts.push(part.text);
@@ -704,7 +803,7 @@ function geminiAdapter(opts) {
704
803
  ...text.length > 0 ? { text } : {},
705
804
  ...reasoningText !== void 0 ? { reasoningText } : {},
706
805
  ...rawStructured !== void 0 ? { rawStructured } : {},
707
- ...finishReason !== void 0 ? { finishReason } : {},
806
+ ...toolCalls.length > 0 ? { toolCalls, finishReason: "tool_calls" } : finishReason !== void 0 ? { finishReason } : {},
708
807
  ...response.modelVersion !== void 0 ? { modelVersion: response.modelVersion } : {},
709
808
  ...response.responseId !== void 0 ? { responseId: response.responseId } : {},
710
809
  // Build providerMetadata — merge promptFeedback + groundingMetadata when present.
@@ -720,6 +819,12 @@ function geminiAdapter(opts) {
720
819
  meta["groundingMetadata"] = gm;
721
820
  }
722
821
  return { providerMetadata: meta };
822
+ })(),
823
+ ...(() => {
824
+ const gm = candidate.groundingMetadata;
825
+ if (gm === void 0) return {};
826
+ const citations = normalizeGroundingCitations(gm);
827
+ return citations.length > 0 ? { citations } : {};
723
828
  })()
724
829
  };
725
830
  return result;
@@ -732,13 +837,23 @@ function geminiAdapter(opts) {
732
837
  );
733
838
  }
734
839
  const contents = mapMessagesToGeminiContents(req.messages);
840
+ const countTools = req.tools !== void 0 && req.tools.length > 0 ? [
841
+ {
842
+ functionDeclarations: req.tools.map((tool) => ({
843
+ name: tool.name,
844
+ description: tool.description,
845
+ parameters: tool.inputJsonSchema
846
+ }))
847
+ }
848
+ ] : void 0;
735
849
  const params = {
736
850
  model: req.model,
737
851
  contents,
738
- ...req.system !== void 0 || ctx.signal !== void 0 ? {
852
+ ...req.system !== void 0 || ctx.signal !== void 0 || countTools !== void 0 ? {
739
853
  config: {
740
854
  ...req.system !== void 0 ? { systemInstruction: { parts: [{ text: req.system }] } } : {},
741
- ...ctx.signal !== void 0 ? { abortSignal: ctx.signal } : {}
855
+ ...ctx.signal !== void 0 ? { abortSignal: ctx.signal } : {},
856
+ ...countTools !== void 0 ? { tools: countTools } : {}
742
857
  }
743
858
  } : {}
744
859
  };
@@ -755,6 +870,7 @@ function geminiAdapter(opts) {
755
870
  const details = response.cachedContentTokenCount !== void 0 ? { cached: response.cachedContentTokenCount } : void 0;
756
871
  return {
757
872
  totalTokens: response.totalTokens,
873
+ accuracy: "exact",
758
874
  ...details !== void 0 ? { details } : {},
759
875
  raw: response
760
876
  };
@@ -2538,6 +2654,7 @@ var geminiModelDescriptors = [
2538
2654
  sampling: "tunable",
2539
2655
  caching: { explicit: true, minTokens: 2048 },
2540
2656
  grounding: true,
2657
+ functionCalling: true,
2541
2658
  serviceTiers: ["flex", "standard"]
2542
2659
  },
2543
2660
  configSchema: Gemini25ProConfigSchema,
@@ -2558,6 +2675,7 @@ var geminiModelDescriptors = [
2558
2675
  sampling: "tunable",
2559
2676
  caching: { explicit: true, minTokens: 2048 },
2560
2677
  grounding: true,
2678
+ functionCalling: true,
2561
2679
  serviceTiers: ["flex", "standard"]
2562
2680
  },
2563
2681
  configSchema: Gemini25FlashConfigSchema,
@@ -2578,6 +2696,7 @@ var geminiModelDescriptors = [
2578
2696
  sampling: "tunable",
2579
2697
  caching: { explicit: true, minTokens: 2048 },
2580
2698
  grounding: true,
2699
+ functionCalling: true,
2581
2700
  serviceTiers: ["flex", "standard"]
2582
2701
  },
2583
2702
  configSchema: Gemini25FlashLiteConfigSchema,
@@ -2598,6 +2717,7 @@ var geminiModelDescriptors = [
2598
2717
  sampling: "fixed",
2599
2718
  caching: { explicit: true, minTokens: 2048 },
2600
2719
  grounding: true,
2720
+ functionCalling: true,
2601
2721
  serviceTiers: ["flex", "standard"]
2602
2722
  },
2603
2723
  configSchema: Gemini35FlashConfigSchema,
@@ -2618,6 +2738,7 @@ var geminiModelDescriptors = [
2618
2738
  sampling: "fixed",
2619
2739
  caching: { explicit: true, minTokens: 2048 },
2620
2740
  grounding: true,
2741
+ functionCalling: true,
2621
2742
  serviceTiers: ["flex", "standard"]
2622
2743
  },
2623
2744
  configSchema: Gemini31FlashLiteConfigSchema,
@@ -2638,6 +2759,7 @@ var geminiModelDescriptors = [
2638
2759
  sampling: "fixed",
2639
2760
  caching: { explicit: true, minTokens: 2048 },
2640
2761
  grounding: true,
2762
+ functionCalling: true,
2641
2763
  serviceTiers: ["flex", "standard"]
2642
2764
  },
2643
2765
  configSchema: Gemini31ProPreviewConfigSchema,
@@ -2658,6 +2780,7 @@ var geminiModelDescriptors = [
2658
2780
  sampling: "fixed",
2659
2781
  caching: { explicit: true, minTokens: 2048 },
2660
2782
  grounding: true,
2783
+ functionCalling: true,
2661
2784
  serviceTiers: ["flex", "standard"]
2662
2785
  },
2663
2786
  configSchema: Gemini3FlashPreviewConfigSchema,
@@ -3193,44 +3316,6 @@ var GoogleCacheStore = class {
3193
3316
  }
3194
3317
  }
3195
3318
  };
3196
-
3197
- // src/grounding.ts
3198
- function hostnameFrom(url) {
3199
- return url.hostname.startsWith("www.") ? url.hostname.slice(4) : url.hostname;
3200
- }
3201
- function toCitation(chunk) {
3202
- if (chunk === null || typeof chunk !== "object") return void 0;
3203
- const web = chunk["web"];
3204
- if (web === null || typeof web !== "object") return void 0;
3205
- const uri = web["uri"];
3206
- if (typeof uri !== "string" || uri.length === 0) return void 0;
3207
- let parsed;
3208
- try {
3209
- parsed = new URL(uri);
3210
- } catch {
3211
- return void 0;
3212
- }
3213
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
3214
- if (parsed.hostname === "") return void 0;
3215
- const title = web["title"];
3216
- const sourceName = typeof title === "string" && title.length > 0 ? title : hostnameFrom(parsed);
3217
- return { url: uri, sourceName };
3218
- }
3219
- function normalizeGroundingCitations(groundingMetadata) {
3220
- if (groundingMetadata === null || typeof groundingMetadata !== "object") return [];
3221
- const chunks = groundingMetadata["groundingChunks"];
3222
- if (!Array.isArray(chunks)) return [];
3223
- const seen = /* @__PURE__ */ new Set();
3224
- const citations = [];
3225
- for (const chunk of chunks) {
3226
- const citation = toCitation(chunk);
3227
- if (citation === void 0) continue;
3228
- if (seen.has(citation.url)) continue;
3229
- seen.add(citation.url);
3230
- citations.push(citation);
3231
- }
3232
- return citations;
3233
- }
3234
3319
  function badRequest(message) {
3235
3320
  return new core.LlmError(message, { kind: "bad_request", retryable: false });
3236
3321
  }
@@ -3273,13 +3358,37 @@ function convertPart(part, location) {
3273
3358
  (key) => key === "text" || key === "inlineData" || key === "fileData"
3274
3359
  );
3275
3360
  if (baseKeys.length === 0) {
3361
+ if (keys.includes("functionCall") && keys.every((k) => k === "functionCall")) {
3362
+ const fc = part.functionCall;
3363
+ if (fc === void 0 || typeof fc.name !== "string" || fc.name.length === 0) {
3364
+ throw badRequest(`${location}: functionCall.name is required.`);
3365
+ }
3366
+ return {
3367
+ kind: "tool-call",
3368
+ toolCallId: fc.name,
3369
+ toolName: fc.name,
3370
+ args: fc.args ?? {}
3371
+ };
3372
+ }
3373
+ if (keys.includes("functionResponse") && keys.every((k) => k === "functionResponse")) {
3374
+ const fr = part.functionResponse;
3375
+ if (fr === void 0 || typeof fr.name !== "string" || fr.name.length === 0) {
3376
+ throw badRequest(`${location}: functionResponse.name is required.`);
3377
+ }
3378
+ return {
3379
+ kind: "tool-result",
3380
+ toolCallId: fr.name,
3381
+ toolName: fr.name,
3382
+ result: fr.response ?? null
3383
+ };
3384
+ }
3276
3385
  if (keys.length === 0) {
3277
3386
  throw badRequest(
3278
3387
  `${location}: Part has no recognized fields set (expected exactly one of text, inlineData, fileData).`
3279
3388
  );
3280
3389
  }
3281
3390
  throw badRequest(
3282
- `${location}: Part keys [${keys.join(", ")}] are not supported by geminiContentToMessages \u2014 only text, inlineData, and fileData parts can be represented in any-llm.`
3391
+ `${location}: Part keys [${keys.join(", ")}] are not supported by geminiContentToMessages \u2014 only text, inlineData, fileData, functionCall, and functionResponse parts can be represented in any-llm.`
3283
3392
  );
3284
3393
  }
3285
3394
  if (baseKeys.length > 1) {
@@ -3418,7 +3527,6 @@ exports.geminiPricingSource = geminiPricingSource;
3418
3527
  exports.gemmaModelDescriptors = gemmaModelDescriptors;
3419
3528
  exports.googleProvider = googleProvider;
3420
3529
  exports.isGeminiCapacityError = isGeminiCapacityError;
3421
- exports.normalizeGroundingCitations = normalizeGroundingCitations;
3422
3530
  exports.pricingVersion = pricingVersion;
3423
3531
  exports.requireApiKey = requireApiKey;
3424
3532
  //# sourceMappingURL=index.cjs.map