@dudousxd/nestjs-agent 1.23.0 → 1.24.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-D1z3WjFI.cjs';
4
+ import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-Crf9uwum.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-CwxJDrBM.js';
4
+ import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-ZQkqb1Yw.js';
5
5
  import { DynamicModule } from '@nestjs/common';
6
6
  import '@dudousxd/nestjs-agent-core/genui';
7
7
  import '@standard-schema/spec';
@@ -1718,8 +1718,33 @@ AgentDepsFactory2 = _ts_decorate6([
1718
1718
  ], AgentDepsFactory2);
1719
1719
 
1720
1720
  // src/agent.service.ts
1721
- import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY4, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS4, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE6, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval as mayDecideApproval2, readElicitationQuestions, settleDeadRun as settleDeadRun2, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
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";
1721
+ import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY4, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS4, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE6, DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY as DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY2, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval as mayDecideApproval2, readElicitationQuestions, settleDeadRun as settleDeadRun2, textActionProposalReply as textActionProposalReply2, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
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, PayloadTooLargeException, UnsupportedMediaTypeException } from "@nestjs/common";
1723
+
1724
+ // src/attachment-limits.ts
1725
+ var DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
1726
+ var DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES = [
1727
+ "image/png",
1728
+ "image/jpeg",
1729
+ "image/gif",
1730
+ "image/webp",
1731
+ "application/pdf",
1732
+ "text/plain",
1733
+ "text/csv"
1734
+ ];
1735
+ var MAX_ATTACHMENTS_PER_MESSAGE = 10;
1736
+ function attachmentLimits(options, staging) {
1737
+ const declared = staging?.describe?.();
1738
+ const own = options?.attachments;
1739
+ return {
1740
+ enabled: staging !== void 0,
1741
+ upload: staging === void 0 ? null : declared?.upload ?? "multipart",
1742
+ maxBytes: declared?.maxBytes ?? own?.maxBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES,
1743
+ allowedContentTypes: declared?.allowedContentTypes ?? own?.allowedContentTypes ?? DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES,
1744
+ maxPerMessage: MAX_ATTACHMENTS_PER_MESSAGE
1745
+ };
1746
+ }
1747
+ __name(attachmentLimits, "attachmentLimits");
1723
1748
 
1724
1749
  // src/proposals/action-proposal.service.ts
1725
1750
  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";
@@ -1877,6 +1902,10 @@ var ActionProposalService = class {
1877
1902
  text: this.reply(result, decision)
1878
1903
  };
1879
1904
  }
1905
+ /** The configured words a text decision is made of (`actionProposalText.vocabulary`). */
1906
+ textVocabulary() {
1907
+ return this.vocabulary;
1908
+ }
1880
1909
  /** The configured reply to a decision the store answered with `result`. */
1881
1910
  reply(result, decision) {
1882
1911
  return textActionProposalReply(result, decision, this.replies);
@@ -2503,6 +2532,65 @@ var AgentService = class {
2503
2532
  };
2504
2533
  return this.proposals.handleTextDecision(threadId, actor, text);
2505
2534
  }
2535
+ /** How `action` tools wait for their approval here — `AgentModule.forRoot({ actionApprovalMode })`. */
2536
+ actionApprovalMode() {
2537
+ return this.options?.actionApprovalMode ?? "blocking";
2538
+ }
2539
+ /**
2540
+ * The words a text decision is made of (`actionProposalText.vocabulary`, English by default) — what
2541
+ * a surface without buttons tells the person to reply.
2542
+ */
2543
+ actionProposalVocabulary() {
2544
+ return this.proposals?.textVocabulary() ?? DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY2;
2545
+ }
2546
+ /** The configured reply (`actionProposalText.replies`) to a decision the store answered with `result`. */
2547
+ actionProposalReply(result, decision) {
2548
+ return this.proposals?.reply(result, decision) ?? textActionProposalReply2(result, decision);
2549
+ }
2550
+ /**
2551
+ * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
2552
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page). `404`
2553
+ * when independent approvals are not on.
2554
+ */
2555
+ async listActionProposals(actor, threadId) {
2556
+ if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
2557
+ throw new NotFoundException2("Proposal not found");
2558
+ }
2559
+ return this.proposals.list(threadId, actor);
2560
+ }
2561
+ /**
2562
+ * The attachment rules in force — what `GET <base>/config` serves and `POST <base>/attachments`
2563
+ * enforces. `enabled: false` when no `AGENT_ATTACHMENT_STAGING` provider is bound.
2564
+ */
2565
+ attachmentLimits() {
2566
+ return attachmentLimits(this.options, this.staging);
2567
+ }
2568
+ /**
2569
+ * Stage a file for a later turn, held to the same limits as `POST <base>/attachments` — for a
2570
+ * surface that receives files itself (a messaging webhook). Refuses with `501` when attachments are
2571
+ * off, `415` for a type outside the allowlist and `413` past the size cap; send the returned
2572
+ * `mediaId` as `attachments: [{ mediaId }]`.
2573
+ */
2574
+ async stageAttachment(actor, file) {
2575
+ if (this.staging === void 0) {
2576
+ throw new NotImplementedException3("Attachments are not configured on this server: bind AGENT_ATTACHMENT_STAGING (e.g. import AgentMediaAttachmentsModule).");
2577
+ }
2578
+ const { allowedContentTypes, maxBytes } = this.attachmentLimits();
2579
+ const contentType = file.contentType.split(";")[0]?.trim().toLowerCase() ?? "";
2580
+ if (!allowedContentTypes.includes(contentType)) {
2581
+ throw new UnsupportedMediaTypeException(`content type "${contentType}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
2582
+ }
2583
+ if (file.data.byteLength > maxBytes) {
2584
+ throw new PayloadTooLargeException(`file exceeds the ${maxBytes}-byte limit`);
2585
+ }
2586
+ return this.staging.stage({
2587
+ data: file.data,
2588
+ filename: file.filename,
2589
+ contentType,
2590
+ sizeBytes: file.data.byteLength,
2591
+ actor
2592
+ });
2593
+ }
2506
2594
  /** The queue service when the bound store can hold a queue, else `undefined`. */
2507
2595
  queueing() {
2508
2596
  return this.queue?.supported === true ? this.queue : void 0;
@@ -3784,35 +3872,8 @@ AgentsController = _ts_decorate11([
3784
3872
 
3785
3873
  // src/controller/attachments.controller.ts
3786
3874
  import { AGENT_ACTOR_RESOLVER, AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING3, AGENT_OPTIONS as AGENT_OPTIONS5 } from "@dudousxd/nestjs-agent-core";
3787
- import { BadRequestException as BadRequestException3, Controller as Controller2, Get as Get2, Inject as Inject10, NotImplementedException as NotImplementedException5, Optional as Optional7, PayloadTooLargeException, Post, Req, UnsupportedMediaTypeException, UploadedFile, UseInterceptors } from "@nestjs/common";
3875
+ import { BadRequestException as BadRequestException3, Controller as Controller2, Get as Get2, Inject as Inject10, NotImplementedException as NotImplementedException5, Optional as Optional7, PayloadTooLargeException as PayloadTooLargeException2, Post, Req, UnsupportedMediaTypeException as UnsupportedMediaTypeException2, UploadedFile, UseInterceptors } from "@nestjs/common";
3788
3876
  import { FileInterceptor } from "@nestjs/platform-express";
3789
-
3790
- // src/attachment-limits.ts
3791
- var DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
3792
- var DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES = [
3793
- "image/png",
3794
- "image/jpeg",
3795
- "image/gif",
3796
- "image/webp",
3797
- "application/pdf",
3798
- "text/plain",
3799
- "text/csv"
3800
- ];
3801
- var MAX_ATTACHMENTS_PER_MESSAGE = 10;
3802
- function attachmentLimits(options, staging) {
3803
- const declared = staging?.describe?.();
3804
- const own = options?.attachments;
3805
- return {
3806
- enabled: staging !== void 0,
3807
- upload: staging === void 0 ? null : declared?.upload ?? "multipart",
3808
- maxBytes: declared?.maxBytes ?? own?.maxBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES,
3809
- allowedContentTypes: declared?.allowedContentTypes ?? own?.allowedContentTypes ?? DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES,
3810
- maxPerMessage: MAX_ATTACHMENTS_PER_MESSAGE
3811
- };
3812
- }
3813
- __name(attachmentLimits, "attachmentLimits");
3814
-
3815
- // src/controller/attachments.controller.ts
3816
3877
  function _ts_decorate12(decorators, target, key, desc) {
3817
3878
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3818
3879
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -3879,10 +3940,10 @@ var AttachmentsController = class {
3879
3940
  }
3880
3941
  const { allowedContentTypes, maxBytes } = attachmentLimits(this.options, this.staging);
3881
3942
  if (!allowedContentTypes.includes(file.mimetype)) {
3882
- throw new UnsupportedMediaTypeException(`content type "${file.mimetype}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
3943
+ throw new UnsupportedMediaTypeException2(`content type "${file.mimetype}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
3883
3944
  }
3884
3945
  if (file.size > maxBytes) {
3885
- throw new PayloadTooLargeException(`file exceeds the ${maxBytes}-byte limit`);
3946
+ throw new PayloadTooLargeException2(`file exceeds the ${maxBytes}-byte limit`);
3886
3947
  }
3887
3948
  const actor = await this.actorResolver.resolve(req);
3888
3949
  return this.staging.stage({
@@ -6103,6 +6164,34 @@ var ActionProposalWorkerService = class _ActionProposalWorkerService {
6103
6164
  deps;
6104
6165
  worker;
6105
6166
  logger = new Logger7(_ActionProposalWorkerService.name);
6167
+ settledListeners = /* @__PURE__ */ new Set();
6168
+ /**
6169
+ * Run `listener` after every proposal this process's worker settles — how a module relays an
6170
+ * outcome somewhere the turn's stream no longer reaches (a text channel's conversation). Next to
6171
+ * `actionProposalWorker.onSettled`; a listener that throws is logged and does not stop the others.
6172
+ * Returns the unsubscribe. Only the replicas that run the worker (not `surface: 'http'`) call it.
6173
+ */
6174
+ onSettled(listener) {
6175
+ this.settledListeners.add(listener);
6176
+ return () => {
6177
+ this.settledListeners.delete(listener);
6178
+ };
6179
+ }
6180
+ async settled(proposal) {
6181
+ const configured = this.options.actionProposalWorker?.onSettled;
6182
+ for (const listener of [
6183
+ ...configured ? [
6184
+ configured
6185
+ ] : [],
6186
+ ...this.settledListeners
6187
+ ]) {
6188
+ try {
6189
+ await listener(proposal);
6190
+ } catch (error) {
6191
+ this.logger.error(error);
6192
+ }
6193
+ }
6194
+ }
6106
6195
  constructor(store, options, deps) {
6107
6196
  this.store = store;
6108
6197
  this.options = options;
@@ -6118,6 +6207,7 @@ var ActionProposalWorkerService = class _ActionProposalWorkerService {
6118
6207
  store: this.store,
6119
6208
  workerId: randomUUID2(),
6120
6209
  ...this.options.actionProposalWorker,
6210
+ onSettled: /* @__PURE__ */ __name((proposal) => this.settled(proposal), "onSettled"),
6121
6211
  executor: new ActionProposalExecutor({
6122
6212
  resolver,
6123
6213
  resolveExecution: /* @__PURE__ */ __name(async (proposal, actor) => {