agentfootprint 9.12.0 → 9.13.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.
Files changed (41) hide show
  1. package/dist/adapters/llm/GeminiProvider.js +519 -0
  2. package/dist/adapters/llm/GeminiProvider.js.map +1 -0
  3. package/dist/adapters/llm/contextWindow.js +20 -0
  4. package/dist/adapters/llm/contextWindow.js.map +1 -1
  5. package/dist/adapters/llm/createProvider.js +3 -0
  6. package/dist/adapters/llm/createProvider.js.map +1 -1
  7. package/dist/adapters/llm/googleGenAI.js +109 -0
  8. package/dist/adapters/llm/googleGenAI.js.map +1 -0
  9. package/dist/embedders/index.js +233 -1
  10. package/dist/embedders/index.js.map +1 -1
  11. package/dist/esm/adapters/llm/GeminiProvider.d.ts +204 -0
  12. package/dist/esm/adapters/llm/GeminiProvider.js +514 -0
  13. package/dist/esm/adapters/llm/GeminiProvider.js.map +1 -0
  14. package/dist/esm/adapters/llm/contextWindow.js +20 -0
  15. package/dist/esm/adapters/llm/contextWindow.js.map +1 -1
  16. package/dist/esm/adapters/llm/createProvider.d.ts +4 -1
  17. package/dist/esm/adapters/llm/createProvider.js +3 -0
  18. package/dist/esm/adapters/llm/createProvider.js.map +1 -1
  19. package/dist/esm/adapters/llm/googleGenAI.d.ts +67 -0
  20. package/dist/esm/adapters/llm/googleGenAI.js +105 -0
  21. package/dist/esm/adapters/llm/googleGenAI.js.map +1 -0
  22. package/dist/esm/embedders/index.d.ts +188 -1
  23. package/dist/esm/embedders/index.js +231 -0
  24. package/dist/esm/embedders/index.js.map +1 -1
  25. package/dist/esm/providers.d.ts +3 -0
  26. package/dist/esm/providers.js +2 -0
  27. package/dist/esm/providers.js.map +1 -1
  28. package/dist/providers.js +5 -1
  29. package/dist/providers.js.map +1 -1
  30. package/dist/types/adapters/llm/GeminiProvider.d.ts +205 -0
  31. package/dist/types/adapters/llm/GeminiProvider.d.ts.map +1 -0
  32. package/dist/types/adapters/llm/contextWindow.d.ts.map +1 -1
  33. package/dist/types/adapters/llm/createProvider.d.ts +4 -1
  34. package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
  35. package/dist/types/adapters/llm/googleGenAI.d.ts +68 -0
  36. package/dist/types/adapters/llm/googleGenAI.d.ts.map +1 -0
  37. package/dist/types/embedders/index.d.ts +188 -1
  38. package/dist/types/embedders/index.d.ts.map +1 -1
  39. package/dist/types/providers.d.ts +3 -0
  40. package/dist/types/providers.d.ts.map +1 -1
  41. package/package.json +7 -1
@@ -1 +1 @@
1
- {"version":3,"file":"createProvider.js","sourceRoot":"","sources":["../../../../src/adapters/llm/createProvider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createProvider.js","sourceRoot":"","sources":["../../../../src/adapters/llm/createProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * googleGenAI — the one place a `GoogleGenAI` client gets built (9.13.0).
3
+ *
4
+ * Two adapters reach for the same SDK client — `gemini()` (the LLM provider)
5
+ * and `geminiEmbedder()` — and they need the SAME two doors, the SAME refusals
6
+ * and the SAME `_client` seam. Duplicating that is how the two drift: one grows
7
+ * a fix, the other keeps the bug, and the difference is invisible until a
8
+ * consumer hits the door that was not fixed.
9
+ *
10
+ * So the connection lives here and the adapters own only their own calls.
11
+ * Nothing in this file knows what a model, a message or a vector is.
12
+ *
13
+ * ─── The two doors ──────────────────────────────────────────────────
14
+ *
15
+ * • **Vertex** — `{ project, location }` → `new GoogleGenAI({ vertexai: true,
16
+ * project, location, googleAuthOptions })`. Credentials come from
17
+ * Application Default Credentials: `GOOGLE_APPLICATION_CREDENTIALS`, the
18
+ * gcloud user credentials, or the metadata server on GCE / Cloud Run. There
19
+ * is no API key on this path.
20
+ * • **Gemini API (AI Studio)** — `{ apiKey }` → `new GoogleGenAI({ apiKey })`.
21
+ * One key, no cloud project.
22
+ *
23
+ * ─── The API version, which is not what you would guess ─────────────
24
+ *
25
+ * The SDK's DEFAULT API version is `v1beta1` on Vertex and `v1beta` on the
26
+ * Gemini API — verified against @google/genai 2.16.0 and pinned by
27
+ * `test/adapters/google/google-surface-pin.test.ts`. It is not `v1`. Callers
28
+ * who need a specific version pass `apiVersion`; callers who do not at least
29
+ * find the truth stated instead of assuming a GA path.
30
+ */
31
+ /** The connection half of a Google adapter's options — the same on both. */
32
+ export interface GoogleGenAIConnectionOptions {
33
+ /** Google Cloud project id — the VERTEX door. Env fallback (read by the SDK):
34
+ * `GOOGLE_CLOUD_PROJECT`. */
35
+ readonly project?: string;
36
+ /** Vertex location, e.g. `'us-central1'` or `'global'` (the SDK's default).
37
+ * Env fallback: `GOOGLE_CLOUD_LOCATION`. */
38
+ readonly location?: string;
39
+ /** API key — the GEMINI API door. Env fallbacks: `GEMINI_API_KEY`, then
40
+ * `GOOGLE_API_KEY`. */
41
+ readonly apiKey?: string;
42
+ /** Force the door instead of inferring it: `true` = Vertex, `false` = the
43
+ * Gemini API. */
44
+ readonly vertexai?: boolean;
45
+ /** Pin the API version. Unset means the SDK's default — `v1beta1` on Vertex,
46
+ * `v1beta` on the Gemini API. */
47
+ readonly apiVersion?: string;
48
+ /** `GoogleAuthOptions` for the Vertex door — a key file, scopes, or an
49
+ * `AuthClient` you built (workload identity federation, impersonation). */
50
+ readonly googleAuthOptions?: Record<string, unknown>;
51
+ }
52
+ /**
53
+ * Build (or accept) a `GoogleGenAI` client.
54
+ *
55
+ * `injected` is the `_client` test seam: a structural double exposing only the
56
+ * namespace members the calling adapter uses. When it is present the SDK is
57
+ * never required, no environment is read and no refusal fires — a test drives
58
+ * the adapter's translation with no Google anywhere near it.
59
+ *
60
+ * @param factory the public name to put in refusals — `'gemini'`,
61
+ * `'geminiEmbedder'`. A message that names the wrong factory
62
+ * sends the reader to the wrong line of their own code.
63
+ * @throws when neither a project nor an API key is resolvable; when
64
+ * `vertexai: true` is asked for without a project; and when
65
+ * `@google/genai` is not installed.
66
+ */
67
+ export declare function resolveGoogleGenAIClient<T>(options: GoogleGenAIConnectionOptions, factory: string, injected?: T): T;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * googleGenAI — the one place a `GoogleGenAI` client gets built (9.13.0).
3
+ *
4
+ * Two adapters reach for the same SDK client — `gemini()` (the LLM provider)
5
+ * and `geminiEmbedder()` — and they need the SAME two doors, the SAME refusals
6
+ * and the SAME `_client` seam. Duplicating that is how the two drift: one grows
7
+ * a fix, the other keeps the bug, and the difference is invisible until a
8
+ * consumer hits the door that was not fixed.
9
+ *
10
+ * So the connection lives here and the adapters own only their own calls.
11
+ * Nothing in this file knows what a model, a message or a vector is.
12
+ *
13
+ * ─── The two doors ──────────────────────────────────────────────────
14
+ *
15
+ * • **Vertex** — `{ project, location }` → `new GoogleGenAI({ vertexai: true,
16
+ * project, location, googleAuthOptions })`. Credentials come from
17
+ * Application Default Credentials: `GOOGLE_APPLICATION_CREDENTIALS`, the
18
+ * gcloud user credentials, or the metadata server on GCE / Cloud Run. There
19
+ * is no API key on this path.
20
+ * • **Gemini API (AI Studio)** — `{ apiKey }` → `new GoogleGenAI({ apiKey })`.
21
+ * One key, no cloud project.
22
+ *
23
+ * ─── The API version, which is not what you would guess ─────────────
24
+ *
25
+ * The SDK's DEFAULT API version is `v1beta1` on Vertex and `v1beta` on the
26
+ * Gemini API — verified against @google/genai 2.16.0 and pinned by
27
+ * `test/adapters/google/google-surface-pin.test.ts`. It is not `v1`. Callers
28
+ * who need a specific version pass `apiVersion`; callers who do not at least
29
+ * find the truth stated instead of assuming a GA path.
30
+ */
31
+ import { lazyRequire } from '../../lib/lazyRequire.js';
32
+ /**
33
+ * Build (or accept) a `GoogleGenAI` client.
34
+ *
35
+ * `injected` is the `_client` test seam: a structural double exposing only the
36
+ * namespace members the calling adapter uses. When it is present the SDK is
37
+ * never required, no environment is read and no refusal fires — a test drives
38
+ * the adapter's translation with no Google anywhere near it.
39
+ *
40
+ * @param factory the public name to put in refusals — `'gemini'`,
41
+ * `'geminiEmbedder'`. A message that names the wrong factory
42
+ * sends the reader to the wrong line of their own code.
43
+ * @throws when neither a project nor an API key is resolvable; when
44
+ * `vertexai: true` is asked for without a project; and when
45
+ * `@google/genai` is not installed.
46
+ */
47
+ export function resolveGoogleGenAIClient(options, factory, injected) {
48
+ if (injected)
49
+ return injected;
50
+ const env = (typeof process !== 'undefined' ? process.env : {});
51
+ // Read HERE only to decide whether there is enough configuration to build
52
+ // anything at all. The values are never copied into the constructor, so the
53
+ // SDK stays the single resolver of its own environment variables — one place
54
+ // that answers "where did my project come from", not two that can disagree.
55
+ // An EMPTY variable is not a setting. `GOOGLE_CLOUD_PROJECT=` in a shell or a
56
+ // CI matrix is how a project comes to be "present" and unusable — the guards
57
+ // below would pass and the SDK would fail on the first call instead.
58
+ const set = (value) => value !== undefined && value.trim().length > 0 ? value : undefined;
59
+ const project = set(options.project) ?? set(env.GOOGLE_CLOUD_PROJECT);
60
+ const apiKey = set(options.apiKey) ?? set(env.GEMINI_API_KEY) ?? set(env.GOOGLE_API_KEY);
61
+ const vertexai = options.vertexai ?? (project !== undefined ? true : undefined);
62
+ if (vertexai !== false && project === undefined && apiKey === undefined) {
63
+ throw new Error(`${factory}: no Google project and no API key — this factory cannot tell which service ` +
64
+ 'you meant, and the SDK would warn on stderr, construct anyway, and fail on the first ' +
65
+ 'call with something that reads like a network problem.\n' +
66
+ ` Vertex: ${factory}({ project: "my-project", location: "us-central1" }) — or set ` +
67
+ 'GOOGLE_CLOUD_PROJECT; credentials come from Application Default Credentials.\n' +
68
+ ` Gemini API: ${factory}({ apiKey: process.env.GEMINI_API_KEY }) — or set ` +
69
+ 'GEMINI_API_KEY.\n' +
70
+ ` Tests: ${factory}({ _client }) with a { models: { … } } double — no SDK, no ` +
71
+ 'network, no credentials.');
72
+ }
73
+ if (vertexai === true && project === undefined) {
74
+ throw new Error(`${factory}: \`vertexai: true\` selects Vertex, which is addressed by PROJECT and not by ` +
75
+ 'key. Pass `{ project }` (or set GOOGLE_CLOUD_PROJECT), or drop `vertexai` and pass ' +
76
+ '`{ apiKey }` to use the Gemini API instead.');
77
+ }
78
+ let GoogleGenAI;
79
+ try {
80
+ const mod = lazyRequire('@google/genai');
81
+ GoogleGenAI = (mod.GoogleGenAI ?? mod.default?.GoogleGenAI);
82
+ }
83
+ catch {
84
+ throw new Error(`${factory} requires the \`@google/genai\` package.\n` +
85
+ ' Install: npm install @google/genai\n' +
86
+ ' Or pass `_client` for test injection.');
87
+ }
88
+ if (!GoogleGenAI) {
89
+ throw new Error(`${factory}: \`@google/genai\` is installed but exports no \`GoogleGenAI\`. These adapters ` +
90
+ 'are built against @google/genai 2.x — update the package.');
91
+ }
92
+ // Only what was ASKED for goes on the wire; everything omitted is left to the
93
+ // SDK's own environment resolution.
94
+ return new GoogleGenAI({
95
+ ...(vertexai !== undefined && { vertexai }),
96
+ ...(options.project !== undefined && { project: options.project }),
97
+ ...(options.location !== undefined && { location: options.location }),
98
+ ...(options.apiKey !== undefined && { apiKey: options.apiKey }),
99
+ ...(options.apiVersion !== undefined && { apiVersion: options.apiVersion }),
100
+ ...(options.googleAuthOptions !== undefined && {
101
+ googleAuthOptions: options.googleAuthOptions,
102
+ }),
103
+ });
104
+ }
105
+ //# sourceMappingURL=googleGenAI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"googleGenAI.js","sourceRoot":"","sources":["../../../../src/adapters/llm/googleGenAI.ts"],"names":[],"mappings":"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"}
@@ -12,12 +12,17 @@
12
12
  * v3 (the model id picks the body shape); credentials
13
13
  * come from the AWS chain, so no key option at all.
14
14
  * peer dep: @aws-sdk/client-bedrock-runtime.
15
+ * geminiEmbedder() — hosted on Google; Vertex (project + Application
16
+ * Default Credentials) or the Gemini API (one key), the
17
+ * same two doors as `gemini()`. Matryoshka sizes, real
18
+ * task types, and it REFUSES a vector the service admits
19
+ * it clipped. peer dep: @google/genai.
15
20
  * localEmbedder() — on-device sentence-transformer; no key; offline after a
16
21
  * one-time model fetch. peer dep: @huggingface/transformers.
17
22
  * staticEmbedder() — pure-JS Model2Vec static vectors; no key, no network
18
23
  * (weights bundled). peer dep: @yarflam/potion-base-8m.
19
24
  *
20
- * All four satisfy the same `Embedder` shape, so they drop into
25
+ * All five satisfy the same `Embedder` shape, so they drop into
21
26
  * `toolChoiceRecorder({ embedder })` / `semanticPipeline({ embedder })` etc.
22
27
  * unchanged. Dimensions differ per model — never mix two in one store.
23
28
  *
@@ -43,6 +48,7 @@
43
48
  */
44
49
  export type { Embedder } from '../memory/embedding/types.js';
45
50
  import type { Embedder } from '../memory/embedding/types.js';
51
+ import { type GoogleGenAIConnectionOptions } from '../adapters/llm/googleGenAI.js';
46
52
  export interface OpenAIEmbedderOptions {
47
53
  /** Default: process.env.OPENAI_API_KEY. */
48
54
  readonly apiKey?: string;
@@ -272,6 +278,187 @@ export interface BedrockEmbedderOptions {
272
278
  * ```
273
279
  */
274
280
  export declare function bedrockEmbedder(options?: BedrockEmbedderOptions): Embedder;
281
+ /**
282
+ * The slice of `@google/genai` {@link geminiEmbedder} uses — one method.
283
+ *
284
+ * Structural, so the real `GoogleGenAI`, a client shared with `gemini()`, or a
285
+ * `{ models: { embedContent } }` double all satisfy it without this package
286
+ * taking a hard type dependency on the optional peer. The double is what the
287
+ * Google pin injects.
288
+ */
289
+ export interface GeminiEmbedClientLike {
290
+ readonly models: {
291
+ embedContent(params: GeminiEmbedParams): Promise<GeminiEmbedResponse>;
292
+ };
293
+ }
294
+ /** `EmbedContentParameters`, narrowed to what this adapter sends. */
295
+ export interface GeminiEmbedParams {
296
+ readonly model: string;
297
+ readonly contents: readonly string[];
298
+ readonly config?: {
299
+ readonly taskType?: string;
300
+ readonly outputDimensionality?: number;
301
+ readonly abortSignal?: AbortSignal;
302
+ };
303
+ }
304
+ /** `EmbedContentResponse`, narrowed to what this adapter reads. */
305
+ export interface GeminiEmbedResponse {
306
+ readonly embeddings?: readonly {
307
+ readonly values?: readonly number[];
308
+ /**
309
+ * Present on Vertex. `truncated: true` is the service TELLING us it clipped
310
+ * the input — the one signal that turns the silent half of
311
+ * {@link Embedder.maxInputChars} into something an adapter can act on.
312
+ */
313
+ readonly statistics?: {
314
+ readonly truncated?: boolean;
315
+ readonly tokenCount?: number;
316
+ };
317
+ }[];
318
+ }
319
+ /**
320
+ * Gemini's `task_type` — a real parameter, not a hint.
321
+ *
322
+ * `RETRIEVAL_QUERY` and `RETRIEVAL_DOCUMENT` embed a question and a passage
323
+ * into deliberately different projections that are MEANT to be compared with
324
+ * each other, so using one value for both halves is a measurable loss of
325
+ * retrieval quality. The rest are separate objectives; mixing them in one store
326
+ * is mixing spaces.
327
+ */
328
+ export type GeminiEmbeddingTaskType = 'RETRIEVAL_QUERY' | 'RETRIEVAL_DOCUMENT' | 'SEMANTIC_SIMILARITY' | 'CLASSIFICATION' | 'CLUSTERING' | 'CODE_RETRIEVAL_QUERY' | 'QUESTION_ANSWERING' | 'FACT_VERIFICATION';
329
+ /** What this adapter does when the service says it clipped the input. */
330
+ export type GeminiTruncationPolicy = 'refuse' | 'allow';
331
+ export interface GeminiEmbedderOptions extends GoogleGenAIConnectionOptions {
332
+ /**
333
+ * Embedding model id. Default `'gemini-embedding-001'`.
334
+ *
335
+ * Two are known by name (see {@link GEMINI_EMBEDDING_MODELS}); anything else
336
+ * is a model this library has never met, so pass `dimensions` with it — its
337
+ * vector length is not something this can know, and a wrong `.dimensions`
338
+ * corrupts a vector store in silence.
339
+ */
340
+ readonly model?: string;
341
+ /**
342
+ * Vector length to request (`outputDimensionality`).
343
+ *
344
+ * Both known models are Matryoshka models: they emit 3072 numbers and can be
345
+ * asked for fewer, and the shorter vector is a genuinely usable embedding
346
+ * rather than a slice of a longer one. The value is SENT to the model AND
347
+ * reported as `.dimensions`, so the two can never disagree. Google recommends
348
+ * 768, 1536 or 3072; any length up to the model's native size is accepted.
349
+ *
350
+ * Required for a model outside {@link GEMINI_EMBEDDING_MODELS}.
351
+ *
352
+ * Note for stores that rank by DOT PRODUCT or euclidean distance: Google's
353
+ * shortened vectors are not re-normalised, and Google recommends normalising
354
+ * them yourself. Nothing in this library needs it — `cosineSimilarity`
355
+ * divides by both magnitudes — so this adapter returns the model's numbers
356
+ * unchanged rather than quietly rescaling what you store.
357
+ */
358
+ readonly dimensions?: number;
359
+ /**
360
+ * Pin `task_type` instead of deriving it from the call.
361
+ *
362
+ * Unset — the default — `embed()` sends `RETRIEVAL_QUERY` and `embedBatch()`
363
+ * sends `RETRIEVAL_DOCUMENT`, because that is what this library's own two
364
+ * call sites are: retrieval embeds ONE question (`loadRelevant`), indexing
365
+ * embeds MANY passages (`indexDocuments`, `embedMessages`). Pin it when your
366
+ * own code uses the two calls differently, or when the objective is
367
+ * classification or clustering rather than search.
368
+ *
369
+ * Refused by name on a model that does not take the parameter.
370
+ */
371
+ readonly taskType?: GeminiEmbeddingTaskType;
372
+ /**
373
+ * What to do when the service reports it CLIPPED the input. Default
374
+ * `'refuse'`.
375
+ *
376
+ * Over-long input is not rejected by Gemini — it is silently truncated, and a
377
+ * full-looking vector comes back for the opening of the passage. An indexer
378
+ * then stores the whole chunk as the passage and the clipped vector as its
379
+ * index, so retrieval cannot find text that is visibly present in the passage
380
+ * it later serves. Nothing throws, nothing scores zero; the corpus is quietly,
381
+ * partially indexed.
382
+ *
383
+ * `.maxInputChars` exists to stop that BEFORE the call, and it is an
384
+ * assumption (see {@link CHARS_PER_TOKEN}) — dense text tokenises tighter.
385
+ * `statistics.truncated` is the service saying it happened anyway, and this
386
+ * adapter is the only thing that sees it. `'refuse'` turns it into an error
387
+ * naming the fix; `'allow'` returns the clipped vector, which is what every
388
+ * library that does not look at the field already does.
389
+ *
390
+ * Detection depends on the service RETURNING `statistics` — Vertex does. When
391
+ * it is absent this adapter cannot tell, and says so here rather than
392
+ * implying a guarantee.
393
+ */
394
+ readonly onTruncation?: GeminiTruncationPolicy;
395
+ /**
396
+ * The longest input this model reads whole, in CHARACTERS
397
+ * ({@link Embedder.maxInputChars}). Declared for every known model from its
398
+ * documented token window; this option is how a model this library does not
399
+ * know states its own. An explicit value always wins.
400
+ */
401
+ readonly maxInputChars?: number;
402
+ /** @internal Test injection — skips the SDK require entirely. */
403
+ readonly _client?: GeminiEmbedClientLike;
404
+ }
405
+ /**
406
+ * Google's hosted embeddings, through `models.embedContent` — on Vertex or on
407
+ * the Gemini API.
408
+ *
409
+ * The door is chosen exactly as `gemini()` chooses it: `{ project, location }`
410
+ * is Vertex with Application Default Credentials, `{ apiKey }` is the Gemini
411
+ * API, and neither is guessed. One `GoogleGenAI` client serves both this and
412
+ * the LLM provider, so an app that talks to both can build the client once and
413
+ * pass it as `_client`.
414
+ *
415
+ * ── Why the id carries the DIMENSION COUNT ───────────────────────────────
416
+ * `'gemini:<model>:<dims>'`, for the reason `bedrockEmbedder` gives at length:
417
+ * `MemoryEntry.embeddingModel` stores the id ALONE and it is the only thing
418
+ * `SearchOptions.embedderId` filters on. One model id at 768 and the same model
419
+ * id at 3072 are different embedding spaces, so an id without the size cannot
420
+ * tell them apart — and neither can the size alone, since both known models are
421
+ * 3072 natively.
422
+ *
423
+ * The `taskType` is deliberately NOT in the id, matching `bedrockEmbedder`'s
424
+ * treatment of Cohere's `input_type`: the default query/document pair is ONE
425
+ * space by construction (the two projections exist to be compared with each
426
+ * other), and a pinned value pins both call sites at once, so the store stays
427
+ * self-consistent either way.
428
+ *
429
+ * ── The input ceiling, and the service telling on itself (9.1.0) ─────────
430
+ * `.maxInputChars` is per MODEL, from its documented token window at the stated
431
+ * {@link CHARS_PER_TOKEN} assumption: **8,000** for `gemini-embedding-001`
432
+ * (2,048 tokens) and **32,000** for `gemini-embedding-2` (8,192). A quarter of
433
+ * the newer model's window is not a rounding difference — it is the whole
434
+ * reason the ceiling is per-model. Past the ceiling Gemini CLIPS rather than
435
+ * refuses, so `onTruncation` decides what happens when the response admits it.
436
+ *
437
+ * @throws if `model` is unknown and `dimensions` was not supplied; if
438
+ * `dimensions` exceeds what the model can produce; if `taskType` is set
439
+ * on a model that takes none; if neither a project nor an API key is
440
+ * resolvable; or if `@google/genai` is not installed and no `_client`
441
+ * was passed.
442
+ *
443
+ * @example
444
+ * ```ts
445
+ * import { geminiEmbedder } from 'agentfootprint/providers';
446
+ * import { sqliteVectorStore } from 'agentfootprint/memory';
447
+ * import { indexFolder } from 'agentfootprint/rag';
448
+ *
449
+ * const embedder = geminiEmbedder({ project: 'my-project', dimensions: 768 });
450
+ * await indexFolder('./docs', { to: sqliteVectorStore({ file: './corpus.db' }), embedder });
451
+ * ```
452
+ *
453
+ * @example The Gemini API door, and a store that must never hold a clipped vector
454
+ * ```ts
455
+ * const embedder = geminiEmbedder({
456
+ * apiKey: process.env.GEMINI_API_KEY,
457
+ * onTruncation: 'refuse', // the default — stated here because it is the point
458
+ * });
459
+ * ```
460
+ */
461
+ export declare function geminiEmbedder(options?: GeminiEmbedderOptions): Embedder;
275
462
  /**
276
463
  * The slice of `@huggingface/transformers` {@link localEmbedder} uses.
277
464
  *
@@ -1,4 +1,5 @@
1
1
  import { lazyRequire } from '../lib/lazyRequire.js';
2
+ import { resolveGoogleGenAIClient, } from '../adapters/llm/googleGenAI.js';
2
3
  /**
3
4
  * Native output size of every OpenAI embedding model, so `.dimensions` reports
4
5
  * the truth instead of one hard-coded guess.
@@ -543,6 +544,236 @@ function describeShape(value) {
543
544
  }
544
545
  return typeof value;
545
546
  }
547
+ /**
548
+ * The Gemini embedding models this library knows by name.
549
+ *
550
+ * **`gemini-embedding-001`** — text only, 3072 dimensions, a **2,048-token**
551
+ * input window, and the full `task_type` vocabulary. It is the default because
552
+ * this library's two call sites are a query and a passage, and the query /
553
+ * document task types are exactly that distinction.
554
+ *
555
+ * **`gemini-embedding-2`** — natively multimodal, 3072 dimensions, an
556
+ * **8,192-token** text window, and **no `task_type` parameter at all**: Google
557
+ * replaced it with task instructions written into the prompt. That difference
558
+ * is why the field is a per-model fact here rather than a constant — asked for
559
+ * on the model that does not take it, `task_type` is a request field the
560
+ * service will reject, and this adapter refuses first with the reason.
561
+ *
562
+ * A model outside this table has a length this library cannot know and must
563
+ * state its own — the same rule `openaiEmbedder` and `bedrockEmbedder` apply,
564
+ * for the same reason.
565
+ */
566
+ const GEMINI_EMBEDDING_MODELS = {
567
+ 'gemini-embedding-001': { dimensions: 3072, maxInputTokens: 2048, supportsTaskType: true },
568
+ 'gemini-embedding-2': { dimensions: 3072, maxInputTokens: 8192, supportsTaskType: false },
569
+ };
570
+ /**
571
+ * How many texts Gemini embeds in ONE `embedContent` call: **one**.
572
+ *
573
+ * Not a conservative choice — the documented limit. `gemini-embedding-001`
574
+ * accepts exactly one input text per request, and libraries that assumed
575
+ * otherwise (batching 96 or 250 instances the way an OpenAI or Titan client
576
+ * would) send an oversized request that the service rejects on every batch
577
+ * bigger than a single text. `embedBatch` is therefore N sequential calls, and
578
+ * says so rather than looking like a batch that is secretly a loop.
579
+ */
580
+ const GEMINI_MAX_TEXTS_PER_CALL = 1;
581
+ /**
582
+ * Google's hosted embeddings, through `models.embedContent` — on Vertex or on
583
+ * the Gemini API.
584
+ *
585
+ * The door is chosen exactly as `gemini()` chooses it: `{ project, location }`
586
+ * is Vertex with Application Default Credentials, `{ apiKey }` is the Gemini
587
+ * API, and neither is guessed. One `GoogleGenAI` client serves both this and
588
+ * the LLM provider, so an app that talks to both can build the client once and
589
+ * pass it as `_client`.
590
+ *
591
+ * ── Why the id carries the DIMENSION COUNT ───────────────────────────────
592
+ * `'gemini:<model>:<dims>'`, for the reason `bedrockEmbedder` gives at length:
593
+ * `MemoryEntry.embeddingModel` stores the id ALONE and it is the only thing
594
+ * `SearchOptions.embedderId` filters on. One model id at 768 and the same model
595
+ * id at 3072 are different embedding spaces, so an id without the size cannot
596
+ * tell them apart — and neither can the size alone, since both known models are
597
+ * 3072 natively.
598
+ *
599
+ * The `taskType` is deliberately NOT in the id, matching `bedrockEmbedder`'s
600
+ * treatment of Cohere's `input_type`: the default query/document pair is ONE
601
+ * space by construction (the two projections exist to be compared with each
602
+ * other), and a pinned value pins both call sites at once, so the store stays
603
+ * self-consistent either way.
604
+ *
605
+ * ── The input ceiling, and the service telling on itself (9.1.0) ─────────
606
+ * `.maxInputChars` is per MODEL, from its documented token window at the stated
607
+ * {@link CHARS_PER_TOKEN} assumption: **8,000** for `gemini-embedding-001`
608
+ * (2,048 tokens) and **32,000** for `gemini-embedding-2` (8,192). A quarter of
609
+ * the newer model's window is not a rounding difference — it is the whole
610
+ * reason the ceiling is per-model. Past the ceiling Gemini CLIPS rather than
611
+ * refuses, so `onTruncation` decides what happens when the response admits it.
612
+ *
613
+ * @throws if `model` is unknown and `dimensions` was not supplied; if
614
+ * `dimensions` exceeds what the model can produce; if `taskType` is set
615
+ * on a model that takes none; if neither a project nor an API key is
616
+ * resolvable; or if `@google/genai` is not installed and no `_client`
617
+ * was passed.
618
+ *
619
+ * @example
620
+ * ```ts
621
+ * import { geminiEmbedder } from 'agentfootprint/providers';
622
+ * import { sqliteVectorStore } from 'agentfootprint/memory';
623
+ * import { indexFolder } from 'agentfootprint/rag';
624
+ *
625
+ * const embedder = geminiEmbedder({ project: 'my-project', dimensions: 768 });
626
+ * await indexFolder('./docs', { to: sqliteVectorStore({ file: './corpus.db' }), embedder });
627
+ * ```
628
+ *
629
+ * @example The Gemini API door, and a store that must never hold a clipped vector
630
+ * ```ts
631
+ * const embedder = geminiEmbedder({
632
+ * apiKey: process.env.GEMINI_API_KEY,
633
+ * onTruncation: 'refuse', // the default — stated here because it is the point
634
+ * });
635
+ * ```
636
+ */
637
+ export function geminiEmbedder(options = {}) {
638
+ const model = options.model ?? 'gemini-embedding-001';
639
+ const facts = GEMINI_EMBEDDING_MODELS[model];
640
+ const requested = options.dimensions;
641
+ const dimensions = requested ?? facts?.dimensions;
642
+ if (dimensions === undefined) {
643
+ throw new Error(`geminiEmbedder: unknown model '${model}' — its vector length is not something this ` +
644
+ `library can know, and reporting a wrong .dimensions silently corrupts a vector store. ` +
645
+ `Pass { dimensions } with the length that model returns.`);
646
+ }
647
+ if (requested !== undefined && (!Number.isInteger(requested) || requested < 1)) {
648
+ throw new Error(`geminiEmbedder: \`dimensions\` must be a positive whole number — received ` +
649
+ `${String(requested)}.`);
650
+ }
651
+ if (requested !== undefined && facts !== undefined && requested > facts.dimensions) {
652
+ throw new Error(`geminiEmbedder: '${model}' produces at most ${facts.dimensions} dimensions — received ` +
653
+ `${String(requested)}. Asking for more would be reported as .dimensions and never be ` +
654
+ `the length that comes back.`);
655
+ }
656
+ if (options.taskType !== undefined && facts !== undefined && !facts.supportsTaskType) {
657
+ throw new Error(`geminiEmbedder: '${model}' takes no \`task_type\` — Google replaced the parameter with ` +
658
+ `task instructions written into the text itself on this model. Drop \`taskType\` and ` +
659
+ `prefix your input (for example "task: search result | query: …"), or use ` +
660
+ `'gemini-embedding-001', which does take it.`);
661
+ }
662
+ const sendTaskType = facts?.supportsTaskType ?? true;
663
+ const maxInputChars = options.maxInputChars ?? charsFor(facts?.maxInputTokens);
664
+ const onTruncation = options.onTruncation ?? 'refuse';
665
+ let client;
666
+ const connect = () => {
667
+ client ??= resolveGoogleGenAIClient(options, 'geminiEmbedder', options._client);
668
+ return client;
669
+ };
670
+ /**
671
+ * ONE `embedContent` round-trip, carrying however many texts the model takes
672
+ * ({@link GEMINI_MAX_TEXTS_PER_CALL}) — one, today. Written as a slice rather
673
+ * than a single string so that the day Google raises the limit is a
674
+ * one-number change here and nowhere else.
675
+ */
676
+ async function embedChunk(texts, taskType, signal) {
677
+ const config = {
678
+ ...(sendTaskType && { taskType: options.taskType ?? taskType }),
679
+ ...(requested !== undefined && { outputDimensionality: requested }),
680
+ ...(signal && { abortSignal: signal }),
681
+ };
682
+ const response = await connect().models.embedContent({
683
+ model,
684
+ contents: [...texts],
685
+ ...(Object.keys(config).length > 0 && { config }),
686
+ });
687
+ return readGeminiEmbeddings(response, model, dimensions, texts, onTruncation);
688
+ }
689
+ /** The calls one batch becomes, in order, one chunk at a time. */
690
+ async function embedAll(texts, taskType, signal) {
691
+ const out = [];
692
+ for (let i = 0; i < texts.length; i += GEMINI_MAX_TEXTS_PER_CALL) {
693
+ signal?.throwIfAborted();
694
+ out.push(...(await embedChunk(texts.slice(i, i + GEMINI_MAX_TEXTS_PER_CALL), taskType, signal)));
695
+ }
696
+ return out;
697
+ }
698
+ return {
699
+ dimensions,
700
+ // The embedding SPACE, size included — see the note above.
701
+ id: `gemini:${model}:${dimensions}`,
702
+ // Spread, so a model this factory does not know declares NO ceiling rather
703
+ // than one it cannot stand behind.
704
+ ...(maxInputChars !== undefined && { maxInputChars }),
705
+ async embed({ text, signal }) {
706
+ // ONE text is this library's query shape (`loadRelevant` embeds the
707
+ // question).
708
+ return (await embedChunk([text], 'RETRIEVAL_QUERY', signal))[0];
709
+ },
710
+ async embedBatch({ texts, signal }) {
711
+ // MANY texts is this library's document shape, and Gemini embeds a
712
+ // document into a different place from a query on purpose.
713
+ //
714
+ // Sequential rather than parallel: callers of this path are usually
715
+ // indexing a whole corpus, and `indexCorpus` already fans out over
716
+ // batches with its own bounded parallelism and retry. Racing N more
717
+ // requests inside one of those branches is how a corpus index meets a
718
+ // throttling error. The abort is checked between calls as well as passed
719
+ // into each one.
720
+ return embedAll(texts, 'RETRIEVAL_DOCUMENT', signal);
721
+ },
722
+ };
723
+ }
724
+ /**
725
+ * Read the vectors out of an `embedContent` response — and refuse everything
726
+ * else by name.
727
+ *
728
+ * Four refusals, each for a failure that would otherwise be silent:
729
+ * • a count that disagrees with the texts sent — vectors are paired with
730
+ * texts by POSITION, so a short answer does not lose one passage, it
731
+ * re-labels every later one;
732
+ * • no readable `values` — an embedder that returns `[]` writes a row a store
733
+ * will never rank, which is indistinguishable from "the corpus does not
734
+ * mention that";
735
+ * • a length that disagrees with the declared `.dimensions` — the number a
736
+ * vector store fingerprints on;
737
+ * • `statistics.truncated` under `onTruncation: 'refuse'` — the service
738
+ * saying it indexed the opening of the passage and threw the rest away.
739
+ */
740
+ function readGeminiEmbeddings(response, model, dimensions, texts, onTruncation) {
741
+ const rows = response.embeddings;
742
+ if (!Array.isArray(rows) || rows.length !== texts.length) {
743
+ throw new Error(`geminiEmbedder: '${model}' was sent ${texts.length} text(s) and answered with ` +
744
+ `${Array.isArray(rows) ? rows.length : describeShape(response)} — vectors are paired ` +
745
+ `with texts by POSITION, so a mismatched answer would attach every later vector to the ` +
746
+ `wrong passage, and the corpus would rank confidently and wrongly forever.`);
747
+ }
748
+ return rows.map((row, index) => {
749
+ const values = row?.values;
750
+ if (!Array.isArray(values) || !values.every((n) => typeof n === 'number')) {
751
+ throw new Error(`geminiEmbedder: '${model}' returned no \`embeddings[${index}].values\` array. The ` +
752
+ `service answered with ${describeShape(response)}, which this adapter cannot read — ` +
753
+ `check that the model id names an EMBEDDING model.`);
754
+ }
755
+ if (values.length !== dimensions) {
756
+ throw new Error(`geminiEmbedder: '${model}' answered with a ${values.length}-number vector while this ` +
757
+ `embedder reports .dimensions ${dimensions}. A store fingerprints on the reported ` +
758
+ `number, so writing this vector would make the store's own record of its shape a lie. ` +
759
+ `Pass { dimensions: ${values.length} } if that is the size you meant.`);
760
+ }
761
+ if (row?.statistics?.truncated === true && onTruncation === 'refuse') {
762
+ const chars = (texts[index] ?? '').length;
763
+ throw new Error(`geminiEmbedder: '${model}' CLIPPED this input — it reported ` +
764
+ `\`statistics.truncated\`, so the vector represents only the opening of the ` +
765
+ `${chars.toLocaleString('en-US')}-character text it was given. Storing it would ` +
766
+ `index a passage by a prefix of itself, and retrieval would then fail to find words ` +
767
+ `the passage visibly contains.\n` +
768
+ ` Fix 1 — cut smaller: pass \`maxChunkChars\` to the indexer (it wins over this ` +
769
+ `embedder's declared .maxInputChars, which is a characters-per-token ASSUMPTION and ` +
770
+ `is optimistic for code, tables and CJK).\n` +
771
+ ` Fix 2 — \`onTruncation: 'allow'\` if a prefix embedding is genuinely what you ` +
772
+ `want. The vector is returned unchanged; only this refusal goes away.`);
773
+ }
774
+ return [...values];
775
+ });
776
+ }
546
777
  /**
547
778
  * The MEASURED cliff of the default model, and the origin of the whole
548
779
  * mechanism: `Xenova/all-MiniLM-L6-v2` silently truncates at 512 wordpiece