@gullabs/google 0.6.1 → 0.8.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/dist/index.d.cts CHANGED
@@ -1,4 +1,44 @@
1
- import { AuthMaterial, ProviderAdapter, LlmError, Logger } from '@gullabs/core';
1
+ import { AuthMaterial, ProviderAdapter, ProviderPlugin, ModelRegistry, ModelDescriptor, PricingSource, ModelRates, LlmError, Logger, Message } from '@gullabs/core';
2
+ import { Content } from '@google/genai';
3
+
4
+ /**
5
+ * Google-specific provider options for `@gullabs/google`.
6
+ *
7
+ * Importing anything from this module (including this type-only re-export)
8
+ * pulls in the `declare module '@gullabs/core'` augmentation below, which
9
+ * adds the `google` key to `ProviderOptionsMap`. `packages/google/src/index.ts`
10
+ * re-exports these types unconditionally so the augmentation always loads
11
+ * when anything is imported from `@gullabs/google`.
12
+ *
13
+ * @module
14
+ */
15
+ type GoogleSafetySetting = {
16
+ category: string;
17
+ threshold: string;
18
+ };
19
+ type GoogleSearchTool = {
20
+ googleSearch: Record<string, never>;
21
+ };
22
+ type GoogleProviderOptions = {
23
+ /** Google cached content resource name. */
24
+ cachedContent?: string;
25
+ /** Allowlisted Google safety settings. */
26
+ safetySettings?: GoogleSafetySetting[];
27
+ /** Exact Google tool declarations admitted by the selected model schema. */
28
+ tools?: GoogleSearchTool[];
29
+ /** Allowlisted Google transport options. */
30
+ httpOptions?: {
31
+ /** Per-request Google transport timeout in milliseconds. */
32
+ timeout?: number;
33
+ };
34
+ /** Allow provider fallback from flex when flex was explicitly selected. */
35
+ flexFallback?: boolean;
36
+ };
37
+ declare module '@gullabs/core' {
38
+ interface ProviderOptionsMap {
39
+ google?: GoogleProviderOptions;
40
+ }
41
+ }
2
42
 
3
43
  /**
4
44
  * Structural GeminiClientLike interface + buildGoogleClient factory.
@@ -241,16 +281,45 @@ interface GeminiGenerateParams {
241
281
  contents: GeminiContent[];
242
282
  config?: GeminiGenerateConfig;
243
283
  }
284
+ /**
285
+ * Parameters for models.countTokens.
286
+ * Real type: CountTokensParameters.
287
+ */
288
+ interface GeminiCountTokensParams {
289
+ model: string;
290
+ contents: GeminiContent[];
291
+ config?: {
292
+ systemInstruction?: {
293
+ parts: GeminiContentPart[];
294
+ };
295
+ /**
296
+ * Real field: CountTokensConfig.abortSignal. countTokens has no
297
+ * tier-timeout dance (no flex/standard default ceilings) — `ctx.signal`
298
+ * is forwarded here directly, unlike `run()`'s combined timer signal.
299
+ */
300
+ abortSignal?: AbortSignal;
301
+ };
302
+ }
303
+ /**
304
+ * Response shape for models.countTokens.
305
+ * Real type: CountTokensResponse.
306
+ */
307
+ interface GeminiCountTokensResponseShape {
308
+ totalTokens?: number;
309
+ cachedContentTokenCount?: number;
310
+ }
244
311
  /**
245
312
  * Structural interface for the @google/genai client surface the adapter uses.
246
313
  *
247
314
  * Satisfied by:
248
315
  * - The real GoogleGenAI client (via buildGoogleClient wrapper).
249
- * - FakeGeminiClient from @gullabs/testing (its generateContent accepts unknown).
316
+ * - FakeGeminiClient from @gullabs/testing (its generateContent/countTokens
317
+ * accept unknown).
250
318
  */
251
319
  interface GeminiClientLike {
252
320
  models: {
253
321
  generateContent(params: GeminiGenerateParams): Promise<GeminiResponseShape>;
322
+ countTokens(params: GeminiCountTokensParams): Promise<GeminiCountTokensResponseShape>;
254
323
  };
255
324
  }
256
325
  /**
@@ -296,6 +365,151 @@ interface GeminiAdapterOptions {
296
365
  */
297
366
  declare function geminiAdapter(opts?: GeminiAdapterOptions): ProviderAdapter;
298
367
 
368
+ /**
369
+ * `googleProvider` — {@link ProviderPlugin} factory for @gullabs/google.
370
+ *
371
+ * Bundles the Gemini adapter, the built-in Gemini + Gemma model descriptors,
372
+ * and the Gemini pricing source into a single plugin for
373
+ * {@link composeProviders}.
374
+ *
375
+ * @module
376
+ */
377
+
378
+ /**
379
+ * Create a {@link ProviderPlugin} for the Gemini provider.
380
+ *
381
+ * @param opts - Forwarded to {@link geminiAdapter}.
382
+ * @returns A plugin bundling the Gemini adapter, Gemini + Gemma model
383
+ * descriptors, and the built-in Gemini pricing source.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * import { createClient, composeProviders } from '@gullabs/core'
388
+ * import { googleProvider } from '@gullabs/google'
389
+ *
390
+ * const client = createClient({
391
+ * ...composeProviders([googleProvider()]),
392
+ * })
393
+ * ```
394
+ */
395
+ declare function googleProvider(opts?: GeminiAdapterOptions): ProviderPlugin;
396
+
397
+ /**
398
+ * Model descriptor registry for @gullabs/google.
399
+ *
400
+ * Centralises Gemini/Gemma model knowledge: reasoning-effort vocabularies,
401
+ * capability flags, and the built-in descriptor arrays. `@gullabs/core` owns
402
+ * only the generic registry machinery (`ModelDescriptor`, `ModelRegistry`,
403
+ * `createModelRegistry`) — this module supplies the Gemini-specific data.
404
+ *
405
+ * @module
406
+ */
407
+
408
+ declare const geminiModelDescriptors: ModelDescriptor[];
409
+ declare const gemmaModelDescriptors: ModelDescriptor[];
410
+ /**
411
+ * Pre-built registry of all built-in Gemini + Gemma descriptors.
412
+ *
413
+ * Most callers should prefer {@link googleProvider} (which bundles this same
414
+ * descriptor set via `composeProviders`); this export remains for callers
415
+ * that need a bare `ModelRegistry` without going through the plugin seam.
416
+ */
417
+ declare const defaultGeminiRegistry: ModelRegistry;
418
+
419
+ /**
420
+ * Gemini pricing source for @gullabs/google.
421
+ *
422
+ * Provides `geminiPricingSource` — a factory returning a `PricingSource` port
423
+ * implementation backed by the frozen Gemini pricing snapshot ({@link
424
+ * GEMINI_PRICING}). Walks the rates table (exact-then-longest-prefix match)
425
+ * and delegates the actual arithmetic to `@gullabs/core`'s `computeCost`,
426
+ * supplying this package's rates table + tier-factor map as explicit
427
+ * parameters — core itself carries zero Gemini pricing knowledge.
428
+ *
429
+ * @module
430
+ */
431
+
432
+ /**
433
+ * Factory that returns the **google-scoped** {@link PricingSource} port
434
+ * implementation backed by the built-in Gemini pricing snapshot.
435
+ *
436
+ * `PricingSource` is provider-scoped by contract — this source only knows
437
+ * bare Gemini/Gemma model keys. Compose it into `ClientConfig.pricingSources`
438
+ * under the `'google'` key (or bundle it via {@link googleProvider}); do not
439
+ * use it for other providers.
440
+ *
441
+ * The returned object is stateless and can be shared across calls.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * import { geminiPricingSource } from '@gullabs/google'
446
+ *
447
+ * const pricing = geminiPricingSource()
448
+ * const cost = pricing.price('gemini-2.5-pro', usage, 'flex')
449
+ * ```
450
+ */
451
+ declare function geminiPricingSource(): PricingSource;
452
+
453
+ /**
454
+ * Gemini pricing snapshot for @gullabs/google.
455
+ *
456
+ * All rates are in **micro-USD per million tokens** (µUSD/M).
457
+ * To get the cost for N tokens: `cost_µUSD = N * ratePerM / 1_000_000`.
458
+ *
459
+ * **Service tiers.** Rates below are STANDARD-tier. Google's **Batch** tier is a
460
+ * flat 50% discount on standard, and **Flex** matches Batch pricing. The cost
461
+ * engine applies the {@link TIER_FACTOR} multiplier — this snapshot stores
462
+ * standard rates only.
463
+ *
464
+ * **Long-context tier.** Gemini Pro models charge a premium when the GROSS input
465
+ * token count exceeds 200,000. Selected by `inputTokens` (incl. cached), not by
466
+ * billable input.
467
+ *
468
+ * **Thinking tokens.** Already inside `outputTokens` (GROSS convention) and
469
+ * billed at the standard output rate — no separate thinking lane.
470
+ *
471
+ * **Modality caveat (v1 = text).** Gemini 2.5 Flash / Flash-Lite charge a higher
472
+ * INPUT rate for audio tokens than for text/image/video. v1 is text-only and uses
473
+ * the text/img/vid input rate. Per-modality input pricing is a deferred seam
474
+ * (see DESIGN.md) — revisit when audio input is supported.
475
+ *
476
+ * Verified against https://ai.google.dev/gemini-api/docs/pricing on 2026-06-28.
477
+ *
478
+ * @module
479
+ */
480
+
481
+ /**
482
+ * Identifies this pricing snapshot — bump the date when rates change.
483
+ *
484
+ * This is a snapshot date tied to Gemini's own pricing page, not a generic
485
+ * core concept — it lives here (not `@gullabs/core`) alongside the rates it
486
+ * dates.
487
+ */
488
+ declare const pricingVersion: "gemini-2026-06-28";
489
+ /**
490
+ * Service-tier price multipliers. Batch and Flex are a flat 50% of standard
491
+ * (per Google's pricing page: "Batch API — 50% cost reduction"; Flex matches Batch).
492
+ *
493
+ * `serviceTier` is an opaque, provider-defined string end-to-end — this map is
494
+ * the *only* place a tier name is resolved to a multiplier. A tier key not
495
+ * present here is never coerced to `standard`: `computeCost` (`@gullabs/core`)
496
+ * treats that as an unpriced call (reject-don't-map), not a mapping to this
497
+ * table's default. `undefined` (no tier requested) is the one case that
498
+ * legitimately defaults to `standard` — that is documented default behavior,
499
+ * not a guess.
500
+ */
501
+ declare const TIER_FACTOR: Readonly<Record<string, number>>;
502
+ /**
503
+ * Frozen Gemini pricing snapshot (STANDARD tier; per-1M in µUSD).
504
+ *
505
+ * Keys are model-string prefixes / exact identifiers used in routing. The cost
506
+ * engine matches exact first, then longest-prefix (see `lookupRates` in
507
+ * `cost.ts`).
508
+ *
509
+ * Source: https://ai.google.dev/gemini-api/docs/pricing (2026-06-28).
510
+ */
511
+ declare const GEMINI_PRICING: Readonly<Record<string, ModelRates>>;
512
+
299
513
  declare function isGeminiCapacityError(err: LlmError): boolean;
300
514
 
301
515
  /**
@@ -456,8 +670,8 @@ interface GeminiCachesClientLike {
456
670
  create(params: {
457
671
  model: string;
458
672
  config: {
459
- contents?: unknown;
460
- systemInstruction?: unknown;
673
+ contents?: Content[];
674
+ systemInstruction?: Content | string;
461
675
  ttl?: string;
462
676
  displayName?: string;
463
677
  };
@@ -500,6 +714,35 @@ interface GoogleCacheStoreOptions {
500
714
  logger?: Logger;
501
715
  /** Injectable clock for deterministic tests. Default: `Date.now`. */
502
716
  now?: () => number;
717
+ /**
718
+ * Opt-in pre-flight token-count gate applied before every cache create
719
+ * (both `create()` directly and the `getOrCreate()` path it delegates to,
720
+ * including the coalesced path — enforced once, inside `create()`, so
721
+ * there is no separate "in-flight" gap to close).
722
+ */
723
+ preflight?: {
724
+ /** Minimum token count required before a cache create is allowed to proceed. */
725
+ minTokens: number;
726
+ /**
727
+ * Counts tokens for the exact token-bearing payload of the impending
728
+ * create — `model` + `contents` + `systemInstruction` only. `ttl` and
729
+ * `displayName` are excluded: they carry no tokens and are irrelevant to
730
+ * the pre-flight check.
731
+ *
732
+ * This callback receives genai-native `Content[]`/`Content|string` — it
733
+ * does NOT receive the library's `Message[]` shape and there is no
734
+ * automatic conversion between the two (explicit seam, by design). Hosts
735
+ * using genai-native content directly can wire this to a raw
736
+ * `client.models.countTokens` call; hosts building from the library's
737
+ * `Message[]` should call `@gullabs/core`'s `client.countTokens` rather
738
+ * than expecting this callback to convert for them.
739
+ */
740
+ countTokens: (payload: {
741
+ model: string;
742
+ contents?: Content[];
743
+ systemInstruction?: Content | string;
744
+ }) => Promise<number>;
745
+ };
503
746
  }
504
747
  /**
505
748
  * Process-scoped helper for the Gemini Context Cache API.
@@ -526,6 +769,7 @@ declare class GoogleCacheStore {
526
769
  private readonly onDeleteError;
527
770
  private readonly logger;
528
771
  private readonly now;
772
+ private readonly preflight;
529
773
  /** Memoised client promise — built at most once per store instance. */
530
774
  private clientPromise;
531
775
  /** In-process cache of live handles, keyed by `${model}:${stableKey}`. */
@@ -545,8 +789,8 @@ declare class GoogleCacheStore {
545
789
  create(input: {
546
790
  model: string;
547
791
  ttlSeconds: number;
548
- contents?: unknown;
549
- systemInstruction?: unknown;
792
+ contents?: Content[];
793
+ systemInstruction?: Content | string;
550
794
  displayName?: string;
551
795
  }): Promise<GoogleCacheHandle>;
552
796
  /**
@@ -560,8 +804,8 @@ declare class GoogleCacheStore {
560
804
  */
561
805
  getOrCreate(key: CacheKey, factory: () => Promise<{
562
806
  ttlSeconds: number;
563
- contents?: unknown;
564
- systemInstruction?: unknown;
807
+ contents?: Content[];
808
+ systemInstruction?: Content | string;
565
809
  }>): Promise<GoogleCacheHandle>;
566
810
  /**
567
811
  * Extend the cache TTL if it will expire within `thresholdSeconds`.
@@ -612,4 +856,85 @@ interface Citation {
612
856
  */
613
857
  declare function normalizeGroundingCitations(groundingMetadata: unknown): Citation[];
614
858
 
615
- export { type CacheKey, type Citation, FLEX_DEFAULT_TIMEOUT_MS, type GeminiAdapterOptions, type GeminiCachesClientLike, type GeminiClientLike, type GeminiFilesClientLike, type GoogleCacheHandle, GoogleCacheStore, type GoogleCacheStoreOptions, type GoogleFileHandle, GoogleFileStore, type GoogleFileStoreOptions, STANDARD_DEFAULT_TIMEOUT_MS, TRANSPORT_TIMEOUT_BUFFER_MS, buildGoogleClient, geminiAdapter, isGeminiCapacityError, normalizeGroundingCitations, requireApiKey };
859
+ /**
860
+ * geminiContentToMessages — migration utility: `@google/genai` `Content[]` →
861
+ * any-llm's normalized `Message[]`.
862
+ *
863
+ * For consumers moving hand-authored `@google/genai` prompts onto this
864
+ * library. Uses `@google/genai` TYPES ONLY (no runtime SDK dependency — the
865
+ * package is a peer dep, imported here with `import type`).
866
+ *
867
+ * Reject-don't-map: every genai `Content`/`Part` shape this utility cannot
868
+ * losslessly represent in any-llm's normalized types throws a typed
869
+ * `LlmError('bad_request')` naming the offending field or kind. Nothing is
870
+ * ever silently dropped — that silent-loss failure mode is exactly what this
871
+ * utility exists to eliminate for migrating callers.
872
+ *
873
+ * Validation is an exhaustive own-key scan: the set of defined keys on each
874
+ * `Part` must be EXACTLY one of the recognized combinations (`['text']`,
875
+ * `['inlineData']`, `['inlineData', 'mediaResolution']`, `['fileData']`,
876
+ * `['fileData', 'mediaResolution']`). Any other key — including unknown
877
+ * future SDK fields — or any combination outside that set throws. Keys whose
878
+ * value is `undefined` are treated as absent (genai types are all-optional;
879
+ * only defined values count).
880
+ *
881
+ * @module
882
+ */
883
+
884
+ /**
885
+ * Input accepted by {@link geminiContentToMessages}.
886
+ */
887
+ interface GeminiContentToMessagesInput {
888
+ /** Raw `@google/genai` conversation history to convert. */
889
+ contents: Content[];
890
+ /**
891
+ * Raw `@google/genai` system instruction — either the SDK's shorthand
892
+ * plain string, or a full `Content` (only text parts supported; any
893
+ * non-text part throws).
894
+ */
895
+ systemInstruction?: Content | string;
896
+ }
897
+ /**
898
+ * Output produced by {@link geminiContentToMessages}.
899
+ */
900
+ interface GeminiContentToMessagesResult {
901
+ /** Concatenated system text, present only when `systemInstruction` was given. */
902
+ system?: string;
903
+ /** Normalized any-llm messages, one per input `Content`. */
904
+ messages: Message[];
905
+ }
906
+ /**
907
+ * Convert `@google/genai` `Content[]` / `Part[]` into any-llm's normalized
908
+ * `{ system?, messages }` shape.
909
+ *
910
+ * A migration utility for callers moving hand-authored `@google/genai`
911
+ * prompts onto any-llm. Exhaustive and reject-don't-map: any genai shape
912
+ * that cannot be losslessly represented in any-llm's normalized types
913
+ * throws `LlmError('bad_request')` naming the offending kind/field instead
914
+ * of silently dropping it.
915
+ *
916
+ * `system` is derived ONLY from the explicit `systemInstruction` input —
917
+ * never inferred from `contents`.
918
+ *
919
+ * @example
920
+ * ```ts
921
+ * // Before: hand-rolled @google/genai prompt
922
+ * const contents: Content[] = [
923
+ * { role: 'user', parts: [{ text: 'Describe this image.' }, { inlineData: { mimeType: 'image/png', data } }] },
924
+ * { role: 'model', parts: [{ text: 'A red bicycle leaning against a brick wall.' }] },
925
+ * ]
926
+ *
927
+ * // After: migrate onto any-llm's normalized shape in one call
928
+ * import { geminiContentToMessages } from '@gullabs/google'
929
+ *
930
+ * const { system, messages } = geminiContentToMessages({
931
+ * contents,
932
+ * systemInstruction: 'You are a concise visual describer.',
933
+ * })
934
+ *
935
+ * const result = await client.generate({ provider: 'google', model: 'gemini-2.5-pro', system, messages }, { auth })
936
+ * ```
937
+ */
938
+ declare function geminiContentToMessages(input: GeminiContentToMessagesInput): GeminiContentToMessagesResult;
939
+
940
+ export { type CacheKey, type Citation, FLEX_DEFAULT_TIMEOUT_MS, GEMINI_PRICING, type GeminiAdapterOptions, type GeminiCachesClientLike, type GeminiClientLike, type GeminiContentToMessagesInput, type GeminiContentToMessagesResult, type GeminiCountTokensParams, type GeminiCountTokensResponseShape, type GeminiFilesClientLike, type GoogleCacheHandle, GoogleCacheStore, type GoogleCacheStoreOptions, type GoogleFileHandle, GoogleFileStore, type GoogleFileStoreOptions, type GoogleProviderOptions, type GoogleSafetySetting, type GoogleSearchTool, STANDARD_DEFAULT_TIMEOUT_MS, TIER_FACTOR, TRANSPORT_TIMEOUT_BUFFER_MS, buildGoogleClient, defaultGeminiRegistry, geminiAdapter, geminiContentToMessages, geminiModelDescriptors, geminiPricingSource, gemmaModelDescriptors, googleProvider, isGeminiCapacityError, normalizeGroundingCitations, pricingVersion, requireApiKey };