@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.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +410 -375
- package/dist/cli/factories/commandFactory.d.ts +17 -0
- package/dist/cli/factories/commandFactory.js +254 -0
- package/dist/cli/parser.js +2 -0
- package/dist/cli/utils/skillsFlags.d.ts +10 -0
- package/dist/cli/utils/skillsFlags.js +22 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/neurolink.d.ts +28 -0
- package/dist/lib/neurolink.js +117 -0
- package/dist/lib/server/routes/agentRoutes.js +156 -1
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +39 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +300 -41
- package/dist/lib/server/utils/validation.d.ts +32 -0
- package/dist/lib/server/utils/validation.js +18 -0
- package/dist/lib/session/globalSessionState.d.ts +10 -1
- package/dist/lib/session/globalSessionState.js +18 -0
- package/dist/lib/skills/skillMatcher.d.ts +20 -0
- package/dist/lib/skills/skillMatcher.js +80 -0
- package/dist/lib/skills/skillStoreRedis.d.ts +22 -0
- package/dist/lib/skills/skillStoreRedis.js +98 -0
- package/dist/lib/skills/skillStoreS3.d.ts +41 -0
- package/dist/lib/skills/skillStoreS3.js +233 -0
- package/dist/lib/skills/skillStores.d.ts +44 -0
- package/dist/lib/skills/skillStores.js +252 -0
- package/dist/lib/skills/skillTools.d.ts +19 -0
- package/dist/lib/skills/skillTools.js +340 -0
- package/dist/lib/skills/skillsManager.d.ts +54 -0
- package/dist/lib/skills/skillsManager.js +220 -0
- package/dist/lib/types/config.d.ts +10 -0
- package/dist/lib/types/generate.d.ts +8 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/proxy.d.ts +30 -2
- package/dist/lib/types/skills.d.ts +296 -0
- package/dist/lib/types/skills.js +17 -0
- package/dist/lib/types/stream.d.ts +8 -0
- package/dist/neurolink.d.ts +28 -0
- package/dist/neurolink.js +117 -0
- package/dist/server/routes/agentRoutes.js +156 -1
- package/dist/server/routes/claudeProxyRoutes.d.ts +39 -1
- package/dist/server/routes/claudeProxyRoutes.js +300 -41
- package/dist/server/utils/validation.d.ts +32 -0
- package/dist/server/utils/validation.js +18 -0
- package/dist/session/globalSessionState.d.ts +10 -1
- package/dist/session/globalSessionState.js +18 -0
- package/dist/skills/skillMatcher.d.ts +20 -0
- package/dist/skills/skillMatcher.js +79 -0
- package/dist/skills/skillStoreRedis.d.ts +22 -0
- package/dist/skills/skillStoreRedis.js +97 -0
- package/dist/skills/skillStoreS3.d.ts +41 -0
- package/dist/skills/skillStoreS3.js +232 -0
- package/dist/skills/skillStores.d.ts +44 -0
- package/dist/skills/skillStores.js +251 -0
- package/dist/skills/skillTools.d.ts +19 -0
- package/dist/skills/skillTools.js +339 -0
- package/dist/skills/skillsManager.d.ts +54 -0
- package/dist/skills/skillsManager.js +219 -0
- package/dist/types/config.d.ts +10 -0
- package/dist/types/generate.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/proxy.d.ts +30 -2
- package/dist/types/skills.d.ts +296 -0
- package/dist/types/skills.js +16 -0
- package/dist/types/stream.d.ts +8 -0
- package/package.json +2 -1
package/dist/neurolink.d.ts
CHANGED
|
@@ -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;
|