@rudderjs/ai 1.5.0 → 1.6.1

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 (163) hide show
  1. package/README.md +482 -4
  2. package/boost/guidelines.md +60 -0
  3. package/boost/skills/ai-agents/SKILL.md +7 -0
  4. package/boost/skills/ai-tools/SKILL.md +7 -0
  5. package/dist/agent.d.ts +35 -1
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +118 -16
  8. package/dist/agent.js.map +1 -1
  9. package/dist/budget/pricing.d.ts +124 -0
  10. package/dist/budget/pricing.d.ts.map +1 -0
  11. package/dist/budget/pricing.js +175 -0
  12. package/dist/budget/pricing.js.map +1 -0
  13. package/dist/budget/storage.d.ts +104 -0
  14. package/dist/budget/storage.d.ts.map +1 -0
  15. package/dist/budget/storage.js +0 -0
  16. package/dist/budget/storage.js.map +1 -0
  17. package/dist/budget/with-budget.d.ts +119 -0
  18. package/dist/budget/with-budget.d.ts.map +1 -0
  19. package/dist/budget/with-budget.js +175 -0
  20. package/dist/budget/with-budget.js.map +1 -0
  21. package/dist/budget-orm/index.d.ts +96 -0
  22. package/dist/budget-orm/index.d.ts.map +1 -0
  23. package/dist/budget-orm/index.js +177 -0
  24. package/dist/budget-orm/index.js.map +1 -0
  25. package/dist/commands/ai-eval.d.ts +93 -0
  26. package/dist/commands/ai-eval.d.ts.map +1 -0
  27. package/dist/commands/ai-eval.js +378 -0
  28. package/dist/commands/ai-eval.js.map +1 -0
  29. package/dist/computer-use/actions.d.ts +214 -0
  30. package/dist/computer-use/actions.d.ts.map +1 -0
  31. package/dist/computer-use/actions.js +48 -0
  32. package/dist/computer-use/actions.js.map +1 -0
  33. package/dist/computer-use/errors.d.ts +57 -0
  34. package/dist/computer-use/errors.d.ts.map +1 -0
  35. package/dist/computer-use/errors.js +76 -0
  36. package/dist/computer-use/errors.js.map +1 -0
  37. package/dist/computer-use/index.d.ts +53 -0
  38. package/dist/computer-use/index.d.ts.map +1 -0
  39. package/dist/computer-use/index.js +51 -0
  40. package/dist/computer-use/index.js.map +1 -0
  41. package/dist/computer-use/playwright.d.ts +76 -0
  42. package/dist/computer-use/playwright.d.ts.map +1 -0
  43. package/dist/computer-use/playwright.js +270 -0
  44. package/dist/computer-use/playwright.js.map +1 -0
  45. package/dist/computer-use/tool.d.ts +154 -0
  46. package/dist/computer-use/tool.d.ts.map +1 -0
  47. package/dist/computer-use/tool.js +210 -0
  48. package/dist/computer-use/tool.js.map +1 -0
  49. package/dist/eval/fixtures.d.ts +65 -0
  50. package/dist/eval/fixtures.d.ts.map +1 -0
  51. package/dist/eval/fixtures.js +110 -0
  52. package/dist/eval/fixtures.js.map +1 -0
  53. package/dist/eval/html-reporter.d.ts +25 -0
  54. package/dist/eval/html-reporter.d.ts.map +1 -0
  55. package/dist/eval/html-reporter.js +209 -0
  56. package/dist/eval/html-reporter.js.map +1 -0
  57. package/dist/eval/index.d.ts +271 -0
  58. package/dist/eval/index.d.ts.map +1 -0
  59. package/dist/eval/index.js +510 -0
  60. package/dist/eval/index.js.map +1 -0
  61. package/dist/eval/json-reporter.d.ts +43 -0
  62. package/dist/eval/json-reporter.d.ts.map +1 -0
  63. package/dist/eval/json-reporter.js +40 -0
  64. package/dist/eval/json-reporter.js.map +1 -0
  65. package/dist/fake.d.ts +36 -1
  66. package/dist/fake.d.ts.map +1 -1
  67. package/dist/fake.js +49 -2
  68. package/dist/fake.js.map +1 -1
  69. package/dist/file-search.d.ts +168 -0
  70. package/dist/file-search.d.ts.map +1 -0
  71. package/dist/file-search.js +158 -0
  72. package/dist/file-search.js.map +1 -0
  73. package/dist/index.d.ts +22 -2
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +17 -1
  76. package/dist/index.js.map +1 -1
  77. package/dist/mcp/client-tools.d.ts +39 -0
  78. package/dist/mcp/client-tools.d.ts.map +1 -0
  79. package/dist/mcp/client-tools.js +147 -0
  80. package/dist/mcp/client-tools.js.map +1 -0
  81. package/dist/mcp/index.d.ts +16 -0
  82. package/dist/mcp/index.d.ts.map +1 -0
  83. package/dist/mcp/index.js +15 -0
  84. package/dist/mcp/index.js.map +1 -0
  85. package/dist/mcp/server-from-agent.d.ts +24 -0
  86. package/dist/mcp/server-from-agent.d.ts.map +1 -0
  87. package/dist/mcp/server-from-agent.js +113 -0
  88. package/dist/mcp/server-from-agent.js.map +1 -0
  89. package/dist/mcp/types.d.ts +64 -0
  90. package/dist/mcp/types.d.ts.map +1 -0
  91. package/dist/mcp/types.js +6 -0
  92. package/dist/mcp/types.js.map +1 -0
  93. package/dist/memory-embedding/index.d.ts +121 -0
  94. package/dist/memory-embedding/index.d.ts.map +1 -0
  95. package/dist/memory-embedding/index.js +229 -0
  96. package/dist/memory-embedding/index.js.map +1 -0
  97. package/dist/memory-extract.d.ts +60 -0
  98. package/dist/memory-extract.d.ts.map +1 -0
  99. package/dist/memory-extract.js +163 -0
  100. package/dist/memory-extract.js.map +1 -0
  101. package/dist/memory-inject.d.ts +39 -0
  102. package/dist/memory-inject.d.ts.map +1 -0
  103. package/dist/memory-inject.js +135 -0
  104. package/dist/memory-inject.js.map +1 -0
  105. package/dist/memory-orm/index.d.ts +118 -0
  106. package/dist/memory-orm/index.d.ts.map +1 -0
  107. package/dist/memory-orm/index.js +187 -0
  108. package/dist/memory-orm/index.js.map +1 -0
  109. package/dist/memory.d.ts +55 -0
  110. package/dist/memory.d.ts.map +1 -0
  111. package/dist/memory.js +132 -0
  112. package/dist/memory.js.map +1 -0
  113. package/dist/observers.d.ts +22 -0
  114. package/dist/observers.d.ts.map +1 -1
  115. package/dist/observers.js.map +1 -1
  116. package/dist/provider-tools.d.ts +15 -1
  117. package/dist/provider-tools.d.ts.map +1 -1
  118. package/dist/provider-tools.js +21 -1
  119. package/dist/provider-tools.js.map +1 -1
  120. package/dist/providers/anthropic.d.ts.map +1 -1
  121. package/dist/providers/anthropic.js +61 -6
  122. package/dist/providers/anthropic.js.map +1 -1
  123. package/dist/providers/elevenlabs.d.ts +98 -0
  124. package/dist/providers/elevenlabs.d.ts.map +1 -0
  125. package/dist/providers/elevenlabs.js +229 -0
  126. package/dist/providers/elevenlabs.js.map +1 -0
  127. package/dist/providers/google.d.ts +83 -1
  128. package/dist/providers/google.d.ts.map +1 -1
  129. package/dist/providers/google.js +491 -8
  130. package/dist/providers/google.js.map +1 -1
  131. package/dist/providers/openai.d.ts +3 -1
  132. package/dist/providers/openai.d.ts.map +1 -1
  133. package/dist/providers/openai.js +209 -5
  134. package/dist/providers/openai.js.map +1 -1
  135. package/dist/providers/voyage.d.ts +91 -0
  136. package/dist/providers/voyage.d.ts.map +1 -0
  137. package/dist/providers/voyage.js +166 -0
  138. package/dist/providers/voyage.js.map +1 -0
  139. package/dist/queue-job.d.ts +69 -4
  140. package/dist/queue-job.d.ts.map +1 -1
  141. package/dist/queue-job.js +114 -11
  142. package/dist/queue-job.js.map +1 -1
  143. package/dist/registry.d.ts +3 -1
  144. package/dist/registry.d.ts.map +1 -1
  145. package/dist/registry.js +10 -0
  146. package/dist/registry.js.map +1 -1
  147. package/dist/server/provider.d.ts.map +1 -1
  148. package/dist/server/provider.js +23 -1
  149. package/dist/server/provider.js.map +1 -1
  150. package/dist/similarity-search.d.ts +163 -0
  151. package/dist/similarity-search.d.ts.map +1 -0
  152. package/dist/similarity-search.js +147 -0
  153. package/dist/similarity-search.js.map +1 -0
  154. package/dist/tool.d.ts.map +1 -1
  155. package/dist/tool.js +13 -4
  156. package/dist/tool.js.map +1 -1
  157. package/dist/types.d.ts +246 -0
  158. package/dist/types.d.ts.map +1 -1
  159. package/dist/vector-stores/index.d.ts +96 -0
  160. package/dist/vector-stores/index.d.ts.map +1 -0
  161. package/dist/vector-stores/index.js +153 -0
  162. package/dist/vector-stores/index.js.map +1 -0
  163. 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,EAIZ,MAAM,aAAa,CAAA;AACpB,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;CAG3B;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;IA0DpB,QAAQ,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmBnE,MAAM,CAAC,OAAO,EAAE,sBAAsB,GAAG,aAAa,CAAC,WAAW,CAAC;CAkD3E"}
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"}
@@ -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'] = [{ functionDeclarations: geminiTools }];
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: [{ functionDeclarations: geminiTools }] } : {}),
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
- return tools.map(t => ({
235
- name: t.name,
236
- description: t.description,
237
- parameters: t.parameters,
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