@gullabs/google 0.2.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/LICENSE +191 -0
- package/README.md +46 -0
- package/dist/index.cjs +918 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +591 -0
- package/dist/index.d.ts +591 -0
- package/dist/index.js +910 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { AuthMaterial, ProviderAdapter, Logger } from '@gullabs/core';
|
|
2
|
+
import { ZodTypeAny } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Structural GeminiClientLike interface + buildGoogleClient factory.
|
|
6
|
+
*
|
|
7
|
+
* This module defines the structural interface the adapter depends on.
|
|
8
|
+
* The real @google/genai SDK is imported ONLY in buildGoogleClient so tests
|
|
9
|
+
* can inject a fake without pulling in the real SDK.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default HTTP transport timeout for Gemini Flex service-tier calls (ms).
|
|
16
|
+
*
|
|
17
|
+
* The default per-attempt transport (httpOptions) timeout applied ONLY when a
|
|
18
|
+
* flex call provides no explicit `timeoutMs`. Set to 25 minutes (1_500_000 ms)
|
|
19
|
+
* to cover the ~20-minute flex workload ceiling with margin. The @google/genai
|
|
20
|
+
* SDK defaults to 1 minute, which would terminate a long flex call prematurely.
|
|
21
|
+
* Callers SHOULD still set `timeoutMs` for a precise per-attempt ceiling; this
|
|
22
|
+
* value is only the backstop for callers that do not.
|
|
23
|
+
*/
|
|
24
|
+
declare const FLEX_DEFAULT_TIMEOUT_MS = 1500000;
|
|
25
|
+
/**
|
|
26
|
+
* Buffer added above `timeoutMs` when setting the SDK transport timeout.
|
|
27
|
+
*
|
|
28
|
+
* When `timeoutMs` is set, the engine arms an AbortSignal at exactly
|
|
29
|
+
* `timeoutMs`. If the SDK transport timer fired at the same instant it
|
|
30
|
+
* could preempt the signal and produce a raw SDK error instead of the
|
|
31
|
+
* engine's clean `kind:'timeout'`. By setting the transport timeout to
|
|
32
|
+
* `timeoutMs + TRANSPORT_TIMEOUT_BUFFER_MS` we ensure the engine's
|
|
33
|
+
* AbortSignal always fires first.
|
|
34
|
+
*/
|
|
35
|
+
declare const TRANSPORT_TIMEOUT_BUFFER_MS = 5000;
|
|
36
|
+
/** A single text/thought part in a Gemini candidate content. */
|
|
37
|
+
interface GeminiPartShape {
|
|
38
|
+
text?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Present and `true` on thought-summary parts.
|
|
41
|
+
* Real field name in @google/genai Candidate.content.parts: `thought`.
|
|
42
|
+
*/
|
|
43
|
+
thought?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** A candidate returned by Gemini generateContent. */
|
|
46
|
+
interface GeminiCandidateShape {
|
|
47
|
+
content?: {
|
|
48
|
+
parts?: GeminiPartShape[];
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Why the model stopped.
|
|
52
|
+
* Real SDK enum (FinishReason): "STOP", "MAX_TOKENS", "SAFETY",
|
|
53
|
+
* "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", etc.
|
|
54
|
+
*/
|
|
55
|
+
finishReason?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Grounding metadata returned when Google Search grounding is active.
|
|
58
|
+
* Real SDK type: GroundingMetadata. Kept as `unknown` to avoid a hard
|
|
59
|
+
* coupling to @google/genai types; cast to JsonValue at the adapter boundary.
|
|
60
|
+
*/
|
|
61
|
+
groundingMetadata?: unknown;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Token usage metadata returned alongside a Gemini response.
|
|
65
|
+
*
|
|
66
|
+
* Real type: GenerateContentResponseUsageMetadata.
|
|
67
|
+
* NOTE: thoughtsTokenCount is SEPARATE from candidatesTokenCount.
|
|
68
|
+
* The adapter must add them to get GROSS outputTokens.
|
|
69
|
+
*/
|
|
70
|
+
interface GeminiUsageMetadataShape {
|
|
71
|
+
promptTokenCount?: number;
|
|
72
|
+
candidatesTokenCount?: number;
|
|
73
|
+
cachedContentTokenCount?: number;
|
|
74
|
+
thoughtsTokenCount?: number;
|
|
75
|
+
totalTokenCount?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Structural equivalent of @google/genai's GenerateContentResponse.
|
|
79
|
+
* Only the fields the adapter reads are represented here.
|
|
80
|
+
*/
|
|
81
|
+
interface GeminiResponseShape {
|
|
82
|
+
candidates?: GeminiCandidateShape[];
|
|
83
|
+
usageMetadata?: GeminiUsageMetadataShape;
|
|
84
|
+
/** Real field: GenerateContentResponse.modelVersion */
|
|
85
|
+
modelVersion?: string;
|
|
86
|
+
/** Real field: GenerateContentResponse.responseId */
|
|
87
|
+
responseId?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Safety-block metadata.
|
|
90
|
+
* Real type: GenerateContentResponsePromptFeedback.
|
|
91
|
+
* Present when the prompt (not output) was blocked by safety filters.
|
|
92
|
+
*/
|
|
93
|
+
promptFeedback?: {
|
|
94
|
+
/** Real type: BlockedReason (string enum). e.g. "SAFETY", "OTHER". */
|
|
95
|
+
blockReason?: string;
|
|
96
|
+
blockReasonMessage?: string;
|
|
97
|
+
safetyRatings?: unknown[];
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Per-part media-resolution hint emitted on inline/file parts.
|
|
102
|
+
* Real type: @google/genai `PartMediaResolution`; `level` values come from
|
|
103
|
+
* the `PartMediaResolutionLevel` string enum (we only emit the LOW/MEDIUM/HIGH
|
|
104
|
+
* subset our normalized `mediaResolution` maps to).
|
|
105
|
+
*/
|
|
106
|
+
interface GeminiPartMediaResolution {
|
|
107
|
+
level?: 'MEDIA_RESOLUTION_LOW' | 'MEDIA_RESOLUTION_MEDIUM' | 'MEDIA_RESOLUTION_HIGH';
|
|
108
|
+
}
|
|
109
|
+
/** A text part in a content object we construct. */
|
|
110
|
+
interface GeminiTextContentPart {
|
|
111
|
+
text: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* An inline binary media part in a content object we construct.
|
|
115
|
+
* `data` must be raw base64 — no `data:…;base64,` prefix.
|
|
116
|
+
*/
|
|
117
|
+
interface GeminiInlineDataContentPart {
|
|
118
|
+
inlineData: {
|
|
119
|
+
/** IANA media type, e.g. `"image/png"`. */
|
|
120
|
+
mimeType: string;
|
|
121
|
+
/** Raw base64-encoded bytes (no data-URL prefix). */
|
|
122
|
+
data: string;
|
|
123
|
+
};
|
|
124
|
+
/** Optional per-part media-resolution hint (real field: `Part.mediaResolution`). */
|
|
125
|
+
mediaResolution?: GeminiPartMediaResolution;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A provider-hosted file reference part in a content object we construct.
|
|
129
|
+
* The Gemini service dereferences `fileUri` server-side.
|
|
130
|
+
*/
|
|
131
|
+
interface GeminiFileDataContentPart {
|
|
132
|
+
fileData: {
|
|
133
|
+
/** IANA media type of the referenced file. */
|
|
134
|
+
mimeType: string;
|
|
135
|
+
/** Provider-assigned file URI, e.g. from the Gemini File API. */
|
|
136
|
+
fileUri: string;
|
|
137
|
+
};
|
|
138
|
+
/** Optional per-part media-resolution hint (real field: `Part.mediaResolution`). */
|
|
139
|
+
mediaResolution?: GeminiPartMediaResolution;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Union of all part shapes the adapter may produce for `GeminiContent.parts`.
|
|
143
|
+
* Each member (including the optional per-part `mediaResolution`) is a
|
|
144
|
+
* structural subset of the real `@google/genai` `Part` type for the fields we use.
|
|
145
|
+
*/
|
|
146
|
+
type GeminiContentPart = GeminiTextContentPart | GeminiInlineDataContentPart | GeminiFileDataContentPart;
|
|
147
|
+
/** A content object (message) we construct. */
|
|
148
|
+
interface GeminiContent {
|
|
149
|
+
role: string;
|
|
150
|
+
parts: GeminiContentPart[];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Schema shape we pass as responseSchema.
|
|
154
|
+
* Structurally compatible with @google/genai Schema.
|
|
155
|
+
*/
|
|
156
|
+
interface GeminiSchema {
|
|
157
|
+
type?: string;
|
|
158
|
+
description?: string;
|
|
159
|
+
properties?: Record<string, GeminiSchema>;
|
|
160
|
+
required?: string[];
|
|
161
|
+
items?: GeminiSchema;
|
|
162
|
+
enum?: string[];
|
|
163
|
+
nullable?: boolean;
|
|
164
|
+
format?: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Thinking configuration.
|
|
168
|
+
* Real type: ThinkingConfig in @google/genai.
|
|
169
|
+
* - thinkingBudget: 0 = DISABLED, -1 = AUTOMATIC
|
|
170
|
+
* - thinkingLevel: ThinkingLevel enum ("LOW", "MEDIUM", "HIGH", "MINIMAL")
|
|
171
|
+
*/
|
|
172
|
+
interface GeminiThinkingConfig {
|
|
173
|
+
includeThoughts?: boolean;
|
|
174
|
+
thinkingBudget?: number;
|
|
175
|
+
/**
|
|
176
|
+
* Real type: ThinkingLevel enum.
|
|
177
|
+
* Values: "LOW" | "MEDIUM" | "HIGH" | "MINIMAL" | "THINKING_LEVEL_UNSPECIFIED"
|
|
178
|
+
*/
|
|
179
|
+
thinkingLevel?: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Config object passed in GenerateContentParameters.config.
|
|
183
|
+
* Real type: GenerateContentConfig.
|
|
184
|
+
* abortSignal is inside config, NOT in the top-level params.
|
|
185
|
+
*/
|
|
186
|
+
interface GeminiGenerateConfig {
|
|
187
|
+
systemInstruction?: {
|
|
188
|
+
parts: GeminiContentPart[];
|
|
189
|
+
};
|
|
190
|
+
temperature?: number;
|
|
191
|
+
topP?: number;
|
|
192
|
+
topK?: number;
|
|
193
|
+
maxOutputTokens?: number;
|
|
194
|
+
stopSequences?: string[];
|
|
195
|
+
responseMimeType?: string;
|
|
196
|
+
responseSchema?: GeminiSchema;
|
|
197
|
+
thinkingConfig?: GeminiThinkingConfig;
|
|
198
|
+
/** Real type: ServiceTier enum. Values: "flex" | "standard". */
|
|
199
|
+
serviceTier?: string;
|
|
200
|
+
/** Real field: GenerateContentConfig.abortSignal (NOT in top-level params). */
|
|
201
|
+
abortSignal?: AbortSignal;
|
|
202
|
+
/**
|
|
203
|
+
* Per-request HTTP options forwarded to the @google/genai transport.
|
|
204
|
+
* We use this to set a transport-level timeout that is >= the AbortSignal
|
|
205
|
+
* deadline so the SDK fetch does not preempt the abort.
|
|
206
|
+
*
|
|
207
|
+
* Real field: GenerateContentConfig.httpOptions.timeout (milliseconds).
|
|
208
|
+
* Real field: GenerateContentConfig.httpOptions.headers (Record<string,string>).
|
|
209
|
+
* Used by FIX A-1 to inject Vertex flex routing headers.
|
|
210
|
+
*/
|
|
211
|
+
httpOptions?: {
|
|
212
|
+
timeout?: number;
|
|
213
|
+
headers?: Record<string, string>;
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Parameters for models.generateContent.
|
|
218
|
+
* Real type: GenerateContentParameters.
|
|
219
|
+
*/
|
|
220
|
+
interface GeminiGenerateParams {
|
|
221
|
+
model: string;
|
|
222
|
+
contents: GeminiContent[];
|
|
223
|
+
config?: GeminiGenerateConfig;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Structural interface for the @google/genai client surface the adapter uses.
|
|
227
|
+
*
|
|
228
|
+
* Satisfied by:
|
|
229
|
+
* - The real GoogleGenAI client (via buildGoogleClient wrapper).
|
|
230
|
+
* - FakeGeminiClient from @gullabs/testing (its generateContent accepts unknown).
|
|
231
|
+
*/
|
|
232
|
+
interface GeminiClientLike {
|
|
233
|
+
models: {
|
|
234
|
+
generateContent(params: GeminiGenerateParams): Promise<GeminiResponseShape>;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Build a real @google/genai client from AuthMaterial.
|
|
239
|
+
*
|
|
240
|
+
* Returns a GeminiClientLike wrapper around the real GoogleGenAI client.
|
|
241
|
+
* Only API-key authentication is supported; Vertex AI is not.
|
|
242
|
+
*
|
|
243
|
+
* @param auth - API key credentials ({ apiKey }).
|
|
244
|
+
*/
|
|
245
|
+
declare function buildGoogleClient(auth: AuthMaterial): Promise<GeminiClientLike>;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* geminiAdapter — @gullabs/google Gemini provider adapter.
|
|
249
|
+
*
|
|
250
|
+
* Pure request⇄response mapping over @google/genai (via GeminiClientLike).
|
|
251
|
+
* Never persists, never computes cost, never loops.
|
|
252
|
+
*
|
|
253
|
+
* @module
|
|
254
|
+
*/
|
|
255
|
+
|
|
256
|
+
interface GeminiAdapterOptions {
|
|
257
|
+
/**
|
|
258
|
+
* Inject a pre-built client (real or fake).
|
|
259
|
+
* When omitted, `buildGoogleClient` is called with `ctx.auth` at call time,
|
|
260
|
+
* inside the classified try/catch so any construction failure is wrapped
|
|
261
|
+
* as a typed `LlmError`.
|
|
262
|
+
*/
|
|
263
|
+
client?: GeminiClientLike;
|
|
264
|
+
/**
|
|
265
|
+
* @internal Testing-only.
|
|
266
|
+
*
|
|
267
|
+
* Override the default `buildGoogleClient` factory. Allows unit tests to
|
|
268
|
+
* simulate construction failures (e.g. bad credentials) without importing
|
|
269
|
+
* the real `@google/genai` SDK. Never set this in production code.
|
|
270
|
+
*/
|
|
271
|
+
_clientFactory?: (auth: AuthMaterial) => GeminiClientLike | Promise<GeminiClientLike>;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Create a Gemini provider adapter.
|
|
275
|
+
*
|
|
276
|
+
* @param opts.client - Optional pre-built client (e.g. for testing).
|
|
277
|
+
*/
|
|
278
|
+
declare function geminiAdapter(opts?: GeminiAdapterOptions): ProviderAdapter;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* zodToGeminiSchema — best-effort Zod-to-Gemini Schema converter.
|
|
282
|
+
*
|
|
283
|
+
* Supports common shapes: object, string, number (int detection), boolean,
|
|
284
|
+
* array, enum, optional, nullable, default, literal.
|
|
285
|
+
*
|
|
286
|
+
* Returns `undefined` for unsupported shapes — the caller emits a Warning and
|
|
287
|
+
* falls back to plain JSON output without a responseSchema. The engine always
|
|
288
|
+
* Zod-validates output regardless; this is only a best-effort model hint.
|
|
289
|
+
*
|
|
290
|
+
* @module
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Convert a Zod schema to a Gemini Schema object.
|
|
295
|
+
*
|
|
296
|
+
* @param schema - Any Zod type.
|
|
297
|
+
* @returns A GeminiSchema on success; `undefined` for unsupported shapes.
|
|
298
|
+
*/
|
|
299
|
+
declare function zodToGeminiSchema(schema: ZodTypeAny): GeminiSchema | undefined;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* GoogleFileStore — thin wrapper over the Gemini File API.
|
|
303
|
+
*
|
|
304
|
+
* Handles upload + polling until ACTIVE, and tracked deletion.
|
|
305
|
+
* Injectable sleep and client for tests (no network required).
|
|
306
|
+
*
|
|
307
|
+
* @module
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
/** A handle to a file stored in the Gemini File API. */
|
|
311
|
+
interface GoogleFileHandle {
|
|
312
|
+
/** Resource name, e.g. "files/abc123". */
|
|
313
|
+
name: string;
|
|
314
|
+
/** URI to pass as FileUriPart.uri in an LlmRequest. */
|
|
315
|
+
uri: string;
|
|
316
|
+
mimeType: string;
|
|
317
|
+
/** Provider auto-deletes ~48 h after upload. Absent when not returned. */
|
|
318
|
+
expiresAt?: Date;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Minimal structural interface for the Gemini Files client surface we use.
|
|
322
|
+
* Satisfied by the real ai.files object or a test fake.
|
|
323
|
+
*/
|
|
324
|
+
interface GeminiFilesClientLike {
|
|
325
|
+
upload(params: {
|
|
326
|
+
file: Uint8Array | Blob;
|
|
327
|
+
config?: {
|
|
328
|
+
mimeType?: string;
|
|
329
|
+
displayName?: string;
|
|
330
|
+
};
|
|
331
|
+
}): Promise<{
|
|
332
|
+
name?: string;
|
|
333
|
+
uri?: string;
|
|
334
|
+
mimeType?: string;
|
|
335
|
+
state?: string;
|
|
336
|
+
expirationTime?: string;
|
|
337
|
+
}>;
|
|
338
|
+
get(params: {
|
|
339
|
+
name: string;
|
|
340
|
+
}): Promise<{
|
|
341
|
+
name?: string;
|
|
342
|
+
uri?: string;
|
|
343
|
+
mimeType?: string;
|
|
344
|
+
state?: string;
|
|
345
|
+
expirationTime?: string;
|
|
346
|
+
}>;
|
|
347
|
+
delete(params: {
|
|
348
|
+
name: string;
|
|
349
|
+
}): Promise<void>;
|
|
350
|
+
}
|
|
351
|
+
interface GoogleFileStoreOptions {
|
|
352
|
+
auth: AuthMaterial;
|
|
353
|
+
/** Injectable client for tests; skips SDK import when provided. */
|
|
354
|
+
client?: GeminiFilesClientLike;
|
|
355
|
+
/** Called on delete failures instead of rethrowing. Default: console.error. */
|
|
356
|
+
onDeleteError?: (name: string, err: unknown) => void;
|
|
357
|
+
/** Optional structured logger. When provided, routes delete failures to logger.error. */
|
|
358
|
+
logger?: Logger;
|
|
359
|
+
poll?: {
|
|
360
|
+
/** Delay between state polls. Default: 3000 ms. */
|
|
361
|
+
intervalMs?: number;
|
|
362
|
+
/** Max time to wait for ACTIVE. Default: 300 000 ms (5 min). */
|
|
363
|
+
timeoutMs?: number;
|
|
364
|
+
};
|
|
365
|
+
/** Injectable sleep for tests. Default: real setTimeout. */
|
|
366
|
+
sleep?: (ms: number) => Promise<void>;
|
|
367
|
+
/** Injectable clock for deterministic tests. Default: `Date.now`. */
|
|
368
|
+
now?: () => number;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* **Auth snapshot note:** this store captures the `AuthMaterial` at construction
|
|
372
|
+
* time and memoizes a single SDK client from it (`clientPromise`). This is
|
|
373
|
+
* correct and sufficient for static API keys. If refreshable credentials
|
|
374
|
+
* (short-lived OAuth/STS tokens) are added in the future, this memoization is
|
|
375
|
+
* the seam that will need rework: the cached client would hold stale credentials
|
|
376
|
+
* for the lifetime of a long-lived store instance. At that point, the store
|
|
377
|
+
* will need to either rebuild the client on each operation or accept a
|
|
378
|
+
* credential-resolver callback rather than a plain `AuthMaterial` value.
|
|
379
|
+
* See ADR-020 in DECISIONS.md.
|
|
380
|
+
*/
|
|
381
|
+
declare class GoogleFileStore {
|
|
382
|
+
private readonly auth;
|
|
383
|
+
private readonly clientOverride;
|
|
384
|
+
private readonly onDeleteError;
|
|
385
|
+
private readonly logger;
|
|
386
|
+
private readonly intervalMs;
|
|
387
|
+
private readonly timeoutMs;
|
|
388
|
+
private readonly sleep;
|
|
389
|
+
private readonly now;
|
|
390
|
+
/** Memoised client promise — built at most once per store instance. */
|
|
391
|
+
private clientPromise;
|
|
392
|
+
constructor(opts: GoogleFileStoreOptions);
|
|
393
|
+
private getClient;
|
|
394
|
+
/**
|
|
395
|
+
* Upload bytes to the Gemini File API and wait until the file is ACTIVE.
|
|
396
|
+
*
|
|
397
|
+
* @param source - Raw bytes or Blob.
|
|
398
|
+
* @param mimeType - IANA media type, e.g. `"image/png"`.
|
|
399
|
+
* @param opts - Optional display name.
|
|
400
|
+
*/
|
|
401
|
+
upload(source: Uint8Array | Blob, mimeType: string, opts?: {
|
|
402
|
+
displayName?: string;
|
|
403
|
+
signal?: AbortSignal;
|
|
404
|
+
}): Promise<GoogleFileHandle>;
|
|
405
|
+
/**
|
|
406
|
+
* Delete a single uploaded file.
|
|
407
|
+
* Errors are forwarded to `onDeleteError` and NOT rethrown.
|
|
408
|
+
*/
|
|
409
|
+
delete(handle: GoogleFileHandle): Promise<void>;
|
|
410
|
+
/**
|
|
411
|
+
* Delete multiple files.
|
|
412
|
+
* Each failure is individually forwarded to `onDeleteError`; none are thrown.
|
|
413
|
+
*/
|
|
414
|
+
deleteAll(handles: GoogleFileHandle[]): Promise<void>;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* GoogleCacheStore — thin wrapper over the Gemini Context Cache API.
|
|
419
|
+
*
|
|
420
|
+
* Manages create / get-or-create / refresh / delete lifecycle for cached
|
|
421
|
+
* contents. In-memory cache entries are PROCESS-SCOPED; they are not shared
|
|
422
|
+
* across processes, workers, or restarts.
|
|
423
|
+
*
|
|
424
|
+
* Injectable client and `now` function keep tests free of network and clock.
|
|
425
|
+
*
|
|
426
|
+
* @module
|
|
427
|
+
*/
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* A handle to a cached content resource in the Gemini Context Cache API.
|
|
431
|
+
*
|
|
432
|
+
* Pass `cacheName` as `providerOptions.google.cachedContent` in LlmRequest.
|
|
433
|
+
*/
|
|
434
|
+
interface GoogleCacheHandle {
|
|
435
|
+
/** Resource name, e.g. "cachedContents/xyz". */
|
|
436
|
+
cacheName: string;
|
|
437
|
+
/**
|
|
438
|
+
* Local view of when the cache expires (server-authoritative; computed from
|
|
439
|
+
* ttl or parsed from the server response `expireTime`).
|
|
440
|
+
*/
|
|
441
|
+
expiresAt: Date;
|
|
442
|
+
/** Caches are model-bound; never use a handle with a different model. */
|
|
443
|
+
model: string;
|
|
444
|
+
}
|
|
445
|
+
/** Key used to look up or create an entry in the in-process cache map. */
|
|
446
|
+
interface CacheKey {
|
|
447
|
+
model: string;
|
|
448
|
+
/** Caller-chosen stable identifier, e.g. a hash of the cached content. */
|
|
449
|
+
stableKey: string;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Minimal structural interface for the Gemini Caches client surface we use.
|
|
453
|
+
* Satisfied by the real `ai.caches` object or a test fake.
|
|
454
|
+
*/
|
|
455
|
+
interface GeminiCachesClientLike {
|
|
456
|
+
create(params: {
|
|
457
|
+
model: string;
|
|
458
|
+
config: {
|
|
459
|
+
contents?: unknown;
|
|
460
|
+
systemInstruction?: unknown;
|
|
461
|
+
ttl?: string;
|
|
462
|
+
displayName?: string;
|
|
463
|
+
};
|
|
464
|
+
}): Promise<{
|
|
465
|
+
name?: string;
|
|
466
|
+
model?: string;
|
|
467
|
+
expireTime?: string;
|
|
468
|
+
}>;
|
|
469
|
+
update(params: {
|
|
470
|
+
name: string;
|
|
471
|
+
config: {
|
|
472
|
+
ttl?: string;
|
|
473
|
+
expireTime?: string;
|
|
474
|
+
};
|
|
475
|
+
}): Promise<{
|
|
476
|
+
name?: string;
|
|
477
|
+
expireTime?: string;
|
|
478
|
+
}>;
|
|
479
|
+
delete(params: {
|
|
480
|
+
name: string;
|
|
481
|
+
}): Promise<unknown>;
|
|
482
|
+
}
|
|
483
|
+
interface GoogleCacheStoreOptions {
|
|
484
|
+
auth: AuthMaterial;
|
|
485
|
+
/** Injectable client for tests; skips SDK import when provided. */
|
|
486
|
+
client?: GeminiCachesClientLike;
|
|
487
|
+
/**
|
|
488
|
+
* When true, concurrent `getOrCreate` calls for the same key share one
|
|
489
|
+
* in-flight create. Default false.
|
|
490
|
+
*/
|
|
491
|
+
coalesce?: boolean;
|
|
492
|
+
/**
|
|
493
|
+
* Subtracted from the local expiry so we stop using a cache slightly before
|
|
494
|
+
* the server evicts it. Default 30 s.
|
|
495
|
+
*/
|
|
496
|
+
expirySkewSeconds?: number;
|
|
497
|
+
/** Called on delete failures instead of rethrowing. */
|
|
498
|
+
onDeleteError?: (cacheName: string, err: unknown) => void;
|
|
499
|
+
/** Optional structured logger. When provided, routes delete failures to logger.error. */
|
|
500
|
+
logger?: Logger;
|
|
501
|
+
/** Injectable clock for deterministic tests. Default: `Date.now`. */
|
|
502
|
+
now?: () => number;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Process-scoped helper for the Gemini Context Cache API.
|
|
506
|
+
*
|
|
507
|
+
* NOTE: `getOrCreate` reuse is PROCESS-SCOPED only. Entries survive only for
|
|
508
|
+
* the lifetime of this `GoogleCacheStore` instance. Across restarts, new
|
|
509
|
+
* caches will be created (and old ones will be server-evicted after their TTL).
|
|
510
|
+
*
|
|
511
|
+
* **Auth snapshot note:** this store captures the `AuthMaterial` at construction
|
|
512
|
+
* time and memoizes a single SDK client from it (`clientPromise`). This is
|
|
513
|
+
* correct and sufficient for static API keys. If refreshable credentials
|
|
514
|
+
* (short-lived OAuth/STS tokens) are added in the future, this memoization is
|
|
515
|
+
* the seam that will need rework: the cached client would hold stale credentials
|
|
516
|
+
* for the lifetime of a long-lived store instance. At that point, the store
|
|
517
|
+
* will need to either rebuild the client on each operation or accept a
|
|
518
|
+
* credential-resolver callback rather than a plain `AuthMaterial` value.
|
|
519
|
+
* See ADR-020 in DECISIONS.md.
|
|
520
|
+
*/
|
|
521
|
+
declare class GoogleCacheStore {
|
|
522
|
+
private readonly auth;
|
|
523
|
+
private readonly clientOverride;
|
|
524
|
+
private readonly coalesce;
|
|
525
|
+
private readonly skewMs;
|
|
526
|
+
private readonly onDeleteError;
|
|
527
|
+
private readonly logger;
|
|
528
|
+
private readonly now;
|
|
529
|
+
/** Memoised client promise — built at most once per store instance. */
|
|
530
|
+
private clientPromise;
|
|
531
|
+
/** In-process cache of live handles, keyed by `${model}:${stableKey}`. */
|
|
532
|
+
private readonly entries;
|
|
533
|
+
/** In-flight create promises when coalescing is enabled. */
|
|
534
|
+
private readonly inflight;
|
|
535
|
+
constructor(opts: GoogleCacheStoreOptions);
|
|
536
|
+
private getClient;
|
|
537
|
+
private isLive;
|
|
538
|
+
/**
|
|
539
|
+
* Create a new cached content resource.
|
|
540
|
+
*
|
|
541
|
+
* The `expiresAt` on the returned handle is computed from the server's
|
|
542
|
+
* `expireTime` when available, with a local-clock fallback of
|
|
543
|
+
* `now + ttlSeconds * 1000`.
|
|
544
|
+
*/
|
|
545
|
+
create(input: {
|
|
546
|
+
model: string;
|
|
547
|
+
ttlSeconds: number;
|
|
548
|
+
contents?: unknown;
|
|
549
|
+
systemInstruction?: unknown;
|
|
550
|
+
displayName?: string;
|
|
551
|
+
}): Promise<GoogleCacheHandle>;
|
|
552
|
+
/**
|
|
553
|
+
* Return a live cached-content handle, creating one if none exists or the
|
|
554
|
+
* cached entry has expired (accounting for skew).
|
|
555
|
+
*
|
|
556
|
+
* Reuse is PROCESS-SCOPED only — this store instance's in-memory map.
|
|
557
|
+
*
|
|
558
|
+
* When `coalesce` is enabled, concurrent calls for the same key share one
|
|
559
|
+
* in-flight create.
|
|
560
|
+
*/
|
|
561
|
+
getOrCreate(key: CacheKey, factory: () => Promise<{
|
|
562
|
+
ttlSeconds: number;
|
|
563
|
+
contents?: unknown;
|
|
564
|
+
systemInstruction?: unknown;
|
|
565
|
+
}>): Promise<GoogleCacheHandle>;
|
|
566
|
+
/**
|
|
567
|
+
* Extend the cache TTL if it will expire within `thresholdSeconds`.
|
|
568
|
+
*
|
|
569
|
+
* Fail-open: if the update call throws, the original handle is returned
|
|
570
|
+
* unchanged. This method NEVER throws.
|
|
571
|
+
*
|
|
572
|
+
* @param handle - Handle to potentially refresh.
|
|
573
|
+
* @param opts.thresholdSeconds - Extend if expiry is within this many seconds.
|
|
574
|
+
* Default 300.
|
|
575
|
+
* @param opts.extensionSeconds - New TTL to set. Default: original TTL from
|
|
576
|
+
* entries map, or 3600 s when the handle was not created via `getOrCreate`.
|
|
577
|
+
*/
|
|
578
|
+
refreshIfExpiringSoon(handle: GoogleCacheHandle, opts?: {
|
|
579
|
+
thresholdSeconds?: number;
|
|
580
|
+
extensionSeconds?: number;
|
|
581
|
+
}): Promise<GoogleCacheHandle>;
|
|
582
|
+
/**
|
|
583
|
+
* Delete a cached content resource.
|
|
584
|
+
*
|
|
585
|
+
* Errors are forwarded to `onDeleteError` and NOT rethrown.
|
|
586
|
+
* The handle is removed from the in-process entries map regardless.
|
|
587
|
+
*/
|
|
588
|
+
delete(handle: GoogleCacheHandle): Promise<void>;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export { type CacheKey, FLEX_DEFAULT_TIMEOUT_MS, type GeminiAdapterOptions, type GeminiCachesClientLike, type GeminiClientLike, type GeminiFilesClientLike, type GeminiSchema, type GoogleCacheHandle, GoogleCacheStore, type GoogleCacheStoreOptions, type GoogleFileHandle, GoogleFileStore, type GoogleFileStoreOptions, TRANSPORT_TIMEOUT_BUFFER_MS, buildGoogleClient, geminiAdapter, zodToGeminiSchema };
|