@dudousxd/nestjs-agent 1.21.0 → 1.22.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.
@@ -1,7 +1,7 @@
1
1
  import { AgentStore, AgentRunInput, AgentLoopResult, LlmStepEnvelope, BufferedModelTurnResult, ToolStepEnvelope, AgentRunner, TokenStreamSink, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
2
2
  import { WorkflowService } from '@dudousxd/nestjs-durable';
3
3
  import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
4
- import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-CGsR40Ct.cjs';
4
+ import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-Dx9OsjcF.cjs';
5
5
  import { DynamicModule } from '@nestjs/common';
6
6
  import '@dudousxd/nestjs-agent-core/genui';
7
7
  import '@standard-schema/spec';
@@ -1,7 +1,7 @@
1
1
  import { AgentStore, AgentRunInput, AgentLoopResult, LlmStepEnvelope, BufferedModelTurnResult, ToolStepEnvelope, AgentRunner, TokenStreamSink, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
2
2
  import { WorkflowService } from '@dudousxd/nestjs-durable';
3
3
  import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
4
- import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-XG9RQCjU.js';
4
+ import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-CTyX_yNr.js';
5
5
  import { DynamicModule } from '@nestjs/common';
6
6
  import '@dudousxd/nestjs-agent-core/genui';
7
7
  import '@standard-schema/spec';
@@ -1186,6 +1186,7 @@ import { defineCatalog, genuiTools } from "@dudousxd/nestjs-agent-core/genui";
1186
1186
  import { Inject as Inject4, Module as Module2 } from "@nestjs/common";
1187
1187
 
1188
1188
  // src/functional-tool.ts
1189
+ import { createFunctionalTool } from "@dudousxd/nestjs-agent-core";
1189
1190
  var AGENT_TOOL_BRAND = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:functional-tool");
1190
1191
  function brand(tool) {
1191
1192
  return {
@@ -1721,7 +1722,7 @@ import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTO
1721
1722
  import { BadRequestException as BadRequestException2, ConflictException as ConflictException2, ForbiddenException as ForbiddenException2, GoneException, HttpException, HttpStatus, Inject as Inject8, Injectable as Injectable7, NotFoundException as NotFoundException2, NotImplementedException as NotImplementedException3, Optional as Optional5 } from "@nestjs/common";
1722
1723
 
1723
1724
  // src/proposals/action-proposal.service.ts
1724
- import { AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_STORE as AGENT_STORE4, mayDecideApproval, parseTextActionProposalCommand, resolveTextActionProposalDecision } from "@dudousxd/nestjs-agent-core";
1725
+ import { AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_STORE as AGENT_STORE4, DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES, DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY, mayDecideApproval, parseTextActionProposalCommand, resolveTextActionProposalDecision, textActionProposalReply, toActionProposalMutationView, toActionProposalView } from "@dudousxd/nestjs-agent-core";
1725
1726
  import { ForbiddenException, Inject as Inject6, Injectable as Injectable5, NotFoundException, NotImplementedException } from "@nestjs/common";
1726
1727
  function _ts_decorate7(decorators, target, key, desc) {
1727
1728
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -1758,7 +1759,19 @@ var ActionProposalService = class {
1758
1759
  constructor(store, options) {
1759
1760
  this.store = store;
1760
1761
  this.options = options;
1762
+ const text = options.actionProposalText;
1763
+ this.vocabulary = {
1764
+ ...DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY,
1765
+ ...text?.vocabulary
1766
+ };
1767
+ this.replies = {
1768
+ ...DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES,
1769
+ ...text?.replies
1770
+ };
1761
1771
  }
1772
+ /** The words a text decision is made of, and what the agent answers — English unless configured. */
1773
+ vocabulary;
1774
+ replies;
1762
1775
  capability() {
1763
1776
  const store = this.store;
1764
1777
  if (typeof store.getThreadActionProposalScope !== "function" || typeof store.listActionProposals !== "function") throw new NotImplementedException("Action proposals are unavailable");
@@ -1787,7 +1800,8 @@ var ActionProposalService = class {
1787
1800
  for (const row of rows) if (scope.actorRef === actor.id || await this.mayDecide(actor, row)) visible.push(row);
1788
1801
  const last = rows.at(-1);
1789
1802
  return {
1790
- items: visible,
1803
+ // The public view: a row carries the worker's lease token and the tool's idempotency key.
1804
+ items: visible.map(toActionProposalView),
1791
1805
  ...rows.length === 1e3 && last ? {
1792
1806
  next: {
1793
1807
  createdAt: last.createdAt,
@@ -1804,7 +1818,7 @@ var ActionProposalService = class {
1804
1818
  const proposal = await this.capability().getActionProposal(scope, proposalId);
1805
1819
  if (!proposal) throw new NotFoundException("Proposal not found");
1806
1820
  if (!await this.mayDecide(actor, proposal)) throw new ForbiddenException("May not decide this proposal");
1807
- return this.capability().decideActionProposal(scope, proposalId, {
1821
+ const result = await this.capability().decideActionProposal(scope, proposalId, {
1808
1822
  decision: command.decision,
1809
1823
  actorRef: actor.id,
1810
1824
  via: command.via ?? "web",
@@ -1815,9 +1829,10 @@ var ActionProposalService = class {
1815
1829
  reason: command.reason
1816
1830
  } : {}
1817
1831
  });
1832
+ return toActionProposalMutationView(result);
1818
1833
  }
1819
1834
  async handleTextDecision(threadId, actor, text) {
1820
- const command = parseTextActionProposalCommand(text);
1835
+ const command = parseTextActionProposalCommand(text, this.vocabulary);
1821
1836
  if (command.status === "unmatched") return command;
1822
1837
  if (command.proposalId !== void 0) {
1823
1838
  const result2 = await this.decide(threadId, command.proposalId, actor, {
@@ -1825,7 +1840,7 @@ var ActionProposalService = class {
1825
1840
  remember: command.remember,
1826
1841
  via: "text"
1827
1842
  });
1828
- return this.textReceipt(threadId, result2);
1843
+ return this.textReceipt(threadId, result2, command.decision);
1829
1844
  }
1830
1845
  const candidates = [];
1831
1846
  const scope = await this.scope(threadId, actor);
@@ -1834,30 +1849,38 @@ var ActionProposalService = class {
1834
1849
  limit: 1e3
1835
1850
  });
1836
1851
  for (const proposal of pending) if (await this.mayDecide(actor, proposal)) candidates.push(proposal);
1837
- const resolution = resolveTextActionProposalDecision(text, candidates);
1852
+ const resolution = resolveTextActionProposalDecision(text, candidates, this.vocabulary);
1838
1853
  if (resolution.status === "unmatched") return resolution;
1839
- if (resolution.status === "ambiguous" || resolution.status === "decision" && pending.length === 1e3 && !text.includes("#")) return {
1840
- threadId,
1841
- proposalDecision: {
1842
- status: "ambiguous",
1843
- proposalIds: candidates.map(({ id }) => id)
1844
- },
1845
- text: `Choose the action to decide: ${candidates.map(({ id }) => id).map((id) => `#${id}`).join(", ")}`
1846
- };
1854
+ const saturated = pending.length === 1e3 && !text.includes("#");
1855
+ if (resolution.status === "ambiguous" || saturated) {
1856
+ const proposalIds = candidates.map(({ id }) => id);
1857
+ return {
1858
+ threadId,
1859
+ proposalDecision: {
1860
+ status: "ambiguous",
1861
+ proposalIds
1862
+ },
1863
+ text: saturated ? this.replies.tooMany : this.replies.ambiguous(proposalIds)
1864
+ };
1865
+ }
1847
1866
  const result = await this.decide(threadId, resolution.proposalId, actor, {
1848
1867
  decision: resolution.decision,
1849
1868
  remember: resolution.remember,
1850
1869
  via: "text"
1851
1870
  });
1852
- return this.textReceipt(threadId, result);
1871
+ return this.textReceipt(threadId, result, resolution.decision);
1853
1872
  }
1854
- textReceipt(threadId, result) {
1873
+ textReceipt(threadId, result, decision) {
1855
1874
  return {
1856
1875
  threadId,
1857
1876
  proposalDecision: result,
1858
- text: result.proposal?.decision === "approved" ? "Action approved and queued for execution." : `Action ${result.proposal?.decision ?? result.status}; no execution was performed by this decision.`
1877
+ text: this.reply(result, decision)
1859
1878
  };
1860
1879
  }
1880
+ /** The configured reply to a decision the store answered with `result`. */
1881
+ reply(result, decision) {
1882
+ return textActionProposalReply(result, decision, this.replies);
1883
+ }
1861
1884
  };
1862
1885
  ActionProposalService = _ts_decorate7([
1863
1886
  Injectable5(),
@@ -1874,6 +1897,33 @@ ActionProposalService = _ts_decorate7([
1874
1897
  import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_OPTIONS as AGENT_OPTIONS3, AGENT_QUOTA_PROVIDER, AGENT_SINK as AGENT_SINK3, AGENT_STORE as AGENT_STORE5, encodeStreamEvent as encodeStreamEvent4, isChatQueueStore, queuedMessageView } from "@dudousxd/nestjs-agent-core";
1875
1898
  import { Inject as Inject7, Injectable as Injectable6, Logger as Logger3, NotImplementedException as NotImplementedException2, Optional as Optional4 } from "@nestjs/common";
1876
1899
 
1900
+ // src/quota-exceeded.ts
1901
+ import { publishAgentQuotaExceeded } from "@dudousxd/nestjs-agent-core";
1902
+ function publishQuotaBlocked(actor, report, blocked) {
1903
+ const window = report.windows.find((candidate) => candidate.period === blocked.period);
1904
+ const payload = {
1905
+ actorId: actor.id,
1906
+ period: blocked.period,
1907
+ ...blocked.reason !== void 0 ? {
1908
+ reason: blocked.reason
1909
+ } : {},
1910
+ ...window?.usedTokens !== void 0 ? {
1911
+ usedTokens: window.usedTokens
1912
+ } : {},
1913
+ ...window?.limitTokens !== void 0 ? {
1914
+ limitTokens: window.limitTokens
1915
+ } : {},
1916
+ ...window !== void 0 ? {
1917
+ usedUsd: window.usedUsd
1918
+ } : {},
1919
+ ...window?.limitUsd !== void 0 ? {
1920
+ limitUsd: window.limitUsd
1921
+ } : {}
1922
+ };
1923
+ publishAgentQuotaExceeded(payload);
1924
+ }
1925
+ __name(publishQuotaBlocked, "publishQuotaBlocked");
1926
+
1877
1927
  // src/thread-persona.ts
1878
1928
  async function threadPersona(store, threadId) {
1879
1929
  const projecting = store;
@@ -2217,12 +2267,14 @@ var ChatQueueService2 = class _ChatQueueService {
2217
2267
  return null;
2218
2268
  }
2219
2269
  try {
2220
- const { blocked } = await this.quotaProvider.report({
2270
+ const report = await this.quotaProvider.report({
2221
2271
  actor
2222
2272
  });
2273
+ const { blocked } = report;
2223
2274
  if (blocked === void 0) {
2224
2275
  return null;
2225
2276
  }
2277
+ publishQuotaBlocked(actor, report, blocked);
2226
2278
  return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
2227
2279
  } catch {
2228
2280
  return null;
@@ -2424,6 +2476,21 @@ var AgentService = class {
2424
2476
  this.queue = queue;
2425
2477
  this.proposals = proposals;
2426
2478
  }
2479
+ /**
2480
+ * Decide one independent proposal through the scoped proposal service — what the native
2481
+ * `action-proposals/:id/approve|reject` routes call — and the reply to show for it. `404` when
2482
+ * proposals are not on, `403`/`404` when this actor may not decide it.
2483
+ */
2484
+ async decideActionProposal(actor, threadId, proposalId, command) {
2485
+ if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
2486
+ throw new NotFoundException2("Proposal not found");
2487
+ }
2488
+ const result = await this.proposals.decide(threadId, proposalId, actor, command);
2489
+ return {
2490
+ proposalDecision: result,
2491
+ text: this.proposals.reply(result, command.decision)
2492
+ };
2493
+ }
2427
2494
  async handleTextDecision(threadId, actor, text) {
2428
2495
  if (this.options?.actionApprovalMode !== "independent" || !this.proposals) return {
2429
2496
  status: "unmatched"
@@ -2471,10 +2538,12 @@ var AgentService = class {
2471
2538
  if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
2472
2539
  return;
2473
2540
  }
2474
- const { blocked } = await this.quotaProvider.report({
2541
+ const report = await this.quotaProvider.report({
2475
2542
  actor
2476
2543
  });
2544
+ const { blocked } = report;
2477
2545
  if (blocked !== void 0) {
2546
+ publishQuotaBlocked(actor, report, blocked);
2478
2547
  throw new HttpException({
2479
2548
  statusCode: HttpStatus.TOO_MANY_REQUESTS,
2480
2549
  code: "quota_exceeded",
@@ -5727,6 +5796,9 @@ var AiToolDiscoveryService = class _AiToolDiscoveryService {
5727
5796
  } : {}
5728
5797
  }, {
5729
5798
  execute: /* @__PURE__ */ __name((input, ctx) => instance.execute(input, ctx), "execute"),
5799
+ ...typeof handler.present === "function" ? {
5800
+ present: /* @__PURE__ */ __name((output, ctx) => instance.present(output, ctx), "present")
5801
+ } : {},
5730
5802
  ...typeof handler.preflight === "function" ? {
5731
5803
  preflight: /* @__PURE__ */ __name((input, ctx, options) => instance.preflight(input, ctx, options), "preflight")
5732
5804
  } : {},
@@ -6763,15 +6835,15 @@ function _ts_decorate30(decorators, target, key, desc) {
6763
6835
  __name(_ts_decorate30, "_ts_decorate");
6764
6836
  var DEFAULT_PATH = "agent";
6765
6837
  var logger = new Logger9("AgentModule");
6766
- function externalStoreWrapper(modules) {
6838
+ function externalWrapper(modules, token) {
6767
6839
  for (const module of modules.values()) {
6768
6840
  if (module.metatype === AgentModule) continue;
6769
- const wrapper = module.providers.get(AGENT_STORE9);
6841
+ const wrapper = module.providers.get(token);
6770
6842
  if (wrapper !== void 0) return wrapper;
6771
6843
  }
6772
6844
  return void 0;
6773
6845
  }
6774
- __name(externalStoreWrapper, "externalStoreWrapper");
6846
+ __name(externalWrapper, "externalWrapper");
6775
6847
  function forwardingStore(wrapper) {
6776
6848
  const target = /* @__PURE__ */ __name(() => {
6777
6849
  const instance = wrapper.instance;
@@ -6794,12 +6866,36 @@ function forwardingStore(wrapper) {
6794
6866
  __name(forwardingStore, "forwardingStore");
6795
6867
  function resolveStore(options, modules) {
6796
6868
  if (options.store !== void 0) return options.store;
6797
- const external = externalStoreWrapper(modules);
6869
+ const external = externalWrapper(modules, AGENT_STORE9);
6798
6870
  if (external !== void 0) return forwardingStore(external);
6799
6871
  logger.warn("No `store` configured and no store module imported \u2014 using the in-memory store. Threads are lost on restart and not shared between processes: not for production. Pass `store`, or import a store module (e.g. MikroOrmAgentStoreModule.forFeature()).");
6800
6872
  return new InMemoryAgentStore();
6801
6873
  }
6802
6874
  __name(resolveStore, "resolveStore");
6875
+ function resolveRolesPolicy(options, modules) {
6876
+ const external = externalWrapper(modules, AGENT_ROLES_POLICY2);
6877
+ if (options.rolesPolicy !== void 0) {
6878
+ if (external !== void 0) {
6879
+ logger.warn("Both a `rolesPolicy` option and a module binding AGENT_ROLES_POLICY (e.g. AgentAuthzModule) are configured \u2014 the `rolesPolicy` option wins.");
6880
+ }
6881
+ return options.rolesPolicy;
6882
+ }
6883
+ if (external !== void 0) return forwardingPolicy(external);
6884
+ return options.emptyRoles === "deny" ? new ClosedRolesPolicy(options.defaultRoles) : new DefaultRolesPolicy(options.defaultRoles);
6885
+ }
6886
+ __name(resolveRolesPolicy, "resolveRolesPolicy");
6887
+ function forwardingPolicy(wrapper) {
6888
+ return {
6889
+ can(actor, tool) {
6890
+ const policy = wrapper.instance;
6891
+ if (policy === void 0 || policy === null) {
6892
+ throw new Error("AgentModule: the AGENT_ROLES_POLICY bound by another module is not ready yet");
6893
+ }
6894
+ return policy.can(actor, tool);
6895
+ }
6896
+ };
6897
+ }
6898
+ __name(forwardingPolicy, "forwardingPolicy");
6803
6899
  function isQuotaProvider(quota) {
6804
6900
  return quota !== void 0 && typeof quota.report === "function";
6805
6901
  }
@@ -6861,9 +6957,10 @@ function sharedProviders(durable) {
6861
6957
  },
6862
6958
  {
6863
6959
  provide: AGENT_ROLES_POLICY2,
6864
- useFactory: /* @__PURE__ */ __name((options) => options.rolesPolicy ?? (options.emptyRoles === "deny" ? new ClosedRolesPolicy(options.defaultRoles) : new DefaultRolesPolicy(options.defaultRoles)), "useFactory"),
6960
+ useFactory: /* @__PURE__ */ __name((options, modules) => resolveRolesPolicy(options, modules), "useFactory"),
6865
6961
  inject: [
6866
- AGENT_OPTIONS9
6962
+ AGENT_OPTIONS9,
6963
+ ModulesContainer
6867
6964
  ]
6868
6965
  },
6869
6966
  {