genai-lite 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +210 -207
  3. package/dist/adapters/image/GenaiElectronImageAdapter.d.ts +1 -0
  4. package/dist/adapters/image/GenaiElectronImageAdapter.js +5 -5
  5. package/dist/adapters/image/OpenAIImageAdapter.d.ts +1 -0
  6. package/dist/adapters/image/OpenAIImageAdapter.js +4 -4
  7. package/dist/config/llm-presets.json +24 -0
  8. package/dist/image/ImageService.js +2 -0
  9. package/dist/index.d.ts +4 -2
  10. package/dist/index.js +6 -1
  11. package/dist/llm/LLMService.d.ts +30 -1
  12. package/dist/llm/LLMService.js +43 -5
  13. package/dist/llm/clients/AnthropicClientAdapter.d.ts +6 -2
  14. package/dist/llm/clients/AnthropicClientAdapter.js +26 -11
  15. package/dist/llm/clients/GeminiClientAdapter.d.ts +6 -2
  16. package/dist/llm/clients/GeminiClientAdapter.js +21 -9
  17. package/dist/llm/clients/LlamaCppClientAdapter.d.ts +8 -4
  18. package/dist/llm/clients/LlamaCppClientAdapter.js +131 -28
  19. package/dist/llm/clients/MistralClientAdapter.d.ts +6 -2
  20. package/dist/llm/clients/MistralClientAdapter.js +22 -11
  21. package/dist/llm/clients/MockClientAdapter.d.ts +2 -2
  22. package/dist/llm/clients/MockClientAdapter.js +36 -19
  23. package/dist/llm/clients/OpenAIClientAdapter.d.ts +6 -2
  24. package/dist/llm/clients/OpenAIClientAdapter.js +33 -9
  25. package/dist/llm/clients/OpenRouterClientAdapter.d.ts +6 -2
  26. package/dist/llm/clients/OpenRouterClientAdapter.js +87 -16
  27. package/dist/llm/clients/types.d.ts +18 -1
  28. package/dist/llm/clients/types.js +4 -0
  29. package/dist/llm/config.d.ts +9 -2
  30. package/dist/llm/config.js +697 -33
  31. package/dist/llm/services/ModelResolver.d.ts +6 -0
  32. package/dist/llm/services/ModelResolver.js +57 -17
  33. package/dist/llm/services/RequestValidator.d.ts +8 -0
  34. package/dist/llm/services/RequestValidator.js +9 -2
  35. package/dist/llm/services/SettingsManager.d.ts +1 -1
  36. package/dist/llm/services/SettingsManager.js +92 -4
  37. package/dist/llm/types.d.ts +127 -0
  38. package/dist/prompting/index.d.ts +1 -1
  39. package/dist/prompting/index.js +2 -1
  40. package/dist/prompting/parser.d.ts +23 -0
  41. package/dist/prompting/parser.js +33 -0
  42. package/dist/shared/adapters/errorUtils.d.ts +15 -0
  43. package/dist/shared/adapters/errorUtils.js +69 -1
  44. package/dist/shared/adapters/logprobsUtils.d.ts +13 -0
  45. package/dist/shared/adapters/logprobsUtils.js +33 -0
  46. package/dist/shared/services/AdapterRegistry.js +3 -1
  47. package/dist/shared/services/withRetry.d.ts +50 -0
  48. package/dist/shared/services/withRetry.js +78 -0
  49. package/dist/types/image.d.ts +2 -0
  50. package/package.json +59 -59
  51. package/src/config/image-presets.json +212 -212
  52. package/src/config/llm-presets.json +623 -599
@@ -3,8 +3,53 @@
3
3
  // Maps common HTTP status codes and network errors to standardized AdapterErrorCode and errorType.
4
4
  // Reduces duplication across OpenAI, Anthropic and other provider adapters.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseRetryAfterMs = parseRetryAfterMs;
7
+ exports.extractRetryAfterMs = extractRetryAfterMs;
6
8
  exports.getCommonMappedErrorDetails = getCommonMappedErrorDetails;
7
9
  const types_1 = require("../../llm/clients/types");
10
+ /**
11
+ * Parses a Retry-After header value (delta-seconds or HTTP-date) into milliseconds.
12
+ *
13
+ * @param value - The raw header value
14
+ * @returns Milliseconds to wait, or undefined when unparseable/negative
15
+ */
16
+ function parseRetryAfterMs(value) {
17
+ if (!value)
18
+ return undefined;
19
+ const seconds = Number(value);
20
+ if (!Number.isNaN(seconds)) {
21
+ return seconds >= 0 ? Math.round(seconds * 1000) : undefined;
22
+ }
23
+ const dateMs = Date.parse(value);
24
+ if (!Number.isNaN(dateMs)) {
25
+ const delta = dateMs - Date.now();
26
+ return delta > 0 ? delta : undefined;
27
+ }
28
+ return undefined;
29
+ }
30
+ /**
31
+ * Extracts a Retry-After value in ms from an SDK error object, tolerating the
32
+ * different places SDKs keep response headers (openai/anthropic `error.headers`
33
+ * as a Headers instance or plain object; Speakeasy-style `error.rawResponse`).
34
+ */
35
+ function extractRetryAfterMs(error) {
36
+ const headerSources = [error?.headers, error?.rawResponse?.headers];
37
+ for (const headers of headerSources) {
38
+ if (!headers)
39
+ continue;
40
+ let raw;
41
+ if (typeof headers.get === 'function') {
42
+ raw = headers.get('retry-after');
43
+ }
44
+ else if (typeof headers === 'object') {
45
+ raw = headers['retry-after'] ?? headers['Retry-After'];
46
+ }
47
+ const parsed = parseRetryAfterMs(raw);
48
+ if (parsed !== undefined)
49
+ return parsed;
50
+ }
51
+ return undefined;
52
+ }
8
53
  /**
9
54
  * Maps common error patterns to standardized error codes and types
10
55
  *
@@ -25,6 +70,28 @@ function getCommonMappedErrorDetails(error, providerMessageOverride) {
25
70
  let errorMessage = providerMessageOverride || error?.message || 'Unknown error occurred';
26
71
  let errorType = 'server_error';
27
72
  let status;
73
+ const retryAfterMs = extractRetryAfterMs(error);
74
+ // Handle user-initiated aborts and client-side timeouts first — the SDKs raise
75
+ // these as named errors (openai: APIUserAbortError/APIConnectionTimeoutError;
76
+ // fetch/undici: AbortError/TimeoutError DOMExceptions)
77
+ if (error &&
78
+ (error.name === 'APIUserAbortError' ||
79
+ error.name === 'AbortError' ||
80
+ (error instanceof DOMException && error.name === 'AbortError'))) {
81
+ return {
82
+ errorCode: types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED,
83
+ errorMessage: providerMessageOverride || error.message || 'Request was aborted',
84
+ errorType: 'abort_error',
85
+ };
86
+ }
87
+ if (error &&
88
+ (error.name === 'APIConnectionTimeoutError' || error.name === 'TimeoutError')) {
89
+ return {
90
+ errorCode: types_1.ADAPTER_ERROR_CODES.REQUEST_TIMEOUT,
91
+ errorMessage: providerMessageOverride || error.message || 'Request timed out',
92
+ errorType: 'timeout_error',
93
+ };
94
+ }
28
95
  // Handle API errors with HTTP status codes
29
96
  if (error && typeof error.status === 'number') {
30
97
  const httpStatus = error.status;
@@ -102,6 +169,7 @@ function getCommonMappedErrorDetails(error, providerMessageOverride) {
102
169
  errorCode,
103
170
  errorMessage,
104
171
  errorType,
105
- status
172
+ status,
173
+ ...(retryAfterMs !== undefined && { retryAfterMs })
106
174
  };
107
175
  }
@@ -0,0 +1,13 @@
1
+ import type { TokenLogprob } from "../../llm/types";
2
+ /**
3
+ * Maps an OpenAI-shaped chat-completions logprobs payload
4
+ * (`choice.logprobs.content[].{token, logprob, top_logprobs[]}`) to the
5
+ * library's normalized {@link TokenLogprob} array.
6
+ *
7
+ * llama-server's chat endpoint produces the same shape, so this mapper is
8
+ * shared across the OpenAI-SDK-based adapters.
9
+ *
10
+ * @param logprobs - The raw `choice.logprobs` object from the provider response
11
+ * @returns Normalized token logprobs, or undefined when absent/empty
12
+ */
13
+ export declare function mapOpenAIChatLogprobs(logprobs: any): TokenLogprob[] | undefined;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ // AI Summary: Shared mapper for OpenAI-shaped chat-completions logprobs payloads.
3
+ // Used by the OpenAI, OpenRouter and llama.cpp adapters (identical wire shape).
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.mapOpenAIChatLogprobs = mapOpenAIChatLogprobs;
6
+ /**
7
+ * Maps an OpenAI-shaped chat-completions logprobs payload
8
+ * (`choice.logprobs.content[].{token, logprob, top_logprobs[]}`) to the
9
+ * library's normalized {@link TokenLogprob} array.
10
+ *
11
+ * llama-server's chat endpoint produces the same shape, so this mapper is
12
+ * shared across the OpenAI-SDK-based adapters.
13
+ *
14
+ * @param logprobs - The raw `choice.logprobs` object from the provider response
15
+ * @returns Normalized token logprobs, or undefined when absent/empty
16
+ */
17
+ function mapOpenAIChatLogprobs(logprobs) {
18
+ const content = logprobs?.content;
19
+ if (!Array.isArray(content) || content.length === 0) {
20
+ return undefined;
21
+ }
22
+ return content.map((entry) => ({
23
+ token: entry.token,
24
+ logprob: entry.logprob,
25
+ ...(Array.isArray(entry.top_logprobs) &&
26
+ entry.top_logprobs.length > 0 && {
27
+ topLogprobs: entry.top_logprobs.map((alt) => ({
28
+ token: alt.token,
29
+ logprob: alt.logprob,
30
+ })),
31
+ }),
32
+ }));
33
+ }
@@ -50,7 +50,9 @@ class AdapterRegistry {
50
50
  if (AdapterClass) {
51
51
  try {
52
52
  const adapterConfig = adapterConfigs[provider.id];
53
- const adapterInstance = new AdapterClass(adapterConfig);
53
+ // Inject logger into adapter config
54
+ const configWithLogger = { ...adapterConfig, logger: this.logger };
55
+ const adapterInstance = new AdapterClass(configWithLogger);
54
56
  this.registerAdapter(providerId, adapterInstance);
55
57
  registeredCount++;
56
58
  successfullyRegisteredProviders.push(provider.id);
@@ -0,0 +1,50 @@
1
+ import type { Logger } from "../../logging/types";
2
+ /**
3
+ * Retry policy configuration.
4
+ */
5
+ export interface RetryPolicy {
6
+ /** Maximum number of retries after the initial attempt (default 2 → up to 3 attempts) */
7
+ maxRetries: number;
8
+ /** Base delay before the first retry, in ms (default 500) */
9
+ initialDelayMs: number;
10
+ /** Upper bound for any single delay, in ms (default 10000) */
11
+ maxDelayMs: number;
12
+ /** Exponential growth factor per attempt (default 2) */
13
+ backoffFactor: number;
14
+ }
15
+ export declare const DEFAULT_RETRY_POLICY: RetryPolicy;
16
+ /**
17
+ * Verdict returned by the caller's shouldRetry callback.
18
+ */
19
+ export interface RetryVerdict {
20
+ /** Whether the operation should be retried */
21
+ retry: boolean;
22
+ /** Provider-suggested wait (e.g. from a Retry-After header); used when larger than the computed backoff */
23
+ retryAfterMs?: number;
24
+ }
25
+ export interface WithRetryOptions extends Partial<RetryPolicy> {
26
+ /** Abort signal — no further retries (or waits) once aborted */
27
+ signal?: AbortSignal;
28
+ /** Logger for retry warnings */
29
+ logger?: Logger;
30
+ /** Label used in log messages (e.g. "openai/gpt-4.1") */
31
+ label?: string;
32
+ }
33
+ /**
34
+ * Runs an async operation with retries and exponential backoff.
35
+ *
36
+ * Unlike typical retry helpers, this operates on RETURNED values rather than
37
+ * exceptions: the caller's `shouldRetry` inspects each result (genai-lite
38
+ * adapters return failure responses instead of throwing) and decides whether
39
+ * to retry. The last result is always returned — never thrown.
40
+ *
41
+ * Delay per retry: `min(maxDelayMs, initialDelayMs * backoffFactor^attempt)`
42
+ * with ±20% jitter; a provider-supplied `retryAfterMs` is honored when larger
43
+ * (still capped at `maxDelayMs`).
44
+ *
45
+ * @param operation - The operation to run; receives the 0-based attempt index
46
+ * @param shouldRetry - Inspects a result and decides whether to retry
47
+ * @param options - Policy overrides, abort signal, logger
48
+ * @returns The final result (successful, non-retryable, or last exhausted attempt)
49
+ */
50
+ export declare function withRetry<T>(operation: (attempt: number) => Promise<T>, shouldRetry: (result: T) => RetryVerdict, options?: WithRetryOptions): Promise<T>;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ // AI Summary: Generic retry helper with exponential backoff and jitter.
3
+ // Works on RETURNED values (genai-lite adapters never throw), honors Retry-After
4
+ // hints and AbortSignals. Used by LLMService's unified retry layer.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_RETRY_POLICY = void 0;
7
+ exports.withRetry = withRetry;
8
+ exports.DEFAULT_RETRY_POLICY = {
9
+ maxRetries: 2,
10
+ initialDelayMs: 500,
11
+ maxDelayMs: 10000,
12
+ backoffFactor: 2,
13
+ };
14
+ /**
15
+ * Runs an async operation with retries and exponential backoff.
16
+ *
17
+ * Unlike typical retry helpers, this operates on RETURNED values rather than
18
+ * exceptions: the caller's `shouldRetry` inspects each result (genai-lite
19
+ * adapters return failure responses instead of throwing) and decides whether
20
+ * to retry. The last result is always returned — never thrown.
21
+ *
22
+ * Delay per retry: `min(maxDelayMs, initialDelayMs * backoffFactor^attempt)`
23
+ * with ±20% jitter; a provider-supplied `retryAfterMs` is honored when larger
24
+ * (still capped at `maxDelayMs`).
25
+ *
26
+ * @param operation - The operation to run; receives the 0-based attempt index
27
+ * @param shouldRetry - Inspects a result and decides whether to retry
28
+ * @param options - Policy overrides, abort signal, logger
29
+ * @returns The final result (successful, non-retryable, or last exhausted attempt)
30
+ */
31
+ async function withRetry(operation, shouldRetry, options) {
32
+ const policy = {
33
+ maxRetries: options?.maxRetries ?? exports.DEFAULT_RETRY_POLICY.maxRetries,
34
+ initialDelayMs: options?.initialDelayMs ?? exports.DEFAULT_RETRY_POLICY.initialDelayMs,
35
+ maxDelayMs: options?.maxDelayMs ?? exports.DEFAULT_RETRY_POLICY.maxDelayMs,
36
+ backoffFactor: options?.backoffFactor ?? exports.DEFAULT_RETRY_POLICY.backoffFactor,
37
+ };
38
+ let result = await operation(0);
39
+ for (let attempt = 0; attempt < policy.maxRetries; attempt++) {
40
+ const verdict = shouldRetry(result);
41
+ if (!verdict.retry || options?.signal?.aborted) {
42
+ return result;
43
+ }
44
+ // Exponential backoff with ±20% jitter; honor Retry-After when larger
45
+ const baseDelay = Math.min(policy.maxDelayMs, policy.initialDelayMs * Math.pow(policy.backoffFactor, attempt));
46
+ const jittered = baseDelay * (0.8 + Math.random() * 0.4);
47
+ const delayMs = Math.min(policy.maxDelayMs, Math.max(jittered, verdict.retryAfterMs ?? 0));
48
+ options?.logger?.warn(`Retrying${options.label ? ` ${options.label}` : ''} after failure ` +
49
+ `(attempt ${attempt + 2}/${policy.maxRetries + 1}, waiting ${Math.round(delayMs)}ms)`);
50
+ const aborted = await sleepUnlessAborted(delayMs, options?.signal);
51
+ if (aborted) {
52
+ return result;
53
+ }
54
+ result = await operation(attempt + 1);
55
+ }
56
+ return result;
57
+ }
58
+ /**
59
+ * Sleeps for the given duration unless the signal aborts first.
60
+ *
61
+ * @returns true when the wait was cut short by an abort
62
+ */
63
+ function sleepUnlessAborted(ms, signal) {
64
+ if (signal?.aborted) {
65
+ return Promise.resolve(true);
66
+ }
67
+ return new Promise((resolve) => {
68
+ const onAbort = () => {
69
+ clearTimeout(timer);
70
+ resolve(true);
71
+ };
72
+ const timer = setTimeout(() => {
73
+ signal?.removeEventListener('abort', onAbort);
74
+ resolve(false);
75
+ }, ms);
76
+ signal?.addEventListener('abort', onAbort, { once: true });
77
+ });
78
+ }
@@ -337,6 +337,8 @@ export interface ImageProviderAdapterConfig {
337
337
  timeout?: number;
338
338
  /** Whether to check health before requests */
339
339
  checkHealth?: boolean;
340
+ /** Logger instance for adapter logging */
341
+ logger?: Logger;
340
342
  }
341
343
  /**
342
344
  * Interface that all image provider adapters must implement
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "genai-lite",
3
- "version": "0.8.2",
4
- "description": "A lightweight, portable toolkit for interacting with various Generative AI APIs.",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "import": "./dist/index.js",
10
- "require": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- },
13
- "./prompting": {
14
- "import": "./dist/prompting/index.js",
15
- "require": "./dist/prompting/index.js",
16
- "types": "./dist/prompting/index.d.ts"
17
- }
18
- },
19
- "author": "Luigi Acerbi <luigi.acerbi@gmail.com>",
20
- "license": "MIT",
21
- "funding": {
22
- "type": "github",
23
- "url": "https://github.com/sponsors/lacerbi"
24
- },
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/lacerbi/genai-lite.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/lacerbi/genai-lite/issues"
31
- },
32
- "homepage": "https://github.com/lacerbi/genai-lite#readme",
33
- "files": [
34
- "dist",
35
- "src/config/llm-presets.json",
36
- "src/config/image-presets.json"
37
- ],
38
- "scripts": {
39
- "build": "tsc",
40
- "test": "jest --coverage",
41
- "test:watch": "jest --watch",
42
- "test:e2e": "npm run build && jest --config jest.e2e.config.js",
43
- "test:e2e:reasoning": "npm run build && jest --config jest.e2e.config.js reasoning.e2e.test.ts"
44
- },
45
- "dependencies": {
46
- "@anthropic-ai/sdk": "^0.71.2",
47
- "@google/genai": "^1.0.1",
48
- "@mistralai/mistralai": "^1.11.0",
49
- "js-tiktoken": "^1.0.20",
50
- "openai": "^6.7.0"
51
- },
52
- "devDependencies": {
53
- "@types/jest": ">=30.0.0",
54
- "@types/node": ">=20.11.24",
55
- "jest": ">=30.0.4",
56
- "ts-jest": ">=29.4.0",
57
- "typescript": ">=5.3.3"
58
- }
59
- }
1
+ {
2
+ "name": "genai-lite",
3
+ "version": "0.9.0",
4
+ "description": "A lightweight, portable toolkit for interacting with various Generative AI APIs.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "require": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./prompting": {
14
+ "import": "./dist/prompting/index.js",
15
+ "require": "./dist/prompting/index.js",
16
+ "types": "./dist/prompting/index.d.ts"
17
+ }
18
+ },
19
+ "author": "Luigi Acerbi <luigi.acerbi@gmail.com>",
20
+ "license": "MIT",
21
+ "funding": {
22
+ "type": "github",
23
+ "url": "https://github.com/sponsors/lacerbi"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/lacerbi/genai-lite.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/lacerbi/genai-lite/issues"
31
+ },
32
+ "homepage": "https://github.com/lacerbi/genai-lite#readme",
33
+ "files": [
34
+ "dist",
35
+ "src/config/llm-presets.json",
36
+ "src/config/image-presets.json"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsc",
40
+ "test": "jest --coverage",
41
+ "test:watch": "jest --watch",
42
+ "test:e2e": "npm run build && jest --config jest.e2e.config.js",
43
+ "test:e2e:reasoning": "npm run build && jest --config jest.e2e.config.js reasoning.e2e.test.ts"
44
+ },
45
+ "dependencies": {
46
+ "@anthropic-ai/sdk": "^0.71.2",
47
+ "@google/genai": "^1.0.1",
48
+ "@mistralai/mistralai": "^1.11.0",
49
+ "js-tiktoken": "^1.0.20",
50
+ "openai": "^6.7.0"
51
+ },
52
+ "devDependencies": {
53
+ "@types/jest": ">=30.0.0",
54
+ "@types/node": ">=20.11.24",
55
+ "jest": ">=30.0.4",
56
+ "ts-jest": ">=29.4.0",
57
+ "typescript": ">=5.3.3"
58
+ }
59
+ }