@dudousxd/nestjs-agent 1.21.0 → 1.23.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.js CHANGED
@@ -17,6 +17,7 @@ import { defineCatalog, genuiTools } from "@dudousxd/nestjs-agent-core/genui";
17
17
  import { Inject, Module } from "@nestjs/common";
18
18
 
19
19
  // src/functional-tool.ts
20
+ import { createFunctionalTool } from "@dudousxd/nestjs-agent-core";
20
21
  var AGENT_TOOL_BRAND = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:functional-tool");
21
22
  function brand(tool) {
22
23
  return {
@@ -53,6 +54,7 @@ function provideAgentTools(factory, inject) {
53
54
  };
54
55
  }
55
56
  __name(provideAgentTools, "provideAgentTools");
57
+ var defineTool = createFunctionalTool;
56
58
 
57
59
  // src/genui/agent-genui.module.ts
58
60
  function _ts_decorate(decorators, target, key, desc) {
@@ -589,7 +591,7 @@ function childSinkWriter(writer) {
589
591
  __name(childSinkWriter, "childSinkWriter");
590
592
 
591
593
  // src/proposals/action-proposal.service.ts
592
- import { AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_STORE as AGENT_STORE2, mayDecideApproval, parseTextActionProposalCommand, resolveTextActionProposalDecision } from "@dudousxd/nestjs-agent-core";
594
+ import { AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_STORE as AGENT_STORE2, DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES, DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY, mayDecideApproval, parseTextActionProposalCommand, resolveTextActionProposalDecision, textActionProposalReply, toActionProposalMutationView, toActionProposalView } from "@dudousxd/nestjs-agent-core";
593
595
  import { ForbiddenException, Inject as Inject3, Injectable as Injectable2, NotFoundException, NotImplementedException } from "@nestjs/common";
594
596
  function _ts_decorate3(decorators, target, key, desc) {
595
597
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -626,7 +628,19 @@ var ActionProposalService = class {
626
628
  constructor(store, options) {
627
629
  this.store = store;
628
630
  this.options = options;
631
+ const text = options.actionProposalText;
632
+ this.vocabulary = {
633
+ ...DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY,
634
+ ...text?.vocabulary
635
+ };
636
+ this.replies = {
637
+ ...DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES,
638
+ ...text?.replies
639
+ };
629
640
  }
641
+ /** The words a text decision is made of, and what the agent answers — English unless configured. */
642
+ vocabulary;
643
+ replies;
630
644
  capability() {
631
645
  const store = this.store;
632
646
  if (typeof store.getThreadActionProposalScope !== "function" || typeof store.listActionProposals !== "function") throw new NotImplementedException("Action proposals are unavailable");
@@ -655,7 +669,8 @@ var ActionProposalService = class {
655
669
  for (const row of rows) if (scope.actorRef === actor.id || await this.mayDecide(actor, row)) visible.push(row);
656
670
  const last = rows.at(-1);
657
671
  return {
658
- items: visible,
672
+ // The public view: a row carries the worker's lease token and the tool's idempotency key.
673
+ items: visible.map(toActionProposalView),
659
674
  ...rows.length === 1e3 && last ? {
660
675
  next: {
661
676
  createdAt: last.createdAt,
@@ -672,7 +687,7 @@ var ActionProposalService = class {
672
687
  const proposal = await this.capability().getActionProposal(scope, proposalId);
673
688
  if (!proposal) throw new NotFoundException("Proposal not found");
674
689
  if (!await this.mayDecide(actor, proposal)) throw new ForbiddenException("May not decide this proposal");
675
- return this.capability().decideActionProposal(scope, proposalId, {
690
+ const result = await this.capability().decideActionProposal(scope, proposalId, {
676
691
  decision: command.decision,
677
692
  actorRef: actor.id,
678
693
  via: command.via ?? "web",
@@ -683,9 +698,10 @@ var ActionProposalService = class {
683
698
  reason: command.reason
684
699
  } : {}
685
700
  });
701
+ return toActionProposalMutationView(result);
686
702
  }
687
703
  async handleTextDecision(threadId, actor, text) {
688
- const command = parseTextActionProposalCommand(text);
704
+ const command = parseTextActionProposalCommand(text, this.vocabulary);
689
705
  if (command.status === "unmatched") return command;
690
706
  if (command.proposalId !== void 0) {
691
707
  const result2 = await this.decide(threadId, command.proposalId, actor, {
@@ -693,7 +709,7 @@ var ActionProposalService = class {
693
709
  remember: command.remember,
694
710
  via: "text"
695
711
  });
696
- return this.textReceipt(threadId, result2);
712
+ return this.textReceipt(threadId, result2, command.decision);
697
713
  }
698
714
  const candidates = [];
699
715
  const scope = await this.scope(threadId, actor);
@@ -702,30 +718,38 @@ var ActionProposalService = class {
702
718
  limit: 1e3
703
719
  });
704
720
  for (const proposal of pending) if (await this.mayDecide(actor, proposal)) candidates.push(proposal);
705
- const resolution = resolveTextActionProposalDecision(text, candidates);
721
+ const resolution = resolveTextActionProposalDecision(text, candidates, this.vocabulary);
706
722
  if (resolution.status === "unmatched") return resolution;
707
- if (resolution.status === "ambiguous" || resolution.status === "decision" && pending.length === 1e3 && !text.includes("#")) return {
708
- threadId,
709
- proposalDecision: {
710
- status: "ambiguous",
711
- proposalIds: candidates.map(({ id }) => id)
712
- },
713
- text: `Choose the action to decide: ${candidates.map(({ id }) => id).map((id) => `#${id}`).join(", ")}`
714
- };
723
+ const saturated = pending.length === 1e3 && !text.includes("#");
724
+ if (resolution.status === "ambiguous" || saturated) {
725
+ const proposalIds = candidates.map(({ id }) => id);
726
+ return {
727
+ threadId,
728
+ proposalDecision: {
729
+ status: "ambiguous",
730
+ proposalIds
731
+ },
732
+ text: saturated ? this.replies.tooMany : this.replies.ambiguous(proposalIds)
733
+ };
734
+ }
715
735
  const result = await this.decide(threadId, resolution.proposalId, actor, {
716
736
  decision: resolution.decision,
717
737
  remember: resolution.remember,
718
738
  via: "text"
719
739
  });
720
- return this.textReceipt(threadId, result);
740
+ return this.textReceipt(threadId, result, resolution.decision);
721
741
  }
722
- textReceipt(threadId, result) {
742
+ textReceipt(threadId, result, decision) {
723
743
  return {
724
744
  threadId,
725
745
  proposalDecision: result,
726
- text: result.proposal?.decision === "approved" ? "Action approved and queued for execution." : `Action ${result.proposal?.decision ?? result.status}; no execution was performed by this decision.`
746
+ text: this.reply(result, decision)
727
747
  };
728
748
  }
749
+ /** The configured reply to a decision the store answered with `result`. */
750
+ reply(result, decision) {
751
+ return textActionProposalReply(result, decision, this.replies);
752
+ }
729
753
  };
730
754
  ActionProposalService = _ts_decorate3([
731
755
  Injectable2(),
@@ -742,6 +766,33 @@ ActionProposalService = _ts_decorate3([
742
766
  import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY, AGENT_OPTIONS as AGENT_OPTIONS3, AGENT_QUOTA_PROVIDER, AGENT_SINK as AGENT_SINK2, AGENT_STORE as AGENT_STORE3, encodeStreamEvent, isChatQueueStore, queuedMessageView } from "@dudousxd/nestjs-agent-core";
743
767
  import { Inject as Inject4, Injectable as Injectable3, Logger, NotImplementedException as NotImplementedException2, Optional as Optional2 } from "@nestjs/common";
744
768
 
769
+ // src/quota-exceeded.ts
770
+ import { publishAgentQuotaExceeded } from "@dudousxd/nestjs-agent-core";
771
+ function publishQuotaBlocked(actor, report, blocked) {
772
+ const window = report.windows.find((candidate) => candidate.period === blocked.period);
773
+ const payload = {
774
+ actorId: actor.id,
775
+ period: blocked.period,
776
+ ...blocked.reason !== void 0 ? {
777
+ reason: blocked.reason
778
+ } : {},
779
+ ...window?.usedTokens !== void 0 ? {
780
+ usedTokens: window.usedTokens
781
+ } : {},
782
+ ...window?.limitTokens !== void 0 ? {
783
+ limitTokens: window.limitTokens
784
+ } : {},
785
+ ...window !== void 0 ? {
786
+ usedUsd: window.usedUsd
787
+ } : {},
788
+ ...window?.limitUsd !== void 0 ? {
789
+ limitUsd: window.limitUsd
790
+ } : {}
791
+ };
792
+ publishAgentQuotaExceeded(payload);
793
+ }
794
+ __name(publishQuotaBlocked, "publishQuotaBlocked");
795
+
745
796
  // src/thread-persona.ts
746
797
  async function threadPersona(store, threadId) {
747
798
  const projecting = store;
@@ -1041,6 +1092,9 @@ var ChatQueueService = class _ChatQueueService {
1041
1092
  } : {},
1042
1093
  ...input.uiCapabilities !== void 0 ? {
1043
1094
  uiCapabilities: input.uiCapabilities
1095
+ } : {},
1096
+ ...input.hostContext !== void 0 ? {
1097
+ hostContext: input.hostContext
1044
1098
  } : {}
1045
1099
  });
1046
1100
  await store.setQueuePause(threadId, {
@@ -1085,12 +1139,14 @@ var ChatQueueService = class _ChatQueueService {
1085
1139
  return null;
1086
1140
  }
1087
1141
  try {
1088
- const { blocked } = await this.quotaProvider.report({
1142
+ const report = await this.quotaProvider.report({
1089
1143
  actor
1090
1144
  });
1145
+ const { blocked } = report;
1091
1146
  if (blocked === void 0) {
1092
1147
  return null;
1093
1148
  }
1149
+ publishQuotaBlocked(actor, report, blocked);
1094
1150
  return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
1095
1151
  } catch {
1096
1152
  return null;
@@ -1124,6 +1180,9 @@ var ChatQueueService = class _ChatQueueService {
1124
1180
  ...message.uiCapabilities !== void 0 ? {
1125
1181
  uiCapabilities: message.uiCapabilities
1126
1182
  } : {},
1183
+ ...message.hostContext !== void 0 ? {
1184
+ hostContext: message.hostContext
1185
+ } : {},
1127
1186
  ...message.model !== void 0 ? {
1128
1187
  model: message.model
1129
1188
  } : {}
@@ -1292,6 +1351,21 @@ var AgentService = class {
1292
1351
  this.queue = queue;
1293
1352
  this.proposals = proposals;
1294
1353
  }
1354
+ /**
1355
+ * Decide one independent proposal through the scoped proposal service — what the native
1356
+ * `action-proposals/:id/approve|reject` routes call — and the reply to show for it. `404` when
1357
+ * proposals are not on, `403`/`404` when this actor may not decide it.
1358
+ */
1359
+ async decideActionProposal(actor, threadId, proposalId, command) {
1360
+ if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
1361
+ throw new NotFoundException2("Proposal not found");
1362
+ }
1363
+ const result = await this.proposals.decide(threadId, proposalId, actor, command);
1364
+ return {
1365
+ proposalDecision: result,
1366
+ text: this.proposals.reply(result, command.decision)
1367
+ };
1368
+ }
1295
1369
  async handleTextDecision(threadId, actor, text) {
1296
1370
  if (this.options?.actionApprovalMode !== "independent" || !this.proposals) return {
1297
1371
  status: "unmatched"
@@ -1339,10 +1413,12 @@ var AgentService = class {
1339
1413
  if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
1340
1414
  return;
1341
1415
  }
1342
- const { blocked } = await this.quotaProvider.report({
1416
+ const report = await this.quotaProvider.report({
1343
1417
  actor
1344
1418
  });
1419
+ const { blocked } = report;
1345
1420
  if (blocked !== void 0) {
1421
+ publishQuotaBlocked(actor, report, blocked);
1346
1422
  throw new HttpException({
1347
1423
  statusCode: HttpStatus.TOO_MANY_REQUESTS,
1348
1424
  code: "quota_exceeded",
@@ -1477,6 +1553,9 @@ var AgentService = class {
1477
1553
  }
1478
1554
  const created = await this.store.createThread({
1479
1555
  actor: params.actor,
1556
+ ...agentName !== void 0 ? {
1557
+ agentName
1558
+ } : {},
1480
1559
  ...params.transient === true ? {
1481
1560
  transient: true
1482
1561
  } : {},
@@ -1489,7 +1568,7 @@ var AgentService = class {
1489
1568
  });
1490
1569
  threadId = created.id;
1491
1570
  } else {
1492
- await this.assertOwnsThread(params.actor, threadId);
1571
+ if (params.authorized !== true) await this.assertOwnsThread(params.actor, threadId);
1493
1572
  if (params.personaId !== void 0) {
1494
1573
  await this.pinThreadPersona(threadId, params.personaId);
1495
1574
  }
@@ -1515,6 +1594,9 @@ var AgentService = class {
1515
1594
  ...params.uiCapabilities !== void 0 ? {
1516
1595
  uiCapabilities: params.uiCapabilities
1517
1596
  } : {},
1597
+ ...params.hostContext !== void 0 ? {
1598
+ hostContext: params.hostContext
1599
+ } : {},
1518
1600
  ...model !== void 0 ? {
1519
1601
  model
1520
1602
  } : {}
@@ -1537,7 +1619,7 @@ var AgentService = class {
1537
1619
  });
1538
1620
  }
1539
1621
  if (live === null && mode !== "queue") {
1540
- const runId = crypto.randomUUID();
1622
+ const runId = this.runner.runIdFor?.(input) ?? crypto.randomUUID();
1541
1623
  if (await queue.queueStore().claimActiveStream(threadId, runId, stale !== null ? {
1542
1624
  replacing: stale
1543
1625
  } : {})) {
@@ -1605,6 +1687,9 @@ var AgentService = class {
1605
1687
  ...input.uiCapabilities !== void 0 ? {
1606
1688
  uiCapabilities: input.uiCapabilities
1607
1689
  } : {},
1690
+ ...input.hostContext !== void 0 ? {
1691
+ hostContext: input.hostContext
1692
+ } : {},
1608
1693
  ...interrupting !== void 0 ? {
1609
1694
  interrupt: true,
1610
1695
  at: "head"
@@ -4633,6 +4718,9 @@ var AiToolDiscoveryService = class _AiToolDiscoveryService {
4633
4718
  } : {}
4634
4719
  }, {
4635
4720
  execute: /* @__PURE__ */ __name((input, ctx) => instance.execute(input, ctx), "execute"),
4721
+ ...typeof handler.present === "function" ? {
4722
+ present: /* @__PURE__ */ __name((output, ctx) => instance.present(output, ctx), "present")
4723
+ } : {},
4636
4724
  ...typeof handler.preflight === "function" ? {
4637
4725
  preflight: /* @__PURE__ */ __name((input, ctx, options) => instance.preflight(input, ctx, options), "preflight")
4638
4726
  } : {},
@@ -5746,15 +5834,15 @@ function _ts_decorate26(decorators, target, key, desc) {
5746
5834
  __name(_ts_decorate26, "_ts_decorate");
5747
5835
  var DEFAULT_PATH = "agent";
5748
5836
  var logger = new Logger7("AgentModule");
5749
- function externalStoreWrapper(modules) {
5837
+ function externalWrapper(modules, token) {
5750
5838
  for (const module of modules.values()) {
5751
5839
  if (module.metatype === AgentModule) continue;
5752
- const wrapper = module.providers.get(AGENT_STORE7);
5840
+ const wrapper = module.providers.get(token);
5753
5841
  if (wrapper !== void 0) return wrapper;
5754
5842
  }
5755
5843
  return void 0;
5756
5844
  }
5757
- __name(externalStoreWrapper, "externalStoreWrapper");
5845
+ __name(externalWrapper, "externalWrapper");
5758
5846
  function forwardingStore(wrapper) {
5759
5847
  const target = /* @__PURE__ */ __name(() => {
5760
5848
  const instance = wrapper.instance;
@@ -5777,23 +5865,47 @@ function forwardingStore(wrapper) {
5777
5865
  __name(forwardingStore, "forwardingStore");
5778
5866
  function resolveStore(options, modules) {
5779
5867
  if (options.store !== void 0) return options.store;
5780
- const external = externalStoreWrapper(modules);
5868
+ const external = externalWrapper(modules, AGENT_STORE7);
5781
5869
  if (external !== void 0) return forwardingStore(external);
5782
5870
  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()).");
5783
5871
  return new InMemoryAgentStore();
5784
5872
  }
5785
5873
  __name(resolveStore, "resolveStore");
5874
+ function resolveRolesPolicy(options, modules) {
5875
+ const external = externalWrapper(modules, AGENT_ROLES_POLICY2);
5876
+ if (options.rolesPolicy !== void 0) {
5877
+ if (external !== void 0) {
5878
+ logger.warn("Both a `rolesPolicy` option and a module binding AGENT_ROLES_POLICY (e.g. AgentAuthzModule) are configured \u2014 the `rolesPolicy` option wins.");
5879
+ }
5880
+ return options.rolesPolicy;
5881
+ }
5882
+ if (external !== void 0) return forwardingPolicy(external);
5883
+ return options.emptyRoles === "deny" ? new ClosedRolesPolicy(options.defaultRoles) : new DefaultRolesPolicy(options.defaultRoles);
5884
+ }
5885
+ __name(resolveRolesPolicy, "resolveRolesPolicy");
5886
+ function forwardingPolicy(wrapper) {
5887
+ return {
5888
+ can(actor, tool) {
5889
+ const policy = wrapper.instance;
5890
+ if (policy === void 0 || policy === null) {
5891
+ throw new Error("AgentModule: the AGENT_ROLES_POLICY bound by another module is not ready yet");
5892
+ }
5893
+ return policy.can(actor, tool);
5894
+ }
5895
+ };
5896
+ }
5897
+ __name(forwardingPolicy, "forwardingPolicy");
5786
5898
  function isQuotaProvider(quota) {
5787
5899
  return quota !== void 0 && typeof quota.report === "function";
5788
5900
  }
5789
5901
  __name(isQuotaProvider, "isQuotaProvider");
5790
5902
  function resolveCatalog(options) {
5791
5903
  if (options.models !== void 0) return options.models;
5792
- const carried = options.model.catalog;
5904
+ const carried = options.model?.catalog;
5793
5905
  return carried !== void 0 && typeof carried.list === "function" ? carried : void 0;
5794
5906
  }
5795
5907
  __name(resolveCatalog, "resolveCatalog");
5796
- function sharedProviders(durable) {
5908
+ function sharedProviders(durable, engine) {
5797
5909
  const providers = [
5798
5910
  {
5799
5911
  provide: AGENT_TOOL_REGISTRY3,
@@ -5844,9 +5956,10 @@ function sharedProviders(durable) {
5844
5956
  },
5845
5957
  {
5846
5958
  provide: AGENT_ROLES_POLICY2,
5847
- useFactory: /* @__PURE__ */ __name((options) => options.rolesPolicy ?? (options.emptyRoles === "deny" ? new ClosedRolesPolicy(options.defaultRoles) : new DefaultRolesPolicy(options.defaultRoles)), "useFactory"),
5959
+ useFactory: /* @__PURE__ */ __name((options, modules) => resolveRolesPolicy(options, modules), "useFactory"),
5848
5960
  inject: [
5849
- AGENT_OPTIONS9
5961
+ AGENT_OPTIONS9,
5962
+ ModulesContainer
5850
5963
  ]
5851
5964
  },
5852
5965
  {
@@ -5923,7 +6036,12 @@ function sharedProviders(durable) {
5923
6036
  ModulesContainer
5924
6037
  ]
5925
6038
  });
5926
- if (durable) {
6039
+ if (engine !== void 0) {
6040
+ providers.push(...engine.providers, {
6041
+ provide: AGENT_RUNNER2,
6042
+ useExisting: engine.runner
6043
+ });
6044
+ } else if (durable) {
5927
6045
  providers.push({
5928
6046
  provide: AGENT_RUNNER2,
5929
6047
  useFactory: /* @__PURE__ */ __name((durableRunner) => {
@@ -5948,8 +6066,9 @@ function sharedProviders(durable) {
5948
6066
  return providers;
5949
6067
  }
5950
6068
  __name(sharedProviders, "sharedProviders");
5951
- function exportsFor() {
6069
+ function exportsFor(engine) {
5952
6070
  return [
6071
+ ...engine?.exports ?? [],
5953
6072
  AGENT_OPTIONS9,
5954
6073
  AGENT_TOOL_REGISTRY3,
5955
6074
  AGENT_REGISTRY6,
@@ -5999,10 +6118,11 @@ function adapterProviders(adapters) {
5999
6118
  return (adapters ?? []).flatMap((adapter) => adapter.providers ?? []);
6000
6119
  }
6001
6120
  __name(adapterProviders, "adapterProviders");
6002
- function controllersForSurface(surface, adapters) {
6121
+ function controllersForSurface(surface, adapters, engine) {
6003
6122
  return surface === "engine" ? [] : [
6004
6123
  ...BASE_CONTROLLERS,
6005
- ...adapterControllers(adapters)
6124
+ ...adapterControllers(adapters),
6125
+ ...engine?.controllers ?? []
6006
6126
  ];
6007
6127
  }
6008
6128
  __name(controllersForSurface, "controllersForSurface");
@@ -6032,12 +6152,22 @@ function routerFor(path) {
6032
6152
  ]);
6033
6153
  }
6034
6154
  __name(routerFor, "routerFor");
6155
+ function assertRunnable(options) {
6156
+ if (options.engine !== void 0 && options.durable === true) {
6157
+ throw new Error(`AgentModule: \`durable: true\` runs turns on the library's loop, but the \`${options.engine.name}\` engine runs them instead. Drop \`durable\` (the engine decides how its turns survive a restart).`);
6158
+ }
6159
+ if (options.engine === void 0 && options.model === void 0) {
6160
+ throw new Error("AgentModule: set `model` (the loop runs the turns) or `engine` (something else does).");
6161
+ }
6162
+ }
6163
+ __name(assertRunnable, "assertRunnable");
6035
6164
  var AgentModule = class _AgentModule {
6036
6165
  static {
6037
6166
  __name(this, "AgentModule");
6038
6167
  }
6039
6168
  static forRoot(options) {
6040
6169
  const path = options.path ?? DEFAULT_PATH;
6170
+ assertRunnable(options);
6041
6171
  applyGuards(options.guards, options.adapters);
6042
6172
  return {
6043
6173
  module: _AgentModule,
@@ -6046,21 +6176,27 @@ var AgentModule = class _AgentModule {
6046
6176
  DiscoveryModule,
6047
6177
  routerFor(path)
6048
6178
  ],
6049
- controllers: controllersForSurface(options.surface, options.adapters),
6179
+ controllers: controllersForSurface(options.surface, options.adapters, options.engine),
6050
6180
  providers: [
6051
6181
  {
6052
6182
  provide: AGENT_OPTIONS9,
6053
6183
  useValue: options
6054
6184
  },
6055
- ...sharedProviders(options.durable ?? false),
6185
+ ...sharedProviders(options.durable ?? false, options.engine),
6056
6186
  ...guardProviders(options.guards),
6057
6187
  ...adapterProviders(options.adapters)
6058
6188
  ],
6059
- exports: exportsFor()
6189
+ exports: exportsFor(options.engine)
6060
6190
  };
6061
6191
  }
6062
6192
  static forRootAsync(options) {
6063
6193
  const path = options.path ?? DEFAULT_PATH;
6194
+ if (options.engine !== void 0) {
6195
+ assertRunnable({
6196
+ engine: options.engine,
6197
+ durable: options.durable ?? false
6198
+ });
6199
+ }
6064
6200
  applyGuards(options.guards, options.adapters);
6065
6201
  return {
6066
6202
  module: _AgentModule,
@@ -6070,7 +6206,7 @@ var AgentModule = class _AgentModule {
6070
6206
  routerFor(path),
6071
6207
  ...options.imports ?? []
6072
6208
  ],
6073
- controllers: controllersForSurface(options.surface, options.adapters),
6209
+ controllers: controllersForSurface(options.surface, options.adapters, options.engine),
6074
6210
  providers: [
6075
6211
  {
6076
6212
  provide: AGENT_OPTIONS9,
@@ -6080,18 +6216,25 @@ var AgentModule = class _AgentModule {
6080
6216
  // anything today (AgentDurableModule takes its own mirror option — see its doc for why),
6081
6217
  // but it's stamped here too so the EFFECTIVE value is always observable off
6082
6218
  // AGENT_OPTIONS, sync or async alike.
6083
- useFactory: /* @__PURE__ */ __name(async (...args) => ({
6084
- ...await options.useFactory(...args),
6085
- durable: options.durable ?? false,
6086
- surface: options.surface ?? "both"
6087
- }), "useFactory"),
6219
+ useFactory: /* @__PURE__ */ __name(async (...args) => {
6220
+ const resolved = {
6221
+ ...await options.useFactory(...args),
6222
+ durable: options.durable ?? false,
6223
+ surface: options.surface ?? "both",
6224
+ ...options.engine !== void 0 ? {
6225
+ engine: options.engine
6226
+ } : {}
6227
+ };
6228
+ assertRunnable(resolved);
6229
+ return resolved;
6230
+ }, "useFactory"),
6088
6231
  inject: options.inject ?? []
6089
6232
  },
6090
- ...sharedProviders(options.durable ?? false),
6233
+ ...sharedProviders(options.durable ?? false, options.engine),
6091
6234
  ...guardProviders(options.guards),
6092
6235
  ...adapterProviders(options.adapters)
6093
6236
  ],
6094
- exports: exportsFor()
6237
+ exports: exportsFor(options.engine)
6095
6238
  };
6096
6239
  }
6097
6240
  };
@@ -6188,6 +6331,19 @@ var AgUiRunHandler = class {
6188
6331
  warnings.push(`The resume entry ${id} answers an interrupt this agent did not raise.`);
6189
6332
  }
6190
6333
  const first = plan.decisions[0];
6334
+ if (first !== void 0 && plan.decisions.every((d) => d.address.kind === "proposal")) {
6335
+ const decided = await this.decideProposals(actor, plan.decisions);
6336
+ this.writeEvents(res, [
6337
+ ...warningEvents(warnings),
6338
+ ...decided.flatMap((outcome, index) => actionProposalDecisionEvents({
6339
+ threadId: input.threadId,
6340
+ runId: index === 0 ? input.runId : `${input.runId}:${index}`,
6341
+ text: outcome.text,
6342
+ proposalDecision: outcome.proposalDecision
6343
+ }))
6344
+ ]);
6345
+ return;
6346
+ }
6191
6347
  if (first !== void 0) {
6192
6348
  const streamRunId = first.address.stream;
6193
6349
  await this.agent.assertResumable(actor, streamRunId);
@@ -6216,11 +6372,7 @@ var AgUiRunHandler = class {
6216
6372
  if (owner !== null && turn.media.length === 0) {
6217
6373
  const decision = await this.agent.handleTextDecision(input.threadId, actor, turn.text);
6218
6374
  if ("proposalDecision" in decision) {
6219
- res.status(200);
6220
- res.setHeader("Content-Type", "text/event-stream");
6221
- res.setHeader("Cache-Control", "no-cache, no-transform");
6222
- res.flushHeaders();
6223
- for (const event of [
6375
+ this.writeEvents(res, [
6224
6376
  ...warningEvents(warnings),
6225
6377
  ...actionProposalDecisionEvents({
6226
6378
  threadId: input.threadId,
@@ -6228,8 +6380,7 @@ var AgUiRunHandler = class {
6228
6380
  text: decision.text,
6229
6381
  proposalDecision: decision.proposalDecision
6230
6382
  })
6231
- ]) res.write(agUiSse(event));
6232
- res.end();
6383
+ ]);
6233
6384
  return;
6234
6385
  }
6235
6386
  }
@@ -6321,9 +6472,11 @@ var AgUiRunHandler = class {
6321
6472
  */
6322
6473
  async settle(actor, decisions) {
6323
6474
  const deliveries = [];
6475
+ const proposals = decisions.filter((decision) => decision.address.kind === "proposal");
6324
6476
  for (const { address, entry } of decisions) {
6325
6477
  const { toolCallId: toolCallId2 } = address;
6326
6478
  const abandoned = entry.status === "cancelled";
6479
+ if (address.kind === "proposal") continue;
6327
6480
  if (address.kind === "approval") {
6328
6481
  await this.agent.checkDecision(actor, toolCallId2);
6329
6482
  const decision = abandoned ? {
@@ -6360,9 +6513,53 @@ var AgUiRunHandler = class {
6360
6513
  via: AG_UI_VIA
6361
6514
  }));
6362
6515
  }
6516
+ if (proposals.length > 0) await this.decideProposals(actor, proposals);
6363
6517
  for (const deliver of deliveries) await deliver();
6364
6518
  }
6365
6519
  /**
6520
+ * Decide independent proposals through the proposal service — the call the native
6521
+ * `action-proposals/:id/approve|reject` routes make — never by signalling the origin run, which
6522
+ * is not waiting. Every payload is checked before the first decision; a refusal is the service's
6523
+ * own `403`/`404`.
6524
+ */
6525
+ async decideProposals(actor, decisions) {
6526
+ const planned = decisions.map(({ address, entry }) => {
6527
+ const decision = entry.status === "cancelled" ? {
6528
+ approved: false
6529
+ } : readApprovalPayload(entry.payload);
6530
+ if (decision === null || address.proposalId === void 0 || address.threadId === void 0) {
6531
+ throw invalid("invalid_resume", "an approval is answered with { approved: boolean, reason?, remember? }");
6532
+ }
6533
+ return {
6534
+ address,
6535
+ decision
6536
+ };
6537
+ });
6538
+ const outcomes = [];
6539
+ for (const { address, decision } of planned) {
6540
+ outcomes.push(await this.agent.decideActionProposal(actor, address.threadId, address.proposalId, {
6541
+ decision: decision.approved ? "approved" : "rejected",
6542
+ via: AG_UI_VIA,
6543
+ ...decision.remember !== void 0 ? {
6544
+ remember: decision.remember
6545
+ } : {},
6546
+ ...decision.reason !== void 0 ? {
6547
+ reason: decision.reason
6548
+ } : {}
6549
+ }));
6550
+ }
6551
+ return outcomes;
6552
+ }
6553
+ /** Answer with a complete list of events, no run behind them. */
6554
+ writeEvents(res, events) {
6555
+ res.status(200);
6556
+ res.setHeader("Content-Type", "text/event-stream");
6557
+ res.setHeader("Cache-Control", "no-cache, no-transform");
6558
+ res.flushHeaders();
6559
+ for (const event of events) res.write(agUiSse(event));
6560
+ res.end();
6561
+ }
6562
+ /**
6366
6563
  * Pipe a run to the client as AG-UI 1.0 over SSE: one event per `data:` line, from `RUN_STARTED`
6367
6564
  * to the run's terminal event. It ends when the run stops to ask — the library run stays parked,
6368
6565
  * and the request that answers re-attaches to it.
@@ -6569,6 +6766,7 @@ export {
6569
6766
  attachmentLimits,
6570
6767
  declaredSkillProvider,
6571
6768
  defaultRequestUserMapper,
6769
+ defineTool,
6572
6770
  delegateToolName,
6573
6771
  provideAgentTool,
6574
6772
  provideAgentTools,