@tstdl/base 0.93.110 → 0.93.112

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.
@@ -1,2 +1,4 @@
1
1
  export * from './helpers.js';
2
2
  export * from './module.js';
3
+ export * from './multi-region.plugin.js';
4
+ export * from './types.js';
@@ -1,2 +1,4 @@
1
1
  export * from './helpers.js';
2
2
  export * from './module.js';
3
+ export * from './multi-region.plugin.js';
4
+ export * from './types.js';
@@ -1,5 +1,7 @@
1
- import { vertexAI } from '@genkit-ai/google-genai';
1
+ import { vertexAI, type GeminiConfig } from '@genkit-ai/google-genai';
2
2
  import { type Genkit, type GenkitOptions } from 'genkit';
3
+ import { type GeminiModelReference } from './multi-region.plugin.js';
4
+ import type { VertexAiMultiLocationOptions } from './types.js';
3
5
  /**
4
6
  * Options for configuring the AI Module.
5
7
  */
@@ -20,6 +22,8 @@ export declare class GenkitModuleOptions {
20
22
  /** Path to the Google Cloud credentials file */
21
23
  keyFile?: string;
22
24
  };
25
+ /** Multi-region configuration options. */
26
+ multiLocationVertex?: VertexAiMultiLocationOptions;
23
27
  /** Plugins to register automatically */
24
28
  plugins?: GenkitOptions['plugins'];
25
29
  /** Default Genkit options */
@@ -32,4 +36,5 @@ export declare class GenkitModuleOptions {
32
36
  export declare function configureGenkit(options: GenkitModuleOptions): void;
33
37
  export declare function injectGenkit(options?: GenkitOptions): Genkit;
34
38
  declare const _injectModel: typeof vertexAI.model;
39
+ export declare function injectMultiLocationModel(model: string, config?: GeminiConfig): GeminiModelReference;
35
40
  export { _injectModel as injectModel };
@@ -1,8 +1,11 @@
1
1
  import { googleAI, vertexAI } from '@genkit-ai/google-genai';
2
2
  import { genkit } from 'genkit';
3
+ import { CircuitBreakerProvider } from '../../circuit-breaker/provider.js';
3
4
  import { inject } from '../../injector/inject.js';
4
5
  import { Injector } from '../../injector/injector.js';
6
+ import { Logger } from '../../logger/logger.js';
5
7
  import { isDefined, isNotNull } from '../../utils/type-guards.js';
8
+ import { vertexAiMultiLocation } from './multi-region.plugin.js';
6
9
  /**
7
10
  * Options for configuring the AI Module.
8
11
  */
@@ -11,6 +14,8 @@ export class GenkitModuleOptions {
11
14
  gemini;
12
15
  /** Vertex AI specific options. If provided, the service will use Vertex AI endpoints. */
13
16
  vertex;
17
+ /** Multi-region configuration options. */
18
+ multiLocationVertex;
14
19
  /** Plugins to register automatically */
15
20
  plugins;
16
21
  /** Default Genkit options */
@@ -36,12 +41,21 @@ export function injectGenkit(options) {
36
41
  const geminiPlugin = isDefined(gemini)
37
42
  ? googleAI({ apiKey: gemini.apiKey })
38
43
  : null;
44
+ const multiLocationVertexPlugin = isDefined(moduleOptions.multiLocationVertex)
45
+ ? vertexAiMultiLocation({
46
+ locations: moduleOptions.multiLocationVertex.locations,
47
+ circuitBreakerProvider: inject(CircuitBreakerProvider),
48
+ logger: inject(Logger, 'VertexAiMultiRegion'),
49
+ circuitBreakerConfig: moduleOptions.multiLocationVertex.circuitBreakerConfig,
50
+ })
51
+ : null;
39
52
  return genkit({
40
53
  ...moduleOptions.options,
41
54
  ...options,
42
55
  plugins: [
43
56
  geminiPlugin,
44
57
  vertexPlugin,
58
+ multiLocationVertexPlugin,
45
59
  ...(moduleOptions.plugins ?? []),
46
60
  ...(options?.plugins ?? []),
47
61
  ].filter(isNotNull),
@@ -53,4 +67,7 @@ function injectModel(...args) {
53
67
  return provider.model(...args);
54
68
  }
55
69
  const _injectModel = injectModel;
70
+ export function injectMultiLocationModel(model, config) {
71
+ return vertexAiMultiLocation.model(_injectModel(model, config));
72
+ }
56
73
  export { _injectModel as injectModel };
@@ -0,0 +1,424 @@
1
+ import type { CircuitBreakerProvider } from '../../circuit-breaker/provider.js';
2
+ import type { Logger } from '../../logger/logger.js';
3
+ import type { VertexAiMultiLocationOptions } from './types.js';
4
+ export type GeminiModelReference = typeof geminiModelReference;
5
+ declare const geminiModelReference: import("genkit").ModelReference<import("zod").ZodObject<{
6
+ version: import("zod").ZodOptional<import("zod").ZodString>;
7
+ maxOutputTokens: import("zod").ZodOptional<import("zod").ZodNumber>;
8
+ topK: import("zod").ZodOptional<import("zod").ZodNumber>;
9
+ stopSequences: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
10
+ } & {
11
+ apiKey: import("zod").ZodOptional<import("zod").ZodString>;
12
+ labels: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodString>>;
13
+ temperature: import("zod").ZodOptional<import("zod").ZodNumber>;
14
+ topP: import("zod").ZodOptional<import("zod").ZodNumber>;
15
+ location: import("zod").ZodOptional<import("zod").ZodString>;
16
+ safetySettings: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
17
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
18
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
19
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
20
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
21
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
22
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
23
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
24
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
25
+ }, import("zod").ZodTypeAny, "passthrough">>, "many">>;
26
+ vertexRetrieval: import("zod").ZodOptional<import("zod").ZodObject<{
27
+ datastore: import("zod").ZodObject<{
28
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
29
+ location: import("zod").ZodOptional<import("zod").ZodString>;
30
+ dataStoreId: import("zod").ZodString;
31
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
32
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
33
+ location: import("zod").ZodOptional<import("zod").ZodString>;
34
+ dataStoreId: import("zod").ZodString;
35
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
36
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
37
+ location: import("zod").ZodOptional<import("zod").ZodString>;
38
+ dataStoreId: import("zod").ZodString;
39
+ }, import("zod").ZodTypeAny, "passthrough">>;
40
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
41
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
42
+ datastore: import("zod").ZodObject<{
43
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
44
+ location: import("zod").ZodOptional<import("zod").ZodString>;
45
+ dataStoreId: import("zod").ZodString;
46
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
47
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
48
+ location: import("zod").ZodOptional<import("zod").ZodString>;
49
+ dataStoreId: import("zod").ZodString;
50
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
51
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
52
+ location: import("zod").ZodOptional<import("zod").ZodString>;
53
+ dataStoreId: import("zod").ZodString;
54
+ }, import("zod").ZodTypeAny, "passthrough">>;
55
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
56
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
57
+ datastore: import("zod").ZodObject<{
58
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
59
+ location: import("zod").ZodOptional<import("zod").ZodString>;
60
+ dataStoreId: import("zod").ZodString;
61
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
62
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
63
+ location: import("zod").ZodOptional<import("zod").ZodString>;
64
+ dataStoreId: import("zod").ZodString;
65
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
66
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
67
+ location: import("zod").ZodOptional<import("zod").ZodString>;
68
+ dataStoreId: import("zod").ZodString;
69
+ }, import("zod").ZodTypeAny, "passthrough">>;
70
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
71
+ }, import("zod").ZodTypeAny, "passthrough">>>;
72
+ googleSearchRetrieval: import("zod").ZodOptional<import("zod").ZodObject<{
73
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
74
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
75
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
76
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
77
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
78
+ }, import("zod").ZodTypeAny, "passthrough">>>;
79
+ functionCallingConfig: import("zod").ZodOptional<import("zod").ZodObject<{
80
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
81
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
82
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
83
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
84
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
85
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
86
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
87
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
88
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
89
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
90
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
91
+ }, import("zod").ZodTypeAny, "passthrough">>>;
92
+ retrievalConfig: import("zod").ZodOptional<import("zod").ZodObject<{
93
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
94
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
95
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
96
+ }, "strip", import("zod").ZodTypeAny, {
97
+ latitude?: number | undefined;
98
+ longitude?: number | undefined;
99
+ }, {
100
+ latitude?: number | undefined;
101
+ longitude?: number | undefined;
102
+ }>>;
103
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
104
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
105
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
106
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
107
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
108
+ }, "strip", import("zod").ZodTypeAny, {
109
+ latitude?: number | undefined;
110
+ longitude?: number | undefined;
111
+ }, {
112
+ latitude?: number | undefined;
113
+ longitude?: number | undefined;
114
+ }>>;
115
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
116
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
117
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
118
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
119
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
120
+ }, "strip", import("zod").ZodTypeAny, {
121
+ latitude?: number | undefined;
122
+ longitude?: number | undefined;
123
+ }, {
124
+ latitude?: number | undefined;
125
+ longitude?: number | undefined;
126
+ }>>;
127
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
128
+ }, import("zod").ZodTypeAny, "passthrough">>>;
129
+ thinkingConfig: import("zod").ZodOptional<import("zod").ZodObject<{
130
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
131
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
132
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
133
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
134
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
135
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
136
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
137
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
138
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
139
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
140
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
141
+ }, import("zod").ZodTypeAny, "passthrough">>>;
142
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
143
+ version: import("zod").ZodOptional<import("zod").ZodString>;
144
+ maxOutputTokens: import("zod").ZodOptional<import("zod").ZodNumber>;
145
+ topK: import("zod").ZodOptional<import("zod").ZodNumber>;
146
+ stopSequences: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
147
+ } & {
148
+ apiKey: import("zod").ZodOptional<import("zod").ZodString>;
149
+ labels: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodString>>;
150
+ temperature: import("zod").ZodOptional<import("zod").ZodNumber>;
151
+ topP: import("zod").ZodOptional<import("zod").ZodNumber>;
152
+ location: import("zod").ZodOptional<import("zod").ZodString>;
153
+ safetySettings: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
154
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
155
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
156
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
157
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
158
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
159
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
160
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
161
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
162
+ }, import("zod").ZodTypeAny, "passthrough">>, "many">>;
163
+ vertexRetrieval: import("zod").ZodOptional<import("zod").ZodObject<{
164
+ datastore: import("zod").ZodObject<{
165
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
166
+ location: import("zod").ZodOptional<import("zod").ZodString>;
167
+ dataStoreId: import("zod").ZodString;
168
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
169
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
170
+ location: import("zod").ZodOptional<import("zod").ZodString>;
171
+ dataStoreId: import("zod").ZodString;
172
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
173
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
174
+ location: import("zod").ZodOptional<import("zod").ZodString>;
175
+ dataStoreId: import("zod").ZodString;
176
+ }, import("zod").ZodTypeAny, "passthrough">>;
177
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
178
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
179
+ datastore: import("zod").ZodObject<{
180
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
181
+ location: import("zod").ZodOptional<import("zod").ZodString>;
182
+ dataStoreId: import("zod").ZodString;
183
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
184
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
185
+ location: import("zod").ZodOptional<import("zod").ZodString>;
186
+ dataStoreId: import("zod").ZodString;
187
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
188
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
189
+ location: import("zod").ZodOptional<import("zod").ZodString>;
190
+ dataStoreId: import("zod").ZodString;
191
+ }, import("zod").ZodTypeAny, "passthrough">>;
192
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
193
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
194
+ datastore: import("zod").ZodObject<{
195
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
196
+ location: import("zod").ZodOptional<import("zod").ZodString>;
197
+ dataStoreId: import("zod").ZodString;
198
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
199
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
200
+ location: import("zod").ZodOptional<import("zod").ZodString>;
201
+ dataStoreId: import("zod").ZodString;
202
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
203
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
204
+ location: import("zod").ZodOptional<import("zod").ZodString>;
205
+ dataStoreId: import("zod").ZodString;
206
+ }, import("zod").ZodTypeAny, "passthrough">>;
207
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
208
+ }, import("zod").ZodTypeAny, "passthrough">>>;
209
+ googleSearchRetrieval: import("zod").ZodOptional<import("zod").ZodObject<{
210
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
211
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
212
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
213
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
214
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
215
+ }, import("zod").ZodTypeAny, "passthrough">>>;
216
+ functionCallingConfig: import("zod").ZodOptional<import("zod").ZodObject<{
217
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
218
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
219
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
220
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
221
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
222
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
223
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
224
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
225
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
226
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
227
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
228
+ }, import("zod").ZodTypeAny, "passthrough">>>;
229
+ retrievalConfig: import("zod").ZodOptional<import("zod").ZodObject<{
230
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
231
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
232
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
233
+ }, "strip", import("zod").ZodTypeAny, {
234
+ latitude?: number | undefined;
235
+ longitude?: number | undefined;
236
+ }, {
237
+ latitude?: number | undefined;
238
+ longitude?: number | undefined;
239
+ }>>;
240
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
241
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
242
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
243
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
244
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
245
+ }, "strip", import("zod").ZodTypeAny, {
246
+ latitude?: number | undefined;
247
+ longitude?: number | undefined;
248
+ }, {
249
+ latitude?: number | undefined;
250
+ longitude?: number | undefined;
251
+ }>>;
252
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
253
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
254
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
255
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
256
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
257
+ }, "strip", import("zod").ZodTypeAny, {
258
+ latitude?: number | undefined;
259
+ longitude?: number | undefined;
260
+ }, {
261
+ latitude?: number | undefined;
262
+ longitude?: number | undefined;
263
+ }>>;
264
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
265
+ }, import("zod").ZodTypeAny, "passthrough">>>;
266
+ thinkingConfig: import("zod").ZodOptional<import("zod").ZodObject<{
267
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
268
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
269
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
270
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
271
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
272
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
273
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
274
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
275
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
276
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
277
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
278
+ }, import("zod").ZodTypeAny, "passthrough">>>;
279
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
280
+ version: import("zod").ZodOptional<import("zod").ZodString>;
281
+ maxOutputTokens: import("zod").ZodOptional<import("zod").ZodNumber>;
282
+ topK: import("zod").ZodOptional<import("zod").ZodNumber>;
283
+ stopSequences: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
284
+ } & {
285
+ apiKey: import("zod").ZodOptional<import("zod").ZodString>;
286
+ labels: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodString>>;
287
+ temperature: import("zod").ZodOptional<import("zod").ZodNumber>;
288
+ topP: import("zod").ZodOptional<import("zod").ZodNumber>;
289
+ location: import("zod").ZodOptional<import("zod").ZodString>;
290
+ safetySettings: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
291
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
292
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
293
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
294
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
295
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
296
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
297
+ category: import("zod").ZodEnum<["HARM_CATEGORY_UNSPECIFIED", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_SEXUALLY_EXPLICIT"]>;
298
+ threshold: import("zod").ZodEnum<["BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE"]>;
299
+ }, import("zod").ZodTypeAny, "passthrough">>, "many">>;
300
+ vertexRetrieval: import("zod").ZodOptional<import("zod").ZodObject<{
301
+ datastore: import("zod").ZodObject<{
302
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
303
+ location: import("zod").ZodOptional<import("zod").ZodString>;
304
+ dataStoreId: import("zod").ZodString;
305
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
306
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
307
+ location: import("zod").ZodOptional<import("zod").ZodString>;
308
+ dataStoreId: import("zod").ZodString;
309
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
310
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
311
+ location: import("zod").ZodOptional<import("zod").ZodString>;
312
+ dataStoreId: import("zod").ZodString;
313
+ }, import("zod").ZodTypeAny, "passthrough">>;
314
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
315
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
316
+ datastore: import("zod").ZodObject<{
317
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
318
+ location: import("zod").ZodOptional<import("zod").ZodString>;
319
+ dataStoreId: import("zod").ZodString;
320
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
321
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
322
+ location: import("zod").ZodOptional<import("zod").ZodString>;
323
+ dataStoreId: import("zod").ZodString;
324
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
325
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
326
+ location: import("zod").ZodOptional<import("zod").ZodString>;
327
+ dataStoreId: import("zod").ZodString;
328
+ }, import("zod").ZodTypeAny, "passthrough">>;
329
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
330
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
331
+ datastore: import("zod").ZodObject<{
332
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
333
+ location: import("zod").ZodOptional<import("zod").ZodString>;
334
+ dataStoreId: import("zod").ZodString;
335
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
336
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
337
+ location: import("zod").ZodOptional<import("zod").ZodString>;
338
+ dataStoreId: import("zod").ZodString;
339
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
340
+ projectId: import("zod").ZodOptional<import("zod").ZodString>;
341
+ location: import("zod").ZodOptional<import("zod").ZodString>;
342
+ dataStoreId: import("zod").ZodString;
343
+ }, import("zod").ZodTypeAny, "passthrough">>;
344
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
345
+ }, import("zod").ZodTypeAny, "passthrough">>>;
346
+ googleSearchRetrieval: import("zod").ZodOptional<import("zod").ZodObject<{
347
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
348
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
349
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
350
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
351
+ disableAttribution: import("zod").ZodOptional<import("zod").ZodBoolean>;
352
+ }, import("zod").ZodTypeAny, "passthrough">>>;
353
+ functionCallingConfig: import("zod").ZodOptional<import("zod").ZodObject<{
354
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
355
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
356
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
357
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
358
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
359
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
360
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
361
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
362
+ mode: import("zod").ZodOptional<import("zod").ZodEnum<["MODE_UNSPECIFIED", "AUTO", "ANY", "NONE"]>>;
363
+ allowedFunctionNames: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
364
+ streamFunctionCallArguments: import("zod").ZodOptional<import("zod").ZodBoolean>;
365
+ }, import("zod").ZodTypeAny, "passthrough">>>;
366
+ retrievalConfig: import("zod").ZodOptional<import("zod").ZodObject<{
367
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
368
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
369
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
370
+ }, "strip", import("zod").ZodTypeAny, {
371
+ latitude?: number | undefined;
372
+ longitude?: number | undefined;
373
+ }, {
374
+ latitude?: number | undefined;
375
+ longitude?: number | undefined;
376
+ }>>;
377
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
378
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
379
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
380
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
381
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
382
+ }, "strip", import("zod").ZodTypeAny, {
383
+ latitude?: number | undefined;
384
+ longitude?: number | undefined;
385
+ }, {
386
+ latitude?: number | undefined;
387
+ longitude?: number | undefined;
388
+ }>>;
389
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
390
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
391
+ latLng: import("zod").ZodOptional<import("zod").ZodObject<{
392
+ latitude: import("zod").ZodOptional<import("zod").ZodNumber>;
393
+ longitude: import("zod").ZodOptional<import("zod").ZodNumber>;
394
+ }, "strip", import("zod").ZodTypeAny, {
395
+ latitude?: number | undefined;
396
+ longitude?: number | undefined;
397
+ }, {
398
+ latitude?: number | undefined;
399
+ longitude?: number | undefined;
400
+ }>>;
401
+ languageCode: import("zod").ZodOptional<import("zod").ZodString>;
402
+ }, import("zod").ZodTypeAny, "passthrough">>>;
403
+ thinkingConfig: import("zod").ZodOptional<import("zod").ZodObject<{
404
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
405
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
406
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
407
+ }, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
408
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
409
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
410
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
411
+ }, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
412
+ includeThoughts: import("zod").ZodOptional<import("zod").ZodBoolean>;
413
+ thinkingBudget: import("zod").ZodOptional<import("zod").ZodNumber>;
414
+ thinkingLevel: import("zod").ZodOptional<import("zod").ZodEnum<["MINIMAL", "LOW", "MEDIUM", "HIGH"]>>;
415
+ }, import("zod").ZodTypeAny, "passthrough">>>;
416
+ }, import("zod").ZodTypeAny, "passthrough">>>;
417
+ export declare function vertexAiMultiLocation(options: VertexAiMultiLocationOptions & {
418
+ circuitBreakerProvider: CircuitBreakerProvider;
419
+ logger: Logger;
420
+ }): import("genkit/plugin").GenkitPlugin;
421
+ export declare namespace vertexAiMultiLocation {
422
+ var model: <T extends GeminiModelReference>(baseModel: T) => T;
423
+ }
424
+ export {};
@@ -0,0 +1,85 @@
1
+ import { vertexAI } from '@genkit-ai/google-genai';
2
+ import { GenkitError, modelRef } from 'genkit';
3
+ import { genkitPlugin } from 'genkit/plugin';
4
+ import { shuffle } from '../../utils/array/index.js';
5
+ import { isInstanceOf, isNullOrUndefined } from '../../utils/type-guards.js';
6
+ const pluginKey = 'vertexai-multi-location';
7
+ const geminiModelReference = vertexAI.model('gemini-2.5-flash');
8
+ export function vertexAiMultiLocation(options) {
9
+ const createVirtualizedModelAction = async (ai, modelName) => {
10
+ const baseModelName = `vertexai/${modelName}`;
11
+ const target = modelName;
12
+ const baseModelAction = await ai.registry.lookupAction(`/model/${baseModelName}`);
13
+ if (isNullOrUndefined(baseModelAction)) {
14
+ return null;
15
+ }
16
+ return ai.defineModel({
17
+ name: target,
18
+ versions: baseModelAction.__action.metadata?.['model']?.versions,
19
+ supports: baseModelAction.__action.metadata?.['model']?.supports,
20
+ configSchema: baseModelAction.__action.inputSchema?.shape?.config,
21
+ label: `${baseModelAction.__action.description ?? baseModelAction.__action.name} (Multi-Location Routing)`,
22
+ }, async (request, streamingCallback) => {
23
+ const shuffledLocations = shuffle([...options.locations]);
24
+ let lastError;
25
+ for (const location of shuffledLocations) {
26
+ const circuitBreakerKey = `genkit:vertex-ai:location:${location}`;
27
+ const circuitBreaker = options.circuitBreakerProvider.provide(circuitBreakerKey, {
28
+ threshold: 1, // Aggressive for 429
29
+ resetTimeout: options.circuitBreakerConfig?.resetTimeout ?? 30000,
30
+ ...options.circuitBreakerConfig,
31
+ });
32
+ const check = await circuitBreaker.check();
33
+ if (!check.allowed) {
34
+ options.logger.warn(`Location ${location} is currently unhealthy. Skipping...`);
35
+ continue;
36
+ }
37
+ try {
38
+ const result = await baseModelAction({
39
+ ...request,
40
+ config: {
41
+ ...request.config,
42
+ location: location,
43
+ },
44
+ }, {
45
+ onChunk: streamingCallback,
46
+ });
47
+ await circuitBreaker.recordSuccess();
48
+ return result;
49
+ }
50
+ catch (error) {
51
+ lastError = error;
52
+ if (!isInstanceOf(error, GenkitError)) {
53
+ throw error;
54
+ }
55
+ const isRetryable = ((error.status == 'RESOURCE_EXHAUSTED') || (error.status == 'UNAVAILABLE') || error.message.includes('quota'));
56
+ if (!isRetryable) {
57
+ throw error;
58
+ }
59
+ options.logger.warn(`Location ${location} responded with ${error.status}. Tripping circuit breaker and trying next location...`);
60
+ await circuitBreaker.recordFailure();
61
+ }
62
+ }
63
+ throw lastError;
64
+ });
65
+ };
66
+ return genkitPlugin(pluginKey, async (_ai) => {
67
+ // Register nothing initially, rely on lazy resolution via resolver
68
+ }, async (ai, actionType, target) => {
69
+ if (actionType != 'model') {
70
+ return;
71
+ }
72
+ // Register the action immediately so the lookup succeeds
73
+ // We must register it directly in the registry since ai.defineModel is async and might be too late
74
+ (ai.registry).registerActionAsync('model', target, createVirtualizedModelAction(ai, target), { namespace: pluginKey });
75
+ });
76
+ }
77
+ vertexAiMultiLocation.model = function multiLocationModel(baseModel) {
78
+ return modelRef({
79
+ name: `${pluginKey}/${baseModel.name.replace('vertexai/', '')}`,
80
+ configSchema: baseModel.configSchema,
81
+ info: baseModel.info,
82
+ version: baseModel.version,
83
+ config: baseModel.config,
84
+ });
85
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,138 @@
1
+ import { genkit, GenkitError, z } from 'genkit';
2
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { CircuitBreakerState } from '../../../circuit-breaker/index.js';
4
+ import { CircuitBreakerProvider } from '../../../circuit-breaker/provider.js';
5
+ import { Logger } from '../../../logger/logger.js';
6
+ import { setupIntegrationTest } from '../../../unit-test/index.js';
7
+ import { vertexAiMultiLocation } from '../multi-region.plugin.js';
8
+ vi.mock('#/utils/array/index.js', async (importOriginal) => {
9
+ const actual = await importOriginal();
10
+ return {
11
+ ...actual,
12
+ shuffle: vi.fn((items) => [...items]),
13
+ };
14
+ });
15
+ vi.mock('@genkit-ai/google-genai', () => ({
16
+ vertexAI: {
17
+ model: vi.fn((name) => ({
18
+ name: `vertexai/${name}`,
19
+ info: { label: 'mock' },
20
+ configSchema: z.object({}),
21
+ })),
22
+ },
23
+ googleAI: vi.fn(),
24
+ }));
25
+ describe('Genkit vertexai-multi-location Plugin Tests', () => {
26
+ let ai;
27
+ let cbProvider;
28
+ let logger;
29
+ beforeAll(async () => {
30
+ const { injector } = await setupIntegrationTest({ modules: { circuitBreaker: true } });
31
+ cbProvider = injector.resolve(CircuitBreakerProvider);
32
+ logger = injector.resolve(Logger, 'Test');
33
+ ai = genkit({
34
+ plugins: [
35
+ vertexAiMultiLocation({
36
+ locations: ['region-1', 'region-2'],
37
+ circuitBreakerProvider: cbProvider,
38
+ logger,
39
+ circuitBreakerConfig: { resetTimeout: 1000000 },
40
+ }),
41
+ ],
42
+ });
43
+ });
44
+ beforeEach(async () => {
45
+ vi.clearAllMocks();
46
+ const config = { threshold: 1, resetTimeout: 1000000 };
47
+ await cbProvider.provide('genkit:vertex-ai:location:region-1', config).recordSuccess();
48
+ await cbProvider.provide('genkit:vertex-ai:location:region-2', config).recordSuccess();
49
+ await cbProvider.provide('genkit:vertex-ai:location:cb-fail', config).recordSuccess();
50
+ await cbProvider.provide('genkit:vertex-ai:location:cb-success', config).recordSuccess();
51
+ });
52
+ it('should route to a location successfully', async () => {
53
+ // Register a mock for the regional model in the registry
54
+ ai.defineModel({
55
+ name: 'vertexai/gemini-2.5-flash',
56
+ }, async (request) => {
57
+ return {
58
+ message: {
59
+ role: 'model',
60
+ content: [{ text: `hello from ${request.config?.location}` }],
61
+ },
62
+ };
63
+ });
64
+ const response = await ai.generate({
65
+ model: 'vertexai-multi-location/gemini-2.5-flash',
66
+ prompt: 'test',
67
+ });
68
+ expect(response.text).toContain('hello from');
69
+ });
70
+ it('should failover on 429 error', async () => {
71
+ let callCount = 0;
72
+ // Overwrite the mock model to fail once
73
+ ai.defineModel({
74
+ name: 'vertexai/gemini-2.5-flash',
75
+ }, async (request) => {
76
+ callCount++;
77
+ if (callCount === 1) {
78
+ throw new GenkitError({ status: 'RESOURCE_EXHAUSTED', message: 'Rate limit exceeded' });
79
+ }
80
+ return {
81
+ message: {
82
+ role: 'model',
83
+ content: [{ text: `success from ${request.config?.location}` }],
84
+ },
85
+ };
86
+ });
87
+ const response = await ai.generate({
88
+ model: 'vertexai-multi-location/gemini-2.5-flash',
89
+ prompt: 'test',
90
+ });
91
+ expect(response.text).toContain('success from');
92
+ expect(callCount).toBe(2);
93
+ });
94
+ it('should trip circuit breaker on 429 and skip location', async () => {
95
+ const locationToFail = 'cb-fail';
96
+ const locationToSuccess = 'cb-success';
97
+ const locations = [locationToFail, locationToSuccess];
98
+ ai = genkit({
99
+ plugins: [
100
+ vertexAiMultiLocation({
101
+ locations,
102
+ circuitBreakerProvider: cbProvider,
103
+ logger,
104
+ circuitBreakerConfig: { resetTimeout: 1000000 },
105
+ }),
106
+ ],
107
+ });
108
+ ai.defineModel({
109
+ name: 'vertexai/gemini-2.5-flash',
110
+ }, async (request) => {
111
+ if (request.config?.location === locationToFail) {
112
+ throw new GenkitError({ status: 'RESOURCE_EXHAUSTED', message: 'Rate limit' });
113
+ }
114
+ return {
115
+ message: {
116
+ role: 'model',
117
+ content: [{ text: `success from ${request.config?.location}` }],
118
+ },
119
+ };
120
+ });
121
+ // First call to trip the breaker for 'locationToFail'
122
+ const response1 = await ai.generate({
123
+ model: 'vertexai-multi-location/gemini-2.5-flash',
124
+ prompt: 'test',
125
+ });
126
+ expect(response1.text).toBe('success from cb-success');
127
+ const cb = cbProvider.provide(`genkit:vertex-ai:location:${locationToFail}`, { threshold: 1, resetTimeout: 1000000 });
128
+ const status = await cb.check();
129
+ expect(status.state).toBe(CircuitBreakerState.Open);
130
+ expect(status.allowed).toBe(false);
131
+ // Second call should skip 'locationToFail' automatically
132
+ const response2 = await ai.generate({
133
+ model: 'vertexai-multi-location/gemini-2.5-flash',
134
+ prompt: 'test',
135
+ });
136
+ expect(response2.text).toBe('success from cb-success');
137
+ });
138
+ });
@@ -0,0 +1,10 @@
1
+ import type { CircuitBreakerConfig } from '../../circuit-breaker/circuit-breaker.js';
2
+ export interface VertexAiMultiLocationOptions {
3
+ /** The Google Cloud locations to use for routing. */
4
+ locations: string[];
5
+ /**
6
+ * Optional circuit breaker configuration.
7
+ * By default, a threshold of 1 is used for 429 errors.
8
+ */
9
+ circuitBreakerConfig?: Partial<CircuitBreakerConfig>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
2
2
  import { AuthenticationClientService } from '../../authentication/client/index.js';
3
3
  import { AuthenticationAncillaryService, AuthenticationService as AuthenticationServerService } from '../../authentication/server/index.js';
4
4
  import { HttpClientOptions } from '../../http/client/index.js';
@@ -17,6 +17,13 @@ describe('AuthenticationApiController Integration', () => {
17
17
  const schema = 'authentication';
18
18
  const tenantId = crypto.randomUUID();
19
19
  beforeAll(async () => {
20
+ const storage = new Map();
21
+ globalThis.localStorage = {
22
+ getItem: vi.fn((key) => storage.get(key) ?? null),
23
+ setItem: vi.fn((key, value) => storage.set(key, value)),
24
+ removeItem: vi.fn((key) => storage.delete(key)),
25
+ clear: vi.fn(() => storage.clear()),
26
+ };
20
27
  ({ injector, database } = await setupIntegrationTest({
21
28
  modules: { authentication: true, audit: true, keyValueStore: true, test: true, api: true, webServer: true },
22
29
  authenticationAncillaryService: DefaultAuthenticationAncillaryService,
@@ -36,6 +43,7 @@ describe('AuthenticationApiController Integration', () => {
36
43
  await injector?.dispose();
37
44
  });
38
45
  beforeEach(async () => {
46
+ globalThis.localStorage?.clear();
39
47
  await clearTenantData(database, schema, ['credentials', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
40
48
  });
41
49
  test('login should work via API client', async () => {
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from 'vitest';
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
2
2
  import { AuthenticationClientService } from '../../authentication/client/index.js';
3
3
  import { AuthenticationService as AuthenticationServerService } from '../../authentication/server/index.js';
4
4
  import { HttpClientOptions } from '../../http/client/index.js';
@@ -17,6 +17,13 @@ describe('AuthenticationClientService Integration', () => {
17
17
  const schema = 'authentication';
18
18
  const tenantId = crypto.randomUUID();
19
19
  beforeAll(async () => {
20
+ const storage = new Map();
21
+ globalThis.localStorage = {
22
+ getItem: vi.fn((key) => storage.get(key) ?? null),
23
+ setItem: vi.fn((key, value) => storage.set(key, value)),
24
+ removeItem: vi.fn((key) => storage.delete(key)),
25
+ clear: vi.fn(() => storage.clear()),
26
+ };
20
27
  ({ injector, database } = await setupIntegrationTest({
21
28
  modules: { authentication: true, audit: true, keyValueStore: true, test: true, api: true, webServer: true },
22
29
  authenticationAncillaryService: DefaultAuthenticationAncillaryService,
@@ -36,6 +43,7 @@ describe('AuthenticationClientService Integration', () => {
36
43
  await injector?.dispose();
37
44
  });
38
45
  beforeEach(async () => {
46
+ globalThis.localStorage?.clear();
39
47
  await clearTenantData(database, schema, ['credentials', 'session', 'user', 'service_account', 'system_account', 'subject'], tenantId);
40
48
  });
41
49
  test('login and logout should work', async () => {
@@ -4,10 +4,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ import { formatError } from '../../errors/index.js';
7
8
  import { Singleton } from '../../injector/decorators.js';
8
9
  import { supportsColoredStdout } from '../../supports.js';
9
10
  import { enumValueName } from '../../utils/enum.js';
10
- import { formatError } from '../../errors/index.js';
11
11
  import { objectKeys } from '../../utils/object/object.js';
12
12
  import { isNotNullOrUndefined } from '../../utils/type-guards.js';
13
13
  import { LogFormatter } from '../formatter.js';
@@ -4,7 +4,7 @@
4
4
  * simplifying common SQL operations like generating UUIDs, working with intervals,
5
5
  * and aggregating data.
6
6
  */
7
- import { Column, type AnyColumn, type SQL, type SQLChunk } from 'drizzle-orm';
7
+ import { Column, SQL, type AnyColumn, type SQLChunk, type SQLWrapper } from 'drizzle-orm';
8
8
  import type { GetSelectTableSelection, SelectResultField, TableLike } from 'drizzle-orm/query-builders/select.types';
9
9
  import type { EnumerationObject, EnumerationValue, Record } from '../../types/types.js';
10
10
  import { type PgEnumFromEnumeration } from '../enums.js';
@@ -155,12 +155,12 @@ export declare namespace jsonAgg {
155
155
  export declare function coalesce<T extends (Column | SQL | SQL.Aliased)[]>(...columns: T): SQL<SelectResultField<T>[number]>;
156
156
  /**
157
157
  * Creates a PostgreSQL `to_jsonb` function call.
158
- * Converts the input column or SQL expression to a JSONB value.
159
- * @template T - The Drizzle column or SQL expression type.
160
- * @param column - The column or SQL expression to convert.
158
+ * Converts the input SQL expression to a JSONB value.
159
+ * @template T - The value type.
160
+ * @param value - The value to convert.
161
161
  * @returns A Drizzle SQL object representing the value as JSONB.
162
162
  */
163
- export declare function toJsonb<T extends (Column | SQL | SQL.Aliased)>(column: T): SQL<SelectResultField<T>>;
163
+ export declare function toJsonb<T extends SQLWrapper>(value: T): SQL<SelectResultField<T>>;
164
164
  /**
165
165
  * Creates a PostgreSQL `num_nulls` function call.
166
166
  * Counts the number of null arguments.
package/orm/sqls/sqls.js CHANGED
@@ -4,13 +4,27 @@
4
4
  * simplifying common SQL operations like generating UUIDs, working with intervals,
5
5
  * and aggregating data.
6
6
  */
7
- import { and, Column, eq, isSQLWrapper, sql, isNotNull as sqlIsNotNull, isNull as sqlIsNull, Table } from 'drizzle-orm';
7
+ import { and, Column, eq, isSQLWrapper, sql, SQL, isNotNull as sqlIsNotNull, isNull as sqlIsNull, Table } from 'drizzle-orm';
8
8
  import { match, P } from 'ts-pattern';
9
9
  import { distinct, toArray } from '../../utils/array/array.js';
10
10
  import { objectEntries, objectValues } from '../../utils/object/object.js';
11
11
  import { assertDefined, isArray, isBoolean, isDefined, isInstanceOf, isNotNull, isNull, isNullOrUndefined, isNumber, isObject, isString } from '../../utils/type-guards.js';
12
12
  import { getEnumName } from '../enums.js';
13
13
  import { caseWhen } from './case-when.js';
14
+ const isJsonbSymbol = Symbol('isJsonb');
15
+ function markAsJsonb(value) {
16
+ value[isJsonbSymbol] = true;
17
+ return value;
18
+ }
19
+ function isJsonb(value) {
20
+ if (value?.[isJsonbSymbol] == true) {
21
+ return true;
22
+ }
23
+ if (isInstanceOf(value, Column) && (value.getSQLType() === 'jsonb')) {
24
+ return true;
25
+ }
26
+ return false;
27
+ }
14
28
  export const simpleJsonKeyPattern = /^[a-zA-Z0-9_-]+$/u;
15
29
  /** Drizzle SQL helper for getting the current transaction's timestamp. Returns a Date object. */
16
30
  export const TRANSACTION_TIMESTAMP = sql `transaction_timestamp()`;
@@ -171,13 +185,16 @@ export function coalesce(...columns) {
171
185
  }
172
186
  /**
173
187
  * Creates a PostgreSQL `to_jsonb` function call.
174
- * Converts the input column or SQL expression to a JSONB value.
175
- * @template T - The Drizzle column or SQL expression type.
176
- * @param column - The column or SQL expression to convert.
188
+ * Converts the input SQL expression to a JSONB value.
189
+ * @template T - The value type.
190
+ * @param value - The value to convert.
177
191
  * @returns A Drizzle SQL object representing the value as JSONB.
178
192
  */
179
- export function toJsonb(column) {
180
- return sql `to_jsonb(${column})`;
193
+ export function toJsonb(value) {
194
+ if (isJsonb(value)) {
195
+ return value.getSQL();
196
+ }
197
+ return markAsJsonb(sql `to_jsonb(${value})`);
181
198
  }
182
199
  /**
183
200
  * Creates a PostgreSQL `num_nulls` function call.
@@ -395,9 +412,9 @@ export function jsonbBuildObject(properties) {
395
412
  }
396
413
  }
397
414
  if (chunks.length == 0) {
398
- return sql `'{}'::jsonb`;
415
+ return markAsJsonb(sql `'{}'::jsonb`);
399
416
  }
400
- return sql `jsonb_build_object(${sql.join(chunks, sql `, `)})`;
417
+ return markAsJsonb(sql `jsonb_build_object(${sql.join(chunks, sql `, `)})`);
401
418
  }
402
419
  /**
403
420
  * A recursive utility to build PostgreSQL JSONB from TS structures.
@@ -405,20 +422,20 @@ export function jsonbBuildObject(properties) {
405
422
  */
406
423
  export function buildJsonb(value) {
407
424
  if (isSQLWrapper(value)) {
408
- return sql `to_jsonb(${value})`;
425
+ return toJsonb(value);
409
426
  }
410
427
  if (isNullOrUndefined(value)) {
411
- return sql `'null'::jsonb`;
428
+ return markAsJsonb(sql `'null'::jsonb`);
412
429
  }
413
430
  if (isArray(value)) {
414
431
  if (value.length == 0) {
415
- return sql `'[]'::jsonb`;
432
+ return markAsJsonb(sql `'[]'::jsonb`);
416
433
  }
417
434
  const elements = value.map((inner) => buildJsonb(inner));
418
- return sql `jsonb_build_array(${sql.join(elements, sql `, `)})`;
435
+ return markAsJsonb(sql `jsonb_build_array(${sql.join(elements, sql `, `)})`);
419
436
  }
420
437
  if (isObject(value)) {
421
438
  return jsonbBuildObject(value);
422
439
  }
423
- return sql `${JSON.stringify(value)}::jsonb`;
440
+ return markAsJsonb(sql `${JSON.stringify(value)}::jsonb`);
424
441
  }
@@ -68,7 +68,7 @@ describe('ORM Query Converter Complex', () => {
68
68
  const condition = convertQuery(q, table, colMap);
69
69
  const sqlStr = dialect.sqlToQuery(condition).sql;
70
70
  // Tokenizer is supported via JSON object syntax
71
- expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'match\', jsonb_build_object(\'value\', $1, \'tokenizer\', jsonb_build_object(\'type\', $2, \'min_gram\', $3, \'max_gram\', $4)))::pdb.query');
71
+ expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'match\', jsonb_build_object(\'value\', $1::jsonb, \'tokenizer\', jsonb_build_object(\'type\', $2::jsonb, \'min_gram\', $3::jsonb, \'max_gram\', $4::jsonb)))::pdb.query');
72
72
  });
73
73
  test('should handle ParadeDB $parade range', () => {
74
74
  const q = {
@@ -84,7 +84,7 @@ describe('ORM Query Converter Complex', () => {
84
84
  const condition = convertQuery(q, table, colMap);
85
85
  const sqlStr = dialect.sqlToQuery(condition).sql;
86
86
  // This should fall back to convertParadeComparisonQuery with recursive jsonb build
87
- expect(sqlStr).toContain('"test"."complex_items"."value" @@@ jsonb_build_object(\'range\', jsonb_build_object(\'lower_bound\', jsonb_build_object(\'included\', $1), \'upper_bound\', jsonb_build_object(\'excluded\', $2)))::pdb.query');
87
+ expect(sqlStr).toContain('"test"."complex_items"."value" @@@ jsonb_build_object(\'range\', jsonb_build_object(\'lower_bound\', jsonb_build_object(\'included\', $1::jsonb), \'upper_bound\', jsonb_build_object(\'excluded\', $2::jsonb)))::pdb.query');
88
88
  });
89
89
  test('should handle ParadeDB $parade phrasePrefix', () => {
90
90
  const q = {
@@ -96,7 +96,7 @@ describe('ORM Query Converter Complex', () => {
96
96
  };
97
97
  const condition = convertQuery(q, table, colMap);
98
98
  const sqlStr = dialect.sqlToQuery(condition).sql;
99
- expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'phrase_prefix\', jsonb_build_object(\'phrases\', jsonb_build_array($1, $2), \'max_expansions\', $3))::pdb.query');
99
+ expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'phrase_prefix\', jsonb_build_object(\'phrases\', jsonb_build_array($1::jsonb, $2::jsonb), \'max_expansions\', $3::jsonb))::pdb.query');
100
100
  });
101
101
  test('should handle ParadeDB $parade regexPhrase', () => {
102
102
  const q = {
@@ -108,7 +108,7 @@ describe('ORM Query Converter Complex', () => {
108
108
  };
109
109
  const condition = convertQuery(q, table, colMap);
110
110
  const sqlStr = dialect.sqlToQuery(condition).sql;
111
- expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'regex_phrase\', jsonb_build_object(\'regexes\', jsonb_build_array($1, $2), \'slop\', $3))::pdb.query');
111
+ expect(sqlStr).toContain('"test"."complex_items"."name" @@@ jsonb_build_object(\'regex_phrase\', jsonb_build_object(\'regexes\', jsonb_build_array($1::jsonb, $2::jsonb), \'slop\', $3::jsonb))::pdb.query');
112
112
  });
113
113
  test('should handle ParadeDB top-level moreLikeThis', () => {
114
114
  const q = {
@@ -121,6 +121,6 @@ describe('ORM Query Converter Complex', () => {
121
121
  };
122
122
  const condition = convertQuery(q, table, colMap);
123
123
  const sqlStr = dialect.sqlToQuery(condition).sql;
124
- expect(sqlStr).toContain('"test"."complex_items"."id" @@@ jsonb_build_object(\'more_like_this\', jsonb_build_object(\'key_value\', $1, \'fields\', ARRAY[$2, $3]))::pdb.query');
124
+ expect(sqlStr).toContain('"test"."complex_items"."id" @@@ jsonb_build_object(\'more_like_this\', jsonb_build_object(\'key_value\', $1::jsonb, \'fields\', to_jsonb(ARRAY[$2, $3])))::pdb.query');
125
125
  });
126
126
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.110",
3
+ "version": "0.93.112",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -153,7 +153,7 @@
153
153
  "peerDependencies": {
154
154
  "@genkit-ai/google-genai": "^1.28",
155
155
  "@google-cloud/storage": "^7.18",
156
- "@google/genai": "^1.39",
156
+ "@google/genai": "^1.40",
157
157
  "@toon-format/toon": "^2.1.0",
158
158
  "@tstdl/angular": "^0.93",
159
159
  "@zxcvbn-ts/core": "^3.0",
@@ -79,7 +79,7 @@ export const defaultQueueConfig = {
79
79
  };
80
80
  export class TaskQueue extends Transactional {
81
81
  config = this.transactionalContextData ?? (() => { const arg = injectArgument(this); return isString(arg) ? { namespace: arg } : arg; })();
82
- logger = inject(Logger, `TaskQueue:${this.config.namespace}`);
82
+ logger = inject(Logger, TaskQueue.name).with({ namespace: this.config.namespace });
83
83
  batch() {
84
84
  return new TaskQueueEnqueueBatch(this);
85
85
  }
package/test1.js CHANGED
@@ -1,20 +1,16 @@
1
1
  import './polyfills.js';
2
- import { AiService } from './ai/ai.service.js';
3
- import { configureAiService } from './ai/module.js';
2
+ import { z } from 'genkit';
3
+ import { convertToGenkitSchema } from './ai/genkit/helpers.js';
4
+ import { configureGenkit, injectGenkit, injectMultiLocationModel } from './ai/genkit/module.js';
4
5
  import { Application, provideInitializer, provideModule, provideSignalHandler } from './application/index.js';
5
- import { migrateAuditSchema } from './audit/index.js';
6
- import { configureAuthenticationServer } from './authentication/server/module.js';
6
+ import { configurePostgresCircuitBreaker, migratePostgresCircuitBreaker } from './circuit-breaker/postgres/module.js';
7
7
  import { inject, Injector, runInInjectionContext } from './injector/index.js';
8
- import { configurePostgresKeyValueStore, migratePostgresKeyValueStoreSchema } from './key-value-store/postgres/module.js';
9
- import { configurePostgresLock, migratePostgresLockSchema } from './lock/postgres/index.js';
10
8
  import { PrettyPrintLogFormatter, provideConsoleLogTransport } from './logger/index.js';
11
- import { configureOrm, injectRepository } from './orm/server/index.js';
12
- import { configurePostgresTaskQueue, migratePostgresTaskQueueSchema } from './task-queue/postgres/index.js';
13
- import { migrateTestSchema } from './test/module.js';
14
- import { Test, testData } from './test/test.model.js';
15
- import { timedBenchmarkAsync } from './utils/benchmark.js';
9
+ import { configureOrm } from './orm/server/index.js';
10
+ import { object, string } from './schema/index.js';
11
+ import { configurePostgresTaskQueue, migratePostgresTaskQueueSchema } from './task-queue/postgres/module.js';
16
12
  import * as configParser from './utils/config-parser.js';
17
- import { string } from './utils/config-parser.js';
13
+ import { assert } from './utils/type-guards.js';
18
14
  const config = {
19
15
  database: {
20
16
  host: configParser.string('DATABASE_HOST', '127.0.0.1'),
@@ -24,74 +20,95 @@ const config = {
24
20
  database: configParser.string('DATABASE_NAME', 'tstdl'),
25
21
  },
26
22
  ai: {
27
- apiKey: string('AI_API_KEY', undefined),
28
- keyFile: string('AI_API_KEY_FILE', undefined),
23
+ keyFile: configParser.string('AI_API_KEY_FILE', undefined),
29
24
  vertex: {
30
- project: string('AI_VERTEX_PROJECT', undefined),
31
- location: string('AI_VERTEX_LOCATION', undefined),
25
+ project: configParser.string('AI_VERTEX_PROJECT', undefined),
26
+ location: configParser.string('AI_VERTEX_LOCATION', undefined),
27
+ bucket: configParser.string('AI_VERTEX_BUCKET', undefined),
32
28
  },
33
29
  },
34
30
  };
35
31
  async function bootstrap() {
36
32
  const injector = inject(Injector);
37
- configureAiService({
33
+ assert(config.ai.vertex.location?.startsWith('europe') == true, 'AI vertex location must be in europe for data protection reasons');
34
+ configureGenkit({
38
35
  vertex: {
39
- project: '922353391551', // insolytics-application
36
+ projectId: '922353391551', // insolytics-application
40
37
  location: 'europe-west4', // netherlands
38
+ // keyFile: '/home/patrick/.environments/insolytix-application-service-key.json',
39
+ },
40
+ multiLocationVertex: {
41
+ locations: [
42
+ 'europe-west4',
43
+ 'europe-west9',
44
+ ],
41
45
  },
42
- keyFile: '/home/patrick/.secret-files/insolytic-application-service-key.json',
43
46
  });
44
- /*
45
47
  configureOrm({
46
- repositoryConfig: {
47
- schema: 'test',
48
- },
49
- connection: {
50
- host: config.database.host,
51
- port: config.database.port,
52
- user: config.database.user,
53
- password: config.database.pass,
54
- database: config.database.database,
55
- },
56
- });
57
-
58
- configureAuthenticationServer({
59
- serviceOptions: {
60
- secret: '6fze56uz5e6ufrtzufrtu',
61
- },
48
+ repositoryConfig: {
49
+ schema: 'test',
50
+ },
51
+ connection: {
52
+ host: config.database.host,
53
+ port: config.database.port,
54
+ user: config.database.user,
55
+ password: config.database.pass,
56
+ database: config.database.database,
57
+ },
62
58
  });
63
-
64
- configurePostgresLock();
65
- configurePostgresQueue();
66
- configurePostgresKeyValueStore();
67
-
68
- await runInInjectionContext(injector, migratePostgresKeyValueStoreSchema);
69
- await runInInjectionContext(injector, migratePostgresLockSchema);
70
- await runInInjectionContext(injector, migratePostgresQueueSchema);
71
- await runInInjectionContext(injector, migrateAuditSchema);
72
- await runInInjectionContext(injector, migrateTestSchema);
73
- */
59
+ configurePostgresCircuitBreaker();
60
+ configurePostgresTaskQueue();
61
+ await runInInjectionContext(injector, migratePostgresCircuitBreaker);
62
+ await runInInjectionContext(injector, migratePostgresTaskQueueSchema);
74
63
  }
75
64
  async function main(_cancellationSignal) {
76
- const aiService = inject(AiService);
77
- const aiStream = aiService.generateStream({
78
- generationOptions: {
79
- includeThoughts: true,
80
- thinkingBudget: 1000,
65
+ const genkit = injectGenkit();
66
+ const model = injectMultiLocationModel('gemini-2.5-flash');
67
+ console.log('--- TEST 1: Simple Generation ---');
68
+ const response1 = await genkit.generate({
69
+ model,
70
+ prompt: 'Say hello in one word.',
71
+ });
72
+ console.log('Response:', response1.text);
73
+ console.log('\n--- TEST 2: Structured Output ---');
74
+ const response2 = await genkit.generate({
75
+ model,
76
+ prompt: 'Generate a greeting.',
77
+ output: {
78
+ schema: convertToGenkitSchema(object({
79
+ greeting: string(),
80
+ })),
81
81
  },
82
- model: 'gemini-2.5-flash-lite',
83
- contents: [{
84
- role: 'user',
85
- parts: [
86
- { text: 'Count from 1 to 1000. List *every* number.' },
87
- ],
88
- }],
89
82
  });
90
- for await (const { text, usage } of aiStream) {
91
- console.log({ text, usage });
83
+ console.log('Response:', JSON.stringify(response2.output));
84
+ console.log('\n--- TEST 3: Streaming ---');
85
+ const { response: response3Promise, stream } = genkit.generateStream({
86
+ model,
87
+ prompt: 'Write a very short poem about the ocean (max 20 words).',
88
+ });
89
+ process.stdout.write('Stream: ');
90
+ for await (const chunk of stream) {
91
+ process.stdout.write(chunk.text);
92
92
  }
93
- if (1 + 1 == 2)
94
- process.exit(0);
93
+ process.stdout.write('\n');
94
+ const response3 = await response3Promise;
95
+ console.log('Final Response length:', response3.text.length);
96
+ console.log('\n--- TEST 4: Tools ---');
97
+ const getWeather = genkit.defineTool({
98
+ name: 'getWeather',
99
+ description: 'Gets the weather for a location.',
100
+ inputSchema: z.object({ location: z.string() }),
101
+ outputSchema: z.object({ weather: z.string() }),
102
+ }, async (input) => {
103
+ console.log(` [Tool] getWeather called for ${input.location}`);
104
+ return { weather: `Sunny in ${input.location}` };
105
+ });
106
+ const response4 = await genkit.generate({
107
+ model,
108
+ prompt: 'What is the weather in Berlin?',
109
+ tools: [getWeather],
110
+ });
111
+ console.log('Response:', response4.text);
95
112
  }
96
113
  Application.run('Test', [
97
114
  provideInitializer(bootstrap),
package/typst/render.d.ts CHANGED
@@ -20,4 +20,4 @@ export type TypstRenderOptions = {
20
20
  * @param source
21
21
  * @param options
22
22
  */
23
- export declare function renderTypst(source: string, options?: TypstRenderOptions): Promise<Uint8Array>;
23
+ export declare function renderTypst(source: string, options?: TypstRenderOptions): Promise<Uint8Array<ArrayBuffer>>;