@dudousxd/nestjs-agent-core 0.11.0 → 0.12.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/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  DefaultRolesPolicy: () => DefaultRolesPolicy,
51
51
  QuotaExceededError: () => QuotaExceededError,
52
52
  THREAD_DETAIL_CONTENT_CHARS: () => THREAD_DETAIL_CONTENT_CHARS,
53
+ ToolDisabledError: () => ToolDisabledError,
53
54
  ToolForbiddenError: () => ToolForbiddenError,
54
55
  ToolInputInvalidError: () => ToolInputInvalidError,
55
56
  ToolNotFoundError: () => ToolNotFoundError,
@@ -59,12 +60,16 @@ __export(index_exports, {
59
60
  bucketByModel: () => bucketByModel,
60
61
  bucketByThread: () => bucketByThread,
61
62
  bucketUsageTrend: () => bucketUsageTrend,
63
+ canActorUseTool: () => canActorUseTool,
62
64
  dayBoundsUtc: () => dayBoundsUtc,
63
65
  encodeStreamEvent: () => encodeStreamEvent,
64
66
  estimateCost: () => estimateCost,
65
67
  filterToolsByAllowList: () => filterToolsByAllowList,
68
+ filterToolsByCanUse: () => filterToolsByCanUse,
69
+ filterToolsByEnabled: () => filterToolsByEnabled,
66
70
  filterToolsByRole: () => filterToolsByRole,
67
71
  invokeWithTransientRetry: () => invokeWithTransientRetry,
72
+ isToolEnabled: () => isToolEnabled,
68
73
  isTransientToolError: () => isTransientToolError,
69
74
  publishAgentDelegated: () => publishAgentDelegated,
70
75
  publishAgentMessage: () => publishAgentMessage,
@@ -308,6 +313,34 @@ function dayBoundsUtc(range) {
308
313
  __name(dayBoundsUtc, "dayBoundsUtc");
309
314
 
310
315
  // src/tool-filters.ts
316
+ async function isToolEnabled(spec, handler) {
317
+ const declared = typeof spec.enabled === "function" ? await spec.enabled() : spec.enabled ?? true;
318
+ if (!declared) {
319
+ return false;
320
+ }
321
+ return handler?.isEnabled === void 0 ? true : await handler.isEnabled();
322
+ }
323
+ __name(isToolEnabled, "isToolEnabled");
324
+ async function filterToolsByEnabled(entries) {
325
+ const checked = await Promise.all(entries.map(async (entry) => ({
326
+ entry,
327
+ enabled: await isToolEnabled(entry.spec, entry.handler)
328
+ })));
329
+ return checked.filter((row) => row.enabled).map((row) => row.entry);
330
+ }
331
+ __name(filterToolsByEnabled, "filterToolsByEnabled");
332
+ async function canActorUseTool(actor, handler) {
333
+ return handler?.canUse === void 0 ? true : await handler.canUse(actor);
334
+ }
335
+ __name(canActorUseTool, "canActorUseTool");
336
+ async function filterToolsByCanUse(entries, actor) {
337
+ const checked = await Promise.all(entries.map(async (entry) => ({
338
+ entry,
339
+ allowed: await canActorUseTool(actor, entry.handler)
340
+ })));
341
+ return checked.filter((row) => row.allowed).map((row) => row.entry);
342
+ }
343
+ __name(filterToolsByCanUse, "filterToolsByCanUse");
311
344
  async function filterToolsByRole(tools, actor, policy) {
312
345
  const checked = await Promise.all(tools.map(async (tool) => ({
313
346
  tool,
@@ -358,6 +391,16 @@ var ToolForbiddenError = class extends Error {
358
391
  this.name = "ToolForbiddenError";
359
392
  }
360
393
  };
394
+ var ToolDisabledError = class extends Error {
395
+ static {
396
+ __name(this, "ToolDisabledError");
397
+ }
398
+ toolName;
399
+ constructor(toolName) {
400
+ super(`Tool "${toolName}" is disabled in this deployment`), this.toolName = toolName;
401
+ this.name = "ToolDisabledError";
402
+ }
403
+ };
361
404
  var ToolNotFoundError = class extends Error {
362
405
  static {
363
406
  __name(this, "ToolNotFoundError");
@@ -401,10 +444,21 @@ var ToolRegistry = class {
401
444
  ...this.entries.values()
402
445
  ].map((entry) => entry.spec);
403
446
  }
404
- /** The tools to offer the model for this actor+agent, after the two filter layers. */
447
+ /**
448
+ * The tools to offer the model for this actor+agent, after the four filter layers: what this
449
+ * deployment has enabled, what this actor's role allows, what each tool's own `canUse` allows
450
+ * this actor, and finally what this agent pinned.
451
+ *
452
+ * Every layer only ever removes tools, so no arrangement of them can widen what a turn reaches.
453
+ */
405
454
  async definitionsFor(actor, policy, allowedTools) {
406
- const roleScoped = await filterToolsByRole(this.allSpecs(), actor, policy);
407
- const allowScoped = filterToolsByAllowList(roleScoped, allowedTools);
455
+ const live = await filterToolsByEnabled([
456
+ ...this.entries.values()
457
+ ]);
458
+ const allowedByRole = new Set((await filterToolsByRole(live.map((entry) => entry.spec), actor, policy)).map((spec) => spec.name));
459
+ const roleScoped = live.filter((entry) => allowedByRole.has(entry.spec.name));
460
+ const actorScoped = await filterToolsByCanUse(roleScoped, actor);
461
+ const allowScoped = filterToolsByAllowList(actorScoped.map((entry) => entry.spec), allowedTools);
408
462
  return allowScoped.map((spec) => ({
409
463
  name: spec.name,
410
464
  kind: spec.kind,
@@ -412,15 +466,25 @@ var ToolRegistry = class {
412
466
  inputSchema: spec.inputSchema
413
467
  }));
414
468
  }
415
- /** Run a tool. Re-checks the role (defense-in-depth) and re-parses the input via Zod. */
469
+ /**
470
+ * Run a tool. Re-checks that the tool is enabled and that the role allows it (defense-in-depth —
471
+ * a call can reach here from a replayed durable step or an approval granted before the flag
472
+ * moved, neither of which went through `definitionsFor` again) and re-parses the input via Zod.
473
+ */
416
474
  async invoke(name, input, ctx, policy) {
417
475
  const entry = this.entries.get(name);
418
476
  if (entry === void 0) {
419
477
  throw new ToolNotFoundError(name);
420
478
  }
479
+ if (!await isToolEnabled(entry.spec, entry.handler)) {
480
+ throw new ToolDisabledError(name);
481
+ }
421
482
  if (!await policy.can(ctx.actor, entry.spec)) {
422
483
  throw new ToolForbiddenError(name);
423
484
  }
485
+ if (!await canActorUseTool(ctx.actor, entry.handler)) {
486
+ throw new ToolForbiddenError(name);
487
+ }
424
488
  const validation = await entry.spec.inputSchema["~standard"].validate(input);
425
489
  if (validation.issues !== void 0) {
426
490
  throw new ToolInputInvalidError(name, validation.issues);
@@ -1299,6 +1363,7 @@ __name(runAgentLoop, "runAgentLoop");
1299
1363
  DefaultRolesPolicy,
1300
1364
  QuotaExceededError,
1301
1365
  THREAD_DETAIL_CONTENT_CHARS,
1366
+ ToolDisabledError,
1302
1367
  ToolForbiddenError,
1303
1368
  ToolInputInvalidError,
1304
1369
  ToolNotFoundError,
@@ -1308,12 +1373,16 @@ __name(runAgentLoop, "runAgentLoop");
1308
1373
  bucketByModel,
1309
1374
  bucketByThread,
1310
1375
  bucketUsageTrend,
1376
+ canActorUseTool,
1311
1377
  dayBoundsUtc,
1312
1378
  encodeStreamEvent,
1313
1379
  estimateCost,
1314
1380
  filterToolsByAllowList,
1381
+ filterToolsByCanUse,
1382
+ filterToolsByEnabled,
1315
1383
  filterToolsByRole,
1316
1384
  invokeWithTransientRetry,
1385
+ isToolEnabled,
1317
1386
  isTransientToolError,
1318
1387
  publishAgentDelegated,
1319
1388
  publishAgentMessage,