recursive-llm-ts 5.0.2 → 5.2.3
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.
- package/README.md +2 -12
- package/dist/cjs/bridge-factory.d.ts +7 -0
- package/dist/cjs/bridge-factory.js +96 -0
- package/dist/{bridge-interface.d.ts → cjs/bridge-interface.d.ts} +1 -2
- package/dist/{config.js → cjs/config.js} +0 -6
- package/dist/{coordinator.js → cjs/coordinator.js} +1 -1
- package/dist/{go-bridge.d.ts → cjs/go-bridge.d.ts} +2 -2
- package/dist/{go-bridge.js → cjs/go-bridge.js} +36 -4
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/pkg-dir.d.ts +7 -0
- package/dist/cjs/pkg-dir.js +79 -0
- package/dist/{rlm.d.ts → cjs/rlm.d.ts} +1 -1
- package/dist/{rlm.js → cjs/rlm.js} +3 -3
- package/dist/esm/bridge-factory.d.ts +7 -0
- package/dist/esm/bridge-factory.js +60 -0
- package/dist/esm/bridge-interface.d.ts +269 -0
- package/dist/esm/bridge-interface.js +1 -0
- package/dist/esm/cache.d.ts +78 -0
- package/dist/esm/cache.js +207 -0
- package/dist/esm/config.d.ts +37 -0
- package/dist/esm/config.js +152 -0
- package/dist/esm/coordinator.d.ts +17 -0
- package/dist/esm/coordinator.js +41 -0
- package/dist/esm/errors.d.ts +113 -0
- package/dist/esm/errors.js +205 -0
- package/dist/esm/events.d.ts +126 -0
- package/dist/esm/events.js +73 -0
- package/dist/esm/file-storage.d.ts +122 -0
- package/dist/esm/file-storage.js +656 -0
- package/dist/esm/go-bridge.d.ts +5 -0
- package/dist/esm/go-bridge.js +133 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/pkg-dir.d.ts +7 -0
- package/dist/esm/pkg-dir.js +43 -0
- package/dist/esm/retry.d.ts +56 -0
- package/dist/esm/retry.js +181 -0
- package/dist/esm/rlm.d.ts +435 -0
- package/dist/esm/rlm.js +1122 -0
- package/dist/esm/streaming.d.ts +96 -0
- package/dist/esm/streaming.js +205 -0
- package/dist/esm/structured-types.d.ts +28 -0
- package/dist/esm/structured-types.js +1 -0
- package/package.json +32 -5
- package/scripts/build-go-binary.js +44 -5
- package/dist/bridge-factory.d.ts +0 -6
- package/dist/bridge-factory.js +0 -134
- package/dist/bunpy-bridge.d.ts +0 -7
- package/dist/bunpy-bridge.js +0 -37
- package/dist/rlm-bridge.d.ts +0 -8
- package/dist/rlm-bridge.js +0 -179
- /package/dist/{bridge-interface.js → cjs/bridge-interface.js} +0 -0
- /package/dist/{cache.d.ts → cjs/cache.d.ts} +0 -0
- /package/dist/{cache.js → cjs/cache.js} +0 -0
- /package/dist/{config.d.ts → cjs/config.d.ts} +0 -0
- /package/dist/{coordinator.d.ts → cjs/coordinator.d.ts} +0 -0
- /package/dist/{errors.d.ts → cjs/errors.d.ts} +0 -0
- /package/dist/{errors.js → cjs/errors.js} +0 -0
- /package/dist/{events.d.ts → cjs/events.d.ts} +0 -0
- /package/dist/{events.js → cjs/events.js} +0 -0
- /package/dist/{file-storage.d.ts → cjs/file-storage.d.ts} +0 -0
- /package/dist/{file-storage.js → cjs/file-storage.js} +0 -0
- /package/dist/{index.d.ts → cjs/index.d.ts} +0 -0
- /package/dist/{index.js → cjs/index.js} +0 -0
- /package/dist/{retry.d.ts → cjs/retry.d.ts} +0 -0
- /package/dist/{retry.js → cjs/retry.js} +0 -0
- /package/dist/{streaming.d.ts → cjs/streaming.d.ts} +0 -0
- /package/dist/{streaming.js → cjs/streaming.js} +0 -0
- /package/dist/{structured-types.d.ts → cjs/structured-types.d.ts} +0 -0
- /package/dist/{structured-types.js → cjs/structured-types.js} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as path from 'path';
|
|
23
|
+
import { spawn } from 'child_process';
|
|
24
|
+
import { PKG_ROOT_DIR } from './pkg-dir.js';
|
|
25
|
+
const DEFAULT_BINARY_NAME = process.platform === 'win32' ? 'rlm-go.exe' : 'rlm-go';
|
|
26
|
+
/** Platform-specific npm package names for pre-built binaries */
|
|
27
|
+
const PLATFORM_PACKAGES = {
|
|
28
|
+
'darwin-arm64': '@recursive-llm/darwin-arm64',
|
|
29
|
+
'darwin-x64': '@recursive-llm/darwin-x64',
|
|
30
|
+
'linux-x64': '@recursive-llm/linux-x64',
|
|
31
|
+
'linux-arm64': '@recursive-llm/linux-arm64',
|
|
32
|
+
'win32-x64': '@recursive-llm/win32-x64',
|
|
33
|
+
};
|
|
34
|
+
function resolvePlatformBinary() {
|
|
35
|
+
const platform = process.platform;
|
|
36
|
+
const arch = process.arch;
|
|
37
|
+
const key = `${platform}-${arch}`;
|
|
38
|
+
const pkgName = PLATFORM_PACKAGES[key];
|
|
39
|
+
if (!pkgName)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
// require.resolve works in both CJS and ESM (via createRequire)
|
|
43
|
+
const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`));
|
|
44
|
+
const binPath = path.join(pkgDir, 'bin', DEFAULT_BINARY_NAME);
|
|
45
|
+
if (fs.existsSync(binPath))
|
|
46
|
+
return binPath;
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
// Package not installed — fall through
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
function resolveBinaryPath(rlmConfig) {
|
|
54
|
+
const configuredPath = rlmConfig.go_binary_path || process.env.RLM_GO_BINARY;
|
|
55
|
+
if (configuredPath) {
|
|
56
|
+
return configuredPath;
|
|
57
|
+
}
|
|
58
|
+
// 1. Try platform-specific npm package (pre-built binary)
|
|
59
|
+
const platformBin = resolvePlatformBinary();
|
|
60
|
+
if (platformBin)
|
|
61
|
+
return platformBin;
|
|
62
|
+
// 2. Try local locations
|
|
63
|
+
const possiblePaths = [
|
|
64
|
+
path.join(PKG_ROOT_DIR, 'bin', DEFAULT_BINARY_NAME), // NPM package (primary)
|
|
65
|
+
path.join(PKG_ROOT_DIR, 'go', DEFAULT_BINARY_NAME), // Development fallback
|
|
66
|
+
];
|
|
67
|
+
for (const p of possiblePaths) {
|
|
68
|
+
if (fs.existsSync(p)) {
|
|
69
|
+
return p;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return possiblePaths[0]; // Return first path, error will be caught later
|
|
73
|
+
}
|
|
74
|
+
function assertBinaryExists(binaryPath) {
|
|
75
|
+
if (!fs.existsSync(binaryPath)) {
|
|
76
|
+
throw new Error(`Go RLM binary not found at ${binaryPath}.\n` +
|
|
77
|
+
'Build it with: node scripts/build-go-binary.js');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function sanitizeConfig(config) {
|
|
81
|
+
const { go_binary_path, structured } = config, sanitized = __rest(config, ["go_binary_path", "structured"]);
|
|
82
|
+
return { config: sanitized, structured };
|
|
83
|
+
}
|
|
84
|
+
export class GoBridge {
|
|
85
|
+
completion(model_1, query_1, context_1) {
|
|
86
|
+
return __awaiter(this, arguments, void 0, function* (model, query, context, rlmConfig = {}) {
|
|
87
|
+
const binaryPath = resolveBinaryPath(rlmConfig);
|
|
88
|
+
assertBinaryExists(binaryPath);
|
|
89
|
+
const { config, structured } = sanitizeConfig(rlmConfig);
|
|
90
|
+
const payload = JSON.stringify({
|
|
91
|
+
model,
|
|
92
|
+
query,
|
|
93
|
+
context,
|
|
94
|
+
config,
|
|
95
|
+
structured
|
|
96
|
+
});
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const child = spawn(binaryPath, [], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
99
|
+
let stdout = '';
|
|
100
|
+
let stderr = '';
|
|
101
|
+
child.stdout.on('data', (data) => {
|
|
102
|
+
stdout += data.toString();
|
|
103
|
+
});
|
|
104
|
+
child.stderr.on('data', (data) => {
|
|
105
|
+
stderr += data.toString();
|
|
106
|
+
});
|
|
107
|
+
child.on('error', (error) => {
|
|
108
|
+
reject(new Error(`Failed to start Go binary: ${error.message}`));
|
|
109
|
+
});
|
|
110
|
+
child.on('close', (code) => {
|
|
111
|
+
if (code !== 0) {
|
|
112
|
+
reject(new Error(stderr || `Go binary exited with code ${code}`));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(stdout);
|
|
117
|
+
resolve(parsed);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
reject(new Error(`Failed to parse Go response: ${error.message || error}`));
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
child.stdin.write(payload);
|
|
124
|
+
child.stdin.end();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
cleanup() {
|
|
129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
// No persistent processes to clean up.
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { RLM, RLMBuilder, RLMCompletionResult, RLMResultFormatter } from './rlm';
|
|
2
|
+
export { RLMConfig, RLMResult, RLMStats, MetaAgentConfig, ObservabilityConfig, TraceEvent, FileStorageConfig, ContextOverflowConfig } from './bridge-interface';
|
|
3
|
+
export { BridgeType } from './bridge-factory';
|
|
4
|
+
export { StructuredRLMResult, SubTask, CoordinatorConfig, SchemaDecomposition } from './structured-types';
|
|
5
|
+
export { RLMExtendedConfig, ValidationResult, ValidationIssue, ValidationLevel, validateConfig, assertValidConfig } from './config';
|
|
6
|
+
export { RLMError, RLMValidationError, RLMRateLimitError, RLMTimeoutError, RLMProviderError, RLMBinaryError, RLMConfigError, RLMSchemaError, RLMContextOverflowError, RLMAbortError, classifyError, } from './errors';
|
|
7
|
+
export { RLMStream, StreamOptions, StreamChunk, StreamChunkType, TextStreamChunk, PartialObjectStreamChunk, UsageStreamChunk, ErrorStreamChunk, DoneStreamChunk, createSimulatedStream, } from './streaming';
|
|
8
|
+
export { RLMCache, CacheConfig, CacheStats, CacheProvider, MemoryCache, FileCache } from './cache';
|
|
9
|
+
export { RetryConfig, FallbackConfig, withRetry, withFallback } from './retry';
|
|
10
|
+
export { RLMEventEmitter, RLMEventMap, RLMEventType, LLMCallEvent, LLMResponseEvent, ValidationRetryEvent, RecursionEvent, MetaAgentEvent, ErrorEvent, CompletionStartEvent, CompletionEndEvent, CacheEvent, RetryEvent, } from './events';
|
|
11
|
+
export { RLMAgentCoordinator } from './coordinator';
|
|
12
|
+
export { FileContextBuilder, FileStorageProvider, FileStorageResult, FileEntry, LocalFileStorage, S3FileStorage, S3StorageError, buildFileContext, } from './file-storage';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ─── Core ────────────────────────────────────────────────────────────────────
|
|
2
|
+
export { RLM, RLMBuilder, RLMResultFormatter } from './rlm.js';
|
|
3
|
+
export { validateConfig, assertValidConfig } from './config.js';
|
|
4
|
+
// ─── Errors ──────────────────────────────────────────────────────────────────
|
|
5
|
+
export { RLMError, RLMValidationError, RLMRateLimitError, RLMTimeoutError, RLMProviderError, RLMBinaryError, RLMConfigError, RLMSchemaError, RLMContextOverflowError, RLMAbortError, classifyError, } from './errors.js';
|
|
6
|
+
// ─── Streaming ───────────────────────────────────────────────────────────────
|
|
7
|
+
export { RLMStream, createSimulatedStream, } from './streaming.js';
|
|
8
|
+
// ─── Cache ───────────────────────────────────────────────────────────────────
|
|
9
|
+
export { RLMCache, MemoryCache, FileCache } from './cache.js';
|
|
10
|
+
// ─── Retry / Resilience ──────────────────────────────────────────────────────
|
|
11
|
+
export { withRetry, withFallback } from './retry.js';
|
|
12
|
+
// ─── Events ──────────────────────────────────────────────────────────────────
|
|
13
|
+
export { RLMEventEmitter, } from './events.js';
|
|
14
|
+
// ─── Coordinator ─────────────────────────────────────────────────────────────
|
|
15
|
+
export { RLMAgentCoordinator } from './coordinator.js';
|
|
16
|
+
// ─── File Storage ────────────────────────────────────────────────────────────
|
|
17
|
+
export { FileContextBuilder, LocalFileStorage, S3FileStorage, S3StorageError, buildFileContext, } from './file-storage.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Directory containing the compiled JS file (dist/cjs or dist/esm or dist) */
|
|
2
|
+
export declare const PKG_DIST_DIR: string;
|
|
3
|
+
/**
|
|
4
|
+
* Package root directory.
|
|
5
|
+
* Handles both flat (dist/) and nested (dist/cjs/, dist/esm/) layouts.
|
|
6
|
+
*/
|
|
7
|
+
export declare const PKG_ROOT_DIR: string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable package directory resolution.
|
|
3
|
+
*
|
|
4
|
+
* Works in both CommonJS and ESM contexts by detecting the available
|
|
5
|
+
* globals and falling back gracefully. The resolved path always points
|
|
6
|
+
* to the package root (parent of the dist/cjs or dist/esm directory).
|
|
7
|
+
*/
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
function resolveCurrentDir() {
|
|
11
|
+
// CJS — __dirname is defined natively by Node
|
|
12
|
+
if (typeof __dirname !== 'undefined') {
|
|
13
|
+
return __dirname;
|
|
14
|
+
}
|
|
15
|
+
// ESM — derive from import.meta.url via indirect eval to avoid CJS parse errors.
|
|
16
|
+
// This branch only runs in ESM where import.meta is valid syntax.
|
|
17
|
+
try {
|
|
18
|
+
const meta = new Function('return import.meta')();
|
|
19
|
+
if (meta && typeof meta.url === 'string') {
|
|
20
|
+
return path.dirname(fileURLToPath(meta.url));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (_a) {
|
|
24
|
+
// Not in ESM or import.meta not supported
|
|
25
|
+
}
|
|
26
|
+
// Last resort: use process.cwd()
|
|
27
|
+
return process.cwd();
|
|
28
|
+
}
|
|
29
|
+
/** Directory containing the compiled JS file (dist/cjs or dist/esm or dist) */
|
|
30
|
+
export const PKG_DIST_DIR = resolveCurrentDir();
|
|
31
|
+
/**
|
|
32
|
+
* Package root directory.
|
|
33
|
+
* Handles both flat (dist/) and nested (dist/cjs/, dist/esm/) layouts.
|
|
34
|
+
*/
|
|
35
|
+
export const PKG_ROOT_DIR = (() => {
|
|
36
|
+
const parent = path.dirname(PKG_DIST_DIR);
|
|
37
|
+
const parentBase = path.basename(parent);
|
|
38
|
+
// If parent is 'dist', we're in dist/cjs or dist/esm — go up one more
|
|
39
|
+
if (parentBase === 'dist') {
|
|
40
|
+
return path.dirname(parent);
|
|
41
|
+
}
|
|
42
|
+
return parent;
|
|
43
|
+
})();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry and resilience layer for recursive-llm-ts.
|
|
3
|
+
*
|
|
4
|
+
* Provides configurable retry with exponential backoff, jitter,
|
|
5
|
+
* and provider fallback chains.
|
|
6
|
+
*/
|
|
7
|
+
export interface RetryConfig {
|
|
8
|
+
/** Maximum number of retries (default: 3) */
|
|
9
|
+
maxRetries?: number;
|
|
10
|
+
/** Backoff strategy (default: 'exponential') */
|
|
11
|
+
backoff?: 'exponential' | 'linear' | 'fixed';
|
|
12
|
+
/** Base delay in milliseconds (default: 1000) */
|
|
13
|
+
baseDelay?: number;
|
|
14
|
+
/** Maximum delay in milliseconds (default: 30000) */
|
|
15
|
+
maxDelay?: number;
|
|
16
|
+
/** Add jitter to delays (default: true) */
|
|
17
|
+
jitter?: boolean;
|
|
18
|
+
/** Error types that should be retried */
|
|
19
|
+
retryableErrors?: string[];
|
|
20
|
+
/** Called before each retry with retry info */
|
|
21
|
+
onRetry?: (attempt: number, error: Error, delay: number) => void;
|
|
22
|
+
}
|
|
23
|
+
/** Fallback model configuration */
|
|
24
|
+
export interface FallbackConfig {
|
|
25
|
+
/** Ordered list of fallback models to try */
|
|
26
|
+
models?: string[];
|
|
27
|
+
/** Strategy for fallback selection */
|
|
28
|
+
strategy?: 'sequential' | 'round-robin';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Execute a function with retry logic.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const result = await withRetry(
|
|
36
|
+
* () => rlm.completion(query, context),
|
|
37
|
+
* { maxRetries: 3, backoff: 'exponential' }
|
|
38
|
+
* );
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function withRetry<T>(fn: () => Promise<T>, config?: RetryConfig, signal?: AbortSignal): Promise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Execute a function with fallback models.
|
|
44
|
+
* Tries each model in order until one succeeds.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const result = await withFallback(
|
|
49
|
+
* (model) => rlm.completion(query, context, model),
|
|
50
|
+
* { models: ['gpt-4o', 'claude-sonnet-4-20250514', 'gemini-2.0-flash'] }
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function withFallback<T>(fn: (model: string) => Promise<T>, fallbackConfig: FallbackConfig, retryConfig?: RetryConfig, signal?: AbortSignal): Promise<T & {
|
|
55
|
+
_usedModel?: string;
|
|
56
|
+
}>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry and resilience layer for recursive-llm-ts.
|
|
3
|
+
*
|
|
4
|
+
* Provides configurable retry with exponential backoff, jitter,
|
|
5
|
+
* and provider fallback chains.
|
|
6
|
+
*/
|
|
7
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
import { RLMError, RLMRateLimitError, RLMAbortError } from './errors.js';
|
|
17
|
+
const DEFAULT_RETRYABLE_ERRORS = new Set([
|
|
18
|
+
'RATE_LIMIT',
|
|
19
|
+
'TIMEOUT',
|
|
20
|
+
'PROVIDER',
|
|
21
|
+
'UNKNOWN',
|
|
22
|
+
]);
|
|
23
|
+
function resolveConfig(config = {}) {
|
|
24
|
+
var _a, _b, _c, _d, _e;
|
|
25
|
+
return {
|
|
26
|
+
maxRetries: (_a = config.maxRetries) !== null && _a !== void 0 ? _a : 3,
|
|
27
|
+
backoff: (_b = config.backoff) !== null && _b !== void 0 ? _b : 'exponential',
|
|
28
|
+
baseDelay: (_c = config.baseDelay) !== null && _c !== void 0 ? _c : 1000,
|
|
29
|
+
maxDelay: (_d = config.maxDelay) !== null && _d !== void 0 ? _d : 30000,
|
|
30
|
+
jitter: (_e = config.jitter) !== null && _e !== void 0 ? _e : true,
|
|
31
|
+
retryableErrors: config.retryableErrors
|
|
32
|
+
? new Set(config.retryableErrors)
|
|
33
|
+
: DEFAULT_RETRYABLE_ERRORS,
|
|
34
|
+
onRetry: config.onRetry,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// ─── Delay Calculation ───────────────────────────────────────────────────────
|
|
38
|
+
function calculateDelay(attempt, config) {
|
|
39
|
+
let delay;
|
|
40
|
+
switch (config.backoff) {
|
|
41
|
+
case 'exponential':
|
|
42
|
+
delay = config.baseDelay * Math.pow(2, attempt);
|
|
43
|
+
break;
|
|
44
|
+
case 'linear':
|
|
45
|
+
delay = config.baseDelay * (attempt + 1);
|
|
46
|
+
break;
|
|
47
|
+
case 'fixed':
|
|
48
|
+
delay = config.baseDelay;
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
delay = config.baseDelay;
|
|
52
|
+
}
|
|
53
|
+
// Cap at max
|
|
54
|
+
delay = Math.min(delay, config.maxDelay);
|
|
55
|
+
// Add jitter (0.5x to 1.5x)
|
|
56
|
+
if (config.jitter) {
|
|
57
|
+
delay = delay * (0.5 + Math.random());
|
|
58
|
+
}
|
|
59
|
+
return Math.round(delay);
|
|
60
|
+
}
|
|
61
|
+
// ─── Retryable Check ─────────────────────────────────────────────────────────
|
|
62
|
+
function isRetryable(error, config) {
|
|
63
|
+
// Never retry abort errors
|
|
64
|
+
if (error instanceof RLMAbortError)
|
|
65
|
+
return false;
|
|
66
|
+
// Check explicit retryable flag
|
|
67
|
+
if (error instanceof RLMError) {
|
|
68
|
+
if (!error.retryable)
|
|
69
|
+
return false;
|
|
70
|
+
return config.retryableErrors.has(error.code);
|
|
71
|
+
}
|
|
72
|
+
// For non-RLM errors, check message heuristics
|
|
73
|
+
const msg = error.message.toLowerCase();
|
|
74
|
+
if (msg.includes('rate limit') || msg.includes('429'))
|
|
75
|
+
return true;
|
|
76
|
+
if (msg.includes('timeout') || msg.includes('etimedout'))
|
|
77
|
+
return true;
|
|
78
|
+
if (msg.includes('econnreset') || msg.includes('econnrefused'))
|
|
79
|
+
return true;
|
|
80
|
+
if (msg.includes('500') || msg.includes('502') || msg.includes('503') || msg.includes('504'))
|
|
81
|
+
return true;
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// ─── Sleep Helper ────────────────────────────────────────────────────────────
|
|
85
|
+
function sleep(ms, signal) {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
88
|
+
reject(new RLMAbortError());
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const timer = setTimeout(resolve, ms);
|
|
92
|
+
if (signal) {
|
|
93
|
+
const onAbort = () => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
reject(new RLMAbortError());
|
|
96
|
+
};
|
|
97
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// ─── Retry Executor ──────────────────────────────────────────────────────────
|
|
102
|
+
/**
|
|
103
|
+
* Execute a function with retry logic.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const result = await withRetry(
|
|
108
|
+
* () => rlm.completion(query, context),
|
|
109
|
+
* { maxRetries: 3, backoff: 'exponential' }
|
|
110
|
+
* );
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function withRetry(fn, config, signal) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
var _a;
|
|
116
|
+
const resolved = resolveConfig(config);
|
|
117
|
+
let lastError;
|
|
118
|
+
for (let attempt = 0; attempt <= resolved.maxRetries; attempt++) {
|
|
119
|
+
try {
|
|
120
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
121
|
+
throw new RLMAbortError();
|
|
122
|
+
return yield fn();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
126
|
+
lastError = error;
|
|
127
|
+
// Check if we should retry
|
|
128
|
+
if (attempt >= resolved.maxRetries || !isRetryable(error, resolved)) {
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
// Special handling for rate limit with retry-after
|
|
132
|
+
let delay;
|
|
133
|
+
if (error instanceof RLMRateLimitError && error.retryAfter) {
|
|
134
|
+
delay = error.retryAfter * 1000;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
delay = calculateDelay(attempt, resolved);
|
|
138
|
+
}
|
|
139
|
+
// Notify callback
|
|
140
|
+
(_a = resolved.onRetry) === null || _a === void 0 ? void 0 : _a.call(resolved, attempt + 1, error, delay);
|
|
141
|
+
// Wait before retrying
|
|
142
|
+
yield sleep(delay, signal);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw lastError || new Error('Retry failed');
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Execute a function with fallback models.
|
|
150
|
+
* Tries each model in order until one succeeds.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const result = await withFallback(
|
|
155
|
+
* (model) => rlm.completion(query, context, model),
|
|
156
|
+
* { models: ['gpt-4o', 'claude-sonnet-4-20250514', 'gemini-2.0-flash'] }
|
|
157
|
+
* );
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export function withFallback(fn, fallbackConfig, retryConfig, signal) {
|
|
161
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
162
|
+
const models = fallbackConfig.models || [];
|
|
163
|
+
if (models.length === 0) {
|
|
164
|
+
throw new RLMError('No models configured for fallback', { code: 'CONFIG', retryable: false });
|
|
165
|
+
}
|
|
166
|
+
let lastError;
|
|
167
|
+
for (const model of models) {
|
|
168
|
+
try {
|
|
169
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
170
|
+
throw new RLMAbortError();
|
|
171
|
+
const result = yield withRetry(() => fn(model), retryConfig, signal);
|
|
172
|
+
return Object.assign(Object.assign({}, result), { _usedModel: model });
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
176
|
+
// Continue to next model
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
throw lastError || new Error('All fallback models failed');
|
|
180
|
+
});
|
|
181
|
+
}
|