@dudousxd/nestjs-agent 1.2.0 → 1.4.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 +180 -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 +182 -14
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +161 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -4
- package/dist/index.d.ts +47 -4
- package/dist/index.js +163 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -130,6 +130,36 @@ nobody is streaming (including one that has already finished). In-process caller
|
|
|
130
130
|
authorized elsewhere use `AgentService.subscribe(runId)`; anything reachable from a request must go
|
|
131
131
|
through `AgentService.subscribeAs(actor, runId)`.
|
|
132
132
|
|
|
133
|
+
### Who approves an action, and for how long
|
|
134
|
+
|
|
135
|
+
Every `action` tool call waits on the person chatting by default. `approvalPolicy` changes that per
|
|
136
|
+
call:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
AgentModule.forRoot({
|
|
140
|
+
// …
|
|
141
|
+
approvalPolicy: {
|
|
142
|
+
requirementFor: (tool, actor) =>
|
|
143
|
+
tool.name === 'issueRefund'
|
|
144
|
+
? { required: true, approver: 'finance', ttlMs: 15 * 60_000 }
|
|
145
|
+
: { required: true, approver: 'requester' },
|
|
146
|
+
// Optional. Default: 'requester' = the thread's own actor, anything else = a role the decider holds.
|
|
147
|
+
// canDecide: (actor, { approver, requesterRef }) => gate.forUser(actor).allows('approve', approver),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `POST /agent/tool-call/approve` `{ toolCallId, remember?, via? }` and `…/reject` `{ toolCallId,
|
|
153
|
+
reason?, via? }` enforce the recorded approver (`403` otherwise) and refuse a request that has
|
|
154
|
+
lapsed (`410`). The decider is recorded as `executedByRef`, `via` as `decided_via` (`'web'` when
|
|
155
|
+
omitted; the console approvals port takes `decidedVia` too).
|
|
156
|
+
- `remember: true` approves later calls of the same tool in the same thread without asking.
|
|
157
|
+
- A `ttlMs` becomes the approval wait's timeout — `ctx.waitForSignal(…, { timeoutMs })` on the
|
|
158
|
+
durable runner, a timer inline. On expiry the call settles `expired`, the model is told nobody
|
|
159
|
+
approved it in time, and the stream carries `tool-output-denied` + `approval-settled`.
|
|
160
|
+
- The tool-call row gains `approver`, `expires_at`, `remember` and `decided_via`; a reloaded thread
|
|
161
|
+
carries them as `StoredMessage.approvals`.
|
|
162
|
+
|
|
133
163
|
### A durable turn's model call and tools run in a worker
|
|
134
164
|
|
|
135
165
|
Under `durable: true` the turn's model call and each of its tool executions are **dispatched steps**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
1
|
+
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
4
4
|
|
|
@@ -8,6 +8,8 @@ interface AgentDeps {
|
|
|
8
8
|
store: AgentStore;
|
|
9
9
|
registry: ToolRegistry;
|
|
10
10
|
rolesPolicy: RolesPolicy;
|
|
11
|
+
/** Who approves an action call, and for how long (from `forRoot({ approvalPolicy })`). Undefined → the requester, no expiry. */
|
|
12
|
+
approvalPolicy?: ApprovalPolicy;
|
|
11
13
|
quota?: QuotaStore;
|
|
12
14
|
sink: TokenStreamSink;
|
|
13
15
|
/** Fallback accounting label; the provider's turn result overrides it when set. */
|
|
@@ -205,6 +207,13 @@ interface AgentModuleOptions {
|
|
|
205
207
|
quotaLimitTokens?: number;
|
|
206
208
|
/** Tool authorization gate. Defaults to role-in-`defaultRoles`. */
|
|
207
209
|
rolesPolicy?: RolesPolicy;
|
|
210
|
+
/**
|
|
211
|
+
* Who has to approve an `action` tool call, and how long the request stays open. Defaults to the
|
|
212
|
+
* requester (the thread's own actor) with no expiry. A requirement naming another approver (a
|
|
213
|
+
* role) is enforced by `POST tool-call/approve|reject` through `canDecide` — by default, the
|
|
214
|
+
* deciding actor must hold that role. See `ApprovalPolicy`.
|
|
215
|
+
*/
|
|
216
|
+
approvalPolicy?: ApprovalPolicy;
|
|
208
217
|
/** Default roles a tool requires when its `roles` is omitted. Defaults to `['ADMIN']`. */
|
|
209
218
|
defaultRoles?: string[];
|
|
210
219
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
1
|
+
import { ModelProvider, AgentStore, ToolRegistry, RolesPolicy, ApprovalPolicy, QuotaStore, TokenStreamSink, PromptBuilder, PromptContributor, ToolTransientRetrySetting, Retriever, AgentPricingStore, HistoryPolicy, InputProcessor, OutputProcessor, AgentIntake, SkillsConfig, MemoryConfig, ToolSpec, ToolHandler, ActorResolver, AgentHistoryWindow, SkillProvider, ScopeResolver, MemoryProvider, AgentRegistry } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
3
|
import { Provider, FactoryProvider, Type, CanActivate, ModuleMetadata, InjectionToken, OptionalFactoryDependency } from '@nestjs/common';
|
|
4
4
|
|
|
@@ -8,6 +8,8 @@ interface AgentDeps {
|
|
|
8
8
|
store: AgentStore;
|
|
9
9
|
registry: ToolRegistry;
|
|
10
10
|
rolesPolicy: RolesPolicy;
|
|
11
|
+
/** Who approves an action call, and for how long (from `forRoot({ approvalPolicy })`). Undefined → the requester, no expiry. */
|
|
12
|
+
approvalPolicy?: ApprovalPolicy;
|
|
11
13
|
quota?: QuotaStore;
|
|
12
14
|
sink: TokenStreamSink;
|
|
13
15
|
/** Fallback accounting label; the provider's turn result overrides it when set. */
|
|
@@ -205,6 +207,13 @@ interface AgentModuleOptions {
|
|
|
205
207
|
quotaLimitTokens?: number;
|
|
206
208
|
/** Tool authorization gate. Defaults to role-in-`defaultRoles`. */
|
|
207
209
|
rolesPolicy?: RolesPolicy;
|
|
210
|
+
/**
|
|
211
|
+
* Who has to approve an `action` tool call, and how long the request stays open. Defaults to the
|
|
212
|
+
* requester (the thread's own actor) with no expiry. A requirement naming another approver (a
|
|
213
|
+
* role) is enforced by `POST tool-call/approve|reject` through `canDecide` — by default, the
|
|
214
|
+
* deciding actor must hold that role. See `ApprovalPolicy`.
|
|
215
|
+
*/
|
|
216
|
+
approvalPolicy?: ApprovalPolicy;
|
|
208
217
|
/** Default roles a tool requires when its `roles` is omitted. Defaults to `['ADMIN']`. */
|
|
209
218
|
defaultRoles?: string[];
|
|
210
219
|
/**
|
package/dist/durable/index.cjs
CHANGED
|
@@ -304,7 +304,21 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
304
304
|
runId: ctx.runId,
|
|
305
305
|
// A child forwards into the top-level sink but must not end/fail it (the top-level run owns it).
|
|
306
306
|
openSink: /* @__PURE__ */ __name(async () => input.sinkRunId !== void 0 ? childSinkWriter(await deps.sink.open(sinkRunId)) : deps.sink.open(ctx.runId), "openSink"),
|
|
307
|
-
|
|
307
|
+
// A policy's time to live becomes the signal wait's own timeout: the runtime journals the
|
|
308
|
+
// deadline on the first call (a position of its own, reached only for a call the journal says
|
|
309
|
+
// has a ttl) and wakes the run when it passes. The lapse comes back as a Decision rather than a
|
|
310
|
+
// throw, so the loop settles the call `expired` on its ordinary rejection checkpoint.
|
|
311
|
+
awaitApproval: /* @__PURE__ */ __name((call, _toolCtx, opts) => opts?.timeoutMs === void 0 ? ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`) : ctx.waitForSignal(`tool:${ctx.runId}:${call.id}`, {
|
|
312
|
+
timeoutMs: opts.timeoutMs
|
|
313
|
+
}).catch((error) => {
|
|
314
|
+
if (isSignalTimeout(error)) {
|
|
315
|
+
return {
|
|
316
|
+
approved: false,
|
|
317
|
+
expired: true
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
throw error;
|
|
321
|
+
}), "awaitApproval"),
|
|
308
322
|
// The same wait, on the same signal key, carrying a typed answer instead of a yes/no — so a
|
|
309
323
|
// question set and an approval reach a parked run through one path, and the HTTP surface that
|
|
310
324
|
// settles either one is the same `POST /agent/tool-call/*` family.
|
|
@@ -478,6 +492,10 @@ AgentRunWorkflow = _ts_decorate2([
|
|
|
478
492
|
typeof RunGateway === "undefined" ? Object : RunGateway
|
|
479
493
|
])
|
|
480
494
|
], AgentRunWorkflow);
|
|
495
|
+
function isSignalTimeout(error) {
|
|
496
|
+
return error instanceof Error && error.name === "SignalTimeoutError";
|
|
497
|
+
}
|
|
498
|
+
__name(isSignalTimeout, "isSignalTimeout");
|
|
481
499
|
|
|
482
500
|
// src/durable/durable-agent-runner.ts
|
|
483
501
|
var import_node_crypto = require("crypto");
|
|
@@ -908,6 +926,9 @@ var AgentDepsFactory2 = class {
|
|
|
908
926
|
store: this.store,
|
|
909
927
|
sink: this.sink,
|
|
910
928
|
rolesPolicy: this.rolesPolicy,
|
|
929
|
+
...this.options.approvalPolicy !== void 0 ? {
|
|
930
|
+
approvalPolicy: this.options.approvalPolicy
|
|
931
|
+
} : {},
|
|
911
932
|
registry: this.registry,
|
|
912
933
|
promptContributors: this.promptContributors,
|
|
913
934
|
systemPrompt: definition?.systemPrompt ?? "You are a helpful assistant.",
|
|
@@ -1191,18 +1212,35 @@ var AgentService = class {
|
|
|
1191
1212
|
await this.assertOwnsActiveStream(actor, runId);
|
|
1192
1213
|
return this.subscribe(runId);
|
|
1193
1214
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1215
|
+
/**
|
|
1216
|
+
* Approve a parked action call as `actor`. Who may is the call's recorded approver's business
|
|
1217
|
+
* (see {@link assertMayDecide}); `remember` approves later calls of the same tool in the same
|
|
1218
|
+
* thread, and `via` names the surface the decision came through (`'web'`, `'slack'`, …) — both
|
|
1219
|
+
* persisted with the call.
|
|
1220
|
+
*/
|
|
1221
|
+
async approve(actor, toolCallId2, opts = {}) {
|
|
1222
|
+
await this.assertMayDecide(actor, toolCallId2);
|
|
1196
1223
|
return this.signalToolCall(toolCallId2, {
|
|
1197
|
-
approved: true
|
|
1224
|
+
approved: true,
|
|
1225
|
+
executedByRef: actor.id,
|
|
1226
|
+
...opts.remember === true ? {
|
|
1227
|
+
remember: true
|
|
1228
|
+
} : {},
|
|
1229
|
+
...opts.via !== void 0 ? {
|
|
1230
|
+
decidedVia: opts.via
|
|
1231
|
+
} : {}
|
|
1198
1232
|
});
|
|
1199
1233
|
}
|
|
1200
|
-
async reject(actor, toolCallId2, reason) {
|
|
1201
|
-
await this.
|
|
1234
|
+
async reject(actor, toolCallId2, reason, opts = {}) {
|
|
1235
|
+
await this.assertMayDecide(actor, toolCallId2);
|
|
1202
1236
|
return this.signalToolCall(toolCallId2, {
|
|
1203
1237
|
approved: false,
|
|
1238
|
+
executedByRef: actor.id,
|
|
1204
1239
|
...reason !== void 0 ? {
|
|
1205
1240
|
reason
|
|
1241
|
+
} : {},
|
|
1242
|
+
...opts.via !== void 0 ? {
|
|
1243
|
+
decidedVia: opts.via
|
|
1206
1244
|
} : {}
|
|
1207
1245
|
});
|
|
1208
1246
|
}
|
|
@@ -1214,6 +1252,7 @@ var AgentService = class {
|
|
|
1214
1252
|
*/
|
|
1215
1253
|
async answer(actor, toolCallId2, answers2 = {}) {
|
|
1216
1254
|
await this.assertOwnsToolCall(actor, toolCallId2);
|
|
1255
|
+
await this.assertAnswersFit(toolCallId2, answers2);
|
|
1217
1256
|
const reply = {
|
|
1218
1257
|
answers: answers2,
|
|
1219
1258
|
answeredByRef: actor.id
|
|
@@ -1243,6 +1282,7 @@ var AgentService = class {
|
|
|
1243
1282
|
* not the thread's own actor).
|
|
1244
1283
|
*/
|
|
1245
1284
|
async signalToolCall(toolCallId2, reply) {
|
|
1285
|
+
await this.assertNotExpired(toolCallId2);
|
|
1246
1286
|
const runId = await this.resolveRunForToolCall(toolCallId2);
|
|
1247
1287
|
return this.runner.signal(runId, toolCallId2, reply);
|
|
1248
1288
|
}
|
|
@@ -1481,6 +1521,68 @@ var AgentService = class {
|
|
|
1481
1521
|
}
|
|
1482
1522
|
}
|
|
1483
1523
|
/**
|
|
1524
|
+
* Check a reply against the questions it answers before it is signalled: every submitted value a
|
|
1525
|
+
* question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
|
|
1526
|
+
* options), and every `required` question answered — by the reply, or by the defaults an omitted
|
|
1527
|
+
* question falls back to. A reply the loop would have to drop parts of is refused here instead, so
|
|
1528
|
+
* the person who typed it hears why. Needs the store to hand back the call's recorded questions;
|
|
1529
|
+
* without that seam the loop's own filtering is the only check.
|
|
1530
|
+
*/
|
|
1531
|
+
async assertAnswersFit(toolCallId2, answers2) {
|
|
1532
|
+
if (this.store.toolCallInput === void 0) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const questions = (0, import_nestjs_agent_core8.readElicitationQuestions)(await this.store.toolCallInput(toolCallId2));
|
|
1536
|
+
for (const question of questions) {
|
|
1537
|
+
const submitted = Object.hasOwn(answers2, question.id) ? answers2[question.id] : void 0;
|
|
1538
|
+
const problem = (0, import_nestjs_agent_core8.validateElicitationAnswer)(question, submitted ?? question.defaults ?? []);
|
|
1539
|
+
if (problem !== null) {
|
|
1540
|
+
throw new import_common6.BadRequestException(`answers["${question.id}"] ${problem}`);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
|
|
1546
|
+
* the requester (the default, and every call recorded before approvers existed) is the thread's
|
|
1547
|
+
* own actor — the ownership check this always was; any other approver goes through the policy's
|
|
1548
|
+
* `canDecide`, which by default asks whether the actor holds that role.
|
|
1549
|
+
*/
|
|
1550
|
+
async assertMayDecide(actor, toolCallId2) {
|
|
1551
|
+
const approval = await this.store.toolCallApproval?.(toolCallId2);
|
|
1552
|
+
const approver = approval?.approver ?? import_nestjs_agent_core8.REQUESTER_APPROVER;
|
|
1553
|
+
if (approver === import_nestjs_agent_core8.REQUESTER_APPROVER) {
|
|
1554
|
+
return this.assertOwnsToolCall(actor, toolCallId2);
|
|
1555
|
+
}
|
|
1556
|
+
const requesterRef = await this.store.ownerOfToolCall(toolCallId2);
|
|
1557
|
+
if (requesterRef === null) {
|
|
1558
|
+
throw new import_common6.NotFoundException(`tool call ${toolCallId2} not found`);
|
|
1559
|
+
}
|
|
1560
|
+
const allowed = await (0, import_nestjs_agent_core8.mayDecideApproval)(this.deps.forAgent().approvalPolicy, actor, {
|
|
1561
|
+
toolCallId: toolCallId2,
|
|
1562
|
+
approver,
|
|
1563
|
+
requesterRef
|
|
1564
|
+
});
|
|
1565
|
+
if (!allowed) {
|
|
1566
|
+
throw new import_common6.ForbiddenException(`this approval is for ${approver}`);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Refuse a decision on a request that has already lapsed — recorded `expired`, or past its
|
|
1571
|
+
* `expiresAt` with the run's own timer about to fire. Signalling it anyway would race the timeout:
|
|
1572
|
+
* a durable runtime buffers a signal nobody is waiting on, and a late approval must not be what a
|
|
1573
|
+
* later replay of the run reads back.
|
|
1574
|
+
*/
|
|
1575
|
+
async assertNotExpired(toolCallId2) {
|
|
1576
|
+
const approval = await this.store.toolCallApproval?.(toolCallId2);
|
|
1577
|
+
if (approval === null || approval === void 0) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
const lapsed = approval.status === "expired" || approval.status === "pending_approval" && approval.expiresAt !== null && Date.parse(approval.expiresAt) <= Date.now();
|
|
1581
|
+
if (lapsed) {
|
|
1582
|
+
throw new import_common6.GoneException(`the approval request for tool call ${toolCallId2} has expired`);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1484
1586
|
* Resolve the run awaiting `toolCallId`. Derived server-side from the tool call alone — the client
|
|
1485
1587
|
* never knows or supplies a runId, and that run is the sub-agent's own child run when the pending
|
|
1486
1588
|
* call belongs to a delegated agent. The store answers from the CALL'S OWN `runId` where it has
|
|
@@ -1558,6 +1660,12 @@ var AgentApprovalPortAdapter = class {
|
|
|
1558
1660
|
approved: true,
|
|
1559
1661
|
...opts?.executedByRef !== void 0 ? {
|
|
1560
1662
|
executedByRef: opts.executedByRef
|
|
1663
|
+
} : {},
|
|
1664
|
+
...opts?.remember === true ? {
|
|
1665
|
+
remember: true
|
|
1666
|
+
} : {},
|
|
1667
|
+
...opts?.decidedVia !== void 0 ? {
|
|
1668
|
+
decidedVia: opts.decidedVia
|
|
1561
1669
|
} : {}
|
|
1562
1670
|
});
|
|
1563
1671
|
}
|
|
@@ -1569,6 +1677,9 @@ var AgentApprovalPortAdapter = class {
|
|
|
1569
1677
|
} : {},
|
|
1570
1678
|
...opts?.reason !== void 0 ? {
|
|
1571
1679
|
reason: opts.reason
|
|
1680
|
+
} : {},
|
|
1681
|
+
...opts?.decidedVia !== void 0 ? {
|
|
1682
|
+
decidedVia: opts.decidedVia
|
|
1572
1683
|
} : {}
|
|
1573
1684
|
});
|
|
1574
1685
|
}
|
|
@@ -2488,6 +2599,8 @@ var MAX_ANSWERED_QUESTIONS = 100;
|
|
|
2488
2599
|
var MAX_VALUES_PER_QUESTION = 100;
|
|
2489
2600
|
var MAX_ANSWER_VALUE_LENGTH = 4096;
|
|
2490
2601
|
var MAX_REJECT_REASON_LENGTH = 4096;
|
|
2602
|
+
var MAX_VIA_LENGTH = 64;
|
|
2603
|
+
var DEFAULT_VIA = "web";
|
|
2491
2604
|
function toolCallId(claimed) {
|
|
2492
2605
|
if (typeof claimed !== "string" || claimed.length === 0) {
|
|
2493
2606
|
throw new import_common15.BadRequestException("toolCallId must be a non-empty string");
|
|
@@ -2508,6 +2621,26 @@ function rejectReason(claimed) {
|
|
|
2508
2621
|
return claimed;
|
|
2509
2622
|
}
|
|
2510
2623
|
__name(rejectReason, "rejectReason");
|
|
2624
|
+
function remember(claimed) {
|
|
2625
|
+
if (claimed === void 0) {
|
|
2626
|
+
return false;
|
|
2627
|
+
}
|
|
2628
|
+
if (typeof claimed !== "boolean") {
|
|
2629
|
+
throw new import_common15.BadRequestException("remember must be a boolean");
|
|
2630
|
+
}
|
|
2631
|
+
return claimed;
|
|
2632
|
+
}
|
|
2633
|
+
__name(remember, "remember");
|
|
2634
|
+
function via(claimed) {
|
|
2635
|
+
if (claimed === void 0) {
|
|
2636
|
+
return DEFAULT_VIA;
|
|
2637
|
+
}
|
|
2638
|
+
if (typeof claimed !== "string" || claimed.length === 0 || claimed.length > MAX_VIA_LENGTH) {
|
|
2639
|
+
throw new import_common15.BadRequestException(`via must be a string of 1-${MAX_VIA_LENGTH} characters`);
|
|
2640
|
+
}
|
|
2641
|
+
return claimed;
|
|
2642
|
+
}
|
|
2643
|
+
__name(via, "via");
|
|
2511
2644
|
function answers(claimed) {
|
|
2512
2645
|
if (claimed === void 0) {
|
|
2513
2646
|
return void 0;
|
|
@@ -2552,14 +2685,19 @@ var ToolCallController = class {
|
|
|
2552
2685
|
}
|
|
2553
2686
|
async approve(req, body) {
|
|
2554
2687
|
const actor = await this.actorResolver.resolve(req);
|
|
2555
|
-
await this.agent.approve(actor, toolCallId(body.toolCallId)
|
|
2688
|
+
await this.agent.approve(actor, toolCallId(body.toolCallId), {
|
|
2689
|
+
remember: remember(body.remember),
|
|
2690
|
+
via: via(body.via)
|
|
2691
|
+
});
|
|
2556
2692
|
return {
|
|
2557
2693
|
ok: true
|
|
2558
2694
|
};
|
|
2559
2695
|
}
|
|
2560
2696
|
async reject(req, body) {
|
|
2561
2697
|
const actor = await this.actorResolver.resolve(req);
|
|
2562
|
-
await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason)
|
|
2698
|
+
await this.agent.reject(actor, toolCallId(body.toolCallId), rejectReason(body.reason), {
|
|
2699
|
+
via: via(body.via)
|
|
2700
|
+
});
|
|
2563
2701
|
return {
|
|
2564
2702
|
ok: true
|
|
2565
2703
|
};
|
|
@@ -2625,7 +2763,7 @@ _ts_decorate15([
|
|
|
2625
2763
|
_ts_metadata14("design:type", Function),
|
|
2626
2764
|
_ts_metadata14("design:paramtypes", [
|
|
2627
2765
|
typeof Request === "undefined" ? Object : Request,
|
|
2628
|
-
|
|
2766
|
+
Object
|
|
2629
2767
|
]),
|
|
2630
2768
|
_ts_metadata14("design:returntype", Promise)
|
|
2631
2769
|
], ToolCallController.prototype, "skip", null);
|
|
@@ -3370,6 +3508,36 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3370
3508
|
* shape it is delivering — the loop asked for one and only ever gets what the caller sent. A
|
|
3371
3509
|
* cancel settles the wait too, by rejecting it.
|
|
3372
3510
|
*/
|
|
3511
|
+
/**
|
|
3512
|
+
* {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
|
|
3513
|
+
* settles the wait as `expired` — the same Decision the durable runner builds from its signal
|
|
3514
|
+
* timeout — and a decision that arrives first disarms it.
|
|
3515
|
+
*/
|
|
3516
|
+
parkApproval(runId, toolCallId2, timeoutMs) {
|
|
3517
|
+
const waiting = this.park(runId, toolCallId2);
|
|
3518
|
+
if (timeoutMs === void 0) {
|
|
3519
|
+
return waiting;
|
|
3520
|
+
}
|
|
3521
|
+
const key = `${runId}:${toolCallId2}`;
|
|
3522
|
+
let timer;
|
|
3523
|
+
const lapse = new Promise((resolve) => {
|
|
3524
|
+
timer = setTimeout(() => {
|
|
3525
|
+
const entry = this.pending.get(key);
|
|
3526
|
+
if (entry !== void 0) {
|
|
3527
|
+
this.pending.delete(key);
|
|
3528
|
+
resolve({
|
|
3529
|
+
approved: false,
|
|
3530
|
+
expired: true
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
}, timeoutMs);
|
|
3534
|
+
timer.unref?.();
|
|
3535
|
+
});
|
|
3536
|
+
return Promise.race([
|
|
3537
|
+
waiting,
|
|
3538
|
+
lapse
|
|
3539
|
+
]).finally(() => clearTimeout(timer));
|
|
3540
|
+
}
|
|
3373
3541
|
park(runId, toolCallId2) {
|
|
3374
3542
|
return new Promise((resolve, reject) => {
|
|
3375
3543
|
this.pending.set(`${runId}:${toolCallId2}`, {
|
|
@@ -3383,7 +3551,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3383
3551
|
return {
|
|
3384
3552
|
runId,
|
|
3385
3553
|
openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
|
|
3386
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3554
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
3387
3555
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
3388
3556
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
3389
3557
|
parallel: import_nestjs_agent_core22.settleAll,
|
|
@@ -3434,7 +3602,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3434
3602
|
const hooks = {
|
|
3435
3603
|
runId,
|
|
3436
3604
|
openSink: /* @__PURE__ */ __name(() => deps.sink.open(runId), "openSink"),
|
|
3437
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3605
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
3438
3606
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
3439
3607
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
3440
3608
|
parallel: import_nestjs_agent_core22.settleAll,
|
|
@@ -3538,7 +3706,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
|
|
|
3538
3706
|
runId,
|
|
3539
3707
|
// Forward into the top-level stream; the top-level run owns end/fail on that shared sink.
|
|
3540
3708
|
openSink: /* @__PURE__ */ __name(async () => childSinkWriter(await deps.sink.open(sinkRunId)), "openSink"),
|
|
3541
|
-
awaitApproval: /* @__PURE__ */ __name((call) => this.
|
|
3709
|
+
awaitApproval: /* @__PURE__ */ __name((call, _ctx, opts) => this.parkApproval(runId, call.id, opts?.timeoutMs), "awaitApproval"),
|
|
3542
3710
|
awaitAnswers: /* @__PURE__ */ __name((request) => this.park(runId, request.id), "awaitAnswers"),
|
|
3543
3711
|
step: /* @__PURE__ */ __name((_name, fn) => fn(), "step"),
|
|
3544
3712
|
parallel: import_nestjs_agent_core22.settleAll,
|