@dudousxd/nestjs-agent 1.17.0 → 1.17.1
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/durable/index.cjs +894 -884
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.js +924 -914
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +10 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/durable/index.js
CHANGED
|
@@ -2,10 +2,10 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/durable/agent-run.workflow.ts
|
|
5
|
-
import { AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY2, AGENT_STORE
|
|
5
|
+
import { AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY2, AGENT_STORE, RUN_ENDED_BEFORE_TOOL_CALL, RunCancelledError, agentFailureCode, encodeStreamEvent as encodeStreamEvent2, isReplayIntegrityError, publishAgentRunFailed, releaseThreadRun, runAgentLoop, settleAll, settleDeadRun, settleUnsettledDelegation, streamFailure } from "@dudousxd/nestjs-agent-core";
|
|
6
6
|
import { RUN_GATEWAY, Workflow } from "@dudousxd/nestjs-durable";
|
|
7
7
|
import { isWorkflowControlFlowSignal } from "@dudousxd/nestjs-durable-core";
|
|
8
|
-
import { Inject as
|
|
8
|
+
import { Inject as Inject2, Injectable as Injectable2, Logger, Optional } from "@nestjs/common";
|
|
9
9
|
|
|
10
10
|
// src/agent-deps.ts
|
|
11
11
|
function utcDay(date = /* @__PURE__ */ new Date()) {
|
|
@@ -23,9 +23,26 @@ function childSinkWriter(writer) {
|
|
|
23
23
|
}
|
|
24
24
|
__name(childSinkWriter, "childSinkWriter");
|
|
25
25
|
|
|
26
|
-
// src/queue/chat-queue.
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// src/queue/chat-queue.token.ts
|
|
27
|
+
var AGENT_CHAT_QUEUE = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:chat-queue");
|
|
28
|
+
|
|
29
|
+
// src/durable/agent-run.steps.ts
|
|
30
|
+
import { AGENT_DEPS_FACTORY, createFrameBuffer, createUiCollector, encodeStreamEvent, invokeWithTransientRetry, observeTurnFrames, publishAgentToolRetry, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
|
|
31
|
+
import { Step } from "@dudousxd/nestjs-durable";
|
|
32
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
33
|
+
|
|
34
|
+
// src/durable/outside-workflow-ctx.ts
|
|
35
|
+
import { runInWorkflowCtx } from "@dudousxd/nestjs-durable-core";
|
|
36
|
+
function outsideWorkflowCtx(fn) {
|
|
37
|
+
return runInWorkflowCtx(void 0, fn);
|
|
38
|
+
}
|
|
39
|
+
__name(outsideWorkflowCtx, "outsideWorkflowCtx");
|
|
40
|
+
function stepOf(ctx) {
|
|
41
|
+
return (name, fn, ...rest) => ctx.localStep(name, (...args) => outsideWorkflowCtx(() => fn(...args)), ...rest);
|
|
42
|
+
}
|
|
43
|
+
__name(stepOf, "stepOf");
|
|
44
|
+
|
|
45
|
+
// src/durable/agent-run.steps.ts
|
|
29
46
|
function _ts_decorate(decorators, target, key, desc) {
|
|
30
47
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
31
48
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
@@ -52,719 +69,278 @@ function _ts_param(paramIndex, decorator) {
|
|
|
52
69
|
};
|
|
53
70
|
}
|
|
54
71
|
__name(_ts_param, "_ts_param");
|
|
55
|
-
var
|
|
56
|
-
var ChatQueueService = class _ChatQueueService {
|
|
72
|
+
var AgentRunSteps = class {
|
|
57
73
|
static {
|
|
58
|
-
__name(this, "
|
|
74
|
+
__name(this, "AgentRunSteps");
|
|
75
|
+
}
|
|
76
|
+
factory;
|
|
77
|
+
constructor(factory) {
|
|
78
|
+
this.factory = factory;
|
|
59
79
|
}
|
|
60
|
-
store;
|
|
61
|
-
sink;
|
|
62
|
-
quotaProvider;
|
|
63
|
-
options;
|
|
64
|
-
staging;
|
|
65
|
-
logger = new Logger(_ChatQueueService.name);
|
|
66
80
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
81
|
+
* Retryable: every side effect (persist/quota/stream markers) happens loop-side under
|
|
82
|
+
* `hooks.step`, so a durable retry only re-runs the model call and re-streams its tokens — never
|
|
83
|
+
* double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
|
|
84
|
+
* across however many turns/retries it takes.
|
|
69
85
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.store = store;
|
|
73
|
-
this.sink = sink;
|
|
74
|
-
this.quotaProvider = quotaProvider;
|
|
75
|
-
this.options = options;
|
|
76
|
-
this.staging = staging;
|
|
77
|
-
}
|
|
78
|
-
/** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
|
|
79
|
-
get supported() {
|
|
80
|
-
return isChatQueueStore(this.store);
|
|
86
|
+
llm(input) {
|
|
87
|
+
return outsideWorkflowCtx(() => this.runLlm(input));
|
|
81
88
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
async runLlm(input) {
|
|
90
|
+
const deps = this.factory.forAgent(input.agentName);
|
|
91
|
+
const tools = withMemoryTool({
|
|
92
|
+
tools: withSkillTool({
|
|
93
|
+
tools: withAskTool({
|
|
94
|
+
tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList, {
|
|
95
|
+
...input.threadId !== void 0 ? {
|
|
96
|
+
threadId: input.threadId
|
|
97
|
+
} : {},
|
|
98
|
+
...input.agentName !== void 0 ? {
|
|
99
|
+
agentName: input.agentName
|
|
100
|
+
} : {}
|
|
101
|
+
}),
|
|
102
|
+
ask: deps.ask
|
|
103
|
+
}),
|
|
104
|
+
enabled: deps.skills !== void 0
|
|
105
|
+
}),
|
|
106
|
+
enabled: deps.memory?.provider.write !== void 0
|
|
107
|
+
});
|
|
108
|
+
const buffer = input.bufferOutput === true ? createFrameBuffer() : void 0;
|
|
109
|
+
let writer;
|
|
110
|
+
if (buffer !== void 0) {
|
|
111
|
+
writer = buffer.writer;
|
|
112
|
+
} else {
|
|
113
|
+
const opened = await deps.sink.open(input.sinkRunId);
|
|
114
|
+
writer = input.childSink ? childSinkWriter(opened) : opened;
|
|
86
115
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
const frames = observeTurnFrames(writer);
|
|
117
|
+
const turn = withTurnFrames(await traceLlmTurn(input.runId, input.step, () => deps.model.runTurn({
|
|
118
|
+
system: input.system,
|
|
119
|
+
messages: input.messages,
|
|
120
|
+
tools,
|
|
121
|
+
sink: frames.writer,
|
|
122
|
+
...input.model !== void 0 ? {
|
|
123
|
+
model: input.model
|
|
124
|
+
} : {}
|
|
125
|
+
})), frames.summary());
|
|
126
|
+
const stamped = stampToolKinds(turn, deps);
|
|
127
|
+
return buffer === void 0 ? stamped : {
|
|
128
|
+
...stamped,
|
|
129
|
+
bufferedFrames: buffer.frames()
|
|
98
130
|
};
|
|
99
131
|
}
|
|
100
132
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
133
|
+
* NO DURABLE retries — a tool may not be idempotent (a bare `@Step()` carries none, unlike `llm`
|
|
134
|
+
* above). What it does retry, in place, is a CLASSIFIED-TRANSIENT error from the tool's own
|
|
135
|
+
* invocation (a DB deadlock, a lock-wait timeout — see `isTransientToolError`): the server rolled
|
|
136
|
+
* that work back, so re-invoking it is safe, and doing it here (inside this one step, never a new
|
|
137
|
+
* checkpoint) is exactly how the non-dispatched loop path handles it too. The timeout is applied
|
|
138
|
+
* HERE, handler-side, via `withToolTimeout` — never as a durable dispatch `timeoutMs`, which
|
|
139
|
+
* bounds engine liveness (retryable) rather than the tool's own business outcome — and per
|
|
140
|
+
* ATTEMPT, so a retried invocation gets its own fresh timeout window.
|
|
103
141
|
*/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
if (typeof runner.isRunActive !== "function") {
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
return await runner.isRunActive(holder);
|
|
113
|
-
} catch {
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
142
|
+
tool(input) {
|
|
143
|
+
return outsideWorkflowCtx(() => this.runTool(input));
|
|
116
144
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
async holder(threadId, runner) {
|
|
122
|
-
const holder = await this.queueStore().activeRunForThread(threadId);
|
|
123
|
-
if (holder === null) {
|
|
124
|
-
return {
|
|
125
|
-
live: null,
|
|
126
|
-
stale: null
|
|
127
|
-
};
|
|
145
|
+
async runTool(input) {
|
|
146
|
+
const deps = this.factory.forAgent(input.ctx.agentName);
|
|
147
|
+
if (deps.registry.spec(input.toolName)?.kind === "action" && input.toolType !== "action") {
|
|
148
|
+
throw new Error(`tool "${input.toolName}" is an action tool but was dispatched without approval: the dispatching process resolved its kind against a registry that does not have it`);
|
|
128
149
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
const sinkRunId = input.sinkRunId;
|
|
151
|
+
let opened;
|
|
152
|
+
const ui = createUiCollector(input.toolCallId, sinkRunId === void 0 ? void 0 : async (event) => {
|
|
153
|
+
opened ??= Promise.resolve(deps.sink.open(sinkRunId)).then((writer) => input.childSink === true ? childSinkWriter(writer) : writer);
|
|
154
|
+
await (await opened).write(encodeStreamEvent(event));
|
|
155
|
+
});
|
|
156
|
+
const ctx = {
|
|
157
|
+
...toolCallContext(input.ctx, input.toolCallId),
|
|
158
|
+
emitUi: ui.emit
|
|
159
|
+
};
|
|
160
|
+
const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
|
|
161
|
+
const transientRetry = input.transientRetry === false ? false : {
|
|
162
|
+
attempts: input.transientRetry.attempts,
|
|
163
|
+
backoffMs: input.transientRetry.backoffMs,
|
|
164
|
+
...localClassify !== void 0 ? {
|
|
165
|
+
classify: localClassify
|
|
166
|
+
} : {}
|
|
135
167
|
};
|
|
168
|
+
const output = await traceToolExecution(input.ctx.runId, {
|
|
169
|
+
toolCallId: input.toolCallId,
|
|
170
|
+
toolName: input.toolName,
|
|
171
|
+
toolType: input.toolType
|
|
172
|
+
}, () => invokeWithTransientRetry(() => {
|
|
173
|
+
ui.restart();
|
|
174
|
+
const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
|
|
175
|
+
return input.timeoutMs !== void 0 ? withToolTimeout(invocation, input.timeoutMs, input.toolName) : invocation;
|
|
176
|
+
}, transientRetry, {
|
|
177
|
+
onRetry: /* @__PURE__ */ __name((attempt, error) => {
|
|
178
|
+
publishAgentToolRetry({
|
|
179
|
+
toolName: input.toolName,
|
|
180
|
+
toolCallId: input.toolCallId,
|
|
181
|
+
attempt,
|
|
182
|
+
message: error instanceof Error ? error.message : String(error)
|
|
183
|
+
});
|
|
184
|
+
}, "onRetry")
|
|
185
|
+
}));
|
|
186
|
+
return input.collectUi === true ? wrapToolStepOutput(output, ui.components()) : output;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
_ts_decorate([
|
|
190
|
+
Step({
|
|
191
|
+
retries: 3
|
|
192
|
+
}),
|
|
193
|
+
_ts_metadata("design:type", Function),
|
|
194
|
+
_ts_metadata("design:paramtypes", [
|
|
195
|
+
typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
|
|
196
|
+
]),
|
|
197
|
+
_ts_metadata("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
198
|
+
], AgentRunSteps.prototype, "llm", null);
|
|
199
|
+
_ts_decorate([
|
|
200
|
+
Step(),
|
|
201
|
+
_ts_metadata("design:type", Function),
|
|
202
|
+
_ts_metadata("design:paramtypes", [
|
|
203
|
+
typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
|
|
204
|
+
]),
|
|
205
|
+
_ts_metadata("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
206
|
+
], AgentRunSteps.prototype, "tool", null);
|
|
207
|
+
AgentRunSteps = _ts_decorate([
|
|
208
|
+
Injectable(),
|
|
209
|
+
_ts_param(0, Inject(AGENT_DEPS_FACTORY)),
|
|
210
|
+
_ts_metadata("design:type", Function),
|
|
211
|
+
_ts_metadata("design:paramtypes", [
|
|
212
|
+
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
|
|
213
|
+
])
|
|
214
|
+
], AgentRunSteps);
|
|
215
|
+
|
|
216
|
+
// src/durable/agent-run.workflow.ts
|
|
217
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
218
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
219
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
220
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
221
|
+
} else {
|
|
222
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
223
|
+
if (d = decorators[i]) {
|
|
224
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
229
|
+
}
|
|
230
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
231
|
+
function _ts_metadata2(metadataKey, metadataValue) {
|
|
232
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
233
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
237
|
+
function _ts_param2(paramIndex, decorator) {
|
|
238
|
+
return function(target, key) {
|
|
239
|
+
decorator(target, key, paramIndex);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
__name(_ts_param2, "_ts_param");
|
|
243
|
+
var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
244
|
+
static {
|
|
245
|
+
__name(this, "AgentRunWorkflow");
|
|
246
|
+
}
|
|
247
|
+
factory;
|
|
248
|
+
store;
|
|
249
|
+
runs;
|
|
250
|
+
queue;
|
|
251
|
+
logger = new Logger(_AgentRunWorkflow.name);
|
|
252
|
+
constructor(factory, store, runs, queue) {
|
|
253
|
+
this.factory = factory;
|
|
254
|
+
this.store = store;
|
|
255
|
+
this.runs = runs;
|
|
256
|
+
this.queue = queue;
|
|
136
257
|
}
|
|
137
258
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* the queue
|
|
141
|
-
* the stream learns what comes next from the `queue` frame, and so the thread is already free
|
|
142
|
-
* when the client sees the run end.
|
|
259
|
+
* Move the thread past this settling turn — to the next queued message, started here as a
|
|
260
|
+
* fire-and-forget `ctx.startChild` under the message's own id, or to a paused/empty queue — and
|
|
261
|
+
* answer the `queue` frame to write before the turn's terminal.
|
|
143
262
|
*
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return {};
|
|
149
|
-
}
|
|
150
|
-
const store = this.store;
|
|
151
|
-
const { threadId, outcome } = args;
|
|
152
|
-
if (await store.activeRunForThread(threadId) !== args.runId) {
|
|
153
|
-
return {};
|
|
154
|
-
}
|
|
155
|
-
let holding = args.runId;
|
|
156
|
-
for (let attempt = 0; attempt < MAX_DRAIN_ATTEMPTS; attempt += 1) {
|
|
157
|
-
const items = await store.listQueue(threadId);
|
|
158
|
-
const head = items[0];
|
|
159
|
-
if (head === void 0) {
|
|
160
|
-
if (holding !== null) {
|
|
161
|
-
await store.releaseActiveStream(threadId, holding);
|
|
162
|
-
holding = null;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
return {};
|
|
166
|
-
}
|
|
167
|
-
let pause = await store.queuePause(threadId);
|
|
168
|
-
if (pause === null) {
|
|
169
|
-
const reason = pauseReasonFor(outcome, head);
|
|
170
|
-
if (reason !== null) {
|
|
171
|
-
pause = {
|
|
172
|
-
reason,
|
|
173
|
-
...outcome === "failed" && args.error !== void 0 ? {
|
|
174
|
-
message: args.error
|
|
175
|
-
} : {},
|
|
176
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
177
|
-
};
|
|
178
|
-
await store.setQueuePause(threadId, pause);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (pause === null) {
|
|
182
|
-
const blocked = await this.quotaBlock(head.actor);
|
|
183
|
-
if (blocked !== null) {
|
|
184
|
-
pause = {
|
|
185
|
-
reason: "quota_exceeded",
|
|
186
|
-
message: blocked,
|
|
187
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
188
|
-
};
|
|
189
|
-
await store.setQueuePause(threadId, pause);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (pause !== null) {
|
|
193
|
-
if (holding !== null) {
|
|
194
|
-
await store.releaseActiveStream(threadId, holding);
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
frame: queueFrame(items, pause)
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
const claimed = await store.claimActiveStream(threadId, head.id, holding !== null ? {
|
|
201
|
-
replacing: holding
|
|
202
|
-
} : {});
|
|
203
|
-
if (!claimed) {
|
|
204
|
-
return {};
|
|
205
|
-
}
|
|
206
|
-
holding = head.id;
|
|
207
|
-
if (!await store.removeQueuedMessage(head.id)) {
|
|
208
|
-
await store.releaseActiveStream(threadId, head.id);
|
|
209
|
-
holding = null;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
const next = {
|
|
213
|
-
runId: head.id,
|
|
214
|
-
messageId: head.id,
|
|
215
|
-
input: await this.inputFor(head)
|
|
216
|
-
};
|
|
217
|
-
return {
|
|
218
|
-
next,
|
|
219
|
-
frame: queueFrame(items.slice(1), null, {
|
|
220
|
-
messageId: head.id,
|
|
221
|
-
runId: head.id
|
|
222
|
-
})
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
if (holding !== null) {
|
|
226
|
-
await store.releaseActiveStream(threadId, holding);
|
|
227
|
-
}
|
|
228
|
-
return {};
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* {@link plan}, then start what it picked with `start`. What an in-process settle (the inline
|
|
232
|
-
* runner, a durable cancel issued outside a workflow) calls; answers the frame to write.
|
|
233
|
-
*/
|
|
234
|
-
async handoff(args, start) {
|
|
235
|
-
const plan = await this.plan(args);
|
|
236
|
-
if (plan.next === void 0) {
|
|
237
|
-
return plan.frame;
|
|
238
|
-
}
|
|
239
|
-
return await this.launch(args.threadId, plan.next, start) ? plan.frame : this.pausedFrame(args.threadId);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Start the head of an IDLE thread's queue — after a send queued behind a run that turned out to
|
|
243
|
-
* be gone, after a resume, after a stale holder. Does nothing while a live run holds the thread
|
|
244
|
-
* (its settle drains) or while the queue is paused. Answers the started run's id, if any.
|
|
263
|
+
* Journaled: the decision (which message, claimed and popped) is one `localStep`, so a replay
|
|
264
|
+
* reads it back instead of popping a second message, and the start is the runtime's own
|
|
265
|
+
* replay-safe spawn. Gated by `ctx.patched`, so a run recorded before the queue existed replays
|
|
266
|
+
* against the history it has. A no-op for a sub-agent's run and on a store without a queue.
|
|
245
267
|
*/
|
|
246
|
-
async
|
|
247
|
-
|
|
268
|
+
async advanceQueue(ctx, input, outcome, error) {
|
|
269
|
+
const queue = this.queue;
|
|
270
|
+
if (queue === void 0 || !queue.supported || input.sinkRunId !== void 0 || input.deliverTo !== void 0) {
|
|
248
271
|
return void 0;
|
|
249
272
|
}
|
|
250
|
-
|
|
251
|
-
if (live !== null) {
|
|
273
|
+
if (!await ctx.patched("agent:chat-queue")) {
|
|
252
274
|
return void 0;
|
|
253
275
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
276
|
+
const plan = await stepOf(ctx)(`queue:${outcome}`, async () => {
|
|
277
|
+
try {
|
|
278
|
+
const settled = outcome === "completed" && await this.isCancelled(ctx.runId) ? "cancelled" : outcome;
|
|
279
|
+
return await queue.plan({
|
|
280
|
+
threadId: input.threadId,
|
|
281
|
+
runId: ctx.runId,
|
|
282
|
+
outcome: settled,
|
|
283
|
+
...error !== void 0 ? {
|
|
284
|
+
error
|
|
285
|
+
} : {}
|
|
286
|
+
});
|
|
287
|
+
} catch {
|
|
288
|
+
return {};
|
|
289
|
+
}
|
|
261
290
|
});
|
|
262
|
-
|
|
263
|
-
|
|
291
|
+
const next = plan.next;
|
|
292
|
+
if (next === void 0) {
|
|
293
|
+
return plan.frame;
|
|
264
294
|
}
|
|
265
|
-
|
|
266
|
-
runId
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
295
|
+
try {
|
|
296
|
+
await ctx.startChild(_AgentRunWorkflow, next.input, next.runId);
|
|
297
|
+
return plan.frame;
|
|
298
|
+
} catch (failure) {
|
|
299
|
+
if (isWorkflowControlFlowSignal(failure)) {
|
|
300
|
+
throw failure;
|
|
301
|
+
}
|
|
302
|
+
const reason = failure instanceof Error ? failure.message : String(failure);
|
|
303
|
+
return stepOf(ctx)("queue:restore", async () => {
|
|
304
|
+
await queue.restore(input.threadId, next, "start_failed", reason);
|
|
305
|
+
return queue.pausedFrame(input.threadId);
|
|
306
|
+
});
|
|
270
307
|
}
|
|
271
|
-
return started ? plan.next.runId : void 0;
|
|
272
308
|
}
|
|
273
309
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
310
|
+
* Has someone asked this run to stop? Read from the runtime's own run status, which is where
|
|
311
|
+
* `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
|
|
312
|
+
* `cancelled` once it has settled.
|
|
313
|
+
*
|
|
314
|
+
* Costs a run read at each of the loop's observation points (two per step). That is a row the
|
|
315
|
+
* control plane already has, against a turn that is otherwise making model calls, and it buys the
|
|
316
|
+
* property that matters: the answer comes from a place BOTH the canceller and every pod that might
|
|
317
|
+
* replay this run can see.
|
|
318
|
+
*
|
|
319
|
+
* Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
|
|
320
|
+
* unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
|
|
321
|
+
* because a status read timed out. The loop journals that answer like any other, so two processes
|
|
322
|
+
* can never disagree about what was seen here.
|
|
277
323
|
*/
|
|
278
|
-
async
|
|
279
|
-
this.starting.add(next.runId);
|
|
324
|
+
async isCancelled(runId) {
|
|
280
325
|
try {
|
|
281
|
-
|
|
282
|
-
return
|
|
283
|
-
} catch
|
|
284
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
-
this.logger.error(`queued message ${next.messageId} could not start: ${message}`);
|
|
286
|
-
await this.restore(threadId, next, "start_failed", message);
|
|
326
|
+
const status = (await this.runs?.getRunDetail(runId))?.run.status;
|
|
327
|
+
return status === "cancelled" || status === "cancelling";
|
|
328
|
+
} catch {
|
|
287
329
|
return false;
|
|
288
|
-
} finally {
|
|
289
|
-
this.starting.delete(next.runId);
|
|
290
330
|
}
|
|
291
331
|
}
|
|
292
|
-
/** Put a turn that never started back at the head of its queue, paused, and free the thread. */
|
|
293
|
-
async restore(threadId, next, reason, message) {
|
|
294
|
-
const store = this.queueStore();
|
|
295
|
-
const { input } = next;
|
|
296
|
-
await store.enqueueMessage({
|
|
297
|
-
threadId,
|
|
298
|
-
actor: input.actor,
|
|
299
|
-
content: input.userText,
|
|
300
|
-
at: "head",
|
|
301
|
-
...input.attachments !== void 0 ? {
|
|
302
|
-
attachments: input.attachments
|
|
303
|
-
} : {},
|
|
304
|
-
...input.agentName !== void 0 ? {
|
|
305
|
-
agentName: input.agentName
|
|
306
|
-
} : {},
|
|
307
|
-
...input.model !== void 0 ? {
|
|
308
|
-
model: input.model
|
|
309
|
-
} : {},
|
|
310
|
-
...input.pageContext !== void 0 ? {
|
|
311
|
-
pageContext: input.pageContext
|
|
312
|
-
} : {}
|
|
313
|
-
});
|
|
314
|
-
await store.setQueuePause(threadId, {
|
|
315
|
-
reason,
|
|
316
|
-
message,
|
|
317
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
318
|
-
});
|
|
319
|
-
await store.releaseActiveStream(threadId, next.runId);
|
|
320
|
-
}
|
|
321
|
-
/** A `queue` frame with the thread's current state. */
|
|
322
|
-
async pausedFrame(threadId) {
|
|
323
|
-
return {
|
|
324
|
-
kind: "queue",
|
|
325
|
-
queue: await this.state(threadId)
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
332
|
/**
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
333
|
+
* Settle the delegation a DETACHED run was started for, when that run ends without an answer.
|
|
334
|
+
*
|
|
335
|
+
* The loop delivers its own success (`deliver:detached`) but cannot catch its own crash, and the
|
|
336
|
+
* runtime settles the run row without knowing anything delegated it. Left alone, the card in the
|
|
337
|
+
* calling conversation says "started" for ever — the one state a reader can neither wait on nor
|
|
338
|
+
* act on. A no-op for every other run: only a detached one carries a delivery address.
|
|
332
339
|
*/
|
|
333
|
-
async
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
await this.write(holder, {
|
|
338
|
-
kind: "queue",
|
|
339
|
-
queue: state
|
|
340
|
-
}).catch((error) => {
|
|
341
|
-
this.logger.warn(`could not publish the queue of thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
return state;
|
|
345
|
-
}
|
|
346
|
-
async write(runId, frame) {
|
|
347
|
-
const writer = await this.sink.open(runId);
|
|
348
|
-
await writer.write(encodeStreamEvent(frame));
|
|
349
|
-
}
|
|
350
|
-
/** Why `actor` may not start a turn now (the quota message), or `null`. */
|
|
351
|
-
async quotaBlock(actor) {
|
|
352
|
-
if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
try {
|
|
356
|
-
const { blocked } = await this.quotaProvider.report({
|
|
357
|
-
actor
|
|
358
|
-
});
|
|
359
|
-
if (blocked === void 0) {
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
|
|
363
|
-
} catch {
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* The turn a queued message starts: everything it was queued with, today's date, and its
|
|
369
|
-
* attachments' urls minted afresh — the ones stored at enqueue time may have expired while it
|
|
370
|
-
* waited. An attachment the staging store no longer resolves keeps its stored url.
|
|
371
|
-
*/
|
|
372
|
-
async inputFor(message) {
|
|
373
|
-
const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
|
|
374
|
-
return {
|
|
375
|
-
threadId: message.threadId,
|
|
376
|
-
actor: message.actor,
|
|
377
|
-
userText: message.content,
|
|
378
|
-
day: utcDay(),
|
|
379
|
-
...message.agentName !== void 0 ? {
|
|
380
|
-
agentName: message.agentName
|
|
381
|
-
} : {},
|
|
382
|
-
...attachments.length > 0 ? {
|
|
383
|
-
attachments
|
|
384
|
-
} : {},
|
|
385
|
-
...message.pageContext !== void 0 ? {
|
|
386
|
-
pageContext: message.pageContext
|
|
387
|
-
} : {},
|
|
388
|
-
...message.model !== void 0 ? {
|
|
389
|
-
model: message.model
|
|
390
|
-
} : {}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
async freshAttachments(actor, attachments) {
|
|
394
|
-
const staging = this.staging;
|
|
395
|
-
if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
|
|
396
|
-
return attachments;
|
|
397
|
-
}
|
|
398
|
-
return Promise.all(attachments.map(async (attachment) => {
|
|
399
|
-
try {
|
|
400
|
-
return await staging.resolve({
|
|
401
|
-
mediaId: attachment.mediaId,
|
|
402
|
-
actor
|
|
403
|
-
}) ?? attachment;
|
|
404
|
-
} catch {
|
|
405
|
-
return attachment;
|
|
406
|
-
}
|
|
407
|
-
}));
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
ChatQueueService = _ts_decorate([
|
|
411
|
-
Injectable(),
|
|
412
|
-
_ts_param(0, Inject(AGENT_STORE)),
|
|
413
|
-
_ts_param(1, Inject(AGENT_SINK)),
|
|
414
|
-
_ts_param(2, Optional()),
|
|
415
|
-
_ts_param(2, Inject(AGENT_QUOTA_PROVIDER)),
|
|
416
|
-
_ts_param(3, Optional()),
|
|
417
|
-
_ts_param(3, Inject(AGENT_OPTIONS)),
|
|
418
|
-
_ts_param(4, Optional()),
|
|
419
|
-
_ts_param(4, Inject(AGENT_ATTACHMENT_STAGING)),
|
|
420
|
-
_ts_metadata("design:type", Function),
|
|
421
|
-
_ts_metadata("design:paramtypes", [
|
|
422
|
-
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
423
|
-
typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
|
|
424
|
-
typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
|
|
425
|
-
typeof Pick === "undefined" ? Object : Pick,
|
|
426
|
-
typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
|
|
427
|
-
])
|
|
428
|
-
], ChatQueueService);
|
|
429
|
-
function pauseReasonFor(outcome, head) {
|
|
430
|
-
if (outcome === "failed") {
|
|
431
|
-
return "run_failed";
|
|
432
|
-
}
|
|
433
|
-
if (outcome === "cancelled" && head.interrupt !== true) {
|
|
434
|
-
return "cancelled";
|
|
435
|
-
}
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
__name(pauseReasonFor, "pauseReasonFor");
|
|
439
|
-
function queueFrame(items, paused, started) {
|
|
440
|
-
return {
|
|
441
|
-
kind: "queue",
|
|
442
|
-
queue: {
|
|
443
|
-
items: items.map(queuedMessageView),
|
|
444
|
-
paused
|
|
445
|
-
},
|
|
446
|
-
...started !== void 0 ? {
|
|
447
|
-
started
|
|
448
|
-
} : {}
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
__name(queueFrame, "queueFrame");
|
|
452
|
-
|
|
453
|
-
// src/durable/agent-run.steps.ts
|
|
454
|
-
import { AGENT_DEPS_FACTORY, createFrameBuffer, createUiCollector, encodeStreamEvent as encodeStreamEvent2, invokeWithTransientRetry, observeTurnFrames, publishAgentToolRetry, stampToolKinds, toolCallContext, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
|
|
455
|
-
import { Step } from "@dudousxd/nestjs-durable";
|
|
456
|
-
import { Inject as Inject2, Injectable as Injectable2 } from "@nestjs/common";
|
|
457
|
-
|
|
458
|
-
// src/durable/outside-workflow-ctx.ts
|
|
459
|
-
import { runInWorkflowCtx } from "@dudousxd/nestjs-durable-core";
|
|
460
|
-
function outsideWorkflowCtx(fn) {
|
|
461
|
-
return runInWorkflowCtx(void 0, fn);
|
|
462
|
-
}
|
|
463
|
-
__name(outsideWorkflowCtx, "outsideWorkflowCtx");
|
|
464
|
-
function stepOf(ctx) {
|
|
465
|
-
return (name, fn, ...rest) => ctx.localStep(name, (...args) => outsideWorkflowCtx(() => fn(...args)), ...rest);
|
|
466
|
-
}
|
|
467
|
-
__name(stepOf, "stepOf");
|
|
468
|
-
|
|
469
|
-
// src/durable/agent-run.steps.ts
|
|
470
|
-
function _ts_decorate2(decorators, target, key, desc) {
|
|
471
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
472
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
473
|
-
r = Reflect.decorate(decorators, target, key, desc);
|
|
474
|
-
} else {
|
|
475
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
476
|
-
if (d = decorators[i]) {
|
|
477
|
-
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
482
|
-
}
|
|
483
|
-
__name(_ts_decorate2, "_ts_decorate");
|
|
484
|
-
function _ts_metadata2(metadataKey, metadataValue) {
|
|
485
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
486
|
-
return Reflect.metadata(metadataKey, metadataValue);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
__name(_ts_metadata2, "_ts_metadata");
|
|
490
|
-
function _ts_param2(paramIndex, decorator) {
|
|
491
|
-
return function(target, key) {
|
|
492
|
-
decorator(target, key, paramIndex);
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
__name(_ts_param2, "_ts_param");
|
|
496
|
-
var AgentRunSteps = class {
|
|
497
|
-
static {
|
|
498
|
-
__name(this, "AgentRunSteps");
|
|
499
|
-
}
|
|
500
|
-
factory;
|
|
501
|
-
constructor(factory) {
|
|
502
|
-
this.factory = factory;
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Retryable: every side effect (persist/quota/stream markers) happens loop-side under
|
|
506
|
-
* `hooks.step`, so a durable retry only re-runs the model call and re-streams its tokens — never
|
|
507
|
-
* double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
|
|
508
|
-
* across however many turns/retries it takes.
|
|
509
|
-
*/
|
|
510
|
-
llm(input) {
|
|
511
|
-
return outsideWorkflowCtx(() => this.runLlm(input));
|
|
512
|
-
}
|
|
513
|
-
async runLlm(input) {
|
|
514
|
-
const deps = this.factory.forAgent(input.agentName);
|
|
515
|
-
const tools = withMemoryTool({
|
|
516
|
-
tools: withSkillTool({
|
|
517
|
-
tools: withAskTool({
|
|
518
|
-
tools: await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList, {
|
|
519
|
-
...input.threadId !== void 0 ? {
|
|
520
|
-
threadId: input.threadId
|
|
521
|
-
} : {},
|
|
522
|
-
...input.agentName !== void 0 ? {
|
|
523
|
-
agentName: input.agentName
|
|
524
|
-
} : {}
|
|
525
|
-
}),
|
|
526
|
-
ask: deps.ask
|
|
527
|
-
}),
|
|
528
|
-
enabled: deps.skills !== void 0
|
|
529
|
-
}),
|
|
530
|
-
enabled: deps.memory?.provider.write !== void 0
|
|
531
|
-
});
|
|
532
|
-
const buffer = input.bufferOutput === true ? createFrameBuffer() : void 0;
|
|
533
|
-
let writer;
|
|
534
|
-
if (buffer !== void 0) {
|
|
535
|
-
writer = buffer.writer;
|
|
536
|
-
} else {
|
|
537
|
-
const opened = await deps.sink.open(input.sinkRunId);
|
|
538
|
-
writer = input.childSink ? childSinkWriter(opened) : opened;
|
|
539
|
-
}
|
|
540
|
-
const frames = observeTurnFrames(writer);
|
|
541
|
-
const turn = withTurnFrames(await traceLlmTurn(input.runId, input.step, () => deps.model.runTurn({
|
|
542
|
-
system: input.system,
|
|
543
|
-
messages: input.messages,
|
|
544
|
-
tools,
|
|
545
|
-
sink: frames.writer,
|
|
546
|
-
...input.model !== void 0 ? {
|
|
547
|
-
model: input.model
|
|
548
|
-
} : {}
|
|
549
|
-
})), frames.summary());
|
|
550
|
-
const stamped = stampToolKinds(turn, deps);
|
|
551
|
-
return buffer === void 0 ? stamped : {
|
|
552
|
-
...stamped,
|
|
553
|
-
bufferedFrames: buffer.frames()
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
/**
|
|
557
|
-
* NO DURABLE retries — a tool may not be idempotent (a bare `@Step()` carries none, unlike `llm`
|
|
558
|
-
* above). What it does retry, in place, is a CLASSIFIED-TRANSIENT error from the tool's own
|
|
559
|
-
* invocation (a DB deadlock, a lock-wait timeout — see `isTransientToolError`): the server rolled
|
|
560
|
-
* that work back, so re-invoking it is safe, and doing it here (inside this one step, never a new
|
|
561
|
-
* checkpoint) is exactly how the non-dispatched loop path handles it too. The timeout is applied
|
|
562
|
-
* HERE, handler-side, via `withToolTimeout` — never as a durable dispatch `timeoutMs`, which
|
|
563
|
-
* bounds engine liveness (retryable) rather than the tool's own business outcome — and per
|
|
564
|
-
* ATTEMPT, so a retried invocation gets its own fresh timeout window.
|
|
565
|
-
*/
|
|
566
|
-
tool(input) {
|
|
567
|
-
return outsideWorkflowCtx(() => this.runTool(input));
|
|
568
|
-
}
|
|
569
|
-
async runTool(input) {
|
|
570
|
-
const deps = this.factory.forAgent(input.ctx.agentName);
|
|
571
|
-
if (deps.registry.spec(input.toolName)?.kind === "action" && input.toolType !== "action") {
|
|
572
|
-
throw new Error(`tool "${input.toolName}" is an action tool but was dispatched without approval: the dispatching process resolved its kind against a registry that does not have it`);
|
|
573
|
-
}
|
|
574
|
-
const sinkRunId = input.sinkRunId;
|
|
575
|
-
let opened;
|
|
576
|
-
const ui = createUiCollector(input.toolCallId, sinkRunId === void 0 ? void 0 : async (event) => {
|
|
577
|
-
opened ??= Promise.resolve(deps.sink.open(sinkRunId)).then((writer) => input.childSink === true ? childSinkWriter(writer) : writer);
|
|
578
|
-
await (await opened).write(encodeStreamEvent2(event));
|
|
579
|
-
});
|
|
580
|
-
const ctx = {
|
|
581
|
-
...toolCallContext(input.ctx, input.toolCallId),
|
|
582
|
-
emitUi: ui.emit
|
|
583
|
-
};
|
|
584
|
-
const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
|
|
585
|
-
const transientRetry = input.transientRetry === false ? false : {
|
|
586
|
-
attempts: input.transientRetry.attempts,
|
|
587
|
-
backoffMs: input.transientRetry.backoffMs,
|
|
588
|
-
...localClassify !== void 0 ? {
|
|
589
|
-
classify: localClassify
|
|
590
|
-
} : {}
|
|
591
|
-
};
|
|
592
|
-
const output = await traceToolExecution(input.ctx.runId, {
|
|
593
|
-
toolCallId: input.toolCallId,
|
|
594
|
-
toolName: input.toolName,
|
|
595
|
-
toolType: input.toolType
|
|
596
|
-
}, () => invokeWithTransientRetry(() => {
|
|
597
|
-
ui.restart();
|
|
598
|
-
const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
|
|
599
|
-
return input.timeoutMs !== void 0 ? withToolTimeout(invocation, input.timeoutMs, input.toolName) : invocation;
|
|
600
|
-
}, transientRetry, {
|
|
601
|
-
onRetry: /* @__PURE__ */ __name((attempt, error) => {
|
|
602
|
-
publishAgentToolRetry({
|
|
603
|
-
toolName: input.toolName,
|
|
604
|
-
toolCallId: input.toolCallId,
|
|
605
|
-
attempt,
|
|
606
|
-
message: error instanceof Error ? error.message : String(error)
|
|
607
|
-
});
|
|
608
|
-
}, "onRetry")
|
|
609
|
-
}));
|
|
610
|
-
return input.collectUi === true ? wrapToolStepOutput(output, ui.components()) : output;
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
_ts_decorate2([
|
|
614
|
-
Step({
|
|
615
|
-
retries: 3
|
|
616
|
-
}),
|
|
617
|
-
_ts_metadata2("design:type", Function),
|
|
618
|
-
_ts_metadata2("design:paramtypes", [
|
|
619
|
-
typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
|
|
620
|
-
]),
|
|
621
|
-
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
622
|
-
], AgentRunSteps.prototype, "llm", null);
|
|
623
|
-
_ts_decorate2([
|
|
624
|
-
Step(),
|
|
625
|
-
_ts_metadata2("design:type", Function),
|
|
626
|
-
_ts_metadata2("design:paramtypes", [
|
|
627
|
-
typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
|
|
628
|
-
]),
|
|
629
|
-
_ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
|
|
630
|
-
], AgentRunSteps.prototype, "tool", null);
|
|
631
|
-
AgentRunSteps = _ts_decorate2([
|
|
632
|
-
Injectable2(),
|
|
633
|
-
_ts_param2(0, Inject2(AGENT_DEPS_FACTORY)),
|
|
634
|
-
_ts_metadata2("design:type", Function),
|
|
635
|
-
_ts_metadata2("design:paramtypes", [
|
|
636
|
-
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
|
|
637
|
-
])
|
|
638
|
-
], AgentRunSteps);
|
|
639
|
-
|
|
640
|
-
// src/durable/agent-run.workflow.ts
|
|
641
|
-
function _ts_decorate3(decorators, target, key, desc) {
|
|
642
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
643
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
644
|
-
r = Reflect.decorate(decorators, target, key, desc);
|
|
645
|
-
} else {
|
|
646
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
647
|
-
if (d = decorators[i]) {
|
|
648
|
-
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
653
|
-
}
|
|
654
|
-
__name(_ts_decorate3, "_ts_decorate");
|
|
655
|
-
function _ts_metadata3(metadataKey, metadataValue) {
|
|
656
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
657
|
-
return Reflect.metadata(metadataKey, metadataValue);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
__name(_ts_metadata3, "_ts_metadata");
|
|
661
|
-
function _ts_param3(paramIndex, decorator) {
|
|
662
|
-
return function(target, key) {
|
|
663
|
-
decorator(target, key, paramIndex);
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
__name(_ts_param3, "_ts_param");
|
|
667
|
-
var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
668
|
-
static {
|
|
669
|
-
__name(this, "AgentRunWorkflow");
|
|
670
|
-
}
|
|
671
|
-
factory;
|
|
672
|
-
store;
|
|
673
|
-
runs;
|
|
674
|
-
queue;
|
|
675
|
-
logger = new Logger2(_AgentRunWorkflow.name);
|
|
676
|
-
constructor(factory, store, runs, queue) {
|
|
677
|
-
this.factory = factory;
|
|
678
|
-
this.store = store;
|
|
679
|
-
this.runs = runs;
|
|
680
|
-
this.queue = queue;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Move the thread past this settling turn — to the next queued message, started here as a
|
|
684
|
-
* fire-and-forget `ctx.startChild` under the message's own id, or to a paused/empty queue — and
|
|
685
|
-
* answer the `queue` frame to write before the turn's terminal.
|
|
686
|
-
*
|
|
687
|
-
* Journaled: the decision (which message, claimed and popped) is one `localStep`, so a replay
|
|
688
|
-
* reads it back instead of popping a second message, and the start is the runtime's own
|
|
689
|
-
* replay-safe spawn. Gated by `ctx.patched`, so a run recorded before the queue existed replays
|
|
690
|
-
* against the history it has. A no-op for a sub-agent's run and on a store without a queue.
|
|
691
|
-
*/
|
|
692
|
-
async advanceQueue(ctx, input, outcome, error) {
|
|
693
|
-
const queue = this.queue;
|
|
694
|
-
if (queue === void 0 || !queue.supported || input.sinkRunId !== void 0 || input.deliverTo !== void 0) {
|
|
695
|
-
return void 0;
|
|
696
|
-
}
|
|
697
|
-
if (!await ctx.patched("agent:chat-queue")) {
|
|
698
|
-
return void 0;
|
|
699
|
-
}
|
|
700
|
-
const plan = await stepOf(ctx)(`queue:${outcome}`, async () => {
|
|
701
|
-
try {
|
|
702
|
-
const settled = outcome === "completed" && await this.isCancelled(ctx.runId) ? "cancelled" : outcome;
|
|
703
|
-
return await queue.plan({
|
|
704
|
-
threadId: input.threadId,
|
|
705
|
-
runId: ctx.runId,
|
|
706
|
-
outcome: settled,
|
|
707
|
-
...error !== void 0 ? {
|
|
708
|
-
error
|
|
709
|
-
} : {}
|
|
710
|
-
});
|
|
711
|
-
} catch {
|
|
712
|
-
return {};
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
const next = plan.next;
|
|
716
|
-
if (next === void 0) {
|
|
717
|
-
return plan.frame;
|
|
718
|
-
}
|
|
719
|
-
try {
|
|
720
|
-
await ctx.startChild(_AgentRunWorkflow, next.input, next.runId);
|
|
721
|
-
return plan.frame;
|
|
722
|
-
} catch (failure) {
|
|
723
|
-
if (isWorkflowControlFlowSignal(failure)) {
|
|
724
|
-
throw failure;
|
|
725
|
-
}
|
|
726
|
-
const reason = failure instanceof Error ? failure.message : String(failure);
|
|
727
|
-
return stepOf(ctx)("queue:restore", async () => {
|
|
728
|
-
await queue.restore(input.threadId, next, "start_failed", reason);
|
|
729
|
-
return queue.pausedFrame(input.threadId);
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Has someone asked this run to stop? Read from the runtime's own run status, which is where
|
|
735
|
-
* `DurableAgentRunner.cancel` puts it — `cancelling` while a compensating cancel is in flight,
|
|
736
|
-
* `cancelled` once it has settled.
|
|
737
|
-
*
|
|
738
|
-
* Costs a run read at each of the loop's observation points (two per step). That is a row the
|
|
739
|
-
* control plane already has, against a turn that is otherwise making model calls, and it buys the
|
|
740
|
-
* property that matters: the answer comes from a place BOTH the canceller and every pod that might
|
|
741
|
-
* replay this run can see.
|
|
742
|
-
*
|
|
743
|
-
* Failing to ask is not a cancel. A tenant with no gateway wired, or an operator briefly
|
|
744
|
-
* unreachable, answers "no" and the run carries on — the alternative is failing healthy runs
|
|
745
|
-
* because a status read timed out. The loop journals that answer like any other, so two processes
|
|
746
|
-
* can never disagree about what was seen here.
|
|
747
|
-
*/
|
|
748
|
-
async isCancelled(runId) {
|
|
749
|
-
try {
|
|
750
|
-
const status = (await this.runs?.getRunDetail(runId))?.run.status;
|
|
751
|
-
return status === "cancelled" || status === "cancelling";
|
|
752
|
-
} catch {
|
|
753
|
-
return false;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
/**
|
|
757
|
-
* Settle the delegation a DETACHED run was started for, when that run ends without an answer.
|
|
758
|
-
*
|
|
759
|
-
* The loop delivers its own success (`deliver:detached`) but cannot catch its own crash, and the
|
|
760
|
-
* runtime settles the run row without knowing anything delegated it. Left alone, the card in the
|
|
761
|
-
* calling conversation says "started" for ever — the one state a reader can neither wait on nor
|
|
762
|
-
* act on. A no-op for every other run: only a detached one carries a delivery address.
|
|
763
|
-
*/
|
|
764
|
-
async settleDetachedParent(ctx, input, outcome) {
|
|
765
|
-
const delivery = input.deliverTo;
|
|
766
|
-
if (delivery === void 0) {
|
|
767
|
-
return;
|
|
340
|
+
async settleDetachedParent(ctx, input, outcome) {
|
|
341
|
+
const delivery = input.deliverTo;
|
|
342
|
+
if (delivery === void 0) {
|
|
343
|
+
return;
|
|
768
344
|
}
|
|
769
345
|
await stepOf(ctx)("deliver:detached:unsettled", () => settleUnsettledDelegation({
|
|
770
346
|
store: this.store,
|
|
@@ -807,7 +383,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
807
383
|
end: /* @__PURE__ */ __name(async () => {
|
|
808
384
|
const frame = await this.advanceQueue(ctx, input, "completed");
|
|
809
385
|
if (frame !== void 0) {
|
|
810
|
-
await writer.write(
|
|
386
|
+
await writer.write(encodeStreamEvent2(frame));
|
|
811
387
|
}
|
|
812
388
|
await writer.end();
|
|
813
389
|
}, "end")
|
|
@@ -995,26 +571,27 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
995
571
|
});
|
|
996
572
|
const writer = await hooks.openSink();
|
|
997
573
|
if (queueFrame2 !== void 0) {
|
|
998
|
-
await writer.write(
|
|
574
|
+
await writer.write(encodeStreamEvent2(queueFrame2));
|
|
999
575
|
}
|
|
1000
576
|
await writer.fail(streamFailure(error));
|
|
1001
577
|
throw error;
|
|
1002
578
|
}
|
|
1003
579
|
}
|
|
1004
580
|
};
|
|
1005
|
-
AgentRunWorkflow =
|
|
1006
|
-
|
|
581
|
+
AgentRunWorkflow = _ts_decorate2([
|
|
582
|
+
Injectable2(),
|
|
1007
583
|
Workflow({
|
|
1008
584
|
name: "agent.run",
|
|
1009
585
|
version: "1"
|
|
1010
586
|
}),
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
587
|
+
_ts_param2(0, Inject2(AGENT_DEPS_FACTORY2)),
|
|
588
|
+
_ts_param2(1, Inject2(AGENT_STORE)),
|
|
589
|
+
_ts_param2(2, Optional()),
|
|
590
|
+
_ts_param2(2, Inject2(RUN_GATEWAY)),
|
|
591
|
+
_ts_param2(3, Optional()),
|
|
592
|
+
_ts_param2(3, Inject2(AGENT_CHAT_QUEUE)),
|
|
593
|
+
_ts_metadata2("design:type", Function),
|
|
594
|
+
_ts_metadata2("design:paramtypes", [
|
|
1018
595
|
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
|
|
1019
596
|
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
1020
597
|
typeof RunGateway === "undefined" ? Object : RunGateway,
|
|
@@ -1028,10 +605,10 @@ __name(isSignalTimeout, "isSignalTimeout");
|
|
|
1028
605
|
|
|
1029
606
|
// src/durable/durable-agent-runner.ts
|
|
1030
607
|
import { randomUUID } from "crypto";
|
|
1031
|
-
import { AGENT_SINK
|
|
608
|
+
import { AGENT_SINK, AGENT_STORE as AGENT_STORE2, encodeStreamEvent as encodeStreamEvent3, releaseThreadRun as releaseThreadRun2 } from "@dudousxd/nestjs-agent-core";
|
|
1032
609
|
import { RUN_GATEWAY as RUN_GATEWAY2, WorkflowService } from "@dudousxd/nestjs-durable";
|
|
1033
610
|
import { isWorkflowControlFlowSignal as isWorkflowControlFlowSignal2 } from "@dudousxd/nestjs-durable-core";
|
|
1034
|
-
import { Inject as
|
|
611
|
+
import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
|
|
1035
612
|
|
|
1036
613
|
// src/in-process-sink.ts
|
|
1037
614
|
import { AgentStreamError } from "@dudousxd/nestjs-agent-core";
|
|
@@ -1102,7 +679,7 @@ var InProcessTokenStreamSink = class {
|
|
|
1102
679
|
};
|
|
1103
680
|
|
|
1104
681
|
// src/durable/durable-agent-runner.ts
|
|
1105
|
-
function
|
|
682
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
1106
683
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1107
684
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1108
685
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1115,19 +692,19 @@ function _ts_decorate4(decorators, target, key, desc) {
|
|
|
1115
692
|
}
|
|
1116
693
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1117
694
|
}
|
|
1118
|
-
__name(
|
|
1119
|
-
function
|
|
695
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
696
|
+
function _ts_metadata3(metadataKey, metadataValue) {
|
|
1120
697
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1121
698
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
1122
699
|
}
|
|
1123
700
|
}
|
|
1124
|
-
__name(
|
|
1125
|
-
function
|
|
701
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
702
|
+
function _ts_param3(paramIndex, decorator) {
|
|
1126
703
|
return function(target, key) {
|
|
1127
704
|
decorator(target, key, paramIndex);
|
|
1128
705
|
};
|
|
1129
706
|
}
|
|
1130
|
-
__name(
|
|
707
|
+
__name(_ts_param3, "_ts_param");
|
|
1131
708
|
function threadOfRun(input) {
|
|
1132
709
|
if (typeof input !== "object" || input === null || !("threadId" in input)) {
|
|
1133
710
|
return void 0;
|
|
@@ -1148,7 +725,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
1148
725
|
store;
|
|
1149
726
|
sink;
|
|
1150
727
|
queue;
|
|
1151
|
-
logger = new
|
|
728
|
+
logger = new Logger2(_DurableAgentRunner.name);
|
|
1152
729
|
constructor(workflows, runs, store, sink, queue) {
|
|
1153
730
|
this.workflows = workflows;
|
|
1154
731
|
this.runs = runs;
|
|
@@ -1235,7 +812,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
1235
812
|
runId: nextRunId
|
|
1236
813
|
}));
|
|
1237
814
|
if (frame !== void 0) {
|
|
1238
|
-
await writer.write(
|
|
815
|
+
await writer.write(encodeStreamEvent3(frame));
|
|
1239
816
|
}
|
|
1240
817
|
} catch (error) {
|
|
1241
818
|
this.logger.error(`could not advance the queue of thread ${threadId} after cancelling ${runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1243,7 +820,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
1243
820
|
}
|
|
1244
821
|
await releaseThreadRun2(this.store, threadId, runId);
|
|
1245
822
|
}
|
|
1246
|
-
await writer.write(
|
|
823
|
+
await writer.write(encodeStreamEvent3({
|
|
1247
824
|
kind: "cancelled"
|
|
1248
825
|
}));
|
|
1249
826
|
await writer.end();
|
|
@@ -1270,14 +847,15 @@ var DurableAgentRunner = class _DurableAgentRunner {
|
|
|
1270
847
|
}
|
|
1271
848
|
}
|
|
1272
849
|
};
|
|
1273
|
-
DurableAgentRunner =
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
850
|
+
DurableAgentRunner = _ts_decorate3([
|
|
851
|
+
Injectable3(),
|
|
852
|
+
_ts_param3(1, Inject3(RUN_GATEWAY2)),
|
|
853
|
+
_ts_param3(2, Inject3(AGENT_STORE2)),
|
|
854
|
+
_ts_param3(3, Inject3(AGENT_SINK)),
|
|
855
|
+
_ts_param3(4, Optional2()),
|
|
856
|
+
_ts_param3(4, Inject3(AGENT_CHAT_QUEUE)),
|
|
857
|
+
_ts_metadata3("design:type", Function),
|
|
858
|
+
_ts_metadata3("design:paramtypes", [
|
|
1281
859
|
typeof WorkflowService === "undefined" ? Object : WorkflowService,
|
|
1282
860
|
typeof RunGateway === "undefined" ? Object : RunGateway,
|
|
1283
861
|
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
@@ -1289,7 +867,7 @@ DurableAgentRunner = _ts_decorate4([
|
|
|
1289
867
|
// src/durable/agent-durable.module.ts
|
|
1290
868
|
import { AGENT_DURABLE_RUNNER } from "@dudousxd/nestjs-agent-core";
|
|
1291
869
|
import { Global, Module } from "@nestjs/common";
|
|
1292
|
-
function
|
|
870
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
1293
871
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1294
872
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1295
873
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1302,7 +880,7 @@ function _ts_decorate5(decorators, target, key, desc) {
|
|
|
1302
880
|
}
|
|
1303
881
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1304
882
|
}
|
|
1305
|
-
__name(
|
|
883
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
1306
884
|
function runnerProviders() {
|
|
1307
885
|
return [
|
|
1308
886
|
DurableAgentRunner,
|
|
@@ -1318,7 +896,7 @@ var AgentDurableHttpModule = class AgentDurableHttpModule2 {
|
|
|
1318
896
|
__name(this, "AgentDurableHttpModule");
|
|
1319
897
|
}
|
|
1320
898
|
};
|
|
1321
|
-
AgentDurableHttpModule =
|
|
899
|
+
AgentDurableHttpModule = _ts_decorate4([
|
|
1322
900
|
Global(),
|
|
1323
901
|
Module({
|
|
1324
902
|
providers: [
|
|
@@ -1356,7 +934,7 @@ var AgentDurableModule = class _AgentDurableModule {
|
|
|
1356
934
|
};
|
|
1357
935
|
}
|
|
1358
936
|
};
|
|
1359
|
-
AgentDurableModule =
|
|
937
|
+
AgentDurableModule = _ts_decorate4([
|
|
1360
938
|
Global(),
|
|
1361
939
|
Module({
|
|
1362
940
|
providers: [
|
|
@@ -1378,10 +956,10 @@ import { Global as Global2, Logger as Logger8, Module as Module2 } from "@nestjs
|
|
|
1378
956
|
import { DiscoveryModule, ModulesContainer, RouterModule } from "@nestjs/core";
|
|
1379
957
|
|
|
1380
958
|
// src/agent-deps.factory.ts
|
|
1381
|
-
import { AGENT_MEMORY, AGENT_MODEL, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_REGISTRY, AGENT_ROLES_POLICY, AGENT_SINK as
|
|
1382
|
-
import { AGENT_OPTIONS
|
|
1383
|
-
import { Inject as
|
|
1384
|
-
function
|
|
959
|
+
import { AGENT_MEMORY, AGENT_MODEL, AGENT_PRICING_STORE, AGENT_PROMPT_CONTRIBUTORS, AGENT_REGISTRY, AGENT_ROLES_POLICY, AGENT_SINK as AGENT_SINK2, AGENT_SKILLS, AGENT_STORE as AGENT_STORE3, AGENT_TOOL_REGISTRY, AgentRegistry as AgentRegistry2, ToolRegistry as ToolRegistry2, normalizeDelegation, summarizeWithModel, windowHistory } from "@dudousxd/nestjs-agent-core";
|
|
960
|
+
import { AGENT_OPTIONS } from "@dudousxd/nestjs-agent-core";
|
|
961
|
+
import { Inject as Inject4, Injectable as Injectable4, Optional as Optional3 } from "@nestjs/common";
|
|
962
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
1385
963
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1386
964
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1387
965
|
r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1394,19 +972,19 @@ function _ts_decorate6(decorators, target, key, desc) {
|
|
|
1394
972
|
}
|
|
1395
973
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1396
974
|
}
|
|
1397
|
-
__name(
|
|
1398
|
-
function
|
|
975
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
976
|
+
function _ts_metadata4(metadataKey, metadataValue) {
|
|
1399
977
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1400
978
|
return Reflect.metadata(metadataKey, metadataValue);
|
|
1401
979
|
}
|
|
1402
980
|
}
|
|
1403
|
-
__name(
|
|
1404
|
-
function
|
|
981
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
982
|
+
function _ts_param4(paramIndex, decorator) {
|
|
1405
983
|
return function(target, key) {
|
|
1406
984
|
decorator(target, key, paramIndex);
|
|
1407
985
|
};
|
|
1408
986
|
}
|
|
1409
|
-
__name(
|
|
987
|
+
__name(_ts_param4, "_ts_param");
|
|
1410
988
|
function delegateToolName(args) {
|
|
1411
989
|
const slug = args.target.replace(/[^a-zA-Z0-9]+/g, "_");
|
|
1412
990
|
return args.detached === true ? `start_${slug}` : `ask_${slug}`;
|
|
@@ -1414,230 +992,657 @@ function delegateToolName(args) {
|
|
|
1414
992
|
__name(delegateToolName, "delegateToolName");
|
|
1415
993
|
var AgentDepsFactory2 = class {
|
|
1416
994
|
static {
|
|
1417
|
-
__name(this, "AgentDepsFactory");
|
|
995
|
+
__name(this, "AgentDepsFactory");
|
|
996
|
+
}
|
|
997
|
+
options;
|
|
998
|
+
model;
|
|
999
|
+
store;
|
|
1000
|
+
sink;
|
|
1001
|
+
rolesPolicy;
|
|
1002
|
+
registry;
|
|
1003
|
+
agents;
|
|
1004
|
+
promptContributors;
|
|
1005
|
+
pricingStore;
|
|
1006
|
+
skills;
|
|
1007
|
+
memory;
|
|
1008
|
+
constructor(options, model, store, sink, rolesPolicy, registry, agents, promptContributors, pricingStore, skills, memory) {
|
|
1009
|
+
this.options = options;
|
|
1010
|
+
this.model = model;
|
|
1011
|
+
this.store = store;
|
|
1012
|
+
this.sink = sink;
|
|
1013
|
+
this.rolesPolicy = rolesPolicy;
|
|
1014
|
+
this.registry = registry;
|
|
1015
|
+
this.agents = agents;
|
|
1016
|
+
this.promptContributors = promptContributors;
|
|
1017
|
+
this.pricingStore = pricingStore;
|
|
1018
|
+
this.skills = skills;
|
|
1019
|
+
this.memory = memory;
|
|
1020
|
+
}
|
|
1021
|
+
/** The turn's memory seam, shared with the read-back endpoint. Undefined → memory is not configured. */
|
|
1022
|
+
memoryConfig() {
|
|
1023
|
+
return this.memory;
|
|
1024
|
+
}
|
|
1025
|
+
/** The turn's skills seam, shared with the listing endpoint. Undefined → skills are not configured. */
|
|
1026
|
+
skillsConfig() {
|
|
1027
|
+
return this.skills;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* The agent a turn uses when the caller names none: the explicit `defaultAgent` option, else the
|
|
1031
|
+
* sole registered `@Agent` when there is exactly one, else `'default'` (a bare assistant).
|
|
1032
|
+
*/
|
|
1033
|
+
defaultAgentName() {
|
|
1034
|
+
if (this.options.defaultAgent !== void 0) {
|
|
1035
|
+
return this.options.defaultAgent;
|
|
1036
|
+
}
|
|
1037
|
+
const registered = this.agents.list();
|
|
1038
|
+
return registered.length === 1 ? registered[0]?.name ?? "default" : "default";
|
|
1039
|
+
}
|
|
1040
|
+
effectiveTools(definition) {
|
|
1041
|
+
if (definition === void 0) {
|
|
1042
|
+
return void 0;
|
|
1043
|
+
}
|
|
1044
|
+
const delegated = (definition.delegatesTo ?? []).map((edge) => {
|
|
1045
|
+
const { agent, detached } = normalizeDelegation(edge);
|
|
1046
|
+
return delegateToolName({
|
|
1047
|
+
target: agent,
|
|
1048
|
+
detached
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
if (definition.tools === void 0 && delegated.length === 0) {
|
|
1052
|
+
return void 0;
|
|
1053
|
+
}
|
|
1054
|
+
return [
|
|
1055
|
+
...definition.tools ?? [],
|
|
1056
|
+
...delegated
|
|
1057
|
+
];
|
|
1058
|
+
}
|
|
1059
|
+
forAgent(agentName) {
|
|
1060
|
+
const name = agentName ?? this.defaultAgentName();
|
|
1061
|
+
const definition = this.agents.get(name);
|
|
1062
|
+
const toolAllowList = this.effectiveTools(definition);
|
|
1063
|
+
const followUpsCount = this.followUpsCount();
|
|
1064
|
+
const retrieval = this.options.retrieval;
|
|
1065
|
+
const historyPolicy = this.historyPolicy(definition);
|
|
1066
|
+
const ask = definition?.ask ?? this.options.ask;
|
|
1067
|
+
return {
|
|
1068
|
+
model: this.model,
|
|
1069
|
+
store: this.store,
|
|
1070
|
+
sink: this.sink,
|
|
1071
|
+
rolesPolicy: this.rolesPolicy,
|
|
1072
|
+
...this.options.approvalPolicy !== void 0 ? {
|
|
1073
|
+
approvalPolicy: this.options.approvalPolicy
|
|
1074
|
+
} : {},
|
|
1075
|
+
registry: this.registry,
|
|
1076
|
+
promptContributors: this.promptContributors,
|
|
1077
|
+
systemPrompt: definition?.systemPrompt ?? this.options.systemPrompt ?? "You are a helpful assistant.",
|
|
1078
|
+
maxSteps: definition?.maxSteps ?? 8,
|
|
1079
|
+
...definition?.maxDelegationDepth !== void 0 ? {
|
|
1080
|
+
maxDelegationDepth: definition.maxDelegationDepth
|
|
1081
|
+
} : {},
|
|
1082
|
+
...definition?.maxAgentAppearances !== void 0 ? {
|
|
1083
|
+
maxAgentAppearances: definition.maxAgentAppearances
|
|
1084
|
+
} : {},
|
|
1085
|
+
...definition?.modelId !== void 0 ? {
|
|
1086
|
+
modelId: definition.modelId
|
|
1087
|
+
} : {},
|
|
1088
|
+
...this.pricingStore !== void 0 ? {
|
|
1089
|
+
pricingStore: this.pricingStore
|
|
1090
|
+
} : {},
|
|
1091
|
+
...historyPolicy !== void 0 ? {
|
|
1092
|
+
historyPolicy
|
|
1093
|
+
} : {},
|
|
1094
|
+
inputProcessors: this.options.inputProcessors ?? [],
|
|
1095
|
+
outputProcessors: this.options.outputProcessors ?? [],
|
|
1096
|
+
...definition?.outputSchema !== void 0 ? {
|
|
1097
|
+
outputSchema: definition.outputSchema
|
|
1098
|
+
} : {},
|
|
1099
|
+
...definition?.outputRepairAttempts !== void 0 ? {
|
|
1100
|
+
outputRepairAttempts: definition.outputRepairAttempts
|
|
1101
|
+
} : {},
|
|
1102
|
+
...definition?.intake !== void 0 ? {
|
|
1103
|
+
intake: definition.intake
|
|
1104
|
+
} : {},
|
|
1105
|
+
// Most specific wins, exactly as `historyPolicy` resolves: the agent's own `@Agent({ ask })`,
|
|
1106
|
+
// else the module-wide flag. Both are module config, so every process of a deployment agrees.
|
|
1107
|
+
...ask === true ? {
|
|
1108
|
+
ask: true
|
|
1109
|
+
} : {},
|
|
1110
|
+
// Module-wide, never per-agent: a skill is a procedure, not a persona, and an agent that could
|
|
1111
|
+
// opt out of one would make the catalog depend on which agent a turn happened to select — a
|
|
1112
|
+
// per-turn fact the worker re-deriving the tool list cannot see.
|
|
1113
|
+
...this.skills !== void 0 ? {
|
|
1114
|
+
skills: this.skills
|
|
1115
|
+
} : {},
|
|
1116
|
+
// Module-wide, never per-agent, for the same reason skills are: which memories a turn carries
|
|
1117
|
+
// cannot depend on which persona answered it, or the worker re-deriving a dispatched turn's
|
|
1118
|
+
// tool list would disagree with the loop about whether `remember` was offered.
|
|
1119
|
+
...this.memory !== void 0 ? {
|
|
1120
|
+
memory: this.memory
|
|
1121
|
+
} : {},
|
|
1122
|
+
...toolAllowList !== void 0 ? {
|
|
1123
|
+
toolAllowList
|
|
1124
|
+
} : {},
|
|
1125
|
+
...this.options.toolTimeoutMs !== void 0 ? {
|
|
1126
|
+
toolTimeoutMs: this.options.toolTimeoutMs
|
|
1127
|
+
} : {},
|
|
1128
|
+
...this.options.toolTransientRetry !== void 0 ? {
|
|
1129
|
+
toolTransientRetry: this.options.toolTransientRetry
|
|
1130
|
+
} : {},
|
|
1131
|
+
...followUpsCount !== void 0 ? {
|
|
1132
|
+
followUpsCount
|
|
1133
|
+
} : {},
|
|
1134
|
+
...retrieval?.mode === "inject" ? {
|
|
1135
|
+
retriever: retrieval.retriever,
|
|
1136
|
+
...retrieval.topK !== void 0 ? {
|
|
1137
|
+
retrievalTopK: retrieval.topK
|
|
1138
|
+
} : {}
|
|
1139
|
+
} : {}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* The ceiling on how much of a thread this agent's turns carry, most specific wins: the agent's
|
|
1144
|
+
* own `@Agent({ history })`, else a module-wide custom `historyPolicy`, else the module-wide
|
|
1145
|
+
* `history` window. Undefined → unbounded, the behaviour of a module that configures none.
|
|
1146
|
+
*/
|
|
1147
|
+
historyPolicy(definition) {
|
|
1148
|
+
if (definition?.history !== void 0) {
|
|
1149
|
+
return this.window(definition.history);
|
|
1150
|
+
}
|
|
1151
|
+
if (this.options.historyPolicy !== void 0) {
|
|
1152
|
+
return this.options.historyPolicy;
|
|
1153
|
+
}
|
|
1154
|
+
return this.options.history === void 0 ? void 0 : this.window(this.options.history);
|
|
1155
|
+
}
|
|
1156
|
+
/** Build the built-in window policy from the declarative `{ maxMessages, maxTokens, summarize }`. */
|
|
1157
|
+
window(config) {
|
|
1158
|
+
return windowHistory({
|
|
1159
|
+
...config.maxMessages !== void 0 ? {
|
|
1160
|
+
maxMessages: config.maxMessages
|
|
1161
|
+
} : {},
|
|
1162
|
+
...config.maxTokens !== void 0 ? {
|
|
1163
|
+
maxTokens: config.maxTokens
|
|
1164
|
+
} : {},
|
|
1165
|
+
// The shared module model, not a per-agent one: an agent's `modelId` is an accounting label,
|
|
1166
|
+
// and only one provider is ever bound.
|
|
1167
|
+
...config.summarize === true ? {
|
|
1168
|
+
summarize: summarizeWithModel(this.model)
|
|
1169
|
+
} : {}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
/** Normalize the `followUps` option (`true` → 3, `{ count }` → count) to a number, or undefined. */
|
|
1173
|
+
followUpsCount() {
|
|
1174
|
+
const followUps = this.options.followUps;
|
|
1175
|
+
if (followUps === true) {
|
|
1176
|
+
return 3;
|
|
1177
|
+
}
|
|
1178
|
+
if (typeof followUps === "object") {
|
|
1179
|
+
return followUps.count;
|
|
1180
|
+
}
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
AgentDepsFactory2 = _ts_decorate5([
|
|
1185
|
+
Injectable4(),
|
|
1186
|
+
_ts_param4(0, Inject4(AGENT_OPTIONS)),
|
|
1187
|
+
_ts_param4(1, Inject4(AGENT_MODEL)),
|
|
1188
|
+
_ts_param4(2, Inject4(AGENT_STORE3)),
|
|
1189
|
+
_ts_param4(3, Inject4(AGENT_SINK2)),
|
|
1190
|
+
_ts_param4(4, Inject4(AGENT_ROLES_POLICY)),
|
|
1191
|
+
_ts_param4(5, Inject4(AGENT_TOOL_REGISTRY)),
|
|
1192
|
+
_ts_param4(6, Inject4(AGENT_REGISTRY)),
|
|
1193
|
+
_ts_param4(7, Inject4(AGENT_PROMPT_CONTRIBUTORS)),
|
|
1194
|
+
_ts_param4(8, Optional3()),
|
|
1195
|
+
_ts_param4(8, Inject4(AGENT_PRICING_STORE)),
|
|
1196
|
+
_ts_param4(9, Optional3()),
|
|
1197
|
+
_ts_param4(9, Inject4(AGENT_SKILLS)),
|
|
1198
|
+
_ts_param4(10, Optional3()),
|
|
1199
|
+
_ts_param4(10, Inject4(AGENT_MEMORY)),
|
|
1200
|
+
_ts_metadata4("design:type", Function),
|
|
1201
|
+
_ts_metadata4("design:paramtypes", [
|
|
1202
|
+
typeof AgentModuleOptions === "undefined" ? Object : AgentModuleOptions,
|
|
1203
|
+
typeof ModelProvider === "undefined" ? Object : ModelProvider,
|
|
1204
|
+
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
1205
|
+
typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
|
|
1206
|
+
typeof RolesPolicy === "undefined" ? Object : RolesPolicy,
|
|
1207
|
+
typeof ToolRegistry2 === "undefined" ? Object : ToolRegistry2,
|
|
1208
|
+
typeof AgentRegistry2 === "undefined" ? Object : AgentRegistry2,
|
|
1209
|
+
Array,
|
|
1210
|
+
Object,
|
|
1211
|
+
Object,
|
|
1212
|
+
Object
|
|
1213
|
+
])
|
|
1214
|
+
], AgentDepsFactory2);
|
|
1215
|
+
|
|
1216
|
+
// src/agent.service.ts
|
|
1217
|
+
import { AGENT_ATTACHMENT_STAGING as AGENT_ATTACHMENT_STAGING2, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_MODEL_CATALOG, AGENT_OPTIONS as AGENT_OPTIONS3, AGENT_QUOTA_PROVIDER as AGENT_QUOTA_PROVIDER2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE5, REQUESTER_APPROVER, findCatalogModel, mayDecideApproval, readElicitationQuestions, settleDeadRun as settleDeadRun2, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
|
|
1218
|
+
import { BadRequestException, ConflictException as ConflictException2, ForbiddenException, GoneException, HttpException, HttpStatus, Inject as Inject6, Injectable as Injectable6, NotFoundException, NotImplementedException as NotImplementedException2, Optional as Optional5 } from "@nestjs/common";
|
|
1219
|
+
|
|
1220
|
+
// src/queue/chat-queue.service.ts
|
|
1221
|
+
import { AGENT_ATTACHMENT_STAGING, AGENT_OPTIONS as AGENT_OPTIONS2, AGENT_QUOTA_PROVIDER, AGENT_SINK as AGENT_SINK3, AGENT_STORE as AGENT_STORE4, encodeStreamEvent as encodeStreamEvent4, isChatQueueStore, queuedMessageView } from "@dudousxd/nestjs-agent-core";
|
|
1222
|
+
import { Inject as Inject5, Injectable as Injectable5, Logger as Logger3, NotImplementedException, Optional as Optional4 } from "@nestjs/common";
|
|
1223
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
1224
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1225
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
|
|
1226
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
1227
|
+
} else {
|
|
1228
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
1229
|
+
if (d = decorators[i]) {
|
|
1230
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1235
|
+
}
|
|
1236
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1237
|
+
function _ts_metadata5(metadataKey, metadataValue) {
|
|
1238
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
|
|
1239
|
+
return Reflect.metadata(metadataKey, metadataValue);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
1243
|
+
function _ts_param5(paramIndex, decorator) {
|
|
1244
|
+
return function(target, key) {
|
|
1245
|
+
decorator(target, key, paramIndex);
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
__name(_ts_param5, "_ts_param");
|
|
1249
|
+
var MAX_DRAIN_ATTEMPTS = 8;
|
|
1250
|
+
var ChatQueueService2 = class _ChatQueueService {
|
|
1251
|
+
static {
|
|
1252
|
+
__name(this, "ChatQueueService");
|
|
1418
1253
|
}
|
|
1419
|
-
options;
|
|
1420
|
-
model;
|
|
1421
1254
|
store;
|
|
1422
1255
|
sink;
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1256
|
+
quotaProvider;
|
|
1257
|
+
options;
|
|
1258
|
+
staging;
|
|
1259
|
+
logger = new Logger3(_ChatQueueService.name);
|
|
1260
|
+
/**
|
|
1261
|
+
* Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
|
|
1262
|
+
* alive whatever the runner says — it simply has not registered the run yet.
|
|
1263
|
+
*/
|
|
1264
|
+
starting = /* @__PURE__ */ new Set();
|
|
1265
|
+
constructor(store, sink, quotaProvider, options, staging) {
|
|
1433
1266
|
this.store = store;
|
|
1434
1267
|
this.sink = sink;
|
|
1435
|
-
this.
|
|
1436
|
-
this.
|
|
1437
|
-
this.
|
|
1438
|
-
this.promptContributors = promptContributors;
|
|
1439
|
-
this.pricingStore = pricingStore;
|
|
1440
|
-
this.skills = skills;
|
|
1441
|
-
this.memory = memory;
|
|
1268
|
+
this.quotaProvider = quotaProvider;
|
|
1269
|
+
this.options = options;
|
|
1270
|
+
this.staging = staging;
|
|
1442
1271
|
}
|
|
1443
|
-
/**
|
|
1444
|
-
|
|
1445
|
-
return this.
|
|
1272
|
+
/** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
|
|
1273
|
+
get supported() {
|
|
1274
|
+
return isChatQueueStore(this.store);
|
|
1446
1275
|
}
|
|
1447
|
-
/** The
|
|
1448
|
-
|
|
1449
|
-
|
|
1276
|
+
/** The bound store as a queue store, or a 501 naming what is missing. */
|
|
1277
|
+
queueStore() {
|
|
1278
|
+
if (!isChatQueueStore(this.store)) {
|
|
1279
|
+
throw new NotImplementedException("Queueing messages requires an AgentStore that implements ChatQueueStore (enqueueMessage, listQueue, \u2026, claimActiveStream, releaseActiveStream); the bound store does not.");
|
|
1280
|
+
}
|
|
1281
|
+
return this.store;
|
|
1282
|
+
}
|
|
1283
|
+
async state(threadId) {
|
|
1284
|
+
const store = this.queueStore();
|
|
1285
|
+
const [items, paused] = await Promise.all([
|
|
1286
|
+
store.listQueue(threadId),
|
|
1287
|
+
store.queuePause(threadId)
|
|
1288
|
+
]);
|
|
1289
|
+
return {
|
|
1290
|
+
items: items.map(queuedMessageView),
|
|
1291
|
+
paused
|
|
1292
|
+
};
|
|
1450
1293
|
}
|
|
1451
1294
|
/**
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1295
|
+
* Whether `holder` is still running. A run this process is about to start is; otherwise the
|
|
1296
|
+
* runner decides, and a runner that cannot tell says yes.
|
|
1454
1297
|
*/
|
|
1455
|
-
|
|
1456
|
-
if (this.
|
|
1457
|
-
return
|
|
1298
|
+
async isAlive(holder, runner) {
|
|
1299
|
+
if (this.starting.has(holder)) {
|
|
1300
|
+
return true;
|
|
1301
|
+
}
|
|
1302
|
+
if (typeof runner.isRunActive !== "function") {
|
|
1303
|
+
return true;
|
|
1304
|
+
}
|
|
1305
|
+
try {
|
|
1306
|
+
return await runner.isRunActive(holder);
|
|
1307
|
+
} catch {
|
|
1308
|
+
return true;
|
|
1458
1309
|
}
|
|
1459
|
-
const registered = this.agents.list();
|
|
1460
|
-
return registered.length === 1 ? registered[0]?.name ?? "default" : "default";
|
|
1461
1310
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1311
|
+
/**
|
|
1312
|
+
* The run holding `threadId`, or `null` when it is free — a holder the runner reports dead counts
|
|
1313
|
+
* as free, and is returned as `stale` so the caller can claim over it.
|
|
1314
|
+
*/
|
|
1315
|
+
async holder(threadId, runner) {
|
|
1316
|
+
const holder = await this.queueStore().activeRunForThread(threadId);
|
|
1317
|
+
if (holder === null) {
|
|
1318
|
+
return {
|
|
1319
|
+
live: null,
|
|
1320
|
+
stale: null
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
return await this.isAlive(holder, runner) ? {
|
|
1324
|
+
live: holder,
|
|
1325
|
+
stale: null
|
|
1326
|
+
} : {
|
|
1327
|
+
live: null,
|
|
1328
|
+
stale: holder
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Decide what follows a TOP-LEVEL run that is settling on `threadId`, and move the thread there:
|
|
1333
|
+
* hand it to the next queued message (claimed and popped here, started by the caller), or pause
|
|
1334
|
+
* the queue, or release the thread. Call it BEFORE the run's terminal frame, so a client reading
|
|
1335
|
+
* the stream learns what comes next from the `queue` frame, and so the thread is already free
|
|
1336
|
+
* when the client sees the run end.
|
|
1337
|
+
*
|
|
1338
|
+
* A no-op returning `{}` on a store without a queue.
|
|
1339
|
+
*/
|
|
1340
|
+
async plan(args) {
|
|
1341
|
+
if (!isChatQueueStore(this.store)) {
|
|
1342
|
+
return {};
|
|
1343
|
+
}
|
|
1344
|
+
const store = this.store;
|
|
1345
|
+
const { threadId, outcome } = args;
|
|
1346
|
+
if (await store.activeRunForThread(threadId) !== args.runId) {
|
|
1347
|
+
return {};
|
|
1348
|
+
}
|
|
1349
|
+
let holding = args.runId;
|
|
1350
|
+
for (let attempt = 0; attempt < MAX_DRAIN_ATTEMPTS; attempt += 1) {
|
|
1351
|
+
const items = await store.listQueue(threadId);
|
|
1352
|
+
const head = items[0];
|
|
1353
|
+
if (head === void 0) {
|
|
1354
|
+
if (holding !== null) {
|
|
1355
|
+
await store.releaseActiveStream(threadId, holding);
|
|
1356
|
+
holding = null;
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
return {};
|
|
1360
|
+
}
|
|
1361
|
+
let pause = await store.queuePause(threadId);
|
|
1362
|
+
if (pause === null) {
|
|
1363
|
+
const reason = pauseReasonFor(outcome, head);
|
|
1364
|
+
if (reason !== null) {
|
|
1365
|
+
pause = {
|
|
1366
|
+
reason,
|
|
1367
|
+
...outcome === "failed" && args.error !== void 0 ? {
|
|
1368
|
+
message: args.error
|
|
1369
|
+
} : {},
|
|
1370
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1371
|
+
};
|
|
1372
|
+
await store.setQueuePause(threadId, pause);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (pause === null) {
|
|
1376
|
+
const blocked = await this.quotaBlock(head.actor);
|
|
1377
|
+
if (blocked !== null) {
|
|
1378
|
+
pause = {
|
|
1379
|
+
reason: "quota_exceeded",
|
|
1380
|
+
message: blocked,
|
|
1381
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1382
|
+
};
|
|
1383
|
+
await store.setQueuePause(threadId, pause);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
if (pause !== null) {
|
|
1387
|
+
if (holding !== null) {
|
|
1388
|
+
await store.releaseActiveStream(threadId, holding);
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
frame: queueFrame(items, pause)
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
const claimed = await store.claimActiveStream(threadId, head.id, holding !== null ? {
|
|
1395
|
+
replacing: holding
|
|
1396
|
+
} : {});
|
|
1397
|
+
if (!claimed) {
|
|
1398
|
+
return {};
|
|
1399
|
+
}
|
|
1400
|
+
holding = head.id;
|
|
1401
|
+
if (!await store.removeQueuedMessage(head.id)) {
|
|
1402
|
+
await store.releaseActiveStream(threadId, head.id);
|
|
1403
|
+
holding = null;
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const next = {
|
|
1407
|
+
runId: head.id,
|
|
1408
|
+
messageId: head.id,
|
|
1409
|
+
input: await this.inputFor(head)
|
|
1410
|
+
};
|
|
1411
|
+
return {
|
|
1412
|
+
next,
|
|
1413
|
+
frame: queueFrame(items.slice(1), null, {
|
|
1414
|
+
messageId: head.id,
|
|
1415
|
+
runId: head.id
|
|
1416
|
+
})
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
if (holding !== null) {
|
|
1420
|
+
await store.releaseActiveStream(threadId, holding);
|
|
1421
|
+
}
|
|
1422
|
+
return {};
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* {@link plan}, then start what it picked with `start`. What an in-process settle (the inline
|
|
1426
|
+
* runner, a durable cancel issued outside a workflow) calls; answers the frame to write.
|
|
1427
|
+
*/
|
|
1428
|
+
async handoff(args, start) {
|
|
1429
|
+
const plan = await this.plan(args);
|
|
1430
|
+
if (plan.next === void 0) {
|
|
1431
|
+
return plan.frame;
|
|
1432
|
+
}
|
|
1433
|
+
return await this.launch(args.threadId, plan.next, start) ? plan.frame : this.pausedFrame(args.threadId);
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Start the head of an IDLE thread's queue — after a send queued behind a run that turned out to
|
|
1437
|
+
* be gone, after a resume, after a stale holder. Does nothing while a live run holds the thread
|
|
1438
|
+
* (its settle drains) or while the queue is paused. Answers the started run's id, if any.
|
|
1439
|
+
*/
|
|
1440
|
+
async kick(threadId, runner) {
|
|
1441
|
+
if (!isChatQueueStore(this.store)) {
|
|
1464
1442
|
return void 0;
|
|
1465
1443
|
}
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
return
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
});
|
|
1444
|
+
const { live, stale } = await this.holder(threadId, runner);
|
|
1445
|
+
if (live !== null) {
|
|
1446
|
+
return void 0;
|
|
1447
|
+
}
|
|
1448
|
+
if (stale !== null) {
|
|
1449
|
+
this.logger.warn(`thread ${threadId} was held by run ${stale}, which is no longer running`);
|
|
1450
|
+
}
|
|
1451
|
+
const plan = await this.plan({
|
|
1452
|
+
threadId,
|
|
1453
|
+
runId: stale,
|
|
1454
|
+
outcome: "completed"
|
|
1472
1455
|
});
|
|
1473
|
-
if (
|
|
1456
|
+
if (plan.next === void 0) {
|
|
1474
1457
|
return void 0;
|
|
1475
1458
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1459
|
+
const started = await this.launch(threadId, plan.next, (input, runId) => runner.start(input, {
|
|
1460
|
+
runId
|
|
1461
|
+
}));
|
|
1462
|
+
if (started && plan.frame !== void 0) {
|
|
1463
|
+
await this.write(plan.next.runId, plan.frame);
|
|
1464
|
+
}
|
|
1465
|
+
return started ? plan.next.runId : void 0;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Start a turn the thread is already claimed for. On failure the message goes back to the head of
|
|
1469
|
+
* the queue, the queue pauses (`start_failed`), and the claim is released — nothing is lost and
|
|
1470
|
+
* nothing is left holding the thread.
|
|
1471
|
+
*/
|
|
1472
|
+
async launch(threadId, next, start) {
|
|
1473
|
+
this.starting.add(next.runId);
|
|
1474
|
+
try {
|
|
1475
|
+
await start(next.input, next.runId);
|
|
1476
|
+
return true;
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1479
|
+
this.logger.error(`queued message ${next.messageId} could not start: ${message}`);
|
|
1480
|
+
await this.restore(threadId, next, "start_failed", message);
|
|
1481
|
+
return false;
|
|
1482
|
+
} finally {
|
|
1483
|
+
this.starting.delete(next.runId);
|
|
1484
|
+
}
|
|
1480
1485
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
const
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
sink: this.sink,
|
|
1493
|
-
rolesPolicy: this.rolesPolicy,
|
|
1494
|
-
...this.options.approvalPolicy !== void 0 ? {
|
|
1495
|
-
approvalPolicy: this.options.approvalPolicy
|
|
1496
|
-
} : {},
|
|
1497
|
-
registry: this.registry,
|
|
1498
|
-
promptContributors: this.promptContributors,
|
|
1499
|
-
systemPrompt: definition?.systemPrompt ?? this.options.systemPrompt ?? "You are a helpful assistant.",
|
|
1500
|
-
maxSteps: definition?.maxSteps ?? 8,
|
|
1501
|
-
...definition?.maxDelegationDepth !== void 0 ? {
|
|
1502
|
-
maxDelegationDepth: definition.maxDelegationDepth
|
|
1503
|
-
} : {},
|
|
1504
|
-
...definition?.maxAgentAppearances !== void 0 ? {
|
|
1505
|
-
maxAgentAppearances: definition.maxAgentAppearances
|
|
1506
|
-
} : {},
|
|
1507
|
-
...definition?.modelId !== void 0 ? {
|
|
1508
|
-
modelId: definition.modelId
|
|
1509
|
-
} : {},
|
|
1510
|
-
...this.pricingStore !== void 0 ? {
|
|
1511
|
-
pricingStore: this.pricingStore
|
|
1512
|
-
} : {},
|
|
1513
|
-
...historyPolicy !== void 0 ? {
|
|
1514
|
-
historyPolicy
|
|
1515
|
-
} : {},
|
|
1516
|
-
inputProcessors: this.options.inputProcessors ?? [],
|
|
1517
|
-
outputProcessors: this.options.outputProcessors ?? [],
|
|
1518
|
-
...definition?.outputSchema !== void 0 ? {
|
|
1519
|
-
outputSchema: definition.outputSchema
|
|
1520
|
-
} : {},
|
|
1521
|
-
...definition?.outputRepairAttempts !== void 0 ? {
|
|
1522
|
-
outputRepairAttempts: definition.outputRepairAttempts
|
|
1523
|
-
} : {},
|
|
1524
|
-
...definition?.intake !== void 0 ? {
|
|
1525
|
-
intake: definition.intake
|
|
1526
|
-
} : {},
|
|
1527
|
-
// Most specific wins, exactly as `historyPolicy` resolves: the agent's own `@Agent({ ask })`,
|
|
1528
|
-
// else the module-wide flag. Both are module config, so every process of a deployment agrees.
|
|
1529
|
-
...ask === true ? {
|
|
1530
|
-
ask: true
|
|
1531
|
-
} : {},
|
|
1532
|
-
// Module-wide, never per-agent: a skill is a procedure, not a persona, and an agent that could
|
|
1533
|
-
// opt out of one would make the catalog depend on which agent a turn happened to select — a
|
|
1534
|
-
// per-turn fact the worker re-deriving the tool list cannot see.
|
|
1535
|
-
...this.skills !== void 0 ? {
|
|
1536
|
-
skills: this.skills
|
|
1537
|
-
} : {},
|
|
1538
|
-
// Module-wide, never per-agent, for the same reason skills are: which memories a turn carries
|
|
1539
|
-
// cannot depend on which persona answered it, or the worker re-deriving a dispatched turn's
|
|
1540
|
-
// tool list would disagree with the loop about whether `remember` was offered.
|
|
1541
|
-
...this.memory !== void 0 ? {
|
|
1542
|
-
memory: this.memory
|
|
1543
|
-
} : {},
|
|
1544
|
-
...toolAllowList !== void 0 ? {
|
|
1545
|
-
toolAllowList
|
|
1546
|
-
} : {},
|
|
1547
|
-
...this.options.toolTimeoutMs !== void 0 ? {
|
|
1548
|
-
toolTimeoutMs: this.options.toolTimeoutMs
|
|
1486
|
+
/** Put a turn that never started back at the head of its queue, paused, and free the thread. */
|
|
1487
|
+
async restore(threadId, next, reason, message) {
|
|
1488
|
+
const store = this.queueStore();
|
|
1489
|
+
const { input } = next;
|
|
1490
|
+
await store.enqueueMessage({
|
|
1491
|
+
threadId,
|
|
1492
|
+
actor: input.actor,
|
|
1493
|
+
content: input.userText,
|
|
1494
|
+
at: "head",
|
|
1495
|
+
...input.attachments !== void 0 ? {
|
|
1496
|
+
attachments: input.attachments
|
|
1549
1497
|
} : {},
|
|
1550
|
-
...
|
|
1551
|
-
|
|
1498
|
+
...input.agentName !== void 0 ? {
|
|
1499
|
+
agentName: input.agentName
|
|
1552
1500
|
} : {},
|
|
1553
|
-
...
|
|
1554
|
-
|
|
1501
|
+
...input.model !== void 0 ? {
|
|
1502
|
+
model: input.model
|
|
1555
1503
|
} : {},
|
|
1556
|
-
...
|
|
1557
|
-
|
|
1558
|
-
...retrieval.topK !== void 0 ? {
|
|
1559
|
-
retrievalTopK: retrieval.topK
|
|
1560
|
-
} : {}
|
|
1504
|
+
...input.pageContext !== void 0 ? {
|
|
1505
|
+
pageContext: input.pageContext
|
|
1561
1506
|
} : {}
|
|
1507
|
+
});
|
|
1508
|
+
await store.setQueuePause(threadId, {
|
|
1509
|
+
reason,
|
|
1510
|
+
message,
|
|
1511
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1512
|
+
});
|
|
1513
|
+
await store.releaseActiveStream(threadId, next.runId);
|
|
1514
|
+
}
|
|
1515
|
+
/** A `queue` frame with the thread's current state. */
|
|
1516
|
+
async pausedFrame(threadId) {
|
|
1517
|
+
return {
|
|
1518
|
+
kind: "queue",
|
|
1519
|
+
queue: await this.state(threadId)
|
|
1562
1520
|
};
|
|
1563
1521
|
}
|
|
1564
1522
|
/**
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1523
|
+
* Tell whoever is watching the thread that its queue changed: a `queue` frame into the stream of
|
|
1524
|
+
* the run holding it. Best-effort — a thread nobody is streaming has no one to tell, and the next
|
|
1525
|
+
* read of the thread carries the queue anyway.
|
|
1568
1526
|
*/
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1527
|
+
async publish(threadId) {
|
|
1528
|
+
const state = await this.state(threadId);
|
|
1529
|
+
const holder = await this.queueStore().activeRunForThread(threadId);
|
|
1530
|
+
if (holder !== null) {
|
|
1531
|
+
await this.write(holder, {
|
|
1532
|
+
kind: "queue",
|
|
1533
|
+
queue: state
|
|
1534
|
+
}).catch((error) => {
|
|
1535
|
+
this.logger.warn(`could not publish the queue of thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1536
|
+
});
|
|
1572
1537
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1538
|
+
return state;
|
|
1539
|
+
}
|
|
1540
|
+
async write(runId, frame) {
|
|
1541
|
+
const writer = await this.sink.open(runId);
|
|
1542
|
+
await writer.write(encodeStreamEvent4(frame));
|
|
1543
|
+
}
|
|
1544
|
+
/** Why `actor` may not start a turn now (the quota message), or `null`. */
|
|
1545
|
+
async quotaBlock(actor) {
|
|
1546
|
+
if (this.options?.quota === void 0 || this.quotaProvider === void 0) {
|
|
1547
|
+
return null;
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
const { blocked } = await this.quotaProvider.report({
|
|
1551
|
+
actor
|
|
1552
|
+
});
|
|
1553
|
+
if (blocked === void 0) {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
return blocked.reason ?? `The ${blocked.period === "day" ? "daily" : "monthly"} quota is used up`;
|
|
1557
|
+
} catch {
|
|
1558
|
+
return null;
|
|
1575
1559
|
}
|
|
1576
|
-
return this.options.history === void 0 ? void 0 : this.window(this.options.history);
|
|
1577
1560
|
}
|
|
1578
|
-
/**
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1561
|
+
/**
|
|
1562
|
+
* The turn a queued message starts: everything it was queued with, today's date, and its
|
|
1563
|
+
* attachments' urls minted afresh — the ones stored at enqueue time may have expired while it
|
|
1564
|
+
* waited. An attachment the staging store no longer resolves keeps its stored url.
|
|
1565
|
+
*/
|
|
1566
|
+
async inputFor(message) {
|
|
1567
|
+
const attachments = await this.freshAttachments(message.actor, message.attachments ?? []);
|
|
1568
|
+
return {
|
|
1569
|
+
threadId: message.threadId,
|
|
1570
|
+
actor: message.actor,
|
|
1571
|
+
userText: message.content,
|
|
1572
|
+
day: utcDay(),
|
|
1573
|
+
...message.agentName !== void 0 ? {
|
|
1574
|
+
agentName: message.agentName
|
|
1583
1575
|
} : {},
|
|
1584
|
-
...
|
|
1585
|
-
|
|
1576
|
+
...attachments.length > 0 ? {
|
|
1577
|
+
attachments
|
|
1586
1578
|
} : {},
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1579
|
+
...message.pageContext !== void 0 ? {
|
|
1580
|
+
pageContext: message.pageContext
|
|
1581
|
+
} : {},
|
|
1582
|
+
...message.model !== void 0 ? {
|
|
1583
|
+
model: message.model
|
|
1591
1584
|
} : {}
|
|
1592
|
-
}
|
|
1585
|
+
};
|
|
1593
1586
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
return 3;
|
|
1599
|
-
}
|
|
1600
|
-
if (typeof followUps === "object") {
|
|
1601
|
-
return followUps.count;
|
|
1587
|
+
async freshAttachments(actor, attachments) {
|
|
1588
|
+
const staging = this.staging;
|
|
1589
|
+
if (attachments.length === 0 || staging === void 0 || typeof staging.resolve !== "function") {
|
|
1590
|
+
return attachments;
|
|
1602
1591
|
}
|
|
1603
|
-
return
|
|
1592
|
+
return Promise.all(attachments.map(async (attachment) => {
|
|
1593
|
+
try {
|
|
1594
|
+
return await staging.resolve({
|
|
1595
|
+
mediaId: attachment.mediaId,
|
|
1596
|
+
actor
|
|
1597
|
+
}) ?? attachment;
|
|
1598
|
+
} catch {
|
|
1599
|
+
return attachment;
|
|
1600
|
+
}
|
|
1601
|
+
}));
|
|
1604
1602
|
}
|
|
1605
1603
|
};
|
|
1606
|
-
|
|
1604
|
+
ChatQueueService2 = _ts_decorate6([
|
|
1607
1605
|
Injectable5(),
|
|
1608
|
-
_ts_param5(0, Inject5(
|
|
1609
|
-
_ts_param5(1, Inject5(
|
|
1610
|
-
_ts_param5(2,
|
|
1611
|
-
_ts_param5(
|
|
1612
|
-
_ts_param5(
|
|
1613
|
-
_ts_param5(
|
|
1614
|
-
_ts_param5(
|
|
1615
|
-
_ts_param5(
|
|
1616
|
-
_ts_param5(8, Optional4()),
|
|
1617
|
-
_ts_param5(8, Inject5(AGENT_PRICING_STORE)),
|
|
1618
|
-
_ts_param5(9, Optional4()),
|
|
1619
|
-
_ts_param5(9, Inject5(AGENT_SKILLS)),
|
|
1620
|
-
_ts_param5(10, Optional4()),
|
|
1621
|
-
_ts_param5(10, Inject5(AGENT_MEMORY)),
|
|
1606
|
+
_ts_param5(0, Inject5(AGENT_STORE4)),
|
|
1607
|
+
_ts_param5(1, Inject5(AGENT_SINK3)),
|
|
1608
|
+
_ts_param5(2, Optional4()),
|
|
1609
|
+
_ts_param5(2, Inject5(AGENT_QUOTA_PROVIDER)),
|
|
1610
|
+
_ts_param5(3, Optional4()),
|
|
1611
|
+
_ts_param5(3, Inject5(AGENT_OPTIONS2)),
|
|
1612
|
+
_ts_param5(4, Optional4()),
|
|
1613
|
+
_ts_param5(4, Inject5(AGENT_ATTACHMENT_STAGING)),
|
|
1622
1614
|
_ts_metadata5("design:type", Function),
|
|
1623
1615
|
_ts_metadata5("design:paramtypes", [
|
|
1624
|
-
typeof AgentModuleOptions === "undefined" ? Object : AgentModuleOptions,
|
|
1625
|
-
typeof ModelProvider === "undefined" ? Object : ModelProvider,
|
|
1626
1616
|
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
1627
1617
|
typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
|
|
1628
|
-
typeof
|
|
1629
|
-
typeof
|
|
1630
|
-
typeof
|
|
1631
|
-
Array,
|
|
1632
|
-
Object,
|
|
1633
|
-
Object,
|
|
1634
|
-
Object
|
|
1618
|
+
typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
|
|
1619
|
+
typeof Pick === "undefined" ? Object : Pick,
|
|
1620
|
+
typeof AttachmentStagingStore === "undefined" ? Object : AttachmentStagingStore
|
|
1635
1621
|
])
|
|
1636
|
-
],
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1622
|
+
], ChatQueueService2);
|
|
1623
|
+
function pauseReasonFor(outcome, head) {
|
|
1624
|
+
if (outcome === "failed") {
|
|
1625
|
+
return "run_failed";
|
|
1626
|
+
}
|
|
1627
|
+
if (outcome === "cancelled" && head.interrupt !== true) {
|
|
1628
|
+
return "cancelled";
|
|
1629
|
+
}
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
__name(pauseReasonFor, "pauseReasonFor");
|
|
1633
|
+
function queueFrame(items, paused, started) {
|
|
1634
|
+
return {
|
|
1635
|
+
kind: "queue",
|
|
1636
|
+
queue: {
|
|
1637
|
+
items: items.map(queuedMessageView),
|
|
1638
|
+
paused
|
|
1639
|
+
},
|
|
1640
|
+
...started !== void 0 ? {
|
|
1641
|
+
started
|
|
1642
|
+
} : {}
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
__name(queueFrame, "queueFrame");
|
|
1641
1646
|
|
|
1642
1647
|
// src/run-not-active.exception.ts
|
|
1643
1648
|
import { RUN_NOT_ACTIVE_CODE, RUN_NOT_ACTIVE_MESSAGE } from "@dudousxd/nestjs-agent-core";
|
|
@@ -2694,7 +2699,7 @@ AgentService = _ts_decorate7([
|
|
|
2694
2699
|
typeof ModelCatalog === "undefined" ? Object : ModelCatalog,
|
|
2695
2700
|
typeof QuotaProvider === "undefined" ? Object : QuotaProvider,
|
|
2696
2701
|
typeof Pick === "undefined" ? Object : Pick,
|
|
2697
|
-
typeof
|
|
2702
|
+
typeof ChatQueueService2 === "undefined" ? Object : ChatQueueService2
|
|
2698
2703
|
])
|
|
2699
2704
|
], AgentService);
|
|
2700
2705
|
|
|
@@ -5541,7 +5546,7 @@ InlineAgentRunner = _ts_decorate25([
|
|
|
5541
5546
|
_ts_metadata24("design:paramtypes", [
|
|
5542
5547
|
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
|
|
5543
5548
|
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
5544
|
-
typeof
|
|
5549
|
+
typeof ChatQueueService2 === "undefined" ? Object : ChatQueueService2
|
|
5545
5550
|
])
|
|
5546
5551
|
], InlineAgentRunner);
|
|
5547
5552
|
function isThreadTurn2(input) {
|
|
@@ -5756,7 +5761,11 @@ function sharedProviders(durable) {
|
|
|
5756
5761
|
SkillDiscoveryService,
|
|
5757
5762
|
// The thread message queue — every surface, since both runners and the durable workflow settle
|
|
5758
5763
|
// into it, and the chat routes enqueue into it.
|
|
5759
|
-
|
|
5764
|
+
ChatQueueService2,
|
|
5765
|
+
{
|
|
5766
|
+
provide: AGENT_CHAT_QUEUE,
|
|
5767
|
+
useExisting: ChatQueueService2
|
|
5768
|
+
},
|
|
5760
5769
|
InlineAgentRunner,
|
|
5761
5770
|
AgentService,
|
|
5762
5771
|
// Bound ALWAYS (durable or inline) — the console's cross-thread approvals inbox routes decisions
|
|
@@ -5818,7 +5827,8 @@ function exportsFor() {
|
|
|
5818
5827
|
AGENT_DEPS_FACTORY7,
|
|
5819
5828
|
AgentDepsFactory2,
|
|
5820
5829
|
AgentService,
|
|
5821
|
-
|
|
5830
|
+
ChatQueueService2,
|
|
5831
|
+
AGENT_CHAT_QUEUE,
|
|
5822
5832
|
InlineAgentRunner,
|
|
5823
5833
|
AGENT_APPROVAL_PORT
|
|
5824
5834
|
];
|