@juspay/neurolink 9.84.2 → 9.85.1

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 (70) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +410 -375
  3. package/dist/cli/factories/commandFactory.d.ts +17 -0
  4. package/dist/cli/factories/commandFactory.js +254 -0
  5. package/dist/cli/parser.js +2 -0
  6. package/dist/cli/utils/skillsFlags.d.ts +10 -0
  7. package/dist/cli/utils/skillsFlags.js +22 -0
  8. package/dist/index.d.ts +5 -0
  9. package/dist/index.js +6 -0
  10. package/dist/lib/index.d.ts +5 -0
  11. package/dist/lib/index.js +6 -0
  12. package/dist/lib/neurolink.d.ts +28 -0
  13. package/dist/lib/neurolink.js +117 -0
  14. package/dist/lib/server/routes/agentRoutes.js +156 -1
  15. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +39 -1
  16. package/dist/lib/server/routes/claudeProxyRoutes.js +300 -41
  17. package/dist/lib/server/utils/validation.d.ts +32 -0
  18. package/dist/lib/server/utils/validation.js +18 -0
  19. package/dist/lib/session/globalSessionState.d.ts +10 -1
  20. package/dist/lib/session/globalSessionState.js +18 -0
  21. package/dist/lib/skills/skillMatcher.d.ts +20 -0
  22. package/dist/lib/skills/skillMatcher.js +80 -0
  23. package/dist/lib/skills/skillStoreRedis.d.ts +22 -0
  24. package/dist/lib/skills/skillStoreRedis.js +98 -0
  25. package/dist/lib/skills/skillStoreS3.d.ts +41 -0
  26. package/dist/lib/skills/skillStoreS3.js +233 -0
  27. package/dist/lib/skills/skillStores.d.ts +44 -0
  28. package/dist/lib/skills/skillStores.js +252 -0
  29. package/dist/lib/skills/skillTools.d.ts +19 -0
  30. package/dist/lib/skills/skillTools.js +340 -0
  31. package/dist/lib/skills/skillsManager.d.ts +54 -0
  32. package/dist/lib/skills/skillsManager.js +220 -0
  33. package/dist/lib/types/config.d.ts +10 -0
  34. package/dist/lib/types/generate.d.ts +8 -0
  35. package/dist/lib/types/index.d.ts +1 -0
  36. package/dist/lib/types/index.js +1 -0
  37. package/dist/lib/types/proxy.d.ts +30 -2
  38. package/dist/lib/types/skills.d.ts +296 -0
  39. package/dist/lib/types/skills.js +17 -0
  40. package/dist/lib/types/stream.d.ts +8 -0
  41. package/dist/neurolink.d.ts +28 -0
  42. package/dist/neurolink.js +117 -0
  43. package/dist/server/routes/agentRoutes.js +156 -1
  44. package/dist/server/routes/claudeProxyRoutes.d.ts +39 -1
  45. package/dist/server/routes/claudeProxyRoutes.js +300 -41
  46. package/dist/server/utils/validation.d.ts +32 -0
  47. package/dist/server/utils/validation.js +18 -0
  48. package/dist/session/globalSessionState.d.ts +10 -1
  49. package/dist/session/globalSessionState.js +18 -0
  50. package/dist/skills/skillMatcher.d.ts +20 -0
  51. package/dist/skills/skillMatcher.js +79 -0
  52. package/dist/skills/skillStoreRedis.d.ts +22 -0
  53. package/dist/skills/skillStoreRedis.js +97 -0
  54. package/dist/skills/skillStoreS3.d.ts +41 -0
  55. package/dist/skills/skillStoreS3.js +232 -0
  56. package/dist/skills/skillStores.d.ts +44 -0
  57. package/dist/skills/skillStores.js +251 -0
  58. package/dist/skills/skillTools.d.ts +19 -0
  59. package/dist/skills/skillTools.js +339 -0
  60. package/dist/skills/skillsManager.d.ts +54 -0
  61. package/dist/skills/skillsManager.js +219 -0
  62. package/dist/types/config.d.ts +10 -0
  63. package/dist/types/generate.d.ts +8 -0
  64. package/dist/types/index.d.ts +1 -0
  65. package/dist/types/index.js +1 -0
  66. package/dist/types/proxy.d.ts +30 -2
  67. package/dist/types/skills.d.ts +296 -0
  68. package/dist/types/skills.js +16 -0
  69. package/dist/types/stream.d.ts +8 -0
  70. package/package.json +2 -1
@@ -12,6 +12,7 @@ import type { RedisConversationMemoryManager } from "./core/redisConversationMem
12
12
  import { ExternalServerManager } from "./mcp/externalServerManager.js";
13
13
  import { MCPToolRegistry } from "./mcp/toolRegistry.js";
14
14
  import type { DynamicOptions } from "./types/index.js";
15
+ import { SkillsManager } from "./skills/skillsManager.js";
15
16
  import { TaskManager } from "./tasks/taskManager.js";
16
17
  /**
17
18
  * Curator P2-4 dedup (concurrency-safe): native providers emit
@@ -140,6 +141,8 @@ export declare class NeuroLink {
140
141
  private cachedFileTools;
141
142
  private memoryInstance?;
142
143
  private memorySDKConfig?;
144
+ private skillsManagerInstance?;
145
+ private skillsConfig?;
143
146
  /**
144
147
  * Extract and set Langfuse context from options with proper async scoping
145
148
  */
@@ -264,6 +267,31 @@ export declare class NeuroLink {
264
267
  * Only registered when Redis conversation memory is active.
265
268
  */
266
269
  private registerMemoryRetrievalTools;
270
+ /**
271
+ * Lazy initialization for the skills subsystem — mirrors ensureMemoryReady().
272
+ * Returns null (and stays null) when skills are not configured or the
273
+ * store failed to initialize; read paths fail open on that null.
274
+ */
275
+ private ensureSkillsReady;
276
+ /**
277
+ * Register the built-in skill tools (search_skills / list_skills, plus
278
+ * mutation tools when allowMutations is set). Follows the
279
+ * registerMemoryRetrievalTools() pattern: registered via registerTool()
280
+ * so they land in the "user-defined" category that reaches the LLM tool
281
+ * schema, with the manager resolved lazily at execution time.
282
+ */
283
+ private registerSkillTools;
284
+ /**
285
+ * Append the compact skills index (names + descriptions, never
286
+ * instructions) to the system prompt for one generate()/stream() call.
287
+ * Fails open: any error leaves the prompt untouched.
288
+ */
289
+ private applySkillsPromptIndex;
290
+ /**
291
+ * Programmatic access to the skills subsystem (search/list/get/mutations).
292
+ * Returns null when skills are not configured or failed to initialize.
293
+ */
294
+ getSkillsManager(): SkillsManager | null;
267
295
  /** Format memory context for prompt inclusion */
268
296
  private formatMemoryContext;
269
297
  /**
package/dist/neurolink.js CHANGED
@@ -54,6 +54,8 @@ import { MCPToolRegistry } from "./mcp/toolRegistry.js";
54
54
  import { resolveDynamicArgument } from "./dynamic/dynamicResolver.js";
55
55
  import { initializeHippocampus } from "./memory/hippocampusInitializer.js";
56
56
  import { createMemoryRetrievalTools } from "./memory/memoryRetrievalTools.js";
57
+ import { SkillsManager } from "./skills/skillsManager.js";
58
+ import { createSkillTools } from "./skills/skillTools.js";
57
59
  import { getMetricsAggregator, MetricsAggregator, } from "./observability/metricsAggregator.js";
58
60
  import { SpanStatus, SpanType, CircuitBreakerOpenError, ConversationMemoryError, ModelAccessDeniedError, } from "./types/index.js";
59
61
  import { SpanSerializer } from "./observability/utils/spanSerializer.js";
@@ -458,6 +460,10 @@ export class NeuroLink {
458
460
  // Memory instance and config
459
461
  memoryInstance;
460
462
  memorySDKConfig;
463
+ // Skills subsystem — lazily initialized manager + instance config.
464
+ // `undefined` = not yet attempted, `null` = init failed (stay disabled).
465
+ skillsManagerInstance;
466
+ skillsConfig;
461
467
  /**
462
468
  * Extract and set Langfuse context from options with proper async scoping
463
469
  */
@@ -830,6 +836,10 @@ export class NeuroLink {
830
836
  this.initializeMCPEnhancements(config);
831
837
  this.registerFileTools();
832
838
  this.registerMemoryRetrievalTools();
839
+ if (config?.skills?.enabled) {
840
+ this.skillsConfig = config.skills;
841
+ this.registerSkillTools();
842
+ }
833
843
  this.initializeLangfuse(constructorId, constructorStartTime, constructorHrTimeStart);
834
844
  this.initializeMetricsListeners();
835
845
  this.logConstructorComplete(constructorId, constructorStartTime, constructorHrTimeStart);
@@ -1243,6 +1253,107 @@ export class NeuroLink {
1243
1253
  });
1244
1254
  logger.info("[NeuroLink] Memory retrieval tools registered");
1245
1255
  }
1256
+ /**
1257
+ * Lazy initialization for the skills subsystem — mirrors ensureMemoryReady().
1258
+ * Returns null (and stays null) when skills are not configured or the
1259
+ * store failed to initialize; read paths fail open on that null.
1260
+ */
1261
+ ensureSkillsReady() {
1262
+ if (this.skillsManagerInstance !== undefined) {
1263
+ return this.skillsManagerInstance;
1264
+ }
1265
+ if (!this.skillsConfig?.enabled) {
1266
+ this.skillsManagerInstance = null;
1267
+ return null;
1268
+ }
1269
+ try {
1270
+ this.skillsManagerInstance = new SkillsManager(this.skillsConfig);
1271
+ }
1272
+ catch (error) {
1273
+ logger.warn("[NeuroLink] Skills initialization failed — skills disabled for this instance", { error: error instanceof Error ? error.message : String(error) });
1274
+ this.skillsManagerInstance = null;
1275
+ }
1276
+ return this.skillsManagerInstance;
1277
+ }
1278
+ /**
1279
+ * Register the built-in skill tools (search_skills / list_skills, plus
1280
+ * mutation tools when allowMutations is set). Follows the
1281
+ * registerMemoryRetrievalTools() pattern: registered via registerTool()
1282
+ * so they land in the "user-defined" category that reaches the LLM tool
1283
+ * schema, with the manager resolved lazily at execution time.
1284
+ */
1285
+ registerSkillTools() {
1286
+ const canonicalTools = createSkillTools(() => this.ensureSkillsReady(), {
1287
+ allowMutations: this.skillsConfig?.allowMutations === true,
1288
+ });
1289
+ for (const [toolName, toolDef] of Object.entries(canonicalTools)) {
1290
+ this.registerTool(toolName, {
1291
+ name: toolName,
1292
+ description: toolDef.description ?? toolName,
1293
+ // Zod schema — registerTool() detects isZodSchema and preserves it
1294
+ // so ToolsManager gives the LLM full parameter types.
1295
+ inputSchema: toolDef
1296
+ .inputSchema,
1297
+ execute: async (params) => withTimeout(toolDef.execute(params, { toolCallId: "skill-tool", messages: [] }), TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS, ErrorFactory.toolTimeout(toolName, TOOL_TIMEOUTS.EXECUTION_DEFAULT_MS)),
1298
+ });
1299
+ }
1300
+ logger.info(`[NeuroLink] Registered ${Object.keys(canonicalTools).length} skill tools`, { allowMutations: this.skillsConfig?.allowMutations === true });
1301
+ }
1302
+ /**
1303
+ * Append the compact skills index (names + descriptions, never
1304
+ * instructions) to the system prompt for one generate()/stream() call.
1305
+ * Fails open: any error leaves the prompt untouched.
1306
+ */
1307
+ async applySkillsPromptIndex(options) {
1308
+ if (!this.skillsConfig?.enabled || options.skills?.enabled === false) {
1309
+ return;
1310
+ }
1311
+ // Per-call promptIndex wins over instance config; default is on.
1312
+ const promptIndexEnabled = options.skills?.promptIndex ?? this.skillsConfig.promptIndex ?? true;
1313
+ if (!promptIndexEnabled) {
1314
+ return;
1315
+ }
1316
+ // Media-only modes have no meaningful text prompt to augment.
1317
+ const mode = options.output?.mode;
1318
+ if (mode === "avatar" ||
1319
+ mode === "music" ||
1320
+ mode === "video" ||
1321
+ mode === "ppt") {
1322
+ return;
1323
+ }
1324
+ try {
1325
+ const manager = this.ensureSkillsReady();
1326
+ if (!manager) {
1327
+ return;
1328
+ }
1329
+ const block = await manager.buildPromptIndex({
1330
+ ...(options.skills?.scopeId !== undefined
1331
+ ? { scopeId: options.skills.scopeId }
1332
+ : {}),
1333
+ ...(options.skills?.tags !== undefined
1334
+ ? { tags: options.skills.tags }
1335
+ : {}),
1336
+ });
1337
+ if (block) {
1338
+ options.systemPrompt = options.systemPrompt
1339
+ ? `${options.systemPrompt}\n\n${block}`
1340
+ : block;
1341
+ logger.debug("[NeuroLink] Skills prompt index injected", {
1342
+ blockLength: block.length,
1343
+ });
1344
+ }
1345
+ }
1346
+ catch (error) {
1347
+ logger.warn("[NeuroLink] Skills prompt index injection failed — continuing without it", { error: error instanceof Error ? error.message : String(error) });
1348
+ }
1349
+ }
1350
+ /**
1351
+ * Programmatic access to the skills subsystem (search/list/get/mutations).
1352
+ * Returns null when skills are not configured or failed to initialize.
1353
+ */
1354
+ getSkillsManager() {
1355
+ return this.ensureSkillsReady();
1356
+ }
1246
1357
  /** Format memory context for prompt inclusion */
1247
1358
  formatMemoryContext(memoryContext, currentInput) {
1248
1359
  return `Context from previous conversations:
@@ -3499,6 +3610,9 @@ Current user's request: ${currentInput}`;
3499
3610
  });
3500
3611
  }
3501
3612
  }
3613
+ // Skills: append the compact skills index to the system prompt so the
3614
+ // model knows which skills exist (bodies load via search_skills).
3615
+ await this.applySkillsPromptIndex(options);
3502
3616
  // Media-only modes (avatar, music, video, ppt) do not have a meaningful
3503
3617
  // text prompt to augment with memory — skip injection to avoid corrupting
3504
3618
  // the empty/synthesized input.text that was set for these modes.
@@ -7037,6 +7151,9 @@ Current user's request: ${currentInput}`;
7037
7151
  logger.warn("Memory retrieval failed:", error);
7038
7152
  }
7039
7153
  }
7154
+ // Skills: append the compact skills index to the system prompt so the
7155
+ // model knows which skills exist (bodies load via search_skills).
7156
+ await this.applySkillsPromptIndex(options);
7040
7157
  // Apply orchestration if enabled and no specific provider/model requested
7041
7158
  if (this.enableOrchestration && !options.provider && !options.model) {
7042
7159
  try {
@@ -7,7 +7,34 @@ import { ProviderFactory } from "../../factories/providerFactory.js";
7
7
  import { withSpan } from "../../telemetry/withSpan.js";
8
8
  import { tracers } from "../../telemetry/tracers.js";
9
9
  import { createStreamRedactor } from "../utils/redaction.js";
10
- import { AgentExecuteRequestSchema, createErrorResponse as createError, EmbedManyRequestSchema, EmbedRequestSchema, validateRequest, } from "../utils/validation.js";
10
+ import { AgentExecuteRequestSchema, createErrorResponse as createError, EmbedManyRequestSchema, EmbedRequestSchema, SkillCreateRequestSchema, SkillUpdateRequestSchema, validateRequest, } from "../utils/validation.js";
11
+ /**
12
+ * Resolve the skills manager from the server's NeuroLink instance, or a
13
+ * 503 error response when skills are not configured on this server.
14
+ */
15
+ function resolveSkillsManager(ctx) {
16
+ const manager = ctx.neurolink.getSkillsManager();
17
+ if (!manager) {
18
+ return createError("SKILLS_UNAVAILABLE", "Skills are not enabled on this server — construct NeuroLink with a `skills` config.", undefined, ctx.requestId);
19
+ }
20
+ return manager;
21
+ }
22
+ /**
23
+ * Resolve the skills manager for a *mutating* route, or an error response when
24
+ * skills aren't configured (503) or mutations are disabled (allowMutations is
25
+ * not true). The LLM tools are already registration-gated; this applies the
26
+ * same switch to the REST create/update/delete endpoints.
27
+ */
28
+ function resolveMutableSkillsManager(ctx) {
29
+ const manager = resolveSkillsManager(ctx);
30
+ if ("error" in manager) {
31
+ return manager;
32
+ }
33
+ if (!manager.mutationsAllowed) {
34
+ return createError("SKILLS_MUTATIONS_DISABLED", "Skill mutations are disabled on this server \u2014 construct NeuroLink with `skills.allowMutations: true` to enable create/update/delete.", undefined, ctx.requestId);
35
+ }
36
+ return manager;
37
+ }
11
38
  /**
12
39
  * Create agent routes
13
40
  */
@@ -239,6 +266,134 @@ export function createAgentRoutes(basePath = "/api") {
239
266
  description: "Generate embeddings for multiple texts in a batch",
240
267
  tags: ["agent", "embeddings"],
241
268
  },
269
+ {
270
+ method: "GET",
271
+ path: `${basePath}/agent/skills`,
272
+ handler: async (ctx) => {
273
+ const manager = resolveSkillsManager(ctx);
274
+ if ("error" in manager) {
275
+ return manager;
276
+ }
277
+ try {
278
+ const skills = await manager.list(ctx.query.scopeId);
279
+ return { skills, count: skills.length };
280
+ }
281
+ catch (error) {
282
+ return createError("EXECUTION_FAILED", error instanceof Error ? error.message : "Skills listing failed", undefined, ctx.requestId);
283
+ }
284
+ },
285
+ description: "List active skills (index only — no instructions). Optional ?scopeId= filter.",
286
+ tags: ["agent", "skills"],
287
+ },
288
+ {
289
+ method: "GET",
290
+ path: `${basePath}/agent/skills/:id`,
291
+ handler: async (ctx) => {
292
+ const manager = resolveSkillsManager(ctx);
293
+ if ("error" in manager) {
294
+ return manager;
295
+ }
296
+ try {
297
+ const skill = await manager.get(ctx.params.id);
298
+ if (!skill) {
299
+ return createError("NOT_FOUND", `Skill "${ctx.params.id}" not found`, undefined, ctx.requestId);
300
+ }
301
+ return skill;
302
+ }
303
+ catch (error) {
304
+ return createError("EXECUTION_FAILED", error instanceof Error ? error.message : "Skill lookup failed", undefined, ctx.requestId);
305
+ }
306
+ },
307
+ description: "Fetch one skill (by id or exact name) with instructions",
308
+ tags: ["agent", "skills"],
309
+ },
310
+ {
311
+ method: "POST",
312
+ path: `${basePath}/agent/skills`,
313
+ handler: async (ctx) => {
314
+ const manager = resolveMutableSkillsManager(ctx);
315
+ if ("error" in manager) {
316
+ return manager;
317
+ }
318
+ const validation = validateRequest(SkillCreateRequestSchema, ctx.body, ctx.requestId);
319
+ if (!validation.success) {
320
+ return validation.error;
321
+ }
322
+ const { requestedBy, ...skill } = validation.data;
323
+ try {
324
+ const result = await manager.requestMutation({
325
+ type: "create",
326
+ skill,
327
+ // Authenticated identity wins over caller-supplied attribution.
328
+ ...(ctx.user?.id || requestedBy
329
+ ? { requestedBy: ctx.user?.id ?? requestedBy }
330
+ : {}),
331
+ });
332
+ return result;
333
+ }
334
+ catch (error) {
335
+ return createError("EXECUTION_FAILED", error instanceof Error ? error.message : "Skill create failed", undefined, ctx.requestId);
336
+ }
337
+ },
338
+ description: "Create a skill (routed through the host's onMutationRequest gate when configured)",
339
+ tags: ["agent", "skills"],
340
+ },
341
+ {
342
+ method: "PATCH",
343
+ path: `${basePath}/agent/skills/:id`,
344
+ handler: async (ctx) => {
345
+ const manager = resolveMutableSkillsManager(ctx);
346
+ if ("error" in manager) {
347
+ return manager;
348
+ }
349
+ const validation = validateRequest(SkillUpdateRequestSchema, ctx.body, ctx.requestId);
350
+ if (!validation.success) {
351
+ return validation.error;
352
+ }
353
+ const { requestedBy, ...patch } = validation.data;
354
+ try {
355
+ const result = await manager.requestMutation({
356
+ type: "update",
357
+ skillId: ctx.params.id,
358
+ patch,
359
+ ...(ctx.user?.id || requestedBy
360
+ ? { requestedBy: ctx.user?.id ?? requestedBy }
361
+ : {}),
362
+ });
363
+ return result;
364
+ }
365
+ catch (error) {
366
+ const message = error instanceof Error ? error.message : "Skill update failed";
367
+ return createError(message.includes("not found") ? "NOT_FOUND" : "EXECUTION_FAILED", message, undefined, ctx.requestId);
368
+ }
369
+ },
370
+ description: "Update a skill (patch semantics; version is bumped)",
371
+ tags: ["agent", "skills"],
372
+ },
373
+ {
374
+ method: "DELETE",
375
+ path: `${basePath}/agent/skills/:id`,
376
+ handler: async (ctx) => {
377
+ const manager = resolveMutableSkillsManager(ctx);
378
+ if ("error" in manager) {
379
+ return manager;
380
+ }
381
+ try {
382
+ const result = await manager.requestMutation({
383
+ type: "delete",
384
+ skillId: ctx.params.id,
385
+ ...(ctx.user?.id ? { requestedBy: ctx.user.id } : {}),
386
+ });
387
+ return result;
388
+ }
389
+ catch (error) {
390
+ const message = error instanceof Error ? error.message : "Skill delete failed";
391
+ return createError(message.includes("not found") ? "NOT_FOUND" : "EXECUTION_FAILED", message, undefined, ctx.requestId);
392
+ }
393
+ },
394
+ description: "Soft-delete (deprecate) a skill",
395
+ tags: ["agent", "skills"],
396
+ },
242
397
  ],
243
398
  };
244
399
  }
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
13
13
  import type { ModelRouter } from "../../proxy/modelRouter.js";
14
- import type { ParsedClaudeError, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState } from "../../types/index.js";
14
+ import type { AccountCooldownPlan, AccountQuota, ParsedClaudeError, ProxyPassthroughAccount, RouteGroup, RuntimeAccountState } from "../../types/index.js";
15
15
  /** Resolve the configured primary's stable key to its current index in the
16
16
  * request's enabledAccounts list. Returns 0 (insertion-order fallback) when
17
17
  * no key is configured or the key cannot be matched (account disabled/
@@ -23,6 +23,41 @@ declare function resolveHomeIndex(enabledAccounts: ProxyPassthroughAccount[]): n
23
23
  * account once its rate limit window expires. Called at the start of each
24
24
  * request. Home is resolved fresh per call via resolveHomeIndex. */
25
25
  declare function maybeResetPrimaryToHome(enabledAccounts: ProxyPassthroughAccount[]): void;
26
+ /** Convert an Anthropic unified-window reset (Unix epoch SECONDS, per the
27
+ * `anthropic-ratelimit-unified-*-reset` headers) into epoch-ms. Tolerates a
28
+ * value already expressed in ms (some intermediaries normalise it). Returns
29
+ * undefined for absent/zero/past-or-garbage timestamps so callers can fall
30
+ * back to retry-after. */
31
+ declare function resetEpochToMs(resetEpoch: number | undefined, now: number): number | undefined;
32
+ /**
33
+ * Decide how to cool an account after a genuine (non-anti-abuse) 429.
34
+ *
35
+ * The unified subscription limits expose per-window status + reset:
36
+ * - weekly (7d) "rejected" → hard cap for the week; cool until the 7d reset.
37
+ * - session (5h) "rejected" → paced out for this session; cool until the 5h reset.
38
+ * Both mean "retrying this account is futile until its window resets" → rotate
39
+ * immediately (no same-account retries) and park the account until the ACTUAL
40
+ * reset — never the legacy 60s hardcap that let us re-hammer a spent account.
41
+ *
42
+ * Anything else (window still "allowed" but momentarily 429'd — a per-minute
43
+ * burst / acceleration limit) is transient: honor retry-after as a floor,
44
+ * allow a couple of jittered same-account retries, then a short cooldown.
45
+ */
46
+ declare function planCooldownFor429(quota: AccountQuota | null, retryAfterMs: number, now: number): AccountCooldownPlan;
47
+ /**
48
+ * Order accounts to MAXIMIZE quota utilization (fill-first, smart order):
49
+ * spend the account whose window refreshes SOONEST first, so its about-to-reset
50
+ * allowance isn't wasted, then move to accounts with longer-dated resets.
51
+ *
52
+ * Priority among usable accounts:
53
+ * 1. soonest WEEKLY (7d) reset — the scarce, use-it-or-lose-it ceiling
54
+ * 2. soonest SESSION (5h) reset
55
+ * 3. highest weekly utilization — finish off the one closest to done
56
+ * Accounts with no quota data yet keep insertion order (stable sort) and sit
57
+ * after those with a known soonest reset. Cooling/rejected accounts sort last,
58
+ * soonest-back-to-service first, as last resort.
59
+ */
60
+ declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number): ProxyPassthroughAccount[];
26
61
  /**
27
62
  * Create Claude-compatible proxy routes.
28
63
  *
@@ -57,6 +92,9 @@ export declare function isTransientHttpFailure(status: number, errBody: string):
57
92
  export declare const __testHooks: {
58
93
  resolveHomeIndex: typeof resolveHomeIndex;
59
94
  maybeResetPrimaryToHome: typeof maybeResetPrimaryToHome;
95
+ planCooldownFor429: typeof planCooldownFor429;
96
+ orderAccountsByQuota: typeof orderAccountsByQuota;
97
+ resetEpochToMs: typeof resetEpochToMs;
60
98
  setConfiguredPrimaryAccountKey: (key: string | undefined) => void;
61
99
  getConfiguredPrimaryAccountKey: () => string | undefined;
62
100
  setPrimaryAccountIndex: (index: number) => void;