@dudousxd/nestjs-agent 1.2.0 → 1.3.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/README.md +30 -0
- package/dist/{agent-deps.factory-OjrLyAZo.d.cts → agent-deps.factory-CUyVPsa1.d.cts} +10 -1
- package/dist/{agent-deps.factory-OjrLyAZo.d.ts → agent-deps.factory-CUyVPsa1.d.ts} +10 -1
- package/dist/durable/index.cjs +158 -12
- 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 +160 -14
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +139 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -4
- package/dist/index.d.ts +38 -4
- package/dist/index.js +141 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -184,6 +184,9 @@ var AgentDepsFactory2 = class {
|
|
|
184
184
|
store: this.store,
|
|
185
185
|
sink: this.sink,
|
|
186
186
|
rolesPolicy: this.rolesPolicy,
|
|
187
|
+
...this.options.approvalPolicy !== void 0 ? {
|
|
188
|
+
approvalPolicy: this.options.approvalPolicy
|
|
189
|
+
} : {},
|
|
187
190
|
registry: this.registry,
|
|
188
191
|
promptContributors: this.promptContributors,
|
|
189
192
|
systemPrompt: definition?.systemPrompt ?? "You are a helpful assistant.",
|
|
@@ -485,18 +488,35 @@ var AgentService = class {
|
|
|
485
488
|
await this.assertOwnsActiveStream(actor, runId);
|
|
486
489
|
return this.subscribe(runId);
|
|
487
490
|
}
|
|
488
|
-
|
|
489
|
-
|
|
491
|
+
/**
|
|
492
|
+
* Approve a parked action call as `actor`. Who may is the call's recorded approver's business
|
|
493
|
+
* (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
|
|
494
|
+
* thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
|
|
495
|
+
* persisted with the call.
|
|
496
|
+
*/
|
|
497
|
+
async approve(actor, toolCallId2, opts = {}) {
|
|
498
|
+
await this.assertMayDecide(actor, toolCallId2);
|
|
490
499
|
return this.signalToolCall(toolCallId2, {
|
|
491
|
-
approved: true
|
|
500
|
+
approved: true,
|
|
501
|
+
executedByRef: actor.id,
|
|
502
|
+
...opts.remember === true ? {
|
|
503
|
+
remember: true
|
|
504
|
+
} : {},
|
|
505
|
+
...opts.via !== void 0 ? {
|
|
506
|
+
decidedVia: opts.via
|
|
507
|
+
} : {}
|
|
492
508
|
});
|
|
493
509
|
}
|
|
494
|
-
async reject(actor, toolCallId2, reason) {
|
|
495
|
-
await this.
|
|
510
|
+
async reject(actor, toolCallId2, reason, opts = {}) {
|
|
511
|
+
await this.assertMayDecide(actor, toolCallId2);
|
|
496
512
|
return this.signalToolCall(toolCallId2, {
|
|
497
513
|
approved: false,
|
|
514
|
+
executedByRef: actor.id,
|
|
498
515
|
...reason !== void 0 ? {
|
|
499
516
|
reason
|
|
517
|
+
} : {},
|
|
518
|
+
...opts.via !== void 0 ? {
|
|
519
|
+
decidedVia: opts.via
|
|
500
520
|
} : {}
|
|
501
521
|
});
|
|
502
522
|
}
|
|
@@ -537,6 +557,7 @@ var AgentService = class {
|
|
|
537
557
|
* not the thread's own actor).
|
|
538
558
|
*/
|
|
539
559
|
async signalToolCall(toolCallId2, reply) {
|
|
560
|
+
await this.assertNotExpired(toolCallId2);
|
|
540
561
|
const runId = await this.resolveRunForToolCall(toolCallId2);
|
|
541
562
|
return this.runner.signal(runId, toolCallId2, reply);
|
|
542
563
|
}
|
|
@@ -775,6 +796,47 @@ var AgentService = class {
|
|
|
775
796
|
}
|
|
776
797
|
}
|
|
777
798
|
/**
|
|
799
|
+
* Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
|
|
800
|
+
* the requester (the default, and every call recorded before approvers existed) is the thread's
|
|
801
|
+
* own actor — the ownership check this always was; any other approver goes through the policy's
|
|
802
|
+
* `canDecide`, which by default asks whether the actor holds that role.
|
|
803
|
+
*/
|
|
804
|
+
async assertMayDecide(actor, toolCallId2) {
|
|
805
|
+
const approval = await this.store.toolCallApproval?.(toolCallId2);
|
|
806
|
+
const approver = approval?.approver ?? import_nestjs_agent_core3.REQUESTER_APPROVER;
|
|
807
|
+
if (approver === import_nestjs_agent_core3.REQUESTER_APPROVER) {
|
|
808
|
+
return this.assertOwnsToolCall(actor, toolCallId2);
|
|
809
|
+
}
|
|
810
|
+
const requesterRef = await this.store.ownerOfToolCall(toolCallId2);
|
|
811
|
+
if (requesterRef === null) {
|
|
812
|
+
throw new import_common2.NotFoundException(`tool call ${toolCallId2} not found`);
|
|
813
|
+
}
|
|
814
|
+
const allowed = await (0, import_nestjs_agent_core3.mayDecideApproval)(this.deps.forAgent().approvalPolicy, actor, {
|
|
815
|
+
toolCallId: toolCallId2,
|
|
816
|
+
approver,
|
|
817
|
+
requesterRef
|
|
818
|
+
});
|
|
819
|
+
if (!allowed) {
|
|
820
|
+
throw new import_common2.ForbiddenException(`this approval is for ${approver}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
|
|
825
|
+
* `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
|
|
826
|
+
* a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
|
|
827
|
+
* later replay of the run reads back.
|
|
828
|
+
*/
|
|
829
|
+
async assertNotExpired(toolCallId2) {
|
|
830
|
+
const approval = await this.store.toolCallApproval?.(toolCallId2);
|
|
831
|
+
if (approval === null || approval === void 0) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const lapsed = approval.status === "expired" || approval.status === "pending_approval" && approval.expiresAt !== null && Date.parse(approval.expiresAt) <= Date.now();
|
|
835
|
+
if (lapsed) {
|
|
836
|
+
throw new import_common2.GoneException(`the approval request for tool call ${toolCallId2} has expired`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
778
840
|
* Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
|
|
779
841
|
* never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
|
|
780
842
|
* call belongs to a delegated agent. The store answers from the CALL'S OWN `runId` where it has
|
|
@@ -852,6 +914,12 @@ var AgentApprovalPortAdapter = class {
|
|
|
852
914
|
approved: true,
|
|
853
915
|
...opts?.executedByRef !== void 0 ? {
|
|
854
916
|
executedByRef: opts.executedByRef
|
|
917
|
+
} : {},
|
|
918
|
+
...opts?.remember === true ? {
|
|
919
|
+
remember: true
|
|
920
|
+
} : {},
|
|
921
|
+
...opts?.decidedVia !== void 0 ? {
|
|
922
|
+
decidedVia: opts.decidedVia
|
|
855
923
|
} : {}
|
|
856
924
|
});
|
|
857
925
|
}
|
|
@@ -863,6 +931,9 @@ var AgentApprovalPortAdapter = class {
|
|
|
863
931
|
} : {},
|
|
864
932
|
...opts?.reason !== void 0 ? {
|
|
865
933
|
reason: opts.reason
|
|
934
|
+
} : {},
|
|
935
|
+
...opts?.decidedVia !== void 0 ? {
|
|
936
|
+
decidedVia: opts.decidedVia
|
|
866
937
|
} : {}
|
|
867
938
|
});
|
|
868
939
|
}
|
|
@@ -1782,6 +1853,8 @@ var MAX_ANSWERED_QUESTIONS = 100;
|
|
|
1782
1853
|
var MAX_VALUES_PER_QUESTION = 100;
|
|
1783
1854
|
var MAX_ANSWER_VALUE_LENGTH = 4096;
|
|
1784
1855
|
var MAX_REJECT_REASON_LENGTH = 4096;
|
|
1856
|
+
var MAX_VIA_LENGTH = 64;
|
|
1857
|
+
var DEFAULT_VIA = "web";
|
|
1785
1858
|
function toolCallId(claimed) {
|
|
1786
1859
|
if (typeof claimed !== "string" || claimed.length === 0) {
|
|
1787
1860
|
throw new import_common11.BadRequestException("toolCallId must be a non-empty string");
|
|
@@ -1802,6 +1875,26 @@ function rejectReason(claimed) {
|
|
|
1802
1875
|
return claimed;
|
|
1803
1876
|
}
|
|
1804
1877
|
__name(rejectReason, "rejectReason");
|
|
1878
|
+
function remember(claimed) {
|
|
1879
|
+
if (claimed === void 0) {
|
|
1880
|
+
return false;
|
|
1881
|
+
}
|
|
1882
|
+
if (typeof claimed !== "boolean") {
|
|
1883
|
+
throw new import_common11.BadRequestException("remember must be a boolean");
|
|
1884
|
+
}
|
|
1885
|
+
return claimed;
|
|
1886
|
+
}
|
|
1887
|
+
__name(remember, "remember");
|
|
1888
|
+
function via(claimed) {
|
|
1889
|
+
if (claimed === void 0) {
|
|
1890
|
+
return DEFAULT_VIA;
|
|
1891
|
+
}
|
|
1892
|
+
if (typeof claimed !== "string" || claimed.length === 0 || claimed.length > MAX_VIA_LENGTH) {
|
|
1893
|
+
throw new import_common11.BadRequestException(`via must be a string of 1-${MAX_VIA_LENGTH} characters`);
|
|
1894
|
+
}
|
|
1895
|
+
return claimed;
|
|
1896
|
+
}
|
|
1897
|
+
__name(via, "via");
|
|
1805
1898
|
function answers(claimed) {
|
|
1806
1899
|
if (claimed === void 0) {
|
|
1807
1900
|
return void 0;
|
|
@@ -1846,14 +1939,19 @@ var ToolCallController = class {
|
|
|
1846
1939
|
}
|
|
1847
1940
|
async approve(req, body) {
|
|
1848
1941
|
const actor = await this.actorResolver.resolve(req);
|
|
1849
|
-
await this.agent.approve(actor, toolCallId(body.toolCallId)
|
|
1942
|
+
await this.agent.approve(actor, toolCallId(body.toolCallId), {
|
|
1943
|
+
remember: remember(body.remember),
|
|
1944
|
+
via: via(body.via)
|
|
1945
|
+
});
|
|
1850
1946
|
return {
|
|
1851
1947
|
ok: true
|
|
1852
1948
|
};
|
|
1853
1949
|
}
|
|
1854
1950
|
async reject(req, body) {
|
|
1855
1951
|
const actor = await this.actorResolver.resolve(req);
|
|
1856
|
-
await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason)
|
|
1952
|
+
await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason), {
|
|
1953
|
+
via: via(body.via)
|
|
1954
|
+
});
|
|
1857
1955
|
return {
|
|
1858
1956
|
ok: true
|
|
1859
1957
|
};
|
|
@@ -1919,7 +2017,7 @@ _ts_decorate11([
|
|
|
1919
2017
|
_ts_metadata11("design:type", Function),
|
|
1920
2018
|
_ts_metadata11("design:paramtypes", [
|
|
1921
2019
|
typeof Request === "undefined" ? Object : Request,
|
|
1922
|
-
|
|
2020
|
+
Object
|
|
1923
2021
|
]),
|
|
1924
2022
|
_ts_metadata11("design:returntype", Promise)
|
|
1925
2023
|
], ToolCallController.prototype, "skip", null);
|
|
@@ -2785,6 +2883,36 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
2785
2883
|
* shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
|
|
2786
2884
|
* cancel settles the wait too, by rejecting it.
|
|
2787
2885
|
*/
|
|
2886
|
+
/**
|
|
2887
|
+
* {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
|
|
2888
|
+
* settles the wait as `expired` — the same Decision the durable runner builds from its signal
|
|
2889
|
+
* timeout — and a decision that arrives first disarms it.
|
|
2890
|
+
*/
|
|
2891
|
+
parkApproval(runId, toolCallId2, timeoutMs) {
|
|
2892
|
+
const waiting = this.park(runId, toolCallId2);
|
|
2893
|
+
if (timeoutMs === void 0) {
|
|
2894
|
+
return waiting;
|
|
2895
|
+
}
|
|
2896
|
+
const key = `${runId}:${toolCallId2}`;
|
|
2897
|
+
let timer;
|
|
2898
|
+
const lapse = new Promise((resolve) => {
|
|
2899
|
+
timer = setTimeout(() => {
|
|
2900
|
+
const entry = this.pending.get(key);
|
|
2901
|
+
if (entry !== void 0) {
|
|
2902
|
+
this.pending.delete(key);
|
|
2903
|
+
resolve({
|
|
2904
|
+
approved: false,
|
|
2905
|
+
expired: true
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
}, timeoutMs);
|
|
2909
|
+
timer.unref?.();
|
|
2910
|
+
});
|
|
2911
|
+
return Promise.race([
|
|
2912
|
+
waiting,
|
|
2913
|
+
lapse
|
|
2914
|
+
]).finally(() => clearTimeout(timer));
|
|
2915
|
+
}
|
|
2788
2916
|
park(runId, toolCallId2) {
|
|
2789
2917
|
return new Promise((resolve, reject) => {
|
|
2790
2918
|
this.pending.set(`${runId}:${toolCallId2}`, {
|
|
@@ -2798,7 +2926,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
2798
2926
|
return {
|
|
2799
2927
|
runId,
|
|
2800
2928
|
openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
|
|
2801
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
2929
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
2802
2930
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
2803
2931
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
2804
2932
|
parallel: import_nestjs_agent_core18.settleAll,
|
|
@@ -2849,7 +2977,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
2849
2977
|
const hooks = {
|
|
2850
2978
|
runId,
|
|
2851
2979
|
openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
|
|
2852
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
2980
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
2853
2981
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
2854
2982
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
2855
2983
|
parallel: import_nestjs_agent_core18.settleAll,
|
|
@@ -2953,7 +3081,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
2953
3081
|
runId,
|
|
2954
3082
|
// Forward into the top-level stream; the top-level run owns end/fail on that shared sink.
|
|
2955
3083
|
openSink: /* @__PURE__ */ __name(async () => childSinkWriter(await deps.sink.open(sinkRunId)), "openSink"),
|
|
2956
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3084
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
2957
3085
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
2958
3086
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
2959
3087
|
parallel: import_nestjs_agent_core18.settleAll,
|