@juspay/neurolink 9.85.0 → 9.86.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.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +420 -412
- package/dist/context/contextCompactor.js +16 -2
- package/dist/context/stages/slidingWindowTruncator.js +76 -30
- package/dist/core/conversationMemoryManager.js +13 -2
- package/dist/core/redisConversationMemoryManager.js +10 -1
- package/dist/lib/context/contextCompactor.js +16 -2
- package/dist/lib/context/stages/slidingWindowTruncator.js +76 -30
- package/dist/lib/core/conversationMemoryManager.js +13 -2
- package/dist/lib/core/redisConversationMemoryManager.js +10 -1
- package/dist/lib/neurolink.d.ts +31 -6
- package/dist/lib/neurolink.js +163 -33
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +39 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +300 -41
- package/dist/lib/skills/skillMatcher.d.ts +33 -4
- package/dist/lib/skills/skillMatcher.js +81 -6
- package/dist/lib/skills/skillSessionTracker.d.ts +52 -0
- package/dist/lib/skills/skillSessionTracker.js +150 -0
- package/dist/lib/skills/skillStoreRedis.d.ts +8 -0
- package/dist/lib/skills/skillStoreRedis.js +18 -2
- package/dist/lib/skills/skillStoreS3.d.ts +17 -2
- package/dist/lib/skills/skillStoreS3.js +78 -6
- package/dist/lib/skills/skillStores.d.ts +16 -2
- package/dist/lib/skills/skillStores.js +94 -5
- package/dist/lib/skills/skillTools.d.ts +26 -10
- package/dist/lib/skills/skillTools.js +190 -79
- package/dist/lib/skills/skillsManager.d.ts +25 -1
- package/dist/lib/skills/skillsManager.js +46 -4
- package/dist/lib/types/config.d.ts +5 -5
- package/dist/lib/types/conversation.d.ts +27 -0
- package/dist/lib/types/proxy.d.ts +30 -2
- package/dist/lib/types/skills.d.ts +145 -14
- package/dist/lib/types/skills.js +3 -3
- package/dist/lib/utils/conversationMemory.d.ts +1 -1
- package/dist/lib/utils/conversationMemory.js +15 -2
- package/dist/neurolink.d.ts +31 -6
- package/dist/neurolink.js +163 -33
- package/dist/server/routes/claudeProxyRoutes.d.ts +39 -1
- package/dist/server/routes/claudeProxyRoutes.js +300 -41
- package/dist/skills/skillMatcher.d.ts +33 -4
- package/dist/skills/skillMatcher.js +81 -6
- package/dist/skills/skillSessionTracker.d.ts +52 -0
- package/dist/skills/skillSessionTracker.js +149 -0
- package/dist/skills/skillStoreRedis.d.ts +8 -0
- package/dist/skills/skillStoreRedis.js +18 -2
- package/dist/skills/skillStoreS3.d.ts +17 -2
- package/dist/skills/skillStoreS3.js +78 -6
- package/dist/skills/skillStores.d.ts +16 -2
- package/dist/skills/skillStores.js +94 -5
- package/dist/skills/skillTools.d.ts +26 -10
- package/dist/skills/skillTools.js +190 -79
- package/dist/skills/skillsManager.d.ts +25 -1
- package/dist/skills/skillsManager.js +46 -4
- package/dist/types/config.d.ts +5 -5
- package/dist/types/conversation.d.ts +27 -0
- package/dist/types/proxy.d.ts +30 -2
- package/dist/types/skills.d.ts +145 -14
- package/dist/types/skills.js +3 -3
- package/dist/utils/conversationMemory.d.ts +1 -1
- package/dist/utils/conversationMemory.js +15 -2
- package/package.json +1 -1
package/dist/lib/neurolink.js
CHANGED
|
@@ -54,8 +54,10 @@ 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 { isSkillVisibleInScope } from "./skills/skillMatcher.js";
|
|
58
|
+
import { buildSkillActivationMessage } from "./skills/skillSessionTracker.js";
|
|
57
59
|
import { SkillsManager } from "./skills/skillsManager.js";
|
|
58
|
-
import { createSkillTools } from "./skills/skillTools.js";
|
|
60
|
+
import { createSkillCallTools, createSkillTools } from "./skills/skillTools.js";
|
|
59
61
|
import { getMetricsAggregator, MetricsAggregator, } from "./observability/metricsAggregator.js";
|
|
60
62
|
import { SpanStatus, SpanType, CircuitBreakerOpenError, ConversationMemoryError, ModelAccessDeniedError, } from "./types/index.js";
|
|
61
63
|
import { SpanSerializer } from "./observability/utils/spanSerializer.js";
|
|
@@ -1276,7 +1278,7 @@ export class NeuroLink {
|
|
|
1276
1278
|
return this.skillsManagerInstance;
|
|
1277
1279
|
}
|
|
1278
1280
|
/**
|
|
1279
|
-
* Register the built-in skill tools (
|
|
1281
|
+
* Register the built-in skill tools (list_skills, plus
|
|
1280
1282
|
* mutation tools when allowMutations is set). Follows the
|
|
1281
1283
|
* registerMemoryRetrievalTools() pattern: registered via registerTool()
|
|
1282
1284
|
* so they land in the "user-defined" category that reaches the LLM tool
|
|
@@ -1300,19 +1302,22 @@ export class NeuroLink {
|
|
|
1300
1302
|
logger.info(`[NeuroLink] Registered ${Object.keys(canonicalTools).length} skill tools`, { allowMutations: this.skillsConfig?.allowMutations === true });
|
|
1301
1303
|
}
|
|
1302
1304
|
/**
|
|
1303
|
-
*
|
|
1304
|
-
*
|
|
1305
|
-
*
|
|
1305
|
+
* Skills augmentation for one generate()/stream() call:
|
|
1306
|
+
* 1. Discovery — surface the skills listing per the resolved mode:
|
|
1307
|
+
* embedded in the use_skill tool description ("tool", default) or
|
|
1308
|
+
* appended to the system prompt ("system-prompt").
|
|
1309
|
+
* 2. Per-call tools — inject use_skill + read_skill_resource into
|
|
1310
|
+
* options.tools (the RAG-tool pattern; same-name entries shadow
|
|
1311
|
+
* registered tools) with the sessionId closure-bound so activations
|
|
1312
|
+
* pin to the session.
|
|
1313
|
+
* 3. Preload — activate host-requested skills up front, injecting their
|
|
1314
|
+
* instructions into this call's system prompt and pinning them.
|
|
1315
|
+
* Fails open: any error leaves the call untouched.
|
|
1306
1316
|
*/
|
|
1307
|
-
async
|
|
1317
|
+
async applySkillsAugmentation(options) {
|
|
1308
1318
|
if (!this.skillsConfig?.enabled || options.skills?.enabled === false) {
|
|
1309
1319
|
return;
|
|
1310
1320
|
}
|
|
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
1321
|
// Media-only modes have no meaningful text prompt to augment.
|
|
1317
1322
|
const mode = options.output?.mode;
|
|
1318
1323
|
if (mode === "avatar" ||
|
|
@@ -1326,26 +1331,142 @@ export class NeuroLink {
|
|
|
1326
1331
|
if (!manager) {
|
|
1327
1332
|
return;
|
|
1328
1333
|
}
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1334
|
+
const discovery = options.skills?.discovery ?? this.skillsConfig.discovery ?? "tool";
|
|
1335
|
+
const scopeId = options.skills?.scopeId ?? this.skillsConfig.defaultScopeId;
|
|
1336
|
+
const tags = options.skills?.tags;
|
|
1337
|
+
const sessionId = this.resolveSkillSessionId(options.context) ??
|
|
1338
|
+
this.resolveSkillSessionId(options);
|
|
1339
|
+
const userId = this.resolveSkillUserId(options.context) ??
|
|
1340
|
+
this.resolveSkillUserId(options);
|
|
1341
|
+
// Pinning requires somewhere to pin: without conversation memory the
|
|
1342
|
+
// drained messages would be silently discarded while dedup reports
|
|
1343
|
+
// already_loaded — so persistence is only on when memory is
|
|
1344
|
+
// configured (or already initialized).
|
|
1345
|
+
const memoryAvailable = Boolean(this.conversationMemory) ||
|
|
1346
|
+
Boolean(this.conversationMemoryConfig?.conversationMemory?.enabled);
|
|
1347
|
+
const sessionPersistence = (this.skillsConfig.sessionPersistence ?? true) &&
|
|
1348
|
+
Boolean(sessionId) &&
|
|
1349
|
+
memoryAvailable;
|
|
1350
|
+
const visibility = {
|
|
1351
|
+
...(scopeId !== undefined ? { scopeId } : {}),
|
|
1352
|
+
...(tags !== undefined ? { tags } : {}),
|
|
1353
|
+
};
|
|
1354
|
+
if (discovery === "system-prompt") {
|
|
1355
|
+
const block = await manager.buildPromptIndex(visibility);
|
|
1356
|
+
if (block) {
|
|
1357
|
+
options.systemPrompt = options.systemPrompt
|
|
1358
|
+
? `${options.systemPrompt}\n\n${block}`
|
|
1359
|
+
: block;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const listing = discovery === "tool"
|
|
1363
|
+
? await manager.buildToolListing(visibility)
|
|
1364
|
+
: null;
|
|
1365
|
+
const callTools = createSkillCallTools(() => this.ensureSkillsReady(), {
|
|
1366
|
+
...(sessionId ? { sessionId } : {}),
|
|
1367
|
+
...(scopeId !== undefined ? { scopeId } : {}),
|
|
1368
|
+
sessionPersistence,
|
|
1369
|
+
discovery,
|
|
1370
|
+
listing,
|
|
1371
|
+
// userId rides by closure: Redis memory keys sessions by
|
|
1372
|
+
// userId:sessionId, so hydration must read the same key the
|
|
1373
|
+
// store-turn path writes.
|
|
1374
|
+
getStoredMessages: async (sid) => this.conversationMemory
|
|
1375
|
+
? await this.conversationMemory.getSessionMessages(sid, userId)
|
|
1376
|
+
: [],
|
|
1336
1377
|
});
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1378
|
+
// Caller-supplied per-call tools win over the injected ones (a host
|
|
1379
|
+
// may bring its own use_skill); the injected tools still shadow any
|
|
1380
|
+
// registered base tool of the same name via the provider merge.
|
|
1381
|
+
options.tools = {
|
|
1382
|
+
...callTools,
|
|
1383
|
+
...(options.tools ?? {}),
|
|
1384
|
+
};
|
|
1385
|
+
if (options.skills?.preload?.length) {
|
|
1386
|
+
await this.preloadSkills(manager, options, options.skills.preload, {
|
|
1387
|
+
...(sessionId ? { sessionId } : {}),
|
|
1388
|
+
...(userId ? { userId } : {}),
|
|
1389
|
+
sessionPersistence,
|
|
1390
|
+
...(scopeId !== undefined ? { scopeId } : {}),
|
|
1343
1391
|
});
|
|
1344
1392
|
}
|
|
1393
|
+
logger.debug("[NeuroLink] Skills augmentation applied", {
|
|
1394
|
+
discovery,
|
|
1395
|
+
listingLength: listing?.length ?? 0,
|
|
1396
|
+
sessionPersistence,
|
|
1397
|
+
preloadCount: options.skills?.preload?.length ?? 0,
|
|
1398
|
+
});
|
|
1345
1399
|
}
|
|
1346
1400
|
catch (error) {
|
|
1347
|
-
logger.warn("[NeuroLink] Skills
|
|
1401
|
+
logger.warn("[NeuroLink] Skills augmentation failed — continuing without skills", { error: error instanceof Error ? error.message : String(error) });
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
/** Session id for skill activation tracking, from the call context. */
|
|
1405
|
+
resolveSkillSessionId(context) {
|
|
1406
|
+
const fromContext = context
|
|
1407
|
+
?.sessionId;
|
|
1408
|
+
return typeof fromContext === "string" && fromContext
|
|
1409
|
+
? fromContext
|
|
1410
|
+
: undefined;
|
|
1411
|
+
}
|
|
1412
|
+
/** User id for skill-session hydration (Redis keys sessions by userId). */
|
|
1413
|
+
resolveSkillUserId(context) {
|
|
1414
|
+
const fromContext = context?.userId;
|
|
1415
|
+
return typeof fromContext === "string" && fromContext
|
|
1416
|
+
? fromContext
|
|
1417
|
+
: undefined;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Activate host-requested skills before the model runs: instructions go
|
|
1421
|
+
* into this call's system prompt; when persisting, the activation is
|
|
1422
|
+
* pinned so later turns replay it from history instead. Unknown or
|
|
1423
|
+
* already-active names are skipped with a warn/debug log (fail-open).
|
|
1424
|
+
*/
|
|
1425
|
+
async preloadSkills(manager, options, names, call) {
|
|
1426
|
+
const { sessionId, userId, sessionPersistence, scopeId } = call;
|
|
1427
|
+
for (const name of names) {
|
|
1428
|
+
const skill = await manager.get(name);
|
|
1429
|
+
if (!skill || !isSkillVisibleInScope(skill, scopeId)) {
|
|
1430
|
+
logger.warn("[NeuroLink] Preload skill not found — skipping", {
|
|
1431
|
+
skill: name,
|
|
1432
|
+
});
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
let block;
|
|
1436
|
+
if (sessionId && sessionPersistence) {
|
|
1437
|
+
// Always hydrate (empty history when memory isn't initialized yet):
|
|
1438
|
+
// the tracker derives truth from stored pins + this turn's pending,
|
|
1439
|
+
// never from stale in-process records.
|
|
1440
|
+
manager.sessions.hydrate(sessionId, this.conversationMemory
|
|
1441
|
+
? await this.conversationMemory.getSessionMessages(sessionId, userId)
|
|
1442
|
+
: []);
|
|
1443
|
+
if (manager.sessions.isActive(sessionId, skill.id, skill.name)) {
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
block = manager.sessions.recordActivation(sessionId, skill).content;
|
|
1447
|
+
}
|
|
1448
|
+
else {
|
|
1449
|
+
block = buildSkillActivationMessage(skill).content;
|
|
1450
|
+
}
|
|
1451
|
+
options.systemPrompt = options.systemPrompt
|
|
1452
|
+
? `${options.systemPrompt}\n\n${block}`
|
|
1453
|
+
: block;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Pinned skill messages recorded during this turn, ready for
|
|
1458
|
+
* StoreConversationTurnOptions.skillMessages. Empty when skills are off
|
|
1459
|
+
* or nothing was activated.
|
|
1460
|
+
*/
|
|
1461
|
+
drainPendingSkillMessages(sessionId) {
|
|
1462
|
+
if (typeof sessionId !== "string" || !sessionId) {
|
|
1463
|
+
return [];
|
|
1464
|
+
}
|
|
1465
|
+
const manager = this.skillsManagerInstance;
|
|
1466
|
+
if (!manager) {
|
|
1467
|
+
return [];
|
|
1348
1468
|
}
|
|
1469
|
+
return manager.sessions.drainPending(sessionId);
|
|
1349
1470
|
}
|
|
1350
1471
|
/**
|
|
1351
1472
|
* Programmatic access to the skills subsystem (search/list/get/mutations).
|
|
@@ -3610,9 +3731,9 @@ Current user's request: ${currentInput}`;
|
|
|
3610
3731
|
});
|
|
3611
3732
|
}
|
|
3612
3733
|
}
|
|
3613
|
-
// Skills:
|
|
3614
|
-
//
|
|
3615
|
-
await this.
|
|
3734
|
+
// Skills: surface the discovery listing and inject the per-call
|
|
3735
|
+
// use_skill / read_skill_resource tools (bodies load on activation).
|
|
3736
|
+
await this.applySkillsAugmentation(options);
|
|
3616
3737
|
// Media-only modes (avatar, music, video, ppt) do not have a meaningful
|
|
3617
3738
|
// text prompt to augment with memory — skip injection to avoid corrupting
|
|
3618
3739
|
// the empty/synthesized input.text that was set for these modes.
|
|
@@ -4431,7 +4552,7 @@ Current user's request: ${currentInput}`;
|
|
|
4431
4552
|
});
|
|
4432
4553
|
const memStoreStart = Date.now();
|
|
4433
4554
|
try {
|
|
4434
|
-
await storeConversationTurn(this.conversationMemory, options, result, new Date(startTime), requestId);
|
|
4555
|
+
await storeConversationTurn(this.conversationMemory, options, result, new Date(startTime), requestId, this.drainPendingSkillMessages(options.context?.sessionId));
|
|
4435
4556
|
this.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": path }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
4436
4557
|
}
|
|
4437
4558
|
catch (memoryError) {
|
|
@@ -7151,9 +7272,9 @@ Current user's request: ${currentInput}`;
|
|
|
7151
7272
|
logger.warn("Memory retrieval failed:", error);
|
|
7152
7273
|
}
|
|
7153
7274
|
}
|
|
7154
|
-
// Skills:
|
|
7155
|
-
//
|
|
7156
|
-
await this.
|
|
7275
|
+
// Skills: surface the discovery listing and inject the per-call
|
|
7276
|
+
// use_skill / read_skill_resource tools (bodies load on activation).
|
|
7277
|
+
await this.applySkillsAugmentation(options);
|
|
7157
7278
|
// Apply orchestration if enabled and no specific provider/model requested
|
|
7158
7279
|
if (this.enableOrchestration && !options.provider && !options.model) {
|
|
7159
7280
|
try {
|
|
@@ -7526,6 +7647,7 @@ Current user's request: ${currentInput}`;
|
|
|
7526
7647
|
}
|
|
7527
7648
|
const memStoreStart = Date.now();
|
|
7528
7649
|
try {
|
|
7650
|
+
const pendingSkillMessages = this.drainPendingSkillMessages(sessionId);
|
|
7529
7651
|
await this.conversationMemory.storeConversationTurn({
|
|
7530
7652
|
sessionId,
|
|
7531
7653
|
userId,
|
|
@@ -7537,6 +7659,9 @@ Current user's request: ${currentInput}`;
|
|
|
7537
7659
|
events: eventSequence.length > 0 ? eventSequence : undefined,
|
|
7538
7660
|
requestId: enhancedOptions.context
|
|
7539
7661
|
?.requestId,
|
|
7662
|
+
...(pendingSkillMessages.length > 0
|
|
7663
|
+
? { skillMessages: pendingSkillMessages }
|
|
7664
|
+
: {}),
|
|
7540
7665
|
});
|
|
7541
7666
|
this.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "stream" }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
7542
7667
|
logger.debug("[NeuroLink.stream] Stored conversation turn with events", {
|
|
@@ -8075,8 +8200,10 @@ Current user's request: ${currentInput}`;
|
|
|
8075
8200
|
}
|
|
8076
8201
|
const memStoreStart = Date.now();
|
|
8077
8202
|
try {
|
|
8203
|
+
const fallbackSessionId = sessionId || options.context?.sessionId;
|
|
8204
|
+
const pendingSkillMessages = self.drainPendingSkillMessages(fallbackSessionId);
|
|
8078
8205
|
await self.conversationMemory.storeConversationTurn({
|
|
8079
|
-
sessionId:
|
|
8206
|
+
sessionId: fallbackSessionId,
|
|
8080
8207
|
userId: userId || options.context?.userId,
|
|
8081
8208
|
userMessage: originalPrompt ?? "",
|
|
8082
8209
|
aiResponse: fallbackAccumulatedContent,
|
|
@@ -8086,6 +8213,9 @@ Current user's request: ${currentInput}`;
|
|
|
8086
8213
|
requestId: enhancedOptions?.context?.requestId ||
|
|
8087
8214
|
options.context
|
|
8088
8215
|
?.requestId,
|
|
8216
|
+
...(pendingSkillMessages.length > 0
|
|
8217
|
+
? { skillMessages: pendingSkillMessages }
|
|
8218
|
+
: {}),
|
|
8089
8219
|
});
|
|
8090
8220
|
self.recordMemorySpan("memory.store", { "memory.operation": "store", "memory.path": "fallback-stream" }, Date.now() - memStoreStart, SpanStatus.OK);
|
|
8091
8221
|
}
|
|
@@ -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;
|