@dudousxd/nestjs-agent 1.23.1 → 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.
package/dist/index.cjs CHANGED
@@ -673,6 +673,31 @@ function childSinkWriter(writer) {
673
673
  }
674
674
  __name(childSinkWriter, "childSinkWriter");
675
675
 
676
+ // src/attachment-limits.ts
677
+ var DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
678
+ var DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES = [
679
+ "image/png",
680
+ "image/jpeg",
681
+ "image/gif",
682
+ "image/webp",
683
+ "application/pdf",
684
+ "text/plain",
685
+ "text/csv"
686
+ ];
687
+ var MAX_ATTACHMENTS_PER_MESSAGE = 10;
688
+ function attachmentLimits(options, staging) {
689
+ const declared = staging?.describe?.();
690
+ const own = options?.attachments;
691
+ return {
692
+ enabled: staging !== void 0,
693
+ upload: staging === void 0 ? null : declared?.upload ?? "multipart",
694
+ maxBytes: declared?.maxBytes ?? own?.maxBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES,
695
+ allowedContentTypes: declared?.allowedContentTypes ?? own?.allowedContentTypes ?? DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES,
696
+ maxPerMessage: MAX_ATTACHMENTS_PER_MESSAGE
697
+ };
698
+ }
699
+ __name(attachmentLimits, "attachmentLimits");
700
+
676
701
  // src/proposals/action-proposal.service.ts
677
702
  var import_nestjs_agent_core5 = require("@dudousxd/nestjs-agent-core");
678
703
  var import_common3 = require("@nestjs/common");
@@ -829,6 +854,10 @@ var ActionProposalService = class {
829
854
  text: this.reply(result, decision)
830
855
  };
831
856
  }
857
+ /** The configured words a text decision is made of (`actionProposalText.vocabulary`). */
858
+ textVocabulary() {
859
+ return this.vocabulary;
860
+ }
832
861
  /** The configured reply to a decision the store answered with `result`. */
833
862
  reply(result, decision) {
834
863
  return (0, import_nestjs_agent_core5.textActionProposalReply)(result, decision, this.replies);
@@ -1455,6 +1484,65 @@ var AgentService = class {
1455
1484
  };
1456
1485
  return this.proposals.handleTextDecision(threadId, actor, text);
1457
1486
  }
1487
+ /** How `action` tools wait for their approval here — `AgentModule.forRoot({ actionApprovalMode })`. */
1488
+ actionApprovalMode() {
1489
+ return this.options?.actionApprovalMode ?? "blocking";
1490
+ }
1491
+ /**
1492
+ * The words a text decision is made of (`actionProposalText.vocabulary`, English by default) — what
1493
+ * a surface without buttons tells the person to reply.
1494
+ */
1495
+ actionProposalVocabulary() {
1496
+ return this.proposals?.textVocabulary() ?? import_nestjs_agent_core9.DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY;
1497
+ }
1498
+ /** The configured reply (`actionProposalText.replies`) to a decision the store answered with `result`. */
1499
+ actionProposalReply(result, decision) {
1500
+ return this.proposals?.reply(result, decision) ?? (0, import_nestjs_agent_core9.textActionProposalReply)(result, decision);
1501
+ }
1502
+ /**
1503
+ * The proposals of a thread this actor may see — the thread's own actor, or one the policy lets
1504
+ * decide them; what `GET <base>/threads/:threadId/action-proposals` serves (its first page). `404`
1505
+ * when independent approvals are not on.
1506
+ */
1507
+ async listActionProposals(actor, threadId) {
1508
+ if (this.options?.actionApprovalMode !== "independent" || !this.proposals) {
1509
+ throw new import_common6.NotFoundException("Proposal not found");
1510
+ }
1511
+ return this.proposals.list(threadId, actor);
1512
+ }
1513
+ /**
1514
+ * The attachment rules in force — what `GET <base>/config` serves and `POST <base>/attachments`
1515
+ * enforces. `enabled: false` when no `AGENT_ATTACHMENT_STAGING` provider is bound.
1516
+ */
1517
+ attachmentLimits() {
1518
+ return attachmentLimits(this.options, this.staging);
1519
+ }
1520
+ /**
1521
+ * Stage a file for a later turn, held to the same limits as `POST <base>/attachments` — for a
1522
+ * surface that receives files itself (a messaging webhook). Refuses with `501` when attachments are
1523
+ * off, `415` for a type outside the allowlist and `413` past the size cap; send the returned
1524
+ * `mediaId` as `attachments: [{ mediaId }]`.
1525
+ */
1526
+ async stageAttachment(actor, file) {
1527
+ if (this.staging === void 0) {
1528
+ throw new import_common6.NotImplementedException("Attachments are not configured on this server: bind AGENT_ATTACHMENT_STAGING (e.g. import AgentMediaAttachmentsModule).");
1529
+ }
1530
+ const { allowedContentTypes, maxBytes } = this.attachmentLimits();
1531
+ const contentType = file.contentType.split(";")[0]?.trim().toLowerCase() ?? "";
1532
+ if (!allowedContentTypes.includes(contentType)) {
1533
+ throw new import_common6.UnsupportedMediaTypeException(`content type "${contentType}" is not allowed (allowed: ${allowedContentTypes.join(", ")})`);
1534
+ }
1535
+ if (file.data.byteLength > maxBytes) {
1536
+ throw new import_common6.PayloadTooLargeException(`file exceeds the ${maxBytes}-byte limit`);
1537
+ }
1538
+ return this.staging.stage({
1539
+ data: file.data,
1540
+ filename: file.filename,
1541
+ contentType,
1542
+ sizeBytes: file.data.byteLength,
1543
+ actor
1544
+ });
1545
+ }
1458
1546
  /** The queue service when the bound store can hold a queue, else `undefined`. */
1459
1547
  queueing() {
1460
1548
  return this.queue?.supported === true ? this.queue : void 0;
@@ -2738,33 +2826,6 @@ AgentsController = _ts_decorate7([
2738
2826
  var import_nestjs_agent_core11 = require("@dudousxd/nestjs-agent-core");
2739
2827
  var import_common9 = require("@nestjs/common");
2740
2828
  var import_platform_express = require("@nestjs/platform-express");
2741
-
2742
- // src/attachment-limits.ts
2743
- var DEFAULT_MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024;
2744
- var DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES = [
2745
- "image/png",
2746
- "image/jpeg",
2747
- "image/gif",
2748
- "image/webp",
2749
- "application/pdf",
2750
- "text/plain",
2751
- "text/csv"
2752
- ];
2753
- var MAX_ATTACHMENTS_PER_MESSAGE = 10;
2754
- function attachmentLimits(options, staging) {
2755
- const declared = staging?.describe?.();
2756
- const own = options?.attachments;
2757
- return {
2758
- enabled: staging !== void 0,
2759
- upload: staging === void 0 ? null : declared?.upload ?? "multipart",
2760
- maxBytes: declared?.maxBytes ?? own?.maxBytes ?? DEFAULT_MAX_ATTACHMENT_BYTES,
2761
- allowedContentTypes: declared?.allowedContentTypes ?? own?.allowedContentTypes ?? DEFAULT_ALLOWED_ATTACHMENT_CONTENT_TYPES,
2762
- maxPerMessage: MAX_ATTACHMENTS_PER_MESSAGE
2763
- };
2764
- }
2765
- __name(attachmentLimits, "attachmentLimits");
2766
-
2767
- // src/controller/attachments.controller.ts
2768
2829
  function _ts_decorate8(decorators, target, key, desc) {
2769
2830
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2770
2831
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -5167,6 +5228,34 @@ var ActionProposalWorkerService = class _ActionProposalWorkerService {
5167
5228
  deps;
5168
5229
  worker;
5169
5230
  logger = new import_common24.Logger(_ActionProposalWorkerService.name);
5231
+ settledListeners = /* @__PURE__ */ new Set();
5232
+ /**
5233
+ * Run `listener` after every proposal this process's worker settles — how a module relays an
5234
+ * outcome somewhere the turn's stream no longer reaches (a text channel's conversation). Next to
5235
+ * `actionProposalWorker.onSettled`; a listener that throws is logged and does not stop the others.
5236
+ * Returns the unsubscribe. Only the replicas that run the worker (not `surface: 'http'`) call it.
5237
+ */
5238
+ onSettled(listener) {
5239
+ this.settledListeners.add(listener);
5240
+ return () => {
5241
+ this.settledListeners.delete(listener);
5242
+ };
5243
+ }
5244
+ async settled(proposal) {
5245
+ const configured = this.options.actionProposalWorker?.onSettled;
5246
+ for (const listener of [
5247
+ ...configured ? [
5248
+ configured
5249
+ ] : [],
5250
+ ...this.settledListeners
5251
+ ]) {
5252
+ try {
5253
+ await listener(proposal);
5254
+ } catch (error) {
5255
+ this.logger.error(error);
5256
+ }
5257
+ }
5258
+ }
5170
5259
  constructor(store, options, deps) {
5171
5260
  this.store = store;
5172
5261
  this.options = options;
@@ -5182,6 +5271,7 @@ var ActionProposalWorkerService = class _ActionProposalWorkerService {
5182
5271
  store: this.store,
5183
5272
  workerId: (0, import_node_crypto2.randomUUID)(),
5184
5273
  ...this.options.actionProposalWorker,
5274
+ onSettled: /* @__PURE__ */ __name((proposal) => this.settled(proposal), "onSettled"),
5185
5275
  executor: new import_nestjs_agent_core29.ActionProposalExecutor({
5186
5276
  resolver,
5187
5277
  resolveExecution: /* @__PURE__ */ __name(async (proposal, actor) => {