@dudousxd/nestjs-agent 1.36.1 → 1.37.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 +2 -2
- package/dist/a2ui/index.cjs +11 -1
- package/dist/a2ui/index.cjs.map +1 -1
- package/dist/a2ui/index.d.cts +2 -2
- package/dist/a2ui/index.d.ts +2 -2
- package/dist/a2ui/index.js +11 -1
- package/dist/a2ui/index.js.map +1 -1
- package/dist/{chat-queue.service-CzXsKVb1.d.ts → chat-queue.service-YjiKVTKE.d.ts} +2 -2
- package/dist/{chat-queue.service-CF2fcJDx.d.cts → chat-queue.service-fopUN313.d.cts} +2 -2
- package/dist/durable/index.cjs +250 -50
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +6 -18
- package/dist/durable/index.d.ts +6 -18
- package/dist/durable/index.js +250 -50
- package/dist/durable/index.js.map +1 -1
- package/dist/{index-NF93YDF4.d.cts → index-4caNqEQ-.d.cts} +29 -6
- package/dist/{index-DpS9P7so.d.ts → index-yC_Ra3CE.d.ts} +29 -6
- package/dist/index.cjs +244 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +244 -47
- package/dist/index.js.map +1 -1
- package/dist/sink-redis/index.cjs +164 -18
- package/dist/sink-redis/index.cjs.map +1 -1
- package/dist/sink-redis/index.d.cts +36 -7
- package/dist/sink-redis/index.d.ts +36 -7
- package/dist/sink-redis/index.js +165 -19
- package/dist/sink-redis/index.js.map +1 -1
- package/package.json +3 -3
package/dist/durable/index.d.cts
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
import { AgentStore, AgentRunInput, AgentLoopResult, LlmStepEnvelope, BufferedModelTurnResult, ToolStepEnvelope, AgentRunner, TokenStreamSink, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { WorkflowService } from '@dudousxd/nestjs-durable';
|
|
3
3
|
import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
|
|
4
|
-
import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-
|
|
4
|
+
import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-fopUN313.cjs';
|
|
5
5
|
import { DynamicModule } from '@nestjs/common';
|
|
6
6
|
import '@dudousxd/nestjs-agent-core/genui';
|
|
7
7
|
import '@standard-schema/spec';
|
|
8
8
|
import '../genui/index.cjs';
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* The agent turn AS a durable workflow. Persist/stream checkpoints are `ctx.localStep`s — the
|
|
12
|
-
* in-process primitive, NOT the always-dispatched `ctx.step`: their names are dynamic checkpoint
|
|
13
|
-
* identities (`persist:toolcall:<id>`), not routable worker groups, and their deps (store, sink)
|
|
14
|
-
* live in THIS workflow worker's DI. The two LONG steps (model call, tool execution) are `ctx.step`s
|
|
15
|
-
* routed to `AgentRunSteps`, so the run is never pinned to this pod while they execute — the same
|
|
16
|
-
* thing `ctx.step` means anywhere in this ecosystem, and the reason a `@AiTool` handler must
|
|
17
|
-
* establish whatever execution context it needs itself. HITL is `ctx.waitForSignal`, and sub-agent
|
|
18
|
-
* delegation is `ctx.child(AgentRunWorkflow)` — a replay-safe, observable child run (it shows up as
|
|
19
|
-
* a node in the durable dashboard). A child streams into its top-level ancestor's sink
|
|
20
|
-
* (`sinkRunId`) so the human watching the parent sees it and can approve its action tools; the
|
|
21
|
-
* approval routes to the child's own run via `runForToolCall`.
|
|
22
|
-
*/
|
|
23
10
|
declare class AgentRunWorkflow {
|
|
24
11
|
private readonly factory;
|
|
25
12
|
private readonly store;
|
|
@@ -65,10 +52,11 @@ declare class AgentRunWorkflow {
|
|
|
65
52
|
* `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
|
|
66
53
|
* `cancelled` once it has settled.
|
|
67
54
|
*
|
|
68
|
-
* Costs a run read at each of the loop's observation points (two per step)
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
55
|
+
* Costs a run read at each of the loop's observation points (two per step): the run's own row
|
|
56
|
+
* where the gateway can read it alone, else its detail, which carries every checkpoint of the run.
|
|
57
|
+
* That row is one the control plane already has, against a turn that is otherwise making model
|
|
58
|
+
* calls, and it buys the property that matters: the answer comes from a place BOTH the canceller
|
|
59
|
+
* and every pod that might replay this run can see.
|
|
72
60
|
*
|
|
73
61
|
* Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
|
|
74
62
|
* unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
|
package/dist/durable/index.d.ts
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
1
|
import { AgentStore, AgentRunInput, AgentLoopResult, LlmStepEnvelope, BufferedModelTurnResult, ToolStepEnvelope, AgentRunner, TokenStreamSink, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
import { WorkflowService } from '@dudousxd/nestjs-durable';
|
|
3
3
|
import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
|
|
4
|
-
import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-
|
|
4
|
+
import { A as AgentDepsFactory, C as ChatQueueService, a as AgentSurface, b as AgentModuleOptions } from '../chat-queue.service-YjiKVTKE.js';
|
|
5
5
|
import { DynamicModule } from '@nestjs/common';
|
|
6
6
|
import '@dudousxd/nestjs-agent-core/genui';
|
|
7
7
|
import '@standard-schema/spec';
|
|
8
8
|
import '../genui/index.js';
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* The agent turn AS a durable workflow. Persist/stream checkpoints are `ctx.localStep`s — the
|
|
12
|
-
* in-process primitive, NOT the always-dispatched `ctx.step`: their names are dynamic checkpoint
|
|
13
|
-
* identities (`persist:toolcall:<id>`), not routable worker groups, and their deps (store, sink)
|
|
14
|
-
* live in THIS workflow worker's DI. The two LONG steps (model call, tool execution) are `ctx.step`s
|
|
15
|
-
* routed to `AgentRunSteps`, so the run is never pinned to this pod while they execute — the same
|
|
16
|
-
* thing `ctx.step` means anywhere in this ecosystem, and the reason a `@AiTool` handler must
|
|
17
|
-
* establish whatever execution context it needs itself. HITL is `ctx.waitForSignal`, and sub-agent
|
|
18
|
-
* delegation is `ctx.child(AgentRunWorkflow)` — a replay-safe, observable child run (it shows up as
|
|
19
|
-
* a node in the durable dashboard). A child streams into its top-level ancestor's sink
|
|
20
|
-
* (`sinkRunId`) so the human watching the parent sees it and can approve its action tools; the
|
|
21
|
-
* approval routes to the child's own run via `runForToolCall`.
|
|
22
|
-
*/
|
|
23
10
|
declare class AgentRunWorkflow {
|
|
24
11
|
private readonly factory;
|
|
25
12
|
private readonly store;
|
|
@@ -65,10 +52,11 @@ declare class AgentRunWorkflow {
|
|
|
65
52
|
* `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
|
|
66
53
|
* `cancelled` once it has settled.
|
|
67
54
|
*
|
|
68
|
-
* Costs a run read at each of the loop's observation points (two per step)
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
55
|
+
* Costs a run read at each of the loop's observation points (two per step): the run's own row
|
|
56
|
+
* where the gateway can read it alone, else its detail, which carries every checkpoint of the run.
|
|
57
|
+
* That row is one the control plane already has, against a turn that is otherwise making model
|
|
58
|
+
* calls, and it buys the property that matters: the answer comes from a place BOTH the canceller
|
|
59
|
+
* and every pod that might replay this run can see.
|
|
72
60
|
*
|
|
73
61
|
* Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
|
|
74
62
|
* unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
|
package/dist/durable/index.js
CHANGED
|
@@ -552,10 +552,11 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
552
552
|
* `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
|
|
553
553
|
* `cancelled` once it has settled.
|
|
554
554
|
*
|
|
555
|
-
* Costs a run read at each of the loop's observation points (two per step)
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
555
|
+
* Costs a run read at each of the loop's observation points (two per step): the run's own row
|
|
556
|
+
* where the gateway can read it alone, else its detail, which carries every checkpoint of the run.
|
|
557
|
+
* That row is one the control plane already has, against a turn that is otherwise making model
|
|
558
|
+
* calls, and it buys the property that matters: the answer comes from a place BOTH the canceller
|
|
559
|
+
* and every pod that might replay this run can see.
|
|
559
560
|
*
|
|
560
561
|
* Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
|
|
561
562
|
* unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
|
|
@@ -563,8 +564,10 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
563
564
|
* can never disagree about what was seen here.
|
|
564
565
|
*/
|
|
565
566
|
async isCancelled(runId) {
|
|
567
|
+
const runs = this.runs;
|
|
566
568
|
try {
|
|
567
|
-
const
|
|
569
|
+
const run = typeof runs?.getRun === "function" ? await runs.getRun(runId) : (await runs?.getRunDetail(runId))?.run;
|
|
570
|
+
const status = run?.status;
|
|
568
571
|
return status === "cancelled" || status === "cancelling";
|
|
569
572
|
} catch {
|
|
570
573
|
return false;
|
|
@@ -876,49 +879,121 @@ import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Option
|
|
|
876
879
|
|
|
877
880
|
// src/in-process-sink.ts
|
|
878
881
|
import { AgentStreamError } from "@dudousxd/nestjs-agent-core";
|
|
882
|
+
var LIVE_TTL_MS = 36e5;
|
|
883
|
+
var ENDED_GRACE_MS = 6e4;
|
|
879
884
|
var InProcessTokenStreamSink = class {
|
|
880
885
|
static {
|
|
881
886
|
__name(this, "InProcessTokenStreamSink");
|
|
882
887
|
}
|
|
883
888
|
runs = /* @__PURE__ */ new Map();
|
|
889
|
+
/** Subscribers of a run nothing has opened yet, woken by its `open()`. */
|
|
890
|
+
waiting = /* @__PURE__ */ new Map();
|
|
884
891
|
buffer(runId) {
|
|
885
892
|
let buf = this.runs.get(runId);
|
|
886
893
|
if (buf === void 0) {
|
|
887
894
|
buf = {
|
|
888
895
|
chunks: [],
|
|
889
896
|
ended: false,
|
|
890
|
-
notify: /* @__PURE__ */ new Set()
|
|
897
|
+
notify: /* @__PURE__ */ new Set(),
|
|
898
|
+
lastWriteAt: Date.now(),
|
|
899
|
+
expiry: void 0
|
|
891
900
|
};
|
|
892
901
|
this.runs.set(runId, buf);
|
|
902
|
+
this.expireAfter(runId, buf, LIVE_TTL_MS);
|
|
903
|
+
const waiters = this.waiting.get(runId);
|
|
904
|
+
this.waiting.delete(runId);
|
|
905
|
+
for (const resolve2 of waiters ?? []) {
|
|
906
|
+
resolve2();
|
|
907
|
+
}
|
|
893
908
|
}
|
|
894
909
|
return buf;
|
|
895
910
|
}
|
|
911
|
+
expireAfter(runId, buf, ms) {
|
|
912
|
+
if (buf.expiry !== void 0) {
|
|
913
|
+
clearTimeout(buf.expiry);
|
|
914
|
+
}
|
|
915
|
+
buf.expiry = setTimeout(() => {
|
|
916
|
+
buf.expiry = void 0;
|
|
917
|
+
const idle = Date.now() - buf.lastWriteAt;
|
|
918
|
+
if (!buf.ended && idle < LIVE_TTL_MS) {
|
|
919
|
+
this.expireAfter(runId, buf, LIVE_TTL_MS - idle);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
this.drop(runId, buf);
|
|
923
|
+
}, ms);
|
|
924
|
+
buf.expiry.unref?.();
|
|
925
|
+
}
|
|
926
|
+
/** Forget the run, and let anyone still following it finish as if it had ended. */
|
|
927
|
+
drop(runId, buf) {
|
|
928
|
+
if (buf.expiry !== void 0) {
|
|
929
|
+
clearTimeout(buf.expiry);
|
|
930
|
+
buf.expiry = void 0;
|
|
931
|
+
}
|
|
932
|
+
if (this.runs.get(runId) === buf) {
|
|
933
|
+
this.runs.delete(runId);
|
|
934
|
+
}
|
|
935
|
+
buf.ended = true;
|
|
936
|
+
this.wake(buf);
|
|
937
|
+
}
|
|
896
938
|
wake(buf) {
|
|
897
939
|
for (const resolve2 of buf.notify) {
|
|
898
940
|
resolve2();
|
|
899
941
|
}
|
|
900
942
|
buf.notify.clear();
|
|
901
943
|
}
|
|
944
|
+
finish(runId, buf) {
|
|
945
|
+
buf.ended = true;
|
|
946
|
+
this.wake(buf);
|
|
947
|
+
this.expireAfter(runId, buf, ENDED_GRACE_MS);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* The run's writer. Each call finds the run's buffer afresh, so a run that went quiet past the
|
|
951
|
+
* live TTL (parked on a person) and then writes again starts a new buffer its next subscriber
|
|
952
|
+
* follows, rather than writing into one nothing reads.
|
|
953
|
+
*/
|
|
902
954
|
open(runId) {
|
|
903
|
-
|
|
955
|
+
this.buffer(runId);
|
|
904
956
|
return {
|
|
905
957
|
write: /* @__PURE__ */ __name((chunk) => {
|
|
958
|
+
const buf = this.buffer(runId);
|
|
906
959
|
buf.chunks.push(chunk);
|
|
960
|
+
buf.lastWriteAt = Date.now();
|
|
907
961
|
this.wake(buf);
|
|
908
962
|
}, "write"),
|
|
909
963
|
end: /* @__PURE__ */ __name(() => {
|
|
910
|
-
|
|
911
|
-
this.wake(buf);
|
|
964
|
+
this.finish(runId, this.buffer(runId));
|
|
912
965
|
}, "end"),
|
|
913
966
|
fail: /* @__PURE__ */ __name((error) => {
|
|
967
|
+
const buf = this.buffer(runId);
|
|
914
968
|
buf.failure = error;
|
|
915
|
-
|
|
916
|
-
this.wake(buf);
|
|
969
|
+
this.finish(runId, buf);
|
|
917
970
|
}, "fail")
|
|
918
971
|
};
|
|
919
972
|
}
|
|
920
|
-
async *subscribe(runId) {
|
|
921
|
-
const
|
|
973
|
+
async *subscribe(runId, options = {}) {
|
|
974
|
+
const { signal } = options;
|
|
975
|
+
let buf = this.runs.get(runId);
|
|
976
|
+
while (buf === void 0) {
|
|
977
|
+
const waiters = this.waiting.get(runId) ?? /* @__PURE__ */ new Set();
|
|
978
|
+
this.waiting.set(runId, waiters);
|
|
979
|
+
let resolve2 = /* @__PURE__ */ __name(() => {
|
|
980
|
+
}, "resolve");
|
|
981
|
+
const opened = new Promise((r) => {
|
|
982
|
+
resolve2 = r;
|
|
983
|
+
});
|
|
984
|
+
waiters.add(resolve2);
|
|
985
|
+
try {
|
|
986
|
+
if (!await untilAborted(opened, signal)) {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
} finally {
|
|
990
|
+
waiters.delete(resolve2);
|
|
991
|
+
if (waiters.size === 0 && this.waiting.get(runId) === waiters) {
|
|
992
|
+
this.waiting.delete(runId);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
buf = this.runs.get(runId);
|
|
996
|
+
}
|
|
922
997
|
let index = 0;
|
|
923
998
|
while (true) {
|
|
924
999
|
while (index < buf.chunks.length) {
|
|
@@ -934,13 +1009,56 @@ var InProcessTokenStreamSink = class {
|
|
|
934
1009
|
}
|
|
935
1010
|
return;
|
|
936
1011
|
}
|
|
937
|
-
|
|
1012
|
+
const current = buf;
|
|
1013
|
+
let resolve2 = /* @__PURE__ */ __name(() => {
|
|
1014
|
+
}, "resolve");
|
|
1015
|
+
const woken = new Promise((r) => {
|
|
1016
|
+
resolve2 = r;
|
|
1017
|
+
});
|
|
1018
|
+
current.notify.add(resolve2);
|
|
1019
|
+
try {
|
|
1020
|
+
if (!await untilAborted(woken, signal)) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
} finally {
|
|
1024
|
+
current.notify.delete(resolve2);
|
|
1025
|
+
}
|
|
938
1026
|
}
|
|
939
1027
|
}
|
|
940
1028
|
close(runId) {
|
|
1029
|
+
const buf = this.runs.get(runId);
|
|
1030
|
+
if (buf?.expiry !== void 0) {
|
|
1031
|
+
clearTimeout(buf.expiry);
|
|
1032
|
+
}
|
|
941
1033
|
this.runs.delete(runId);
|
|
942
1034
|
}
|
|
943
1035
|
};
|
|
1036
|
+
async function untilAborted(promise, signal) {
|
|
1037
|
+
if (signal === void 0) {
|
|
1038
|
+
await promise;
|
|
1039
|
+
return true;
|
|
1040
|
+
}
|
|
1041
|
+
if (signal.aborted) {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
let onAbort = /* @__PURE__ */ __name(() => {
|
|
1045
|
+
}, "onAbort");
|
|
1046
|
+
const aborted = new Promise((resolve2) => {
|
|
1047
|
+
onAbort = /* @__PURE__ */ __name(() => resolve2(false), "onAbort");
|
|
1048
|
+
signal.addEventListener("abort", onAbort, {
|
|
1049
|
+
once: true
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
try {
|
|
1053
|
+
return await Promise.race([
|
|
1054
|
+
promise.then(() => true),
|
|
1055
|
+
aborted
|
|
1056
|
+
]);
|
|
1057
|
+
} finally {
|
|
1058
|
+
signal.removeEventListener("abort", onAbort);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
__name(untilAborted, "untilAborted");
|
|
944
1062
|
|
|
945
1063
|
// src/durable/durable-agent-runner.ts
|
|
946
1064
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
@@ -2816,6 +2934,26 @@ var RunNotActiveException = class extends ConflictException {
|
|
|
2816
2934
|
}
|
|
2817
2935
|
};
|
|
2818
2936
|
|
|
2937
|
+
// src/thread-reads.ts
|
|
2938
|
+
function threadReads(store, threadId, separately) {
|
|
2939
|
+
const projecting = store;
|
|
2940
|
+
if (typeof projecting.threadSettings !== "function") {
|
|
2941
|
+
return separately;
|
|
2942
|
+
}
|
|
2943
|
+
let settings;
|
|
2944
|
+
const read = /* @__PURE__ */ __name(() => {
|
|
2945
|
+
settings ??= projecting.threadSettings?.(threadId) ?? Promise.resolve(null);
|
|
2946
|
+
return settings;
|
|
2947
|
+
}, "read");
|
|
2948
|
+
return {
|
|
2949
|
+
owner: /* @__PURE__ */ __name(async () => (await read())?.actorRef ?? null, "owner"),
|
|
2950
|
+
defaultAgent: /* @__PURE__ */ __name(async () => (await read())?.defaultAgent ?? null, "defaultAgent"),
|
|
2951
|
+
persona: /* @__PURE__ */ __name(async () => (await read())?.persona ?? null, "persona"),
|
|
2952
|
+
model: /* @__PURE__ */ __name(async () => (await read())?.model ?? null, "model")
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
__name(threadReads, "threadReads");
|
|
2956
|
+
|
|
2819
2957
|
// src/agent.service.ts
|
|
2820
2958
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
2821
2959
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3079,7 +3217,7 @@ var AgentService = class {
|
|
|
3079
3217
|
* else the thread's pinned one, else none. An agent the catalog locks to one model runs on it
|
|
3080
3218
|
* whatever was sent or pinned, and a send naming another model is refused.
|
|
3081
3219
|
*/
|
|
3082
|
-
async resolveModel(actor, agent, requested, threadId) {
|
|
3220
|
+
async resolveModel(actor, agent, requested, threadId, reads) {
|
|
3083
3221
|
const locked = this.models !== void 0 ? (await this.models.list({
|
|
3084
3222
|
actor,
|
|
3085
3223
|
agent
|
|
@@ -3090,7 +3228,7 @@ var AgentService = class {
|
|
|
3090
3228
|
}
|
|
3091
3229
|
return locked.model;
|
|
3092
3230
|
}
|
|
3093
|
-
const model = requested ?? (threadId !== void 0 ? await this.threadModel(threadId) : null);
|
|
3231
|
+
const model = requested ?? (threadId !== void 0 ? await (reads?.model() ?? this.threadModel(threadId)) : null);
|
|
3094
3232
|
if (model === null || model === void 0) {
|
|
3095
3233
|
return void 0;
|
|
3096
3234
|
}
|
|
@@ -3150,10 +3288,11 @@ var AgentService = class {
|
|
|
3150
3288
|
if ("proposalDecision" in decision) return decision;
|
|
3151
3289
|
}
|
|
3152
3290
|
await this.assertWithinQuota(params.actor);
|
|
3153
|
-
const
|
|
3291
|
+
const reads = params.threadId !== void 0 ? this.threadReads(params.threadId) : void 0;
|
|
3292
|
+
const target = this.deps.resolveAgent(await this.resolveAgentName(params.agentName, params.threadId, reads));
|
|
3154
3293
|
const agentName = target.agentName;
|
|
3155
|
-
const persona = await this.resolvePersona(agentName, params.personaId ?? target.persona, params.threadId);
|
|
3156
|
-
const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId);
|
|
3294
|
+
const persona = await this.resolvePersona(agentName, params.personaId ?? target.persona, params.threadId, reads);
|
|
3295
|
+
const model = await this.resolveModel(params.actor, agentName, params.model, params.threadId, reads);
|
|
3157
3296
|
const attachments = await this.resolveAttachments(params.actor, params.attachments ?? []);
|
|
3158
3297
|
let threadId = params.threadId;
|
|
3159
3298
|
if (threadId === void 0) {
|
|
@@ -3177,9 +3316,9 @@ var AgentService = class {
|
|
|
3177
3316
|
});
|
|
3178
3317
|
threadId = created.id;
|
|
3179
3318
|
} else {
|
|
3180
|
-
if (params.authorized !== true) await this.assertOwnsThread(params.actor, threadId);
|
|
3319
|
+
if (params.authorized !== true) await this.assertOwnsThread(params.actor, threadId, reads);
|
|
3181
3320
|
if (params.personaId !== void 0) {
|
|
3182
|
-
await this.pinThreadPersona(threadId, params.personaId);
|
|
3321
|
+
await this.pinThreadPersona(threadId, params.personaId, reads);
|
|
3183
3322
|
}
|
|
3184
3323
|
}
|
|
3185
3324
|
const input = {
|
|
@@ -3505,8 +3644,8 @@ var AgentService = class {
|
|
|
3505
3644
|
* {@link subscribeAs} instead: a runId is the only thing standing between a caller and another
|
|
3506
3645
|
* actor's answer, tool arguments and tool results.
|
|
3507
3646
|
*/
|
|
3508
|
-
subscribe(runId) {
|
|
3509
|
-
return this.deps.forAgent().sink.subscribe(runId);
|
|
3647
|
+
subscribe(runId, options) {
|
|
3648
|
+
return this.deps.forAgent().sink.subscribe(runId, options);
|
|
3510
3649
|
}
|
|
3511
3650
|
/**
|
|
3512
3651
|
* {@link subscribe}, gated the way {@link cancel} is: only the actor whose thread is currently
|
|
@@ -3514,9 +3653,9 @@ var AgentService = class {
|
|
|
3514
3653
|
* replayed — the runner clears the thread's active stream as the run ends, and that field is what
|
|
3515
3654
|
* ownership is derived from.
|
|
3516
3655
|
*/
|
|
3517
|
-
async subscribeAs(actor, runId) {
|
|
3656
|
+
async subscribeAs(actor, runId, options) {
|
|
3518
3657
|
await this.assertOwnsActiveStream(actor, runId);
|
|
3519
|
-
return this.subscribe(runId);
|
|
3658
|
+
return this.subscribe(runId, options);
|
|
3520
3659
|
}
|
|
3521
3660
|
/**
|
|
3522
3661
|
* Approve a parked action call as `actor`. Who may is the call's recorded approver's business
|
|
@@ -3669,9 +3808,28 @@ var AgentService = class {
|
|
|
3669
3808
|
await this.assertOwnsThread(actor, threadId);
|
|
3670
3809
|
return this.store.softDeleteThread(threadId);
|
|
3671
3810
|
}
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3811
|
+
/**
|
|
3812
|
+
* Copy a thread up to `fromMessageId` (inclusive) into a new one. By default only the thread's
|
|
3813
|
+
* owner may, and the copy is theirs. With `params.authorized`, the host has vouched for `actor`
|
|
3814
|
+
* (a share link it checked) and the copy is `actor`'s — see {@link ForkThreadParams}.
|
|
3815
|
+
*/
|
|
3816
|
+
async forkThread(actor, threadId, fromMessageId, params = {}) {
|
|
3817
|
+
if (params.authorized === true) {
|
|
3818
|
+
if (await this.store.ownerOfThread(threadId) === null) {
|
|
3819
|
+
throw new NotFoundException2(`thread ${threadId} not found`);
|
|
3820
|
+
}
|
|
3821
|
+
} else {
|
|
3822
|
+
await this.assertOwnsThread(actor, threadId);
|
|
3823
|
+
}
|
|
3824
|
+
const options = {
|
|
3825
|
+
...params.authorized === true ? {
|
|
3826
|
+
actorRef: actor.id
|
|
3827
|
+
} : {},
|
|
3828
|
+
...params.tenantRef !== void 0 ? {
|
|
3829
|
+
tenantRef: params.tenantRef
|
|
3830
|
+
} : {}
|
|
3831
|
+
};
|
|
3832
|
+
return this.store.forkThread(threadId, fromMessageId, options);
|
|
3675
3833
|
}
|
|
3676
3834
|
/** Kept for source compatibility — a thin wrapper over the more general `updateThread`. */
|
|
3677
3835
|
async renameThread(actor, threadId, title) {
|
|
@@ -3723,7 +3881,7 @@ var AgentService = class {
|
|
|
3723
3881
|
* The persona this send runs under — see {@link AgentDepsFactory.resolvePersona}. The thread's
|
|
3724
3882
|
* pinned persona is read only when the send names none AND the agent has personas to pick from.
|
|
3725
3883
|
*/
|
|
3726
|
-
async resolvePersona(agentName, requested, threadId) {
|
|
3884
|
+
async resolvePersona(agentName, requested, threadId, reads) {
|
|
3727
3885
|
const needsThread = requested === void 0 && threadId !== void 0 && this.deps.personaCatalog(agentName).length > 0;
|
|
3728
3886
|
return this.deps.resolvePersona({
|
|
3729
3887
|
agentName,
|
|
@@ -3731,16 +3889,16 @@ var AgentService = class {
|
|
|
3731
3889
|
requested
|
|
3732
3890
|
} : {},
|
|
3733
3891
|
...needsThread ? {
|
|
3734
|
-
threadPersona: await threadPersona(this.store, threadId)
|
|
3892
|
+
threadPersona: await (reads?.persona() ?? threadPersona(this.store, threadId))
|
|
3735
3893
|
} : {}
|
|
3736
3894
|
});
|
|
3737
3895
|
}
|
|
3738
3896
|
/** Pin `persona` on the thread when it is not already — a no-op on a store that cannot. */
|
|
3739
|
-
async pinThreadPersona(threadId, persona) {
|
|
3897
|
+
async pinThreadPersona(threadId, persona, reads) {
|
|
3740
3898
|
if (this.store.updateThread === void 0) {
|
|
3741
3899
|
return;
|
|
3742
3900
|
}
|
|
3743
|
-
if (await threadPersona(this.store, threadId) !== persona) {
|
|
3901
|
+
if (await (reads?.persona() ?? threadPersona(this.store, threadId)) !== persona) {
|
|
3744
3902
|
await this.store.updateThread(threadId, {
|
|
3745
3903
|
persona
|
|
3746
3904
|
});
|
|
@@ -3767,18 +3925,27 @@ var AgentService = class {
|
|
|
3767
3925
|
* workflow, for a string. A store predating {@link ThreadDefaultAgentReader} still answers, via
|
|
3768
3926
|
* the full read.
|
|
3769
3927
|
*/
|
|
3770
|
-
async resolveAgentName(agentName, threadId) {
|
|
3928
|
+
async resolveAgentName(agentName, threadId, reads) {
|
|
3771
3929
|
if (agentName !== void 0) {
|
|
3772
3930
|
return agentName;
|
|
3773
3931
|
}
|
|
3774
3932
|
if (threadId !== void 0) {
|
|
3775
|
-
const threadDefault = await this.threadDefaultAgent(threadId);
|
|
3933
|
+
const threadDefault = await (reads?.defaultAgent() ?? this.threadDefaultAgent(threadId));
|
|
3776
3934
|
if (threadDefault !== null) {
|
|
3777
3935
|
return threadDefault;
|
|
3778
3936
|
}
|
|
3779
3937
|
}
|
|
3780
3938
|
return this.deps.defaultAgentName();
|
|
3781
3939
|
}
|
|
3940
|
+
/** Everything a send reads off `threadId`, in one read where the store allows it. */
|
|
3941
|
+
threadReads(threadId) {
|
|
3942
|
+
return threadReads(this.store, threadId, {
|
|
3943
|
+
owner: /* @__PURE__ */ __name(() => this.store.ownerOfThread(threadId), "owner"),
|
|
3944
|
+
defaultAgent: /* @__PURE__ */ __name(() => this.threadDefaultAgent(threadId), "defaultAgent"),
|
|
3945
|
+
persona: /* @__PURE__ */ __name(() => threadPersona(this.store, threadId), "persona"),
|
|
3946
|
+
model: /* @__PURE__ */ __name(() => this.threadModel(threadId), "model")
|
|
3947
|
+
});
|
|
3948
|
+
}
|
|
3782
3949
|
async threadDefaultAgent(threadId) {
|
|
3783
3950
|
const projecting = this.store;
|
|
3784
3951
|
if (typeof projecting.defaultAgentForThread === "function") {
|
|
@@ -3948,8 +4115,8 @@ var AgentService = class {
|
|
|
3948
4115
|
* Authorization seam for thread-scoped endpoints: the caller must own the thread. A missing
|
|
3949
4116
|
* thread is a 404; someone else's thread is a 403 — one actor never touches another's thread.
|
|
3950
4117
|
*/
|
|
3951
|
-
async assertOwnsThread(actor, threadId) {
|
|
3952
|
-
const owner = await this.store.ownerOfThread(threadId);
|
|
4118
|
+
async assertOwnsThread(actor, threadId, reads) {
|
|
4119
|
+
const owner = await (reads?.owner() ?? this.store.ownerOfThread(threadId));
|
|
3953
4120
|
if (owner === null) {
|
|
3954
4121
|
throw new NotFoundException2(`thread ${threadId} not found`);
|
|
3955
4122
|
}
|
|
@@ -4389,6 +4556,18 @@ AttachmentsController = _ts_decorate12([
|
|
|
4389
4556
|
import { AGENT_ACTOR_RESOLVER as AGENT_ACTOR_RESOLVER2, AgentStreamError as AgentStreamError2, streamFailure as streamFailure2 } from "@dudousxd/nestjs-agent-core";
|
|
4390
4557
|
import { validateUiCapabilities } from "@dudousxd/nestjs-agent-core/genui";
|
|
4391
4558
|
import { BadRequestException as BadRequestException4, Body, Controller as Controller3, Get as Get3, Inject as Inject11, Param, Post as Post2, Query, Req as Req2, Res } from "@nestjs/common";
|
|
4559
|
+
|
|
4560
|
+
// src/controller/reader-gone.ts
|
|
4561
|
+
function readerGone(res) {
|
|
4562
|
+
const controller = new AbortController();
|
|
4563
|
+
res.once("close", () => controller.abort());
|
|
4564
|
+
return {
|
|
4565
|
+
signal: controller.signal
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
__name(readerGone, "readerGone");
|
|
4569
|
+
|
|
4570
|
+
// src/controller/chat.controller.ts
|
|
4392
4571
|
function _ts_decorate13(decorators, target, key, desc) {
|
|
4393
4572
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4394
4573
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -4522,7 +4701,8 @@ var ChatController = class {
|
|
|
4522
4701
|
return;
|
|
4523
4702
|
}
|
|
4524
4703
|
const { runId, threadId } = result;
|
|
4525
|
-
|
|
4704
|
+
const gone = readerGone(res);
|
|
4705
|
+
await this.pipe(res, runId, this.agent.subscribe(runId, gone), threadId, 0, gone.signal);
|
|
4526
4706
|
}
|
|
4527
4707
|
/**
|
|
4528
4708
|
* Attach to a run's stream: every buffered frame, then live ones. `?after=<seq>` (or the SSE
|
|
@@ -4532,7 +4712,8 @@ var ChatController = class {
|
|
|
4532
4712
|
async stream(req, runId, res, after) {
|
|
4533
4713
|
const actor = await this.actorResolver.resolve(req);
|
|
4534
4714
|
const cursor = parseCursor(after) ?? parseCursor(req.headers["last-event-id"]) ?? 0;
|
|
4535
|
-
|
|
4715
|
+
const gone = readerGone(res);
|
|
4716
|
+
await this.pipe(res, runId, await this.agent.subscribeAs(actor, runId, gone), void 0, cursor, gone.signal);
|
|
4536
4717
|
}
|
|
4537
4718
|
async cancel(req, runId) {
|
|
4538
4719
|
const actor = await this.actorResolver.resolve(req);
|
|
@@ -4548,8 +4729,11 @@ var ChatController = class {
|
|
|
4548
4729
|
* and on any later `GET …/stream`, whichever replica serves it. A reconnecting client passes the
|
|
4549
4730
|
* last one it saw as `?after=`; frames at or below it are read off the sink but not re-sent.
|
|
4550
4731
|
* `meta`, `done` and `error` carry no id: `meta` is re-sent on every attach, the terminals once.
|
|
4732
|
+
*
|
|
4733
|
+
* A reader that disconnects aborts `gone`, which the sink honours by ending the subscription even
|
|
4734
|
+
* while the run is silent (parked on a person, or never writing again).
|
|
4551
4735
|
*/
|
|
4552
|
-
async pipe(res, runId, events, threadId, after = 0) {
|
|
4736
|
+
async pipe(res, runId, events, threadId, after = 0, gone) {
|
|
4553
4737
|
res.setHeader("Content-Type", "text/event-stream");
|
|
4554
4738
|
res.setHeader("Cache-Control", "no-cache");
|
|
4555
4739
|
res.setHeader("Connection", "keep-alive");
|
|
@@ -4578,6 +4762,9 @@ data: ${line}
|
|
|
4578
4762
|
}, "forward");
|
|
4579
4763
|
try {
|
|
4580
4764
|
for await (const chunk of events) {
|
|
4765
|
+
if (gone?.aborted) {
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4581
4768
|
buffer += decoder.decode(chunk, {
|
|
4582
4769
|
stream: true
|
|
4583
4770
|
});
|
|
@@ -4591,11 +4778,17 @@ data: ${line}
|
|
|
4591
4778
|
newline = buffer.indexOf("\n");
|
|
4592
4779
|
}
|
|
4593
4780
|
}
|
|
4781
|
+
if (gone?.aborted) {
|
|
4782
|
+
return;
|
|
4783
|
+
}
|
|
4594
4784
|
if (buffer.length > 0) {
|
|
4595
4785
|
forward(buffer);
|
|
4596
4786
|
}
|
|
4597
4787
|
res.write("event: done\ndata: {}\n\n");
|
|
4598
4788
|
} catch (error) {
|
|
4789
|
+
if (gone?.aborted) {
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4599
4792
|
const payload = error instanceof AgentStreamError2 ? {
|
|
4600
4793
|
code: error.code,
|
|
4601
4794
|
message: error.message
|
|
@@ -6496,13 +6689,22 @@ var LedgerQuotaProvider = class {
|
|
|
6496
6689
|
async report(query) {
|
|
6497
6690
|
const now = query.now ?? /* @__PURE__ */ new Date();
|
|
6498
6691
|
const actorRef = query.actor.id;
|
|
6499
|
-
const
|
|
6500
|
-
|
|
6501
|
-
];
|
|
6502
|
-
if (this.store.
|
|
6503
|
-
const
|
|
6504
|
-
|
|
6505
|
-
|
|
6692
|
+
const day = quotaPeriodRange("day", now);
|
|
6693
|
+
const month = quotaPeriodRange("month", now);
|
|
6694
|
+
const windows = [];
|
|
6695
|
+
if (this.store.usageWindows !== void 0) {
|
|
6696
|
+
const [dayUsed, monthUsed] = await this.store.usageWindows(actorRef, [
|
|
6697
|
+
day,
|
|
6698
|
+
month
|
|
6699
|
+
]);
|
|
6700
|
+
windows.push(this.usedWindow("day", dayUsed, day.resetsAt));
|
|
6701
|
+
windows.push(this.usedWindow("month", monthUsed, month.resetsAt));
|
|
6702
|
+
} else {
|
|
6703
|
+
windows.push(this.usedWindow("day", await this.store.quotaToday(actorRef, day.fromDay), day.resetsAt));
|
|
6704
|
+
if (this.store.usageBetween !== void 0) {
|
|
6705
|
+
const used = await this.store.usageBetween(actorRef, month.fromDay, month.toDay);
|
|
6706
|
+
windows.push(this.usedWindow("month", used, month.resetsAt));
|
|
6707
|
+
}
|
|
6506
6708
|
}
|
|
6507
6709
|
const blocked = exhaustedWindow(windows);
|
|
6508
6710
|
const warning = quotaWarning(windows);
|
|
@@ -6516,10 +6718,8 @@ var LedgerQuotaProvider = class {
|
|
|
6516
6718
|
} : {}
|
|
6517
6719
|
};
|
|
6518
6720
|
}
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
const used = await this.store.quotaToday(actorRef, range.fromDay);
|
|
6522
|
-
return this.window("day", used.usedTokens, this.usd(used), range.resetsAt);
|
|
6721
|
+
usedWindow(period, used, resetsAt) {
|
|
6722
|
+
return this.window(period, used?.usedTokens ?? 0, used === void 0 ? 0 : this.usd(used), resetsAt);
|
|
6523
6723
|
}
|
|
6524
6724
|
/** The window's USD: every priced row, or (with `countEstimatedCost: false`) reported cost only. */
|
|
6525
6725
|
usd(used) {
|