@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/durable/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentStore, AgentRunInput, AgentLoopResult, LlmStepEnvelope, BufferedModelTurnResult, ToolStepEnvelope, AgentRunner, TokenStreamSink, HumanReply } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
|
|
3
|
-
import { A as AgentDepsFactory, a as AgentSurface, b as AgentModuleOptions } from '../agent-deps.factory-
|
|
3
|
+
import { A as AgentDepsFactory, a as AgentSurface, b as AgentModuleOptions } from '../agent-deps.factory-CUyVPsa1.cjs';
|
|
4
4
|
import { WorkflowService } from '@dudousxd/nestjs-durable';
|
|
5
5
|
import { DynamicModule } from '@nestjs/common';
|
|
6
6
|
import '@standard-schema/spec';
|
package/dist/durable/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentStore, AgentRunInput, AgentLoopResult, LlmStepEnvelope, BufferedModelTurnResult, ToolStepEnvelope, AgentRunner, TokenStreamSink, HumanReply } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
|
|
3
|
-
import { A as AgentDepsFactory, a as AgentSurface, b as AgentModuleOptions } from '../agent-deps.factory-
|
|
3
|
+
import { A as AgentDepsFactory, a as AgentSurface, b as AgentModuleOptions } from '../agent-deps.factory-CUyVPsa1.js';
|
|
4
4
|
import { WorkflowService } from '@dudousxd/nestjs-durable';
|
|
5
5
|
import { DynamicModule } from '@nestjs/common';
|
|
6
6
|
import '@standard-schema/spec';
|
package/dist/durable/index.js
CHANGED
|
@@ -276,7 +276,21 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
276
276
|
runId: ctx.runId,
|
|
277
277
|
// A child forwards into the top-level sink but must not end/fail it (the top-level run owns it).
|
|
278
278
|
openSink: /* @__PURE__ */ __name(async () => input.sinkRunId !== void 0 ? childSinkWriter(await deps.sink.open(sinkRunId)) : deps.sink.open(ctx.runId), "openSink"),
|
|
279
|
-
|
|
279
|
+
// A policy's time to live becomes the signal wait's own timeout: the runtime journals the
|
|
280
|
+
// deadline on the first call (a position of its own, reached only for a call the journal says
|
|
281
|
+
// has a ttl) and wakes the run when it passes. The lapse comes back as a Decision rather than a
|
|
282
|
+
// throw, so the loop settles the call `expired` on its ordinary rejection checkpoint.
|
|
283
|
+
awaitApproval: /* @__PURE__ */ __name((call, _toolCtx, opts) => opts?.timeoutMs === void 0 ? ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`) : ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`, {
|
|
284
|
+
timeoutMs: opts.timeoutMs
|
|
285
|
+
}).catch((error) => {
|
|
286
|
+
if (isSignalTimeout(error)) {
|
|
287
|
+
return {
|
|
288
|
+
approved: false,
|
|
289
|
+
expired: true
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
throw error;
|
|
293
|
+
}), "awaitApproval"),
|
|
280
294
|
// The same wait, on the same signal key, carrying a typed answer instead of a yes/no — so a
|
|
281
295
|
// question set and an approval reach a parked run through one path, and the HTTP surface that
|
|
282
296
|
// settles either one is the same `POST /agent/tool-call/*` family.
|
|
@@ -450,6 +464,10 @@ AgentRunWorkflow = _ts_decorate2([
|
|
|
450
464
|
typeof RunGateway === "undefined" ? Object : RunGateway
|
|
451
465
|
])
|
|
452
466
|
], AgentRunWorkflow);
|
|
467
|
+
function isSignalTimeout(error) {
|
|
468
|
+
return error instanceof Error && error.name === "SignalTimeoutError";
|
|
469
|
+
}
|
|
470
|
+
__name(isSignalTimeout, "isSignalTimeout");
|
|
453
471
|
|
|
454
472
|
// src/durable/durable-agent-runner.ts
|
|
455
473
|
import { randomUUID } from "crypto";
|
|
@@ -880,6 +898,9 @@ var AgentDepsFactory2 = class {
|
|
|
880
898
|
store: this.store,
|
|
881
899
|
sink: this.sink,
|
|
882
900
|
rolesPolicy: this.rolesPolicy,
|
|
901
|
+
...this.options.approvalPolicy !== void 0 ? {
|
|
902
|
+
approvalPolicy: this.options.approvalPolicy
|
|
903
|
+
} : {},
|
|
883
904
|
registry: this.registry,
|
|
884
905
|
promptContributors: this.promptContributors,
|
|
885
906
|
systemPrompt: definition?.systemPrompt ?? "You are a helpful assistant.",
|
|
@@ -1027,8 +1048,8 @@ AgentDepsFactory2 = _ts_decorate5([
|
|
|
1027
1048
|
], AgentDepsFactory2);
|
|
1028
1049
|
|
|
1029
1050
|
// src/agent.service.ts
|
|
1030
|
-
import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_QUOTA_STORE as AGENT_QUOTA_STORE2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE4 } from "@dudousxd/nestjs-agent-core";
|
|
1031
|
-
import { BadRequestException, ForbiddenException, Inject as Inject5, Injectable as Injectable5, NotFoundException, NotImplementedException, Optional as Optional3 } from "@nestjs/common";
|
|
1051
|
+
import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_QUOTA_STORE as AGENT_QUOTA_STORE2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE4, REQUESTER_APPROVER, mayDecideApproval } from "@dudousxd/nestjs-agent-core";
|
|
1052
|
+
import { BadRequestException, ForbiddenException, GoneException, Inject as Inject5, Injectable as Injectable5, NotFoundException, NotImplementedException, Optional as Optional3 } from "@nestjs/common";
|
|
1032
1053
|
function _ts_decorate6(decorators, target, key, desc) {
|
|
1033
1054
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1034
1055
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -1163,18 +1184,35 @@ var AgentService = class {
|
|
|
1163
1184
|
await this.assertOwnsActiveStream(actor, runId);
|
|
1164
1185
|
return this.subscribe(runId);
|
|
1165
1186
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1187
|
+
/**
|
|
1188
|
+
* Approve a parked action call as `actor`. Who may is the call's recorded approver's business
|
|
1189
|
+
* (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
|
|
1190
|
+
* thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
|
|
1191
|
+
* persisted with the call.
|
|
1192
|
+
*/
|
|
1193
|
+
async approve(actor, toolCallId2, opts = {}) {
|
|
1194
|
+
await this.assertMayDecide(actor, toolCallId2);
|
|
1168
1195
|
return this.signalToolCall(toolCallId2, {
|
|
1169
|
-
approved: true
|
|
1196
|
+
approved: true,
|
|
1197
|
+
executedByRef: actor.id,
|
|
1198
|
+
...opts.remember === true ? {
|
|
1199
|
+
remember: true
|
|
1200
|
+
} : {},
|
|
1201
|
+
...opts.via !== void 0 ? {
|
|
1202
|
+
decidedVia: opts.via
|
|
1203
|
+
} : {}
|
|
1170
1204
|
});
|
|
1171
1205
|
}
|
|
1172
|
-
async reject(actor, toolCallId2, reason) {
|
|
1173
|
-
await this.
|
|
1206
|
+
async reject(actor, toolCallId2, reason, opts = {}) {
|
|
1207
|
+
await this.assertMayDecide(actor, toolCallId2);
|
|
1174
1208
|
return this.signalToolCall(toolCallId2, {
|
|
1175
1209
|
approved: false,
|
|
1210
|
+
executedByRef: actor.id,
|
|
1176
1211
|
...reason !== void 0 ? {
|
|
1177
1212
|
reason
|
|
1213
|
+
} : {},
|
|
1214
|
+
...opts.via !== void 0 ? {
|
|
1215
|
+
decidedVia: opts.via
|
|
1178
1216
|
} : {}
|
|
1179
1217
|
});
|
|
1180
1218
|
}
|
|
@@ -1215,6 +1253,7 @@ var AgentService = class {
|
|
|
1215
1253
|
* not the thread's own actor).
|
|
1216
1254
|
*/
|
|
1217
1255
|
async signalToolCall(toolCallId2, reply) {
|
|
1256
|
+
await this.assertNotExpired(toolCallId2);
|
|
1218
1257
|
const runId = await this.resolveRunForToolCall(toolCallId2);
|
|
1219
1258
|
return this.runner.signal(runId, toolCallId2, reply);
|
|
1220
1259
|
}
|
|
@@ -1453,6 +1492,47 @@ var AgentService = class {
|
|
|
1453
1492
|
}
|
|
1454
1493
|
}
|
|
1455
1494
|
/**
|
|
1495
|
+
* Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
|
|
1496
|
+
* the requester (the default, and every call recorded before approvers existed) is the thread's
|
|
1497
|
+
* own actor — the ownership check this always was; any other approver goes through the policy's
|
|
1498
|
+
* `canDecide`, which by default asks whether the actor holds that role.
|
|
1499
|
+
*/
|
|
1500
|
+
async assertMayDecide(actor, toolCallId2) {
|
|
1501
|
+
const approval = await this.store.toolCallApproval?.(toolCallId2);
|
|
1502
|
+
const approver = approval?.approver ?? REQUESTER_APPROVER;
|
|
1503
|
+
if (approver === REQUESTER_APPROVER) {
|
|
1504
|
+
return this.assertOwnsToolCall(actor, toolCallId2);
|
|
1505
|
+
}
|
|
1506
|
+
const requesterRef = await this.store.ownerOfToolCall(toolCallId2);
|
|
1507
|
+
if (requesterRef === null) {
|
|
1508
|
+
throw new NotFoundException(`tool call ${toolCallId2} not found`);
|
|
1509
|
+
}
|
|
1510
|
+
const allowed = await mayDecideApproval(this.deps.forAgent().approvalPolicy, actor, {
|
|
1511
|
+
toolCallId: toolCallId2,
|
|
1512
|
+
approver,
|
|
1513
|
+
requesterRef
|
|
1514
|
+
});
|
|
1515
|
+
if (!allowed) {
|
|
1516
|
+
throw new ForbiddenException(`this approval is for ${approver}`);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
|
|
1521
|
+
* `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
|
|
1522
|
+
* a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
|
|
1523
|
+
* later replay of the run reads back.
|
|
1524
|
+
*/
|
|
1525
|
+
async assertNotExpired(toolCallId2) {
|
|
1526
|
+
const approval = await this.store.toolCallApproval?.(toolCallId2);
|
|
1527
|
+
if (approval === null || approval === void 0) {
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
const lapsed = approval.status === "expired" || approval.status === "pending_approval" && approval.expiresAt !== null && Date.parse(approval.expiresAt) <= Date.now();
|
|
1531
|
+
if (lapsed) {
|
|
1532
|
+
throw new GoneException(`the approval request for tool call ${toolCallId2} has expired`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1456
1536
|
* Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
|
|
1457
1537
|
* never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
|
|
1458
1538
|
* call belongs to a delegated agent. The store answers from the CALL'S OWN `runId` where it has
|
|
@@ -1530,6 +1610,12 @@ var AgentApprovalPortAdapter = class {
|
|
|
1530
1610
|
approved: true,
|
|
1531
1611
|
...opts?.executedByRef !== void 0 ? {
|
|
1532
1612
|
executedByRef: opts.executedByRef
|
|
1613
|
+
} : {},
|
|
1614
|
+
...opts?.remember === true ? {
|
|
1615
|
+
remember: true
|
|
1616
|
+
} : {},
|
|
1617
|
+
...opts?.decidedVia !== void 0 ? {
|
|
1618
|
+
decidedVia: opts.decidedVia
|
|
1533
1619
|
} : {}
|
|
1534
1620
|
});
|
|
1535
1621
|
}
|
|
@@ -1541,6 +1627,9 @@ var AgentApprovalPortAdapter = class {
|
|
|
1541
1627
|
} : {},
|
|
1542
1628
|
...opts?.reason !== void 0 ? {
|
|
1543
1629
|
reason: opts.reason
|
|
1630
|
+
} : {},
|
|
1631
|
+
...opts?.decidedVia !== void 0 ? {
|
|
1632
|
+
decidedVia: opts.decidedVia
|
|
1544
1633
|
} : {}
|
|
1545
1634
|
});
|
|
1546
1635
|
}
|
|
@@ -2460,6 +2549,8 @@ var MAX_ANSWERED_QUESTIONS = 100;
|
|
|
2460
2549
|
var MAX_VALUES_PER_QUESTION = 100;
|
|
2461
2550
|
var MAX_ANSWER_VALUE_LENGTH = 4096;
|
|
2462
2551
|
var MAX_REJECT_REASON_LENGTH = 4096;
|
|
2552
|
+
var MAX_VIA_LENGTH = 64;
|
|
2553
|
+
var DEFAULT_VIA = "web";
|
|
2463
2554
|
function toolCallId(claimed) {
|
|
2464
2555
|
if (typeof claimed !== "string" || claimed.length === 0) {
|
|
2465
2556
|
throw new BadRequestException4("toolCallId must be a non-empty string");
|
|
@@ -2480,6 +2571,26 @@ function rejectReason(claimed) {
|
|
|
2480
2571
|
return claimed;
|
|
2481
2572
|
}
|
|
2482
2573
|
__name(rejectReason, "rejectReason");
|
|
2574
|
+
function remember(claimed) {
|
|
2575
|
+
if (claimed === void 0) {
|
|
2576
|
+
return false;
|
|
2577
|
+
}
|
|
2578
|
+
if (typeof claimed !== "boolean") {
|
|
2579
|
+
throw new BadRequestException4("remember must be a boolean");
|
|
2580
|
+
}
|
|
2581
|
+
return claimed;
|
|
2582
|
+
}
|
|
2583
|
+
__name(remember, "remember");
|
|
2584
|
+
function via(claimed) {
|
|
2585
|
+
if (claimed === void 0) {
|
|
2586
|
+
return DEFAULT_VIA;
|
|
2587
|
+
}
|
|
2588
|
+
if (typeof claimed !== "string" || claimed.length === 0 || claimed.length > MAX_VIA_LENGTH) {
|
|
2589
|
+
throw new BadRequestException4(`via must be a string of 1-${MAX_VIA_LENGTH} characters`);
|
|
2590
|
+
}
|
|
2591
|
+
return claimed;
|
|
2592
|
+
}
|
|
2593
|
+
__name(via, "via");
|
|
2483
2594
|
function answers(claimed) {
|
|
2484
2595
|
if (claimed === void 0) {
|
|
2485
2596
|
return void 0;
|
|
@@ -2524,14 +2635,19 @@ var ToolCallController = class {
|
|
|
2524
2635
|
}
|
|
2525
2636
|
async approve(req, body) {
|
|
2526
2637
|
const actor = await this.actorResolver.resolve(req);
|
|
2527
|
-
await this.agent.approve(actor, toolCallId(body.toolCallId)
|
|
2638
|
+
await this.agent.approve(actor, toolCallId(body.toolCallId), {
|
|
2639
|
+
remember: remember(body.remember),
|
|
2640
|
+
via: via(body.via)
|
|
2641
|
+
});
|
|
2528
2642
|
return {
|
|
2529
2643
|
ok: true
|
|
2530
2644
|
};
|
|
2531
2645
|
}
|
|
2532
2646
|
async reject(req, body) {
|
|
2533
2647
|
const actor = await this.actorResolver.resolve(req);
|
|
2534
|
-
await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason)
|
|
2648
|
+
await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason), {
|
|
2649
|
+
via: via(body.via)
|
|
2650
|
+
});
|
|
2535
2651
|
return {
|
|
2536
2652
|
ok: true
|
|
2537
2653
|
};
|
|
@@ -2597,7 +2713,7 @@ _ts_decorate15([
|
|
|
2597
2713
|
_ts_metadata14("design:type", Function),
|
|
2598
2714
|
_ts_metadata14("design:paramtypes", [
|
|
2599
2715
|
typeof Request === "undefined" ? Object : Request,
|
|
2600
|
-
|
|
2716
|
+
Object
|
|
2601
2717
|
]),
|
|
2602
2718
|
_ts_metadata14("design:returntype", Promise)
|
|
2603
2719
|
], ToolCallController.prototype, "skip", null);
|
|
@@ -3342,6 +3458,36 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3342
3458
|
* shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
|
|
3343
3459
|
* cancel settles the wait too, by rejecting it.
|
|
3344
3460
|
*/
|
|
3461
|
+
/**
|
|
3462
|
+
* {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
|
|
3463
|
+
* settles the wait as `expired` — the same Decision the durable runner builds from its signal
|
|
3464
|
+
* timeout — and a decision that arrives first disarms it.
|
|
3465
|
+
*/
|
|
3466
|
+
parkApproval(runId, toolCallId2, timeoutMs) {
|
|
3467
|
+
const waiting = this.park(runId, toolCallId2);
|
|
3468
|
+
if (timeoutMs === void 0) {
|
|
3469
|
+
return waiting;
|
|
3470
|
+
}
|
|
3471
|
+
const key = `${runId}:${toolCallId2}`;
|
|
3472
|
+
let timer;
|
|
3473
|
+
const lapse = new Promise((resolve) => {
|
|
3474
|
+
timer = setTimeout(() => {
|
|
3475
|
+
const entry = this.pending.get(key);
|
|
3476
|
+
if (entry !== void 0) {
|
|
3477
|
+
this.pending.delete(key);
|
|
3478
|
+
resolve({
|
|
3479
|
+
approved: false,
|
|
3480
|
+
expired: true
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
}, timeoutMs);
|
|
3484
|
+
timer.unref?.();
|
|
3485
|
+
});
|
|
3486
|
+
return Promise.race([
|
|
3487
|
+
waiting,
|
|
3488
|
+
lapse
|
|
3489
|
+
]).finally(() => clearTimeout(timer));
|
|
3490
|
+
}
|
|
3345
3491
|
park(runId, toolCallId2) {
|
|
3346
3492
|
return new Promise((resolve, reject) => {
|
|
3347
3493
|
this.pending.set(`${runId}:${toolCallId2}`, {
|
|
@@ -3355,7 +3501,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3355
3501
|
return {
|
|
3356
3502
|
runId,
|
|
3357
3503
|
openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
|
|
3358
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3504
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
3359
3505
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
3360
3506
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
3361
3507
|
parallel: settleAll2,
|
|
@@ -3406,7 +3552,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3406
3552
|
const hooks = {
|
|
3407
3553
|
runId,
|
|
3408
3554
|
openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
|
|
3409
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3555
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
3410
3556
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
3411
3557
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
3412
3558
|
parallel: settleAll2,
|
|
@@ -3510,7 +3656,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3510
3656
|
runId,
|
|
3511
3657
|
// Forward into the top-level stream; the top-level run owns end/fail on that shared sink.
|
|
3512
3658
|
openSink: /* @__PURE__ */ __name(async () => childSinkWriter(await deps.sink.open(sinkRunId)), "openSink"),
|
|
3513
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3659
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
3514
3660
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
3515
3661
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
3516
3662
|
parallel: settleAll2,
|