@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.
@@ -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-D1z3WjFI.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-CwxJDrBM.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;
@@ -2173,6 +2223,9 @@ var ChatQueueService2 = class _ChatQueueService {
2173
2223
  } : {},
2174
2224
  ...input.uiCapabilities !== void 0 ? {
2175
2225
  uiCapabilities: input.uiCapabilities
2226
+ } : {},
2227
+ ...input.hostContext !== void 0 ? {
2228
+ hostContext: input.hostContext
2176
2229
  } : {}
2177
2230
  });
2178
2231
  await store.setQueuePause(threadId, {
@@ -2217,12 +2270,14 @@ var ChatQueueService2 = class _ChatQueueService {
2217
2270
  return null;
2218
2271
  }
2219
2272
  try {
2220
- const { blocked } = await this.quotaProvider.report({
2273
+ const report = await this.quotaProvider.report({
2221
2274
  actor
2222
2275
  });
2276
+ const { blocked } = report;
2223
2277
  if (blocked === void 0) {
2224
2278
  return null;
2225
2279
  }
2280
+ publishQuotaBlocked(actor, report, blocked);
2226
2281
  return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
2227
2282
  } catch {
2228
2283
  return null;
@@ -2256,6 +2311,9 @@ var ChatQueueService2 = class _ChatQueueService {
2256
2311
  ...message.uiCapabilities !== void 0 ? {
2257
2312
  uiCapabilities: message.uiCapabilities
2258
2313
  } : {},
2314
+ ...message.hostContext !== void 0 ? {
2315
+ hostContext: message.hostContext
2316
+ } : {},
2259
2317
  ...message.model !== void 0 ? {
2260
2318
  model: message.model
2261
2319
  } : {}
@@ -2424,6 +2482,21 @@ var AgentService = class {
2424
2482
  this.queue = queue;
2425
2483
  this.proposals = proposals;
2426
2484
  }
2485
+ /**
2486
+ * Decide one independent proposal through the scoped proposal service — what the native
2487
+ * `action-proposals/:id/approve|reject` routes call — and the reply to show for it. `404` when
2488
+ * proposals are not on, `403`/`404` when this actor may not decide it.
2489
+ */
2490
+ async decideActionProposal(actor, threadId, proposalId, command) {
2491
+ if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
2492
+ throw new NotFoundException2("Proposal not found");
2493
+ }
2494
+ const result = await this.proposals.decide(threadId, proposalId, actor, command);
2495
+ return {
2496
+ proposalDecision: result,
2497
+ text: this.proposals.reply(result, command.decision)
2498
+ };
2499
+ }
2427
2500
  async handleTextDecision(threadId, actor, text) {
2428
2501
  if (this.options?.actionApprovalMode !== "independent" || !this.proposals) return {
2429
2502
  status: "unmatched"
@@ -2471,10 +2544,12 @@ var AgentService = class {
2471
2544
  if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
2472
2545
  return;
2473
2546
  }
2474
- const { blocked } = await this.quotaProvider.report({
2547
+ const report = await this.quotaProvider.report({
2475
2548
  actor
2476
2549
  });
2550
+ const { blocked } = report;
2477
2551
  if (blocked !== void 0) {
2552
+ publishQuotaBlocked(actor, report, blocked);
2478
2553
  throw new HttpException({
2479
2554
  statusCode: HttpStatus.TOO_MANY_REQUESTS,
2480
2555
  code: "quota_exceeded",
@@ -2609,6 +2684,9 @@ var AgentService = class {
2609
2684
  }
2610
2685
  const created = await this.store.createThread({
2611
2686
  actor: params.actor,
2687
+ ...agentName !== void 0 ? {
2688
+ agentName
2689
+ } : {},
2612
2690
  ...params.transient === true ? {
2613
2691
  transient: true
2614
2692
  } : {},
@@ -2621,7 +2699,7 @@ var AgentService = class {
2621
2699
  });
2622
2700
  threadId = created.id;
2623
2701
  } else {
2624
- await this.assertOwnsThread(params.actor, threadId);
2702
+ if (params.authorized !== true) await this.assertOwnsThread(params.actor, threadId);
2625
2703
  if (params.personaId !== void 0) {
2626
2704
  await this.pinThreadPersona(threadId, params.personaId);
2627
2705
  }
@@ -2647,6 +2725,9 @@ var AgentService = class {
2647
2725
  ...params.uiCapabilities !== void 0 ? {
2648
2726
  uiCapabilities: params.uiCapabilities
2649
2727
  } : {},
2728
+ ...params.hostContext !== void 0 ? {
2729
+ hostContext: params.hostContext
2730
+ } : {},
2650
2731
  ...model !== void 0 ? {
2651
2732
  model
2652
2733
  } : {}
@@ -2669,7 +2750,7 @@ var AgentService = class {
2669
2750
  });
2670
2751
  }
2671
2752
  if (live === null && mode !== "queue") {
2672
- const runId = crypto.randomUUID();
2753
+ const runId = this.runner.runIdFor?.(input) ?? crypto.randomUUID();
2673
2754
  if (await queue.queueStore().claimActiveStream(threadId, runId, stale !== null ? {
2674
2755
  replacing: stale
2675
2756
  } : {})) {
@@ -2737,6 +2818,9 @@ var AgentService = class {
2737
2818
  ...input.uiCapabilities !== void 0 ? {
2738
2819
  uiCapabilities: input.uiCapabilities
2739
2820
  } : {},
2821
+ ...input.hostContext !== void 0 ? {
2822
+ hostContext: input.hostContext
2823
+ } : {},
2740
2824
  ...interrupting !== void 0 ? {
2741
2825
  interrupt: true,
2742
2826
  at: "head"
@@ -5727,6 +5811,9 @@ var AiToolDiscoveryService = class _AiToolDiscoveryService {
5727
5811
  } : {}
5728
5812
  }, {
5729
5813
  execute: /* @__PURE__ */ __name((input, ctx) => instance.execute(input, ctx), "execute"),
5814
+ ...typeof handler.present === "function" ? {
5815
+ present: /* @__PURE__ */ __name((output, ctx) => instance.present(output, ctx), "present")
5816
+ } : {},
5730
5817
  ...typeof handler.preflight === "function" ? {
5731
5818
  preflight: /* @__PURE__ */ __name((input, ctx, options) => instance.preflight(input, ctx, options), "preflight")
5732
5819
  } : {},
@@ -6763,15 +6850,15 @@ function _ts_decorate30(decorators, target, key, desc) {
6763
6850
  __name(_ts_decorate30, "_ts_decorate");
6764
6851
  var DEFAULT_PATH = "agent";
6765
6852
  var logger = new Logger9("AgentModule");
6766
- function externalStoreWrapper(modules) {
6853
+ function externalWrapper(modules, token) {
6767
6854
  for (const module of modules.values()) {
6768
6855
  if (module.metatype === AgentModule) continue;
6769
- const wrapper = module.providers.get(AGENT_STORE9);
6856
+ const wrapper = module.providers.get(token);
6770
6857
  if (wrapper !== void 0) return wrapper;
6771
6858
  }
6772
6859
  return void 0;
6773
6860
  }
6774
- __name(externalStoreWrapper, "externalStoreWrapper");
6861
+ __name(externalWrapper, "externalWrapper");
6775
6862
  function forwardingStore(wrapper) {
6776
6863
  const target = /* @__PURE__ */ __name(() => {
6777
6864
  const instance = wrapper.instance;
@@ -6794,23 +6881,47 @@ function forwardingStore(wrapper) {
6794
6881
  __name(forwardingStore, "forwardingStore");
6795
6882
  function resolveStore(options, modules) {
6796
6883
  if (options.store !== void 0) return options.store;
6797
- const external = externalStoreWrapper(modules);
6884
+ const external = externalWrapper(modules, AGENT_STORE9);
6798
6885
  if (external !== void 0) return forwardingStore(external);
6799
6886
  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
6887
  return new InMemoryAgentStore();
6801
6888
  }
6802
6889
  __name(resolveStore, "resolveStore");
6890
+ function resolveRolesPolicy(options, modules) {
6891
+ const external = externalWrapper(modules, AGENT_ROLES_POLICY2);
6892
+ if (options.rolesPolicy !== void 0) {
6893
+ if (external !== void 0) {
6894
+ logger.warn("Both a `rolesPolicy` option and a module binding AGENT_ROLES_POLICY (e.g. AgentAuthzModule) are configured \u2014 the `rolesPolicy` option wins.");
6895
+ }
6896
+ return options.rolesPolicy;
6897
+ }
6898
+ if (external !== void 0) return forwardingPolicy(external);
6899
+ return options.emptyRoles === "deny" ? new ClosedRolesPolicy(options.defaultRoles) : new DefaultRolesPolicy(options.defaultRoles);
6900
+ }
6901
+ __name(resolveRolesPolicy, "resolveRolesPolicy");
6902
+ function forwardingPolicy(wrapper) {
6903
+ return {
6904
+ can(actor, tool) {
6905
+ const policy = wrapper.instance;
6906
+ if (policy === void 0 || policy === null) {
6907
+ throw new Error("AgentModule: the AGENT_ROLES_POLICY bound by another module is not ready yet");
6908
+ }
6909
+ return policy.can(actor, tool);
6910
+ }
6911
+ };
6912
+ }
6913
+ __name(forwardingPolicy, "forwardingPolicy");
6803
6914
  function isQuotaProvider(quota) {
6804
6915
  return quota !== void 0 && typeof quota.report === "function";
6805
6916
  }
6806
6917
  __name(isQuotaProvider, "isQuotaProvider");
6807
6918
  function resolveCatalog(options) {
6808
6919
  if (options.models !== void 0) return options.models;
6809
- const carried = options.model.catalog;
6920
+ const carried = options.model?.catalog;
6810
6921
  return carried !== void 0 && typeof carried.list === "function" ? carried : void 0;
6811
6922
  }
6812
6923
  __name(resolveCatalog, "resolveCatalog");
6813
- function sharedProviders(durable) {
6924
+ function sharedProviders(durable, engine) {
6814
6925
  const providers = [
6815
6926
  {
6816
6927
  provide: AGENT_TOOL_REGISTRY3,
@@ -6861,9 +6972,10 @@ function sharedProviders(durable) {
6861
6972
  },
6862
6973
  {
6863
6974
  provide: AGENT_ROLES_POLICY2,
6864
- useFactory: /* @__PURE__ */ __name((options) => options.rolesPolicy ?? (options.emptyRoles === "deny" ? new ClosedRolesPolicy(options.defaultRoles) : new DefaultRolesPolicy(options.defaultRoles)), "useFactory"),
6975
+ useFactory: /* @__PURE__ */ __name((options, modules) => resolveRolesPolicy(options, modules), "useFactory"),
6865
6976
  inject: [
6866
- AGENT_OPTIONS9
6977
+ AGENT_OPTIONS9,
6978
+ ModulesContainer
6867
6979
  ]
6868
6980
  },
6869
6981
  {
@@ -6940,7 +7052,12 @@ function sharedProviders(durable) {
6940
7052
  ModulesContainer
6941
7053
  ]
6942
7054
  });
6943
- if (durable) {
7055
+ if (engine !== void 0) {
7056
+ providers.push(...engine.providers, {
7057
+ provide: AGENT_RUNNER2,
7058
+ useExisting: engine.runner
7059
+ });
7060
+ } else if (durable) {
6944
7061
  providers.push({
6945
7062
  provide: AGENT_RUNNER2,
6946
7063
  useFactory: /* @__PURE__ */ __name((durableRunner) => {
@@ -6965,8 +7082,9 @@ function sharedProviders(durable) {
6965
7082
  return providers;
6966
7083
  }
6967
7084
  __name(sharedProviders, "sharedProviders");
6968
- function exportsFor() {
7085
+ function exportsFor(engine) {
6969
7086
  return [
7087
+ ...engine?.exports ?? [],
6970
7088
  AGENT_OPTIONS9,
6971
7089
  AGENT_TOOL_REGISTRY3,
6972
7090
  AGENT_REGISTRY6,
@@ -7016,10 +7134,11 @@ function adapterProviders(adapters) {
7016
7134
  return (adapters ?? []).flatMap((adapter) => adapter.providers ?? []);
7017
7135
  }
7018
7136
  __name(adapterProviders, "adapterProviders");
7019
- function controllersForSurface(surface, adapters) {
7137
+ function controllersForSurface(surface, adapters, engine) {
7020
7138
  return surface === "engine" ? [] : [
7021
7139
  ...BASE_CONTROLLERS,
7022
- ...adapterControllers(adapters)
7140
+ ...adapterControllers(adapters),
7141
+ ...engine?.controllers ?? []
7023
7142
  ];
7024
7143
  }
7025
7144
  __name(controllersForSurface, "controllersForSurface");
@@ -7049,12 +7168,22 @@ function routerFor(path) {
7049
7168
  ]);
7050
7169
  }
7051
7170
  __name(routerFor, "routerFor");
7171
+ function assertRunnable(options) {
7172
+ if (options.engine !== void 0 && options.durable === true) {
7173
+ 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).`);
7174
+ }
7175
+ if (options.engine === void 0 && options.model === void 0) {
7176
+ throw new Error("AgentModule: set `model` (the loop runs the turns) or `engine` (something else does).");
7177
+ }
7178
+ }
7179
+ __name(assertRunnable, "assertRunnable");
7052
7180
  var AgentModule = class _AgentModule {
7053
7181
  static {
7054
7182
  __name(this, "AgentModule");
7055
7183
  }
7056
7184
  static forRoot(options) {
7057
7185
  const path = options.path ?? DEFAULT_PATH;
7186
+ assertRunnable(options);
7058
7187
  applyGuards(options.guards, options.adapters);
7059
7188
  return {
7060
7189
  module: _AgentModule,
@@ -7063,21 +7192,27 @@ var AgentModule = class _AgentModule {
7063
7192
  DiscoveryModule,
7064
7193
  routerFor(path)
7065
7194
  ],
7066
- controllers: controllersForSurface(options.surface, options.adapters),
7195
+ controllers: controllersForSurface(options.surface, options.adapters, options.engine),
7067
7196
  providers: [
7068
7197
  {
7069
7198
  provide: AGENT_OPTIONS9,
7070
7199
  useValue: options
7071
7200
  },
7072
- ...sharedProviders(options.durable ?? false),
7201
+ ...sharedProviders(options.durable ?? false, options.engine),
7073
7202
  ...guardProviders(options.guards),
7074
7203
  ...adapterProviders(options.adapters)
7075
7204
  ],
7076
- exports: exportsFor()
7205
+ exports: exportsFor(options.engine)
7077
7206
  };
7078
7207
  }
7079
7208
  static forRootAsync(options) {
7080
7209
  const path = options.path ?? DEFAULT_PATH;
7210
+ if (options.engine !== void 0) {
7211
+ assertRunnable({
7212
+ engine: options.engine,
7213
+ durable: options.durable ?? false
7214
+ });
7215
+ }
7081
7216
  applyGuards(options.guards, options.adapters);
7082
7217
  return {
7083
7218
  module: _AgentModule,
@@ -7087,7 +7222,7 @@ var AgentModule = class _AgentModule {
7087
7222
  routerFor(path),
7088
7223
  ...options.imports ?? []
7089
7224
  ],
7090
- controllers: controllersForSurface(options.surface, options.adapters),
7225
+ controllers: controllersForSurface(options.surface, options.adapters, options.engine),
7091
7226
  providers: [
7092
7227
  {
7093
7228
  provide: AGENT_OPTIONS9,
@@ -7097,18 +7232,25 @@ var AgentModule = class _AgentModule {
7097
7232
  // anything today (AgentDurableModule takes its own mirror option — see its doc for why),
7098
7233
  // but it's stamped here too so the EFFECTIVE value is always observable off
7099
7234
  // AGENT_OPTIONS, sync or async alike.
7100
- useFactory: /* @__PURE__ */ __name(async (...args) => ({
7101
- ...await options.useFactory(...args),
7102
- durable: options.durable ?? false,
7103
- surface: options.surface ?? "both"
7104
- }), "useFactory"),
7235
+ useFactory: /* @__PURE__ */ __name(async (...args) => {
7236
+ const resolved = {
7237
+ ...await options.useFactory(...args),
7238
+ durable: options.durable ?? false,
7239
+ surface: options.surface ?? "both",
7240
+ ...options.engine !== void 0 ? {
7241
+ engine: options.engine
7242
+ } : {}
7243
+ };
7244
+ assertRunnable(resolved);
7245
+ return resolved;
7246
+ }, "useFactory"),
7105
7247
  inject: options.inject ?? []
7106
7248
  },
7107
- ...sharedProviders(options.durable ?? false),
7249
+ ...sharedProviders(options.durable ?? false, options.engine),
7108
7250
  ...guardProviders(options.guards),
7109
7251
  ...adapterProviders(options.adapters)
7110
7252
  ],
7111
- exports: exportsFor()
7253
+ exports: exportsFor(options.engine)
7112
7254
  };
7113
7255
  }
7114
7256
  };