@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.
- package/dist/{chat-queue.service-D1z3WjFI.d.cts → chat-queue.service-Crf9uwum.d.cts} +6 -2
- package/dist/{chat-queue.service-CwxJDrBM.d.ts → chat-queue.service-ZQkqb1Yw.d.ts} +6 -2
- package/dist/durable/index.cjs +117 -27
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +1 -1
- package/dist/durable/index.d.ts +1 -1
- package/dist/durable/index.js +122 -32
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +152 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -21
- package/dist/index.d.ts +68 -21
- package/dist/index.js +157 -40
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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) => {
|
|
@@ -6452,7 +6542,10 @@ var AgUiRunHandler = class {
|
|
|
6452
6542
|
if (owner !== null && owner !== actor.id) {
|
|
6453
6543
|
throw new import_common28.ForbiddenException("thread belongs to another actor");
|
|
6454
6544
|
}
|
|
6455
|
-
if (
|
|
6545
|
+
if (forwarded.regenerate === true && owner === null) {
|
|
6546
|
+
throw invalid("invalid_input", "regenerate needs a thread that already has an exchange");
|
|
6547
|
+
}
|
|
6548
|
+
if (owner !== null && forwarded.regenerate !== true && turn.media.length === 0 && turn.staged.length === 0) {
|
|
6456
6549
|
const decision = await this.agent.handleTextDecision(input.threadId, actor, turn.text);
|
|
6457
6550
|
if ("proposalDecision" in decision) {
|
|
6458
6551
|
this.writeEvents(res, [
|
|
@@ -6467,7 +6560,7 @@ var AgUiRunHandler = class {
|
|
|
6467
6560
|
return;
|
|
6468
6561
|
}
|
|
6469
6562
|
}
|
|
6470
|
-
const refs = await this.stage(actor, turn.media, warnings);
|
|
6563
|
+
const refs = await this.stage(actor, turn.media, warnings, turn.staged);
|
|
6471
6564
|
if (turn.text.trim().length === 0 && refs.length === 0) {
|
|
6472
6565
|
throw invalid("no_user_message", "the user message to answer is empty");
|
|
6473
6566
|
}
|
|
@@ -6503,6 +6596,9 @@ var AgUiRunHandler = class {
|
|
|
6503
6596
|
} : {},
|
|
6504
6597
|
...refs.length > 0 ? {
|
|
6505
6598
|
attachments: refs
|
|
6599
|
+
} : {},
|
|
6600
|
+
...forwarded.regenerate === true ? {
|
|
6601
|
+
regenerate: true
|
|
6506
6602
|
} : {}
|
|
6507
6603
|
});
|
|
6508
6604
|
await this.pipe(res, started.runId, {
|
|
@@ -6514,10 +6610,25 @@ var AgUiRunHandler = class {
|
|
|
6514
6610
|
preamble: warningEvents(warnings)
|
|
6515
6611
|
});
|
|
6516
6612
|
}
|
|
6517
|
-
/**
|
|
6518
|
-
|
|
6613
|
+
/**
|
|
6614
|
+
* The turn's attachments: the ones already staged (by mediaId), then its inline media staged
|
|
6615
|
+
* through the bound store — all within the store's limits; say what was not used.
|
|
6616
|
+
*/
|
|
6617
|
+
async stage(actor, media, warnings, staged = []) {
|
|
6519
6618
|
const refs = [];
|
|
6520
6619
|
const limits = attachmentLimits(this.options, this.staging);
|
|
6620
|
+
for (const ref of staged) {
|
|
6621
|
+
const label = `The attachment ${ref.mediaId} was not used`;
|
|
6622
|
+
if (this.staging === void 0) {
|
|
6623
|
+
warnings.push(`${label}: attachments are not enabled on this agent.`);
|
|
6624
|
+
} else if (refs.length >= limits.maxPerMessage) {
|
|
6625
|
+
warnings.push(`${label}: a message carries at most ${limits.maxPerMessage} attachments.`);
|
|
6626
|
+
} else {
|
|
6627
|
+
refs.push({
|
|
6628
|
+
mediaId: ref.mediaId
|
|
6629
|
+
});
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6521
6632
|
for (const item of media) {
|
|
6522
6633
|
const label = `The ${item.kind} "${item.filename}" was not used`;
|
|
6523
6634
|
if (this.staging === void 0) {
|
|
@@ -6530,7 +6641,7 @@ var AgUiRunHandler = class {
|
|
|
6530
6641
|
warnings.push(`${label}: it is larger than ${limits.maxBytes} bytes.`);
|
|
6531
6642
|
} else {
|
|
6532
6643
|
try {
|
|
6533
|
-
const
|
|
6644
|
+
const staged2 = await this.staging.stage({
|
|
6534
6645
|
data: item.data,
|
|
6535
6646
|
filename: item.filename,
|
|
6536
6647
|
contentType: item.contentType,
|
|
@@ -6538,7 +6649,7 @@ var AgUiRunHandler = class {
|
|
|
6538
6649
|
actor
|
|
6539
6650
|
});
|
|
6540
6651
|
refs.push({
|
|
6541
|
-
mediaId:
|
|
6652
|
+
mediaId: staged2.mediaId
|
|
6542
6653
|
});
|
|
6543
6654
|
} catch {
|
|
6544
6655
|
warnings.push(`${label}: it could not be stored.`);
|
|
@@ -6656,11 +6767,17 @@ var AgUiRunHandler = class {
|
|
|
6656
6767
|
res.setHeader("X-Agent-Run-Id", runId);
|
|
6657
6768
|
res.flushHeaders();
|
|
6658
6769
|
let terminal = false;
|
|
6770
|
+
const cursor = {
|
|
6771
|
+
seq: 0
|
|
6772
|
+
};
|
|
6659
6773
|
try {
|
|
6660
6774
|
const frames = (0, import_ag_ui.agUiFramesFromNdjson)(this.agent.subscribe(runId));
|
|
6661
|
-
for await (const event of (0, import_ag_ui.agUiEvents)(frames,
|
|
6775
|
+
for await (const event of (0, import_ag_ui.agUiEvents)(frames, {
|
|
6776
|
+
...options,
|
|
6777
|
+
cursor
|
|
6778
|
+
})) {
|
|
6662
6779
|
if (event.type === "RUN_FINISHED" || event.type === "RUN_ERROR") terminal = true;
|
|
6663
|
-
res.write((0, import_ag_ui.agUiSse)(event));
|
|
6780
|
+
res.write((0, import_ag_ui.agUiSse)(event, cursor.seq));
|
|
6664
6781
|
}
|
|
6665
6782
|
} catch {
|
|
6666
6783
|
if (!terminal) {
|