@tstdl/base 0.93.159 → 0.93.161

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,12 +1,18 @@
1
1
  import { vertexAI } from '@genkit-ai/google-genai';
2
2
  import { GenkitError, modelRef } from 'genkit';
3
3
  import { genkitPlugin } from 'genkit/plugin';
4
- import { shuffle } from '../../utils/array/index.js';
4
+ import { distinct, shuffle } from '../../utils/array/index.js';
5
5
  import { isInstanceOf, isNullOrUndefined } from '../../utils/type-guards.js';
6
6
  import { millisecondsPerMinute, millisecondsPerSecond } from '../../utils/units.js';
7
7
  const pluginKey = 'vertexai-multi-location';
8
8
  const geminiModelReference = vertexAI.model('gemini-2.5-flash');
9
9
  export function vertexAiMultiLocation(options) {
10
+ if (options.locations.length == 0) {
11
+ throw new GenkitError({
12
+ status: 'INVALID_ARGUMENT',
13
+ message: 'At least one location must be provided for vertexAiMultiLocation',
14
+ });
15
+ }
10
16
  const locationConfigs = options.locations.map((location) => {
11
17
  const circuitBreakerKey = `genkit:vertex-ai:location:${location}`;
12
18
  const tokenLimitCircuitBreakerKey = `${circuitBreakerKey}:token-limit`;
@@ -41,16 +47,19 @@ export function vertexAiMultiLocation(options) {
41
47
  const shuffledConfigs = shuffle([...locationConfigs]);
42
48
  let lastError;
43
49
  let isLargeRequest = false;
50
+ const skippedLocations = [];
44
51
  for (const { location, circuitBreaker, tokenLimitCircuitBreaker } of shuffledConfigs) {
45
52
  const check = await circuitBreaker.check();
46
53
  if (!check.allowed) {
47
54
  options.logger.warn(`Location ${location} is currently unhealthy. Skipping...`);
55
+ skippedLocations.push({ location, reason: 'unhealthy' });
48
56
  continue;
49
57
  }
50
58
  if (isLargeRequest) {
51
59
  const tokenCheck = await tokenLimitCircuitBreaker.check();
52
60
  if (!tokenCheck.allowed) {
53
61
  options.logger.warn(`Location ${location} is known to have a low token limit. Skipping for this large request...`);
62
+ skippedLocations.push({ location, reason: 'known to have low token limits' });
54
63
  continue;
55
64
  }
56
65
  }
@@ -88,6 +97,14 @@ export function vertexAiMultiLocation(options) {
88
97
  }
89
98
  }
90
99
  }
100
+ if (isNullOrUndefined(lastError)) {
101
+ const uniqueReasons = distinct(skippedLocations.map((s) => s.reason));
102
+ const reasonsString = uniqueReasons.join(' or ');
103
+ throw new GenkitError({
104
+ status: 'UNAVAILABLE',
105
+ message: `All locations were skipped because they are ${reasonsString}`,
106
+ });
107
+ }
91
108
  throw lastError;
92
109
  });
93
110
  };
@@ -138,4 +138,42 @@ describe('Genkit vertexai-multi-location Plugin Tests', () => {
138
138
  });
139
139
  expect(response2.text).toBe('success from cb-success');
140
140
  });
141
+ it('should throw if no locations are provided', async () => {
142
+ expect(() => {
143
+ vertexAiMultiLocation({
144
+ locations: [],
145
+ circuitBreakerProvider: cbProvider,
146
+ logger,
147
+ });
148
+ }).toThrow('At least one location must be provided');
149
+ });
150
+ it('should throw if all locations are unhealthy', async () => {
151
+ const ai2 = genkit({
152
+ plugins: [
153
+ vertexAiMultiLocation({
154
+ locations: ['unhealthy-1'],
155
+ circuitBreakerProvider: cbProvider,
156
+ logger,
157
+ circuitBreakerConfig: { resetTimeout: 1000000, threshold: 1 },
158
+ }),
159
+ ],
160
+ });
161
+ // Manually trip the circuit breaker
162
+ const cb = cbProvider.provide('genkit:vertex-ai:location:unhealthy-1', { threshold: 1, resetTimeout: 1000000 });
163
+ await cb.recordFailure();
164
+ ai2.defineModel({
165
+ name: 'vertexai/gemini-2.5-flash',
166
+ }, async () => {
167
+ return {
168
+ message: {
169
+ role: 'model',
170
+ content: [{ text: 'success' }],
171
+ },
172
+ };
173
+ });
174
+ await expect(ai2.generate({
175
+ model: 'vertexai-multi-location/gemini-2.5-flash',
176
+ prompt: 'test',
177
+ })).rejects.toThrow('All locations were skipped because they are unhealthy');
178
+ });
141
179
  });
@@ -16,7 +16,7 @@ export type CustomErrorOptions = {
16
16
  /**
17
17
  * Cause for error
18
18
  */
19
- cause?: Error;
19
+ cause?: unknown;
20
20
  /** Skip {@link Error} super call, which improves speed but looses stack trace */
21
21
  fast?: boolean | undefined;
22
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tstdl/base",
3
- "version": "0.93.159",
3
+ "version": "0.93.161",
4
4
  "author": "Patrick Hein",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -787,7 +787,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
787
787
  if (isUndefined(updatedTask)) {
788
788
  return;
789
789
  }
790
- if (updatedTask.status == TaskStatus.Completed) {
790
+ if (updatedTask.status == TaskStatus.Completed || updatedTask.status == TaskStatus.WaitingChildren) {
791
791
  await this.#circuitBreaker.recordSuccess();
792
792
  }
793
793
  await this.resolveDependencies(task.id, updatedTask.status, { namespace: task.namespace, transaction: tx });
@@ -828,7 +828,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
828
828
  .select({ id: updated.id, status: updated.status })
829
829
  .from(updated);
830
830
  if (updatedRows.length > 0) {
831
- if (updatedRows.some((r) => r.status == TaskStatus.Completed)) {
831
+ if (updatedRows.some((r) => r.status == TaskStatus.Completed || r.status == TaskStatus.WaitingChildren)) {
832
832
  await this.#circuitBreaker.recordSuccess();
833
833
  }
834
834
  await this.resolveDependenciesMany(updatedRows.map((r) => ({ id: r.id, status: r.status })), { transaction: tx });
@@ -1171,6 +1171,7 @@ let PostgresTaskQueue = class PostgresTaskQueue extends TaskQueue {
1171
1171
  .where(inArray(taskTable.id, tx.pgTransaction.select().from(selection)))
1172
1172
  .returning({ id: taskTable.id, status: taskTable.status });
1173
1173
  if (updatedRows.length > 0) {
1174
+ await this.#circuitBreaker.recordFailures(updatedRows.length);
1174
1175
  await this.resolveDependenciesMany(updatedRows.map((r) => ({ id: r.id, status: r.status, namespace: this.#namespace })), { transaction: tx });
1175
1176
  }
1176
1177
  return updatedRows;
@@ -12,7 +12,7 @@ export function ensureTaskError(error) {
12
12
  return error;
13
13
  }
14
14
  if (isString(error)) {
15
- return new TaskError(error, undefined);
15
+ return new TaskError(error);
16
16
  }
17
17
  return new TaskError(`A task error occurred: ${formatError(error)}`);
18
18
  }