@rudderjs/ai 1.5.0 → 1.6.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 +399 -0
- package/boost/guidelines.md +60 -0
- package/dist/agent.d.ts +35 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +118 -16
- package/dist/agent.js.map +1 -1
- package/dist/budget/pricing.d.ts +124 -0
- package/dist/budget/pricing.d.ts.map +1 -0
- package/dist/budget/pricing.js +175 -0
- package/dist/budget/pricing.js.map +1 -0
- package/dist/budget/storage.d.ts +104 -0
- package/dist/budget/storage.d.ts.map +1 -0
- package/dist/budget/storage.js +0 -0
- package/dist/budget/storage.js.map +1 -0
- package/dist/budget/with-budget.d.ts +119 -0
- package/dist/budget/with-budget.d.ts.map +1 -0
- package/dist/budget/with-budget.js +175 -0
- package/dist/budget/with-budget.js.map +1 -0
- package/dist/budget-orm/index.d.ts +96 -0
- package/dist/budget-orm/index.d.ts.map +1 -0
- package/dist/budget-orm/index.js +177 -0
- package/dist/budget-orm/index.js.map +1 -0
- package/dist/commands/ai-eval.d.ts +93 -0
- package/dist/commands/ai-eval.d.ts.map +1 -0
- package/dist/commands/ai-eval.js +378 -0
- package/dist/commands/ai-eval.js.map +1 -0
- package/dist/computer-use/actions.d.ts +214 -0
- package/dist/computer-use/actions.d.ts.map +1 -0
- package/dist/computer-use/actions.js +48 -0
- package/dist/computer-use/actions.js.map +1 -0
- package/dist/computer-use/errors.d.ts +57 -0
- package/dist/computer-use/errors.d.ts.map +1 -0
- package/dist/computer-use/errors.js +76 -0
- package/dist/computer-use/errors.js.map +1 -0
- package/dist/computer-use/index.d.ts +53 -0
- package/dist/computer-use/index.d.ts.map +1 -0
- package/dist/computer-use/index.js +51 -0
- package/dist/computer-use/index.js.map +1 -0
- package/dist/computer-use/playwright.d.ts +76 -0
- package/dist/computer-use/playwright.d.ts.map +1 -0
- package/dist/computer-use/playwright.js +270 -0
- package/dist/computer-use/playwright.js.map +1 -0
- package/dist/computer-use/tool.d.ts +154 -0
- package/dist/computer-use/tool.d.ts.map +1 -0
- package/dist/computer-use/tool.js +210 -0
- package/dist/computer-use/tool.js.map +1 -0
- package/dist/eval/fixtures.d.ts +65 -0
- package/dist/eval/fixtures.d.ts.map +1 -0
- package/dist/eval/fixtures.js +110 -0
- package/dist/eval/fixtures.js.map +1 -0
- package/dist/eval/html-reporter.d.ts +25 -0
- package/dist/eval/html-reporter.d.ts.map +1 -0
- package/dist/eval/html-reporter.js +209 -0
- package/dist/eval/html-reporter.js.map +1 -0
- package/dist/eval/index.d.ts +271 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +510 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/json-reporter.d.ts +43 -0
- package/dist/eval/json-reporter.d.ts.map +1 -0
- package/dist/eval/json-reporter.js +40 -0
- package/dist/eval/json-reporter.js.map +1 -0
- package/dist/fake.d.ts +36 -1
- package/dist/fake.d.ts.map +1 -1
- package/dist/fake.js +49 -2
- package/dist/fake.js.map +1 -1
- package/dist/file-search.d.ts +168 -0
- package/dist/file-search.d.ts.map +1 -0
- package/dist/file-search.js +158 -0
- package/dist/file-search.js.map +1 -0
- package/dist/index.d.ts +22 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/client-tools.d.ts +39 -0
- package/dist/mcp/client-tools.d.ts.map +1 -0
- package/dist/mcp/client-tools.js +147 -0
- package/dist/mcp/client-tools.js.map +1 -0
- package/dist/mcp/index.d.ts +16 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server-from-agent.d.ts +24 -0
- package/dist/mcp/server-from-agent.d.ts.map +1 -0
- package/dist/mcp/server-from-agent.js +113 -0
- package/dist/mcp/server-from-agent.js.map +1 -0
- package/dist/mcp/types.d.ts +64 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/memory-embedding/index.d.ts +121 -0
- package/dist/memory-embedding/index.d.ts.map +1 -0
- package/dist/memory-embedding/index.js +229 -0
- package/dist/memory-embedding/index.js.map +1 -0
- package/dist/memory-extract.d.ts +60 -0
- package/dist/memory-extract.d.ts.map +1 -0
- package/dist/memory-extract.js +163 -0
- package/dist/memory-extract.js.map +1 -0
- package/dist/memory-inject.d.ts +39 -0
- package/dist/memory-inject.d.ts.map +1 -0
- package/dist/memory-inject.js +135 -0
- package/dist/memory-inject.js.map +1 -0
- package/dist/memory-orm/index.d.ts +118 -0
- package/dist/memory-orm/index.d.ts.map +1 -0
- package/dist/memory-orm/index.js +187 -0
- package/dist/memory-orm/index.js.map +1 -0
- package/dist/memory.d.ts +55 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +132 -0
- package/dist/memory.js.map +1 -0
- package/dist/observers.d.ts +22 -0
- package/dist/observers.d.ts.map +1 -1
- package/dist/observers.js.map +1 -1
- package/dist/provider-tools.d.ts +15 -1
- package/dist/provider-tools.d.ts.map +1 -1
- package/dist/provider-tools.js +21 -1
- package/dist/provider-tools.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +61 -6
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/elevenlabs.d.ts +98 -0
- package/dist/providers/elevenlabs.d.ts.map +1 -0
- package/dist/providers/elevenlabs.js +229 -0
- package/dist/providers/elevenlabs.js.map +1 -0
- package/dist/providers/google.d.ts +83 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +491 -8
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/openai.d.ts +3 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +209 -5
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/voyage.d.ts +91 -0
- package/dist/providers/voyage.d.ts.map +1 -0
- package/dist/providers/voyage.js +166 -0
- package/dist/providers/voyage.js.map +1 -0
- package/dist/queue-job.d.ts +69 -4
- package/dist/queue-job.d.ts.map +1 -1
- package/dist/queue-job.js +114 -11
- package/dist/queue-job.js.map +1 -1
- package/dist/registry.d.ts +3 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +10 -0
- package/dist/registry.js.map +1 -1
- package/dist/server/provider.d.ts.map +1 -1
- package/dist/server/provider.js +23 -1
- package/dist/server/provider.js.map +1 -1
- package/dist/similarity-search.d.ts +163 -0
- package/dist/similarity-search.d.ts.map +1 -0
- package/dist/similarity-search.js +147 -0
- package/dist/similarity-search.js.map +1 -0
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +13 -4
- package/dist/tool.js.map +1 -1
- package/dist/types.d.ts +246 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-stores/index.d.ts +96 -0
- package/dist/vector-stores/index.d.ts.map +1 -0
- package/dist/vector-stores/index.js +153 -0
- package/dist/vector-stores/index.js.map +1 -0
- package/package.json +41 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ProviderFactory, ProviderAdapter, ProviderRequestOptions, ProviderResponse, StreamChunk, EmbeddingAdapter, ImageGenerationAdapter, FileAdapter } from '../types.js';
|
|
1
|
+
import type { ProviderFactory, ProviderAdapter, ProviderRequestOptions, ProviderResponse, StreamChunk, EmbeddingAdapter, ImageGenerationAdapter, FileAdapter, VectorStoreAdapter, VectorStoreInfo, VectorStoreFileInfo } from '../types.js';
|
|
2
|
+
import type { FileSearchFilter } from '../file-search.js';
|
|
2
3
|
import { GoogleCacheRegistry } from './google-cache-registry.js';
|
|
3
4
|
export interface GoogleConfig {
|
|
4
5
|
apiKey: string;
|
|
@@ -12,6 +13,7 @@ export declare class GoogleProvider implements ProviderFactory {
|
|
|
12
13
|
createEmbedding(model: string): EmbeddingAdapter;
|
|
13
14
|
createImage(model: string): ImageGenerationAdapter;
|
|
14
15
|
createFiles(): FileAdapter;
|
|
16
|
+
createVectorStores(): VectorStoreAdapter;
|
|
15
17
|
}
|
|
16
18
|
export declare class GoogleAdapter implements ProviderAdapter {
|
|
17
19
|
private readonly config;
|
|
@@ -30,4 +32,84 @@ export declare class GoogleAdapter implements ProviderAdapter {
|
|
|
30
32
|
generate(options: ProviderRequestOptions): Promise<ProviderResponse>;
|
|
31
33
|
stream(options: ProviderRequestOptions): AsyncIterable<StreamChunk>;
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Translate a typed `FileSearchFilter` (OpenAI-shaped) into Gemini's
|
|
37
|
+
* `metadataFilter` string syntax (#B8.5).
|
|
38
|
+
*
|
|
39
|
+
* - `{ type: 'eq', key, value }` → `key = value`
|
|
40
|
+
* - `{ type: 'ne', key, value }` → `key != value`
|
|
41
|
+
* - `{ type: 'gt', key, value }` → `key > value`
|
|
42
|
+
* - `{ type: 'gte', key, value }` → `key >= value`
|
|
43
|
+
* - `{ type: 'lt', key, value }` → `key < value`
|
|
44
|
+
* - `{ type: 'lte', key, value }` → `key <= value`
|
|
45
|
+
* - `{ type: 'and', filters }` → `(f1) AND (f2) AND ...`
|
|
46
|
+
* - `{ type: 'or', filters }` → `(f1) OR (f2) OR ...`
|
|
47
|
+
*
|
|
48
|
+
* String values are wrapped in double quotes with `"` and `\` escaped.
|
|
49
|
+
* Numbers and booleans render bare.
|
|
50
|
+
*
|
|
51
|
+
* Exported for unit testing — see `google-vector-stores.test.ts`.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export declare function filterToGeminiString(filter: FileSearchFilter): string;
|
|
56
|
+
/**
|
|
57
|
+
* Map a Gemini `FileSearchStore` resource into the framework's
|
|
58
|
+
* `VectorStoreInfo` shape. `displayNameOverride` lets `create()` populate
|
|
59
|
+
* the human-friendly name from the user-supplied value when the API
|
|
60
|
+
* response omits it (some response variants do).
|
|
61
|
+
*
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare function fromGeminiFileSearchStore(raw: unknown, displayNameOverride?: string): VectorStoreInfo;
|
|
65
|
+
/**
|
|
66
|
+
* Map a Gemini `Document` resource into the framework's
|
|
67
|
+
* `VectorStoreFileInfo` shape. `DocumentState` enum values flatten to the
|
|
68
|
+
* shared `'in_progress' | 'completed' | 'failed' | 'cancelled'` union.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export declare function fromGeminiDocument(raw: unknown, storeId: string): VectorStoreFileInfo;
|
|
73
|
+
/**
|
|
74
|
+
* Convert the framework's flat attribute map to Gemini's `CustomMetadata`
|
|
75
|
+
* array shape. Strings → `stringValue`, numbers → `numericValue`,
|
|
76
|
+
* booleans → `stringValue: 'true' | 'false'` (Gemini has no boolean
|
|
77
|
+
* variant — string is the safe lossless choice; filter-builders can
|
|
78
|
+
* still match on `key = "true"`).
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export declare function attributesToCustomMetadata(attrs: Record<string, string | number | boolean>): Array<{
|
|
83
|
+
key: string;
|
|
84
|
+
stringValue?: string;
|
|
85
|
+
numericValue?: number;
|
|
86
|
+
}>;
|
|
87
|
+
/**
|
|
88
|
+
* Inverse of {@link attributesToCustomMetadata}. Drops `stringListValue`
|
|
89
|
+
* variants (no flat-attribute representation; apps that need lists
|
|
90
|
+
* should read the raw Document via the SDK).
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
export declare function customMetadataToAttributes(metadata: Array<{
|
|
95
|
+
key?: string;
|
|
96
|
+
stringValue?: string;
|
|
97
|
+
numericValue?: number;
|
|
98
|
+
stringListValue?: {
|
|
99
|
+
values?: string[];
|
|
100
|
+
};
|
|
101
|
+
}>): Record<string, string | number | boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* Best-effort MIME type from a filename extension. Gemini's
|
|
104
|
+
* `uploadToFileSearchStore` requires a mimeType on Blob uploads (it
|
|
105
|
+
* reads `blob.type`, which is empty on untyped `new Blob([data])`).
|
|
106
|
+
*
|
|
107
|
+
* Coverage matches Gemini's supported FileSearchStore document formats.
|
|
108
|
+
* Unknown extensions return `''` — the caller drops the field so the
|
|
109
|
+
* Gemini SDK's own error fires loudly rather than silently picking a
|
|
110
|
+
* wrong type.
|
|
111
|
+
*
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
export declare function mimeTypeFromFilename(filename: string): string;
|
|
33
115
|
//# sourceMappingURL=google.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,WAAW,EAKX,gBAAgB,EAEhB,sBAAsB,EAGtB,WAAW,
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,WAAW,EAKX,gBAAgB,EAEhB,sBAAsB,EAGtB,WAAW,EAIX,kBAAkB,EAElB,eAAe,EACf,mBAAmB,EAKpB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EACL,mBAAmB,EAKpB,MAAM,4BAA4B,CAAA;AAEnC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,cAAe,YAAW,eAAe;IACpD,QAAQ,CAAC,IAAI,YAAW;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAqB;gBAExC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,mBAAmB;IAKrE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe;IAItC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB;IAIhD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,sBAAsB;IAIlD,WAAW,IAAI,WAAW;IAI1B,kBAAkB,IAAI,kBAAkB;CAGzC;AAID,qBAAa,aAAc,YAAW,eAAe;IAIjD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IALjC,OAAO,CAAC,MAAM,CAAY;gBAGP,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,aAAa,CAAC,EAAE,mBAAmB,GAAG,SAAS;YAGpD,SAAS;IAQvB;;;;;OAKG;YACW,YAAY;IA4DpB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmBnE,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,aAAa,CAAC,WAAW,CAAC;CAkD3E;AAwHD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAsBrE;AA0cD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,eAAe,CAuBrG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,mBAAmB,CAuBrF;AAWD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAC/C,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAMrE;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,KAAK,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC,GACtH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAc3C;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoB7D"}
|
package/dist/providers/google.js
CHANGED
|
@@ -20,6 +20,9 @@ export class GoogleProvider {
|
|
|
20
20
|
createFiles() {
|
|
21
21
|
return new GoogleFileAdapter(this.config);
|
|
22
22
|
}
|
|
23
|
+
createVectorStores() {
|
|
24
|
+
return new GoogleVectorStoreAdapter(this.config);
|
|
25
|
+
}
|
|
23
26
|
}
|
|
24
27
|
// ─── Adapter ──────────────────────────────────────────────
|
|
25
28
|
export class GoogleAdapter {
|
|
@@ -49,6 +52,8 @@ export class GoogleAdapter {
|
|
|
49
52
|
async buildPayload(options) {
|
|
50
53
|
const client = await this.getClient();
|
|
51
54
|
const { system, contents } = toGeminiContents(options.messages);
|
|
55
|
+
// `toGeminiTools` returns the already-wrapped top-level array
|
|
56
|
+
// ({functionDeclarations: [...]} + any native blocks like google_search).
|
|
52
57
|
const geminiTools = options.tools?.length ? toGeminiTools(options.tools) : undefined;
|
|
53
58
|
const config = {};
|
|
54
59
|
if (options.maxTokens)
|
|
@@ -59,8 +64,8 @@ export class GoogleAdapter {
|
|
|
59
64
|
config['topP'] = options.topP;
|
|
60
65
|
if (options.stop)
|
|
61
66
|
config['stopSequences'] = options.stop;
|
|
62
|
-
if (geminiTools)
|
|
63
|
-
config['tools'] =
|
|
67
|
+
if (geminiTools && geminiTools.length > 0)
|
|
68
|
+
config['tools'] = geminiTools;
|
|
64
69
|
if (options.toolChoice)
|
|
65
70
|
config['toolConfig'] = toGeminiToolConfig(options.toolChoice);
|
|
66
71
|
// The Gemini SDK reads abortSignal from the config block.
|
|
@@ -78,7 +83,7 @@ export class GoogleAdapter {
|
|
|
78
83
|
cacheKey,
|
|
79
84
|
...(options.cache.instructions && system ? { systemInstruction: { parts: [{ text: system }] } } : {}),
|
|
80
85
|
...(cachedSlice.length > 0 ? { contents: cachedSlice } : {}),
|
|
81
|
-
...(options.cache.tools && geminiTools ? { tools:
|
|
86
|
+
...(options.cache.tools && geminiTools && geminiTools.length > 0 ? { tools: geminiTools } : {}),
|
|
82
87
|
...(options.cache.ttl ? { ttl: durationToGoogleTtl(options.cache.ttl) } : {}),
|
|
83
88
|
});
|
|
84
89
|
}
|
|
@@ -230,12 +235,118 @@ function toGeminiContents(messages) {
|
|
|
230
235
|
});
|
|
231
236
|
return { system, contents };
|
|
232
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Build Gemini's `tools` array. The Gemini API accepts a mixed array where
|
|
240
|
+
* function declarations live under one wrapper entry and provider-native
|
|
241
|
+
* blocks (e.g. `{ google_search: {} }`) sit as separate top-level entries:
|
|
242
|
+
*
|
|
243
|
+
* tools: [
|
|
244
|
+
* { functionDeclarations: [...] },
|
|
245
|
+
* { google_search: {} },
|
|
246
|
+
* ]
|
|
247
|
+
*
|
|
248
|
+
* Tools tagged with a recognized `providerHint.type` are emitted as their
|
|
249
|
+
* native top-level block; everything else collects into one
|
|
250
|
+
* `functionDeclarations` entry. Tools with unrecognized hints fall through
|
|
251
|
+
* to the function-declaration shape — the input schema's still there, so
|
|
252
|
+
* the worst case is the model treats it as a regular function-call tool.
|
|
253
|
+
*/
|
|
233
254
|
function toGeminiTools(tools) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
255
|
+
const fnDecls = [];
|
|
256
|
+
const blocks = [];
|
|
257
|
+
for (const t of tools) {
|
|
258
|
+
if (t.providerHint?.type === 'web-search') {
|
|
259
|
+
// Gemini's native search tool. The block's `google_search: {}` form is
|
|
260
|
+
// intentional — Gemini doesn't accept allowed_domains / max_uses on
|
|
261
|
+
// this block, so the WebSearch.domains() / .maxResults() opts are
|
|
262
|
+
// ignored on this provider (documented on WebSearch).
|
|
263
|
+
blocks.push({ google_search: {} });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (t.providerHint?.type === 'file-search') {
|
|
267
|
+
// Gemini's native FileSearch tool (#B8.5). The OpenAI-shaped hint
|
|
268
|
+
// (`vector_store_ids` + typed `filters`) is translated to Gemini's
|
|
269
|
+
// shape (`fileSearchStoreNames` + `metadataFilter` string). `topK`
|
|
270
|
+
// mirrors OpenAI's `max_num_results`.
|
|
271
|
+
const storeNames = t.providerHint['vector_store_ids'] ?? [];
|
|
272
|
+
const fileSearchConfig = {
|
|
273
|
+
fileSearchStoreNames: storeNames,
|
|
274
|
+
};
|
|
275
|
+
const filters = t.providerHint['filters'];
|
|
276
|
+
if (filters !== undefined) {
|
|
277
|
+
fileSearchConfig['metadataFilter'] = filterToGeminiString(filters);
|
|
278
|
+
}
|
|
279
|
+
const maxNumResults = t.providerHint['max_num_results'];
|
|
280
|
+
if (maxNumResults !== undefined) {
|
|
281
|
+
fileSearchConfig['topK'] = maxNumResults;
|
|
282
|
+
}
|
|
283
|
+
blocks.push({ fileSearch: fileSearchConfig });
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
fnDecls.push({
|
|
287
|
+
name: t.name,
|
|
288
|
+
description: t.description,
|
|
289
|
+
parameters: t.parameters,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (fnDecls.length > 0)
|
|
293
|
+
blocks.unshift({ functionDeclarations: fnDecls });
|
|
294
|
+
return blocks;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Translate a typed `FileSearchFilter` (OpenAI-shaped) into Gemini's
|
|
298
|
+
* `metadataFilter` string syntax (#B8.5).
|
|
299
|
+
*
|
|
300
|
+
* - `{ type: 'eq', key, value }` → `key = value`
|
|
301
|
+
* - `{ type: 'ne', key, value }` → `key != value`
|
|
302
|
+
* - `{ type: 'gt', key, value }` → `key > value`
|
|
303
|
+
* - `{ type: 'gte', key, value }` → `key >= value`
|
|
304
|
+
* - `{ type: 'lt', key, value }` → `key < value`
|
|
305
|
+
* - `{ type: 'lte', key, value }` → `key <= value`
|
|
306
|
+
* - `{ type: 'and', filters }` → `(f1) AND (f2) AND ...`
|
|
307
|
+
* - `{ type: 'or', filters }` → `(f1) OR (f2) OR ...`
|
|
308
|
+
*
|
|
309
|
+
* String values are wrapped in double quotes with `"` and `\` escaped.
|
|
310
|
+
* Numbers and booleans render bare.
|
|
311
|
+
*
|
|
312
|
+
* Exported for unit testing — see `google-vector-stores.test.ts`.
|
|
313
|
+
*
|
|
314
|
+
* @internal
|
|
315
|
+
*/
|
|
316
|
+
export function filterToGeminiString(filter) {
|
|
317
|
+
switch (filter.type) {
|
|
318
|
+
case 'eq':
|
|
319
|
+
case 'ne':
|
|
320
|
+
case 'gt':
|
|
321
|
+
case 'gte':
|
|
322
|
+
case 'lt':
|
|
323
|
+
case 'lte': {
|
|
324
|
+
const op = GEMINI_FILTER_OP[filter.type];
|
|
325
|
+
return `${filter.key} ${op} ${formatGeminiValue(filter.value)}`;
|
|
326
|
+
}
|
|
327
|
+
case 'and':
|
|
328
|
+
case 'or': {
|
|
329
|
+
if (filter.filters.length === 0) {
|
|
330
|
+
throw new Error(`[RudderJS AI] Gemini metadataFilter: ${filter.type.toUpperCase()} requires at least one sub-filter.`);
|
|
331
|
+
}
|
|
332
|
+
const joiner = filter.type === 'and' ? ' AND ' : ' OR ';
|
|
333
|
+
return filter.filters.map(f => `(${filterToGeminiString(f)})`).join(joiner);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const GEMINI_FILTER_OP = {
|
|
338
|
+
eq: '=',
|
|
339
|
+
ne: '!=',
|
|
340
|
+
gt: '>',
|
|
341
|
+
gte: '>=',
|
|
342
|
+
lt: '<',
|
|
343
|
+
lte: '<=',
|
|
344
|
+
};
|
|
345
|
+
function formatGeminiValue(value) {
|
|
346
|
+
if (typeof value === 'string') {
|
|
347
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
348
|
+
}
|
|
349
|
+
return String(value);
|
|
239
350
|
}
|
|
240
351
|
function toGeminiToolConfig(choice) {
|
|
241
352
|
if (choice === 'auto')
|
|
@@ -402,4 +513,376 @@ class GoogleFileAdapter {
|
|
|
402
513
|
await client.files.delete({ name: fileId });
|
|
403
514
|
}
|
|
404
515
|
}
|
|
516
|
+
// ─── Vector Stores (Gemini FileSearchStores, #B8.5) ──────
|
|
517
|
+
//
|
|
518
|
+
// Gemini's hosted RAG surface is `ai.fileSearchStores.*` — a direct
|
|
519
|
+
// OpenAI-equivalent that handles ingestion, chunking, embedding, and
|
|
520
|
+
// retrieval server-side. NOT available on Vertex AI; the underlying SDK
|
|
521
|
+
// methods throw for Vertex clients.
|
|
522
|
+
//
|
|
523
|
+
// Mapping decisions:
|
|
524
|
+
// - `VectorStoreInfo.id` is the full Gemini resource name
|
|
525
|
+
// (`fileSearchStores/foo-bar`). Apps pass it back verbatim to `get` /
|
|
526
|
+
// `delete` / `addFile`.
|
|
527
|
+
// - `VectorStoreInfo.name` is `displayName`. The OpenAI adapter populates
|
|
528
|
+
// it from the store's name field; we use the user-supplied display name
|
|
529
|
+
// to keep `create('Knowledge Base')` round-trip-able.
|
|
530
|
+
// - `createdAt` is parsed from ISO 8601 to Unix seconds for parity with
|
|
531
|
+
// OpenAI's `created_at`.
|
|
532
|
+
// - `fileCount` sums `activeDocumentsCount + pendingDocumentsCount` (both
|
|
533
|
+
// string-encoded). `failedDocumentsCount` is dropped — it's surfaced
|
|
534
|
+
// per-file via `addFile`'s status when polling.
|
|
535
|
+
// - `bytesUsed` is parsed from `sizeBytes` (string-encoded).
|
|
536
|
+
// - Store-level `metadata` and `expiresAfter` are NOT supported by Gemini.
|
|
537
|
+
// Passing them throws fail-loud so apps don't silently lose data.
|
|
538
|
+
//
|
|
539
|
+
// `addFile` paths:
|
|
540
|
+
// - `{ fileId }` → `importFile` (re-uses an existing Files API file).
|
|
541
|
+
// - `{ filePath | fileBuffer }` → `uploadToFileSearchStore` (single-shot
|
|
542
|
+
// upload). Both paths return long-running operations; default
|
|
543
|
+
// `wait: true` polls `client.operations.get` until `done`.
|
|
544
|
+
// - `attributes` (Record<string, primitive>) → Gemini's `customMetadata`
|
|
545
|
+
// array shape; booleans coerce to `stringValue: 'true' | 'false'`.
|
|
546
|
+
class GoogleVectorStoreAdapter {
|
|
547
|
+
config;
|
|
548
|
+
client = null;
|
|
549
|
+
constructor(config) {
|
|
550
|
+
this.config = config;
|
|
551
|
+
}
|
|
552
|
+
async getClient() {
|
|
553
|
+
if (this.client)
|
|
554
|
+
return this.client;
|
|
555
|
+
const sdk = await import(/* @vite-ignore */ '@google/genai');
|
|
556
|
+
const GoogleGenAI = sdk.GoogleGenAI ?? sdk.default;
|
|
557
|
+
this.client = new GoogleGenAI({ apiKey: this.config.apiKey });
|
|
558
|
+
return this.client;
|
|
559
|
+
}
|
|
560
|
+
async create(opts) {
|
|
561
|
+
if (opts.metadata) {
|
|
562
|
+
throw new Error('[RudderJS AI] Gemini FileSearchStores does not support store-level metadata. ' +
|
|
563
|
+
'Attach searchable metadata per-document via addFile({ attributes }).');
|
|
564
|
+
}
|
|
565
|
+
if (opts.expiresAfter) {
|
|
566
|
+
throw new Error('[RudderJS AI] Gemini FileSearchStores does not support expiresAfter. ' +
|
|
567
|
+
'Stores persist until explicitly deleted via VectorStores.delete().');
|
|
568
|
+
}
|
|
569
|
+
const client = await this.getClient();
|
|
570
|
+
const response = await client.fileSearchStores.create({
|
|
571
|
+
config: { displayName: opts.name },
|
|
572
|
+
});
|
|
573
|
+
return fromGeminiFileSearchStore(response, opts.name);
|
|
574
|
+
}
|
|
575
|
+
async list(opts) {
|
|
576
|
+
const client = await this.getClient();
|
|
577
|
+
const config = {};
|
|
578
|
+
if (opts?.limit !== undefined)
|
|
579
|
+
config['pageSize'] = opts.limit;
|
|
580
|
+
if (opts?.after !== undefined)
|
|
581
|
+
config['pageToken'] = opts.after;
|
|
582
|
+
// Gemini paginates forward via pageToken only — `before` has no
|
|
583
|
+
// equivalent. Drop it silently (matches OpenAI when `before` is unset).
|
|
584
|
+
const pager = await client.fileSearchStores.list({ config });
|
|
585
|
+
const items = Array.isArray(pager?.page) ? pager.page : [];
|
|
586
|
+
return { stores: items.map(item => fromGeminiFileSearchStore(item)) };
|
|
587
|
+
}
|
|
588
|
+
async get(id) {
|
|
589
|
+
const client = await this.getClient();
|
|
590
|
+
const response = await client.fileSearchStores.get({ name: id });
|
|
591
|
+
return fromGeminiFileSearchStore(response);
|
|
592
|
+
}
|
|
593
|
+
async delete(id) {
|
|
594
|
+
const client = await this.getClient();
|
|
595
|
+
// `force: true` mirrors OpenAI's behavior — deleting a store also
|
|
596
|
+
// drops attached documents. Without `force`, Gemini returns
|
|
597
|
+
// FAILED_PRECONDITION when the store has any documents.
|
|
598
|
+
await client.fileSearchStores.delete({ name: id, config: { force: true } });
|
|
599
|
+
}
|
|
600
|
+
async addFile(storeId, opts) {
|
|
601
|
+
const client = await this.getClient();
|
|
602
|
+
const customMetadata = opts.attributes ? attributesToCustomMetadata(opts.attributes) : undefined;
|
|
603
|
+
// Path 1: re-use an existing Files API file.
|
|
604
|
+
if (opts.fileId) {
|
|
605
|
+
const importConfig = {};
|
|
606
|
+
if (customMetadata)
|
|
607
|
+
importConfig['customMetadata'] = customMetadata;
|
|
608
|
+
if (opts.chunkingStrategy)
|
|
609
|
+
importConfig['chunkingConfig'] = opts.chunkingStrategy;
|
|
610
|
+
const op = await client.fileSearchStores.importFile({
|
|
611
|
+
fileSearchStoreName: storeId,
|
|
612
|
+
fileName: opts.fileId,
|
|
613
|
+
config: importConfig,
|
|
614
|
+
});
|
|
615
|
+
return finishVectorStoreOperation(client, op, storeId, opts);
|
|
616
|
+
}
|
|
617
|
+
// Path 2: upload a local file directly. Either `filePath` or
|
|
618
|
+
// `fileBuffer` is required — Gemini's SDK accepts a path string OR a
|
|
619
|
+
// Blob. For `filePath`, the SDK infers mimeType from the extension;
|
|
620
|
+
// for `fileBuffer`, it reads `blob.type` which is empty on a
|
|
621
|
+
// untyped `new Blob([data])`, so we forward an explicit `mimeType`
|
|
622
|
+
// derived from `filename` to avoid `Can not determine mimeType`.
|
|
623
|
+
if (opts.filePath || opts.fileBuffer) {
|
|
624
|
+
const uploadConfig = {};
|
|
625
|
+
if (customMetadata)
|
|
626
|
+
uploadConfig['customMetadata'] = customMetadata;
|
|
627
|
+
if (opts.chunkingStrategy)
|
|
628
|
+
uploadConfig['chunkingConfig'] = opts.chunkingStrategy;
|
|
629
|
+
if (opts.fileBuffer?.filename)
|
|
630
|
+
uploadConfig['displayName'] = opts.fileBuffer.filename;
|
|
631
|
+
if (opts.fileBuffer?.filename) {
|
|
632
|
+
const mimeType = mimeTypeFromFilename(opts.fileBuffer.filename);
|
|
633
|
+
if (mimeType)
|
|
634
|
+
uploadConfig['mimeType'] = mimeType;
|
|
635
|
+
}
|
|
636
|
+
const file = opts.filePath ?? new Blob([opts.fileBuffer.data]);
|
|
637
|
+
const op = await client.fileSearchStores.uploadToFileSearchStore({
|
|
638
|
+
fileSearchStoreName: storeId,
|
|
639
|
+
file,
|
|
640
|
+
config: uploadConfig,
|
|
641
|
+
});
|
|
642
|
+
return finishVectorStoreOperation(client, op, storeId, opts);
|
|
643
|
+
}
|
|
644
|
+
throw new Error('[RudderJS AI] addFile requires fileId, filePath, or fileBuffer. ' +
|
|
645
|
+
'Pass an existing Gemini Files API id via { fileId } (e.g. `files/abc-123`) or ' +
|
|
646
|
+
'a local source via { filePath } / { fileBuffer }.');
|
|
647
|
+
}
|
|
648
|
+
async removeFile(storeId, fileId) {
|
|
649
|
+
const client = await this.getClient();
|
|
650
|
+
// Document resource names are `fileSearchStores/<store>/documents/<doc>`.
|
|
651
|
+
// Apps that pass the full path use it verbatim; apps that pass only
|
|
652
|
+
// the document id get the store prefix joined for them.
|
|
653
|
+
const name = fileId.includes('/documents/') ? fileId : `${storeId}/documents/${fileId}`;
|
|
654
|
+
await client.fileSearchStores.documents.delete({ name });
|
|
655
|
+
}
|
|
656
|
+
async listFiles(storeId, opts) {
|
|
657
|
+
const client = await this.getClient();
|
|
658
|
+
const config = {};
|
|
659
|
+
if (opts?.limit !== undefined)
|
|
660
|
+
config['pageSize'] = opts.limit;
|
|
661
|
+
if (opts?.after !== undefined)
|
|
662
|
+
config['pageToken'] = opts.after;
|
|
663
|
+
const pager = await client.fileSearchStores.documents.list({ parent: storeId, config });
|
|
664
|
+
const items = Array.isArray(pager?.page) ? pager.page : [];
|
|
665
|
+
return { files: items.map(doc => fromGeminiDocument(doc, storeId)) };
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Wait for a long-running file ingestion operation to finish and map the
|
|
670
|
+
* result into `VectorStoreFileInfo`. Honors `wait`/`pollInterval`/
|
|
671
|
+
* `pollTimeout` from `VectorStoreAddOptions` (defaults: wait=true,
|
|
672
|
+
* interval=1000ms, timeout=120_000ms).
|
|
673
|
+
*
|
|
674
|
+
* The terminal state of a Gemini ingestion op is exposed two ways:
|
|
675
|
+
* - `op.error?: { code, message }` when ingestion failed.
|
|
676
|
+
* - `op.response?: { documentName: 'fileSearchStores/.../documents/...' }`
|
|
677
|
+
* when successful.
|
|
678
|
+
*
|
|
679
|
+
* On success we follow up with a single `documents.get` to fetch
|
|
680
|
+
* `state` / `sizeBytes` / `createTime`. On failure we surface the error
|
|
681
|
+
* message via `lastError` and the status flips to `'failed'`.
|
|
682
|
+
*/
|
|
683
|
+
async function finishVectorStoreOperation(client, initialOp, storeId, opts) {
|
|
684
|
+
if (opts.wait === false) {
|
|
685
|
+
return {
|
|
686
|
+
id: initialOp?.name ?? `${storeId}/documents/pending-${Date.now()}`,
|
|
687
|
+
vectorStoreId: storeId,
|
|
688
|
+
status: 'in_progress',
|
|
689
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
690
|
+
...(opts.attributes ? { attributes: opts.attributes } : {}),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
const pollInterval = opts.pollInterval ?? 1000;
|
|
694
|
+
const pollTimeout = opts.pollTimeout ?? 120_000;
|
|
695
|
+
const deadline = Date.now() + pollTimeout;
|
|
696
|
+
let current = initialOp;
|
|
697
|
+
while (!current?.done) {
|
|
698
|
+
if (Date.now() > deadline) {
|
|
699
|
+
throw new Error(`[RudderJS AI] Gemini FileSearchStore ingestion timed out after ${pollTimeout}ms ` +
|
|
700
|
+
`(store=${storeId}). Increase pollTimeout or set wait: false for fire-and-forget.`);
|
|
701
|
+
}
|
|
702
|
+
await sleep(pollInterval);
|
|
703
|
+
current = await client.operations.get({ operation: current });
|
|
704
|
+
}
|
|
705
|
+
if (current.error) {
|
|
706
|
+
const errMessage = current.error.message ?? 'unknown error';
|
|
707
|
+
return {
|
|
708
|
+
id: current.name ?? `${storeId}/documents/failed-${Date.now()}`,
|
|
709
|
+
vectorStoreId: storeId,
|
|
710
|
+
status: 'failed',
|
|
711
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
712
|
+
lastError: errMessage,
|
|
713
|
+
...(opts.attributes ? { attributes: opts.attributes } : {}),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const documentName = current.response?.documentName;
|
|
717
|
+
if (!documentName) {
|
|
718
|
+
// Op done, no error, no documentName — surface as completed without
|
|
719
|
+
// follow-up details rather than failing.
|
|
720
|
+
return {
|
|
721
|
+
id: current.name ?? `${storeId}/documents/unknown-${Date.now()}`,
|
|
722
|
+
vectorStoreId: storeId,
|
|
723
|
+
status: 'completed',
|
|
724
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
725
|
+
...(opts.attributes ? { attributes: opts.attributes } : {}),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
// Follow up with documents.get to surface real createdAt + sizeBytes.
|
|
729
|
+
// Best-effort: if the get fails (rare race), fall back to the op data.
|
|
730
|
+
try {
|
|
731
|
+
const doc = await client.fileSearchStores.documents.get({ name: documentName });
|
|
732
|
+
const info = fromGeminiDocument(doc, storeId);
|
|
733
|
+
if (opts.attributes && !info.attributes)
|
|
734
|
+
info.attributes = opts.attributes;
|
|
735
|
+
return info;
|
|
736
|
+
}
|
|
737
|
+
catch {
|
|
738
|
+
return {
|
|
739
|
+
id: documentName,
|
|
740
|
+
vectorStoreId: storeId,
|
|
741
|
+
status: 'completed',
|
|
742
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
743
|
+
...(opts.attributes ? { attributes: opts.attributes } : {}),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Map a Gemini `FileSearchStore` resource into the framework's
|
|
749
|
+
* `VectorStoreInfo` shape. `displayNameOverride` lets `create()` populate
|
|
750
|
+
* the human-friendly name from the user-supplied value when the API
|
|
751
|
+
* response omits it (some response variants do).
|
|
752
|
+
*
|
|
753
|
+
* @internal
|
|
754
|
+
*/
|
|
755
|
+
export function fromGeminiFileSearchStore(raw, displayNameOverride) {
|
|
756
|
+
const r = raw;
|
|
757
|
+
const id = r.name ?? '';
|
|
758
|
+
const active = Number(r.activeDocumentsCount ?? 0) || 0;
|
|
759
|
+
const pending = Number(r.pendingDocumentsCount ?? 0) || 0;
|
|
760
|
+
const result = {
|
|
761
|
+
id,
|
|
762
|
+
name: r.displayName ?? displayNameOverride ?? id,
|
|
763
|
+
createdAt: r.createTime ? Math.floor(Date.parse(r.createTime) / 1000) : Math.floor(Date.now() / 1000),
|
|
764
|
+
fileCount: active + pending,
|
|
765
|
+
};
|
|
766
|
+
if (r.sizeBytes !== undefined) {
|
|
767
|
+
const bytes = Number(r.sizeBytes);
|
|
768
|
+
if (Number.isFinite(bytes))
|
|
769
|
+
result.bytesUsed = bytes;
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Map a Gemini `Document` resource into the framework's
|
|
775
|
+
* `VectorStoreFileInfo` shape. `DocumentState` enum values flatten to the
|
|
776
|
+
* shared `'in_progress' | 'completed' | 'failed' | 'cancelled'` union.
|
|
777
|
+
*
|
|
778
|
+
* @internal
|
|
779
|
+
*/
|
|
780
|
+
export function fromGeminiDocument(raw, storeId) {
|
|
781
|
+
const r = raw;
|
|
782
|
+
const status = mapGeminiDocumentState(r.state);
|
|
783
|
+
const result = {
|
|
784
|
+
id: r.name ?? `${storeId}/documents/unknown`,
|
|
785
|
+
vectorStoreId: storeId,
|
|
786
|
+
status,
|
|
787
|
+
createdAt: r.createTime ? Math.floor(Date.parse(r.createTime) / 1000) : Math.floor(Date.now() / 1000),
|
|
788
|
+
};
|
|
789
|
+
if (r.sizeBytes !== undefined) {
|
|
790
|
+
const bytes = Number(r.sizeBytes);
|
|
791
|
+
if (Number.isFinite(bytes))
|
|
792
|
+
result.bytes = bytes;
|
|
793
|
+
}
|
|
794
|
+
if (r.customMetadata && r.customMetadata.length > 0) {
|
|
795
|
+
result.attributes = customMetadataToAttributes(r.customMetadata);
|
|
796
|
+
}
|
|
797
|
+
return result;
|
|
798
|
+
}
|
|
799
|
+
function mapGeminiDocumentState(state) {
|
|
800
|
+
switch (state) {
|
|
801
|
+
case 'STATE_ACTIVE': return 'completed';
|
|
802
|
+
case 'STATE_FAILED': return 'failed';
|
|
803
|
+
case 'STATE_PENDING': return 'in_progress';
|
|
804
|
+
default: return 'in_progress';
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Convert the framework's flat attribute map to Gemini's `CustomMetadata`
|
|
809
|
+
* array shape. Strings → `stringValue`, numbers → `numericValue`,
|
|
810
|
+
* booleans → `stringValue: 'true' | 'false'` (Gemini has no boolean
|
|
811
|
+
* variant — string is the safe lossless choice; filter-builders can
|
|
812
|
+
* still match on `key = "true"`).
|
|
813
|
+
*
|
|
814
|
+
* @internal
|
|
815
|
+
*/
|
|
816
|
+
export function attributesToCustomMetadata(attrs) {
|
|
817
|
+
return Object.entries(attrs).map(([key, value]) => {
|
|
818
|
+
if (typeof value === 'number')
|
|
819
|
+
return { key, numericValue: value };
|
|
820
|
+
if (typeof value === 'boolean')
|
|
821
|
+
return { key, stringValue: value ? 'true' : 'false' };
|
|
822
|
+
return { key, stringValue: value };
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Inverse of {@link attributesToCustomMetadata}. Drops `stringListValue`
|
|
827
|
+
* variants (no flat-attribute representation; apps that need lists
|
|
828
|
+
* should read the raw Document via the SDK).
|
|
829
|
+
*
|
|
830
|
+
* @internal
|
|
831
|
+
*/
|
|
832
|
+
export function customMetadataToAttributes(metadata) {
|
|
833
|
+
const out = {};
|
|
834
|
+
for (const entry of metadata) {
|
|
835
|
+
if (!entry.key)
|
|
836
|
+
continue;
|
|
837
|
+
if (entry.numericValue !== undefined)
|
|
838
|
+
out[entry.key] = entry.numericValue;
|
|
839
|
+
else if (entry.stringValue !== undefined) {
|
|
840
|
+
// Round-trip booleans encoded by attributesToCustomMetadata.
|
|
841
|
+
if (entry.stringValue === 'true')
|
|
842
|
+
out[entry.key] = true;
|
|
843
|
+
else if (entry.stringValue === 'false')
|
|
844
|
+
out[entry.key] = false;
|
|
845
|
+
else
|
|
846
|
+
out[entry.key] = entry.stringValue;
|
|
847
|
+
}
|
|
848
|
+
// stringListValue intentionally dropped.
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
852
|
+
function sleep(ms) {
|
|
853
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Best-effort MIME type from a filename extension. Gemini's
|
|
857
|
+
* `uploadToFileSearchStore` requires a mimeType on Blob uploads (it
|
|
858
|
+
* reads `blob.type`, which is empty on untyped `new Blob([data])`).
|
|
859
|
+
*
|
|
860
|
+
* Coverage matches Gemini's supported FileSearchStore document formats.
|
|
861
|
+
* Unknown extensions return `''` — the caller drops the field so the
|
|
862
|
+
* Gemini SDK's own error fires loudly rather than silently picking a
|
|
863
|
+
* wrong type.
|
|
864
|
+
*
|
|
865
|
+
* @internal
|
|
866
|
+
*/
|
|
867
|
+
export function mimeTypeFromFilename(filename) {
|
|
868
|
+
const ext = filename.toLowerCase().split('.').pop() ?? '';
|
|
869
|
+
switch (ext) {
|
|
870
|
+
case 'txt': return 'text/plain';
|
|
871
|
+
case 'md': return 'text/markdown';
|
|
872
|
+
case 'pdf': return 'application/pdf';
|
|
873
|
+
case 'html':
|
|
874
|
+
case 'htm': return 'text/html';
|
|
875
|
+
case 'json': return 'application/json';
|
|
876
|
+
case 'csv': return 'text/csv';
|
|
877
|
+
case 'tsv': return 'text/tab-separated-values';
|
|
878
|
+
case 'xml': return 'application/xml';
|
|
879
|
+
case 'rtf': return 'application/rtf';
|
|
880
|
+
case 'doc': return 'application/msword';
|
|
881
|
+
case 'docx': return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
882
|
+
case 'js': return 'text/javascript';
|
|
883
|
+
case 'ts': return 'text/x-typescript';
|
|
884
|
+
case 'py': return 'text/x-python';
|
|
885
|
+
default: return '';
|
|
886
|
+
}
|
|
887
|
+
}
|
|
405
888
|
//# sourceMappingURL=google.js.map
|