genai-lite 0.9.1 → 0.9.2

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.
@@ -18,6 +18,7 @@
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.GenaiElectronImageAdapter = void 0;
20
20
  const errorUtils_1 = require("../../shared/adapters/errorUtils");
21
+ const types_1 = require("../../llm/clients/types");
21
22
  const defaultLogger_1 = require("../../logging/defaultLogger");
22
23
  /**
23
24
  * Adapter for genai-electron's local diffusion image generation
@@ -157,6 +158,12 @@ class GenaiElectronImageAdapter {
157
158
  const error = state.error || { message: 'Unknown error', code: 'UNKNOWN_ERROR' };
158
159
  throw this.createGenerationError(error.message, error.code);
159
160
  }
161
+ // Handle out-of-band cancellation (terminal status since genai-electron 0.6.0)
162
+ if (state.status === 'cancelled') {
163
+ const cancelError = new Error(`Image generation was cancelled on the server (ID: ${generationId})`);
164
+ cancelError.code = 'GENERATION_CANCELLED';
165
+ throw cancelError;
166
+ }
160
167
  // Wait before next poll
161
168
  await this.sleep(this.pollInterval);
162
169
  }
@@ -231,8 +238,15 @@ class GenaiElectronImageAdapter {
231
238
  const mapped = (0, errorUtils_1.getCommonMappedErrorDetails)(error);
232
239
  // Enhance error message with context
233
240
  let errorMessage = mapped.errorMessage;
241
+ let errorCode = mapped.errorCode;
242
+ let errorType = mapped.errorType;
234
243
  // Special handling for genai-electron specific errors
235
- if (error.code === 'SERVER_BUSY') {
244
+ if (error.code === 'GENERATION_CANCELLED') {
245
+ errorMessage = error.message;
246
+ errorCode = types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED;
247
+ errorType = 'abort_error';
248
+ }
249
+ else if (error.code === 'SERVER_BUSY') {
236
250
  errorMessage = 'Image generation server is busy. Wait for current generation to complete.';
237
251
  error.type = 'rate_limit_error';
238
252
  }
@@ -258,8 +272,8 @@ class GenaiElectronImageAdapter {
258
272
  }
259
273
  // Create enhanced error with all details
260
274
  const enhancedError = new Error(errorMessage);
261
- enhancedError.code = mapped.errorCode;
262
- enhancedError.type = mapped.errorType;
275
+ enhancedError.code = errorCode;
276
+ enhancedError.type = errorType;
263
277
  enhancedError.status = mapped.status;
264
278
  enhancedError.providerId = this.id;
265
279
  enhancedError.modelId = request.modelId;
@@ -75,6 +75,7 @@ export declare class GeminiClientAdapter implements ILLMClientAdapter {
75
75
  *
76
76
  * @param error - The error from Gemini API
77
77
  * @param request - Original request for context
78
+ * @param context - Adapter-side transport state captured at catch time
78
79
  * @returns Standardized LLM failure response
79
80
  */
80
81
  private createErrorResponse;
@@ -38,6 +38,12 @@ class GeminiClientAdapter {
38
38
  * @returns Promise resolving to success or failure response
39
39
  */
40
40
  async sendMessage(request, apiKey, options) {
41
+ // The SDK enforces httpOptions.timeout by aborting an internal controller and
42
+ // wraps any fetch rejection in a plain Error (no name/status/code), so timeouts
43
+ // and caller aborts cannot be classified from the error object. The adapter owns
44
+ // the timeout instead and classifies from its own state in the catch block.
45
+ let timedOut = false;
46
+ let timeoutHandle;
41
47
  try {
42
48
  // Initialize Gemini client (note: @google/genai performs no automatic retries)
43
49
  const genAI = new genai_1.GoogleGenAI({ apiKey });
@@ -52,6 +58,26 @@ class GeminiClientAdapter {
52
58
  contentsLength: contents.length,
53
59
  safetySettingsCount: safetySettings?.length || 0,
54
60
  });
61
+ let abortSignal = options?.signal;
62
+ if (options?.timeoutMs !== undefined) {
63
+ const controller = new AbortController();
64
+ if (options.signal) {
65
+ if (options.signal.aborted) {
66
+ controller.abort();
67
+ }
68
+ else {
69
+ options.signal.addEventListener("abort", () => controller.abort(), {
70
+ once: true,
71
+ });
72
+ }
73
+ }
74
+ timeoutHandle = setTimeout(() => {
75
+ timedOut = true;
76
+ controller.abort();
77
+ }, options.timeoutMs);
78
+ timeoutHandle.unref?.();
79
+ abortSignal = controller.signal;
80
+ }
55
81
  // Generate content using the modern API
56
82
  const result = await genAI.models.generateContent({
57
83
  model: request.modelId,
@@ -60,9 +86,11 @@ class GeminiClientAdapter {
60
86
  ...generationConfig,
61
87
  safetySettings: safetySettings,
62
88
  ...(systemInstruction && { systemInstruction: systemInstruction }),
63
- ...(options?.signal && { abortSignal: options.signal }),
89
+ ...(abortSignal && { abortSignal }),
90
+ // Keep the SDK's server-side timeout hint (X-Server-Timeout header),
91
+ // padded so the adapter's local timer always fires first
64
92
  ...(options?.timeoutMs !== undefined && {
65
- httpOptions: { timeout: options.timeoutMs },
93
+ httpOptions: { timeout: options.timeoutMs + 1000 },
66
94
  }),
67
95
  },
68
96
  });
@@ -72,7 +100,15 @@ class GeminiClientAdapter {
72
100
  }
73
101
  catch (error) {
74
102
  this.logger.error("Gemini API error:", error);
75
- return this.createErrorResponse(error, request);
103
+ return this.createErrorResponse(error, request, {
104
+ timedOut,
105
+ aborted: options?.signal?.aborted === true,
106
+ });
107
+ }
108
+ finally {
109
+ if (timeoutHandle !== undefined) {
110
+ clearTimeout(timeoutHandle);
111
+ }
76
112
  }
77
113
  }
78
114
  /**
@@ -312,9 +348,38 @@ class GeminiClientAdapter {
312
348
  *
313
349
  * @param error - The error from Gemini API
314
350
  * @param request - Original request for context
351
+ * @param context - Adapter-side transport state captured at catch time
315
352
  * @returns Standardized LLM failure response
316
353
  */
317
- createErrorResponse(error, request) {
354
+ createErrorResponse(error, request, context) {
355
+ // Classify adapter-owned aborts/timeouts first — the SDK surfaces both as
356
+ // plain Errors the common mapping cannot recognize (see sendMessage)
357
+ if (context?.aborted) {
358
+ return {
359
+ provider: request.providerId,
360
+ model: request.modelId,
361
+ error: {
362
+ message: "Request was aborted",
363
+ code: types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED,
364
+ type: "abort_error",
365
+ providerError: error,
366
+ },
367
+ object: "error",
368
+ };
369
+ }
370
+ if (context?.timedOut) {
371
+ return {
372
+ provider: request.providerId,
373
+ model: request.modelId,
374
+ error: {
375
+ message: "Request timed out",
376
+ code: types_1.ADAPTER_ERROR_CODES.REQUEST_TIMEOUT,
377
+ type: "timeout_error",
378
+ providerError: error,
379
+ },
380
+ object: "error",
381
+ };
382
+ }
318
383
  // Use shared error mapping utility for common error patterns
319
384
  const initialProviderMessage = error?.message;
320
385
  let { errorCode, errorMessage, errorType, status, retryAfterMs } = (0, errorUtils_1.getCommonMappedErrorDetails)(error, initialProviderMessage);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genai-lite",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "A lightweight, portable toolkit for interacting with various Generative AI APIs.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",