@dudousxd/nestjs-agent 0.9.0 → 0.10.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/README.md +36 -0
- package/dist/{agent-deps.factory-DNslT7_J.d.cts → agent-deps.factory-D7GdhqdK.d.cts} +33 -1
- package/dist/{agent-deps.factory-DNslT7_J.d.ts → agent-deps.factory-D7GdhqdK.d.ts} +33 -1
- package/dist/durable/index.cjs +214 -168
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +42 -9
- package/dist/durable/index.d.ts +42 -9
- package/dist/durable/index.js +213 -167
- package/dist/durable/index.js.map +1 -1
- package/dist/index.cjs +14 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -41,6 +41,42 @@ The module mounts SSE + REST endpoints under `/agent` (`POST /agent/chat`, tool-
|
|
|
41
41
|
threads, quota). Add `durable: true` + `AgentDurableModule` for the durable runner;
|
|
42
42
|
`AgentModule.forFeature([…])` for multi-agent orchestration.
|
|
43
43
|
|
|
44
|
+
### Deploying split API/worker pods
|
|
45
|
+
|
|
46
|
+
By default `AgentModule`/`AgentDurableModule` wire everything: every controller, the `agent.run`
|
|
47
|
+
durable workflow, and its dispatched steps (`AgentRunSteps.llm`/`.tool`). Fine for a single combined
|
|
48
|
+
pod — but an API/dashboard pod and a worker pod each importing the FULL module means the API pod also
|
|
49
|
+
registers the dispatched-step handlers, subscribing their queues and running LLM/tool work it has no
|
|
50
|
+
business doing (and a worker pod mounts HTTP routes nothing ever calls).
|
|
51
|
+
|
|
52
|
+
`surface: 'http' | 'engine' | 'both'` (default `'both'`) splits that in two:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// worker container: no HTTP routes, but the workflow + dispatched steps ARE registered
|
|
56
|
+
imports: [
|
|
57
|
+
DurableModule.forRoot({ store, transport }),
|
|
58
|
+
AgentModule.forRoot({ model, store, actorResolver, durable: true, surface: 'engine' }),
|
|
59
|
+
AgentDurableModule.forRoot({ surface: 'engine' }),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
// api container: every controller works, but AgentRunSteps is never registered here —
|
|
63
|
+
// pair this with the durable `drive: false` enqueue-only config so this pod also never
|
|
64
|
+
// polls/executes work on its own.
|
|
65
|
+
imports: [
|
|
66
|
+
DurableModule.forRoot({ store, transport, drive: false }),
|
|
67
|
+
AgentModule.forRoot({ model, store, actorResolver, durable: true, surface: 'http' }),
|
|
68
|
+
AgentDurableModule.forRoot({ surface: 'http' }),
|
|
69
|
+
]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`AgentDurableModule.forRoot({ surface })` mirrors `AgentModule`'s own `surface` — set it in both
|
|
73
|
+
places (or use the `agentDurable(options)` helper, which threads one `surface` to both calls). The
|
|
74
|
+
`'http'` side still registers the `agent.run` WORKFLOW (so starting a run succeeds) but never the
|
|
75
|
+
step handlers, so it can never pick up LLM/tool work meant for the worker fleet. This is exactly the
|
|
76
|
+
fix for a durable skew-protection crash-loop where a bare-imported module on the wrong pod type
|
|
77
|
+
subscribed queues it should never have served. flip composes this the same way `APP_TYPE` composes
|
|
78
|
+
its other modules: the worker container loads the `engine` surface, the API container loads `http`.
|
|
79
|
+
|
|
44
80
|
See the [monorepo README](https://github.com/DavideCarvalho/aviary) for the full guide (durability,
|
|
45
81
|
multi-agent, authz, governed SQL, the React frontend, diagnostics).
|
|
46
82
|
|
|
@@ -56,6 +56,26 @@ interface FunctionalTool {
|
|
|
56
56
|
declare function provideAgentTool(tool: FunctionalTool): Provider;
|
|
57
57
|
declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
|
|
61
|
+
* each load `AgentModule` without either one doing the other's job:
|
|
62
|
+
*
|
|
63
|
+
* - `'both'` (default): today's behavior, unconditionally — every controller AND the full durable
|
|
64
|
+
* engine wiring (when `durable: true`). Zero-config single-pod deployments never touch this.
|
|
65
|
+
* - `'http'`: mounts every controller (chat/threads/tool-call/quota/agents/attachments) so the HTTP
|
|
66
|
+
* surface is fully functional, but under `durable: true` this pod must never EXECUTE agent work —
|
|
67
|
+
* see `AgentDurableModuleOptions.surface` (`@dudousxd/nestjs-agent/durable`), which is where the
|
|
68
|
+
* actual step-handler exclusion happens; this flag only controls the controllers here.
|
|
69
|
+
* - `'engine'`: mounts NO controllers (an API pod's routes have no business existing on a worker
|
|
70
|
+
* pod that never receives HTTP traffic) — everything else (registry/tools/model/store/sinks, the
|
|
71
|
+
* `agent.run` workflow, and its dispatched steps under `AgentDurableModule`) stays wired exactly
|
|
72
|
+
* as `'both'`.
|
|
73
|
+
*
|
|
74
|
+
* A durable deployment mirrors this on `AgentDurableModule.forRoot({ surface })` too — see that
|
|
75
|
+
* module's doc for why the split can't be inferred from this option alone (Nest's static module
|
|
76
|
+
* metadata can't be computed from a runtime-injected value).
|
|
77
|
+
*/
|
|
78
|
+
type AgentSurface = 'http' | 'engine' | 'both';
|
|
59
79
|
/**
|
|
60
80
|
* `AgentModuleOptions.attachments` — bounds and content-type allowlist for the optional
|
|
61
81
|
* `POST /agent/attachments` upload controller (see `attachments.upload`).
|
|
@@ -188,6 +208,12 @@ interface AgentModuleOptions {
|
|
|
188
208
|
guards?: Type<CanActivate>[];
|
|
189
209
|
/** Bounds/allowlist for the optional attachment-upload controller. Omit → 20 MiB, the documented default types, not mounted. */
|
|
190
210
|
attachments?: AgentAttachmentsOptions;
|
|
211
|
+
/**
|
|
212
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Omit → `'both'`,
|
|
213
|
+
* today's behavior with zero change. STATIC top-level field (like `durable`/`path`) — it decides
|
|
214
|
+
* which controllers exist at module-build time, not something a request can flip.
|
|
215
|
+
*/
|
|
216
|
+
surface?: AgentSurface;
|
|
191
217
|
}
|
|
192
218
|
interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
193
219
|
inject?: Array<InjectionToken | OptionalFactoryDependency>;
|
|
@@ -239,6 +265,12 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
|
239
265
|
* only the yes/no mount decision has to live here.
|
|
240
266
|
*/
|
|
241
267
|
attachmentsUpload?: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Same static-wiring
|
|
270
|
+
* reasoning as `attachmentsUpload`/`durable` above — `useFactory` resolves too late to decide
|
|
271
|
+
* which controllers exist. Omit → `'both'`, today's behavior with zero change.
|
|
272
|
+
*/
|
|
273
|
+
surface?: AgentSurface;
|
|
242
274
|
}
|
|
243
275
|
|
|
244
276
|
/** The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`. */
|
|
@@ -267,4 +299,4 @@ declare class AgentDepsFactory {
|
|
|
267
299
|
private followUpsCount;
|
|
268
300
|
}
|
|
269
301
|
|
|
270
|
-
export { AgentDepsFactory as A, type FunctionalTool as F, type
|
|
302
|
+
export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, AGENT_TOOL_BRAND as d, type AgentAttachmentsOptions as e, type AgentDeps as f, delegateToolName as g, provideAgentTool as p, utcDay as u };
|
|
@@ -56,6 +56,26 @@ interface FunctionalTool {
|
|
|
56
56
|
declare function provideAgentTool(tool: FunctionalTool): Provider;
|
|
57
57
|
declare function provideAgentTool(factory: (...deps: never[]) => FunctionalTool, inject?: FactoryProvider['inject']): Provider;
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Which half of the module this process mounts — the split that lets an API pod and a WORKER pod
|
|
61
|
+
* each load `AgentModule` without either one doing the other's job:
|
|
62
|
+
*
|
|
63
|
+
* - `'both'` (default): today's behavior, unconditionally — every controller AND the full durable
|
|
64
|
+
* engine wiring (when `durable: true`). Zero-config single-pod deployments never touch this.
|
|
65
|
+
* - `'http'`: mounts every controller (chat/threads/tool-call/quota/agents/attachments) so the HTTP
|
|
66
|
+
* surface is fully functional, but under `durable: true` this pod must never EXECUTE agent work —
|
|
67
|
+
* see `AgentDurableModuleOptions.surface` (`@dudousxd/nestjs-agent/durable`), which is where the
|
|
68
|
+
* actual step-handler exclusion happens; this flag only controls the controllers here.
|
|
69
|
+
* - `'engine'`: mounts NO controllers (an API pod's routes have no business existing on a worker
|
|
70
|
+
* pod that never receives HTTP traffic) — everything else (registry/tools/model/store/sinks, the
|
|
71
|
+
* `agent.run` workflow, and its dispatched steps under `AgentDurableModule`) stays wired exactly
|
|
72
|
+
* as `'both'`.
|
|
73
|
+
*
|
|
74
|
+
* A durable deployment mirrors this on `AgentDurableModule.forRoot({ surface })` too — see that
|
|
75
|
+
* module's doc for why the split can't be inferred from this option alone (Nest's static module
|
|
76
|
+
* metadata can't be computed from a runtime-injected value).
|
|
77
|
+
*/
|
|
78
|
+
type AgentSurface = 'http' | 'engine' | 'both';
|
|
59
79
|
/**
|
|
60
80
|
* `AgentModuleOptions.attachments` — bounds and content-type allowlist for the optional
|
|
61
81
|
* `POST /agent/attachments` upload controller (see `attachments.upload`).
|
|
@@ -188,6 +208,12 @@ interface AgentModuleOptions {
|
|
|
188
208
|
guards?: Type<CanActivate>[];
|
|
189
209
|
/** Bounds/allowlist for the optional attachment-upload controller. Omit → 20 MiB, the documented default types, not mounted. */
|
|
190
210
|
attachments?: AgentAttachmentsOptions;
|
|
211
|
+
/**
|
|
212
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Omit → `'both'`,
|
|
213
|
+
* today's behavior with zero change. STATIC top-level field (like `durable`/`path`) — it decides
|
|
214
|
+
* which controllers exist at module-build time, not something a request can flip.
|
|
215
|
+
*/
|
|
216
|
+
surface?: AgentSurface;
|
|
191
217
|
}
|
|
192
218
|
interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
193
219
|
inject?: Array<InjectionToken | OptionalFactoryDependency>;
|
|
@@ -239,6 +265,12 @@ interface AgentModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
|
239
265
|
* only the yes/no mount decision has to live here.
|
|
240
266
|
*/
|
|
241
267
|
attachmentsUpload?: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Which half of the module this process mounts (see {@link AgentSurface}). Same static-wiring
|
|
270
|
+
* reasoning as `attachmentsUpload`/`durable` above — `useFactory` resolves too late to decide
|
|
271
|
+
* which controllers exist. Omit → `'both'`, today's behavior with zero change.
|
|
272
|
+
*/
|
|
273
|
+
surface?: AgentSurface;
|
|
242
274
|
}
|
|
243
275
|
|
|
244
276
|
/** The synthesized `agent`-kind tool name an orchestrator uses to hand off to `target`. */
|
|
@@ -267,4 +299,4 @@ declare class AgentDepsFactory {
|
|
|
267
299
|
private followUpsCount;
|
|
268
300
|
}
|
|
269
301
|
|
|
270
|
-
export { AgentDepsFactory as A, type FunctionalTool as F, type
|
|
302
|
+
export { AgentDepsFactory as A, type FunctionalTool as F, type AgentSurface as a, type AgentModuleOptions as b, type AgentModuleAsyncOptions as c, AGENT_TOOL_BRAND as d, type AgentAttachmentsOptions as e, type AgentDeps as f, delegateToolName as g, provideAgentTool as p, utcDay as u };
|
package/dist/durable/index.cjs
CHANGED
|
@@ -31,10 +31,10 @@ __export(durable_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(durable_exports);
|
|
32
32
|
|
|
33
33
|
// src/durable/agent-run.workflow.ts
|
|
34
|
-
var
|
|
35
|
-
var
|
|
34
|
+
var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
|
|
35
|
+
var import_nestjs_durable = require("@dudousxd/nestjs-durable");
|
|
36
36
|
var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
|
|
37
|
-
var
|
|
37
|
+
var import_common = require("@nestjs/common");
|
|
38
38
|
|
|
39
39
|
// src/agent-deps.ts
|
|
40
40
|
function utcDay(date = /* @__PURE__ */ new Date()) {
|
|
@@ -52,10 +52,10 @@ function childSinkWriter(writer) {
|
|
|
52
52
|
}
|
|
53
53
|
__name(childSinkWriter, "childSinkWriter");
|
|
54
54
|
|
|
55
|
-
// src/durable/
|
|
56
|
-
var
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
// src/durable/dispatched-steps.token.ts
|
|
56
|
+
var AGENT_DISPATCHED_STEPS = Symbol.for("@dudousxd/nestjs-agent:dispatched-steps");
|
|
57
|
+
|
|
58
|
+
// src/durable/agent-run.workflow.ts
|
|
59
59
|
function _ts_decorate(decorators, target, key, desc) {
|
|
60
60
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
61
61
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -73,122 +73,6 @@ function _ts_param(paramIndex, decorator) {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
__name(_ts_param, "_ts_param");
|
|
76
|
-
var AgentRunSteps = class {
|
|
77
|
-
static {
|
|
78
|
-
__name(this, "AgentRunSteps");
|
|
79
|
-
}
|
|
80
|
-
factory;
|
|
81
|
-
constructor(factory) {
|
|
82
|
-
this.factory = factory;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Retryable: every side effect (persist/quota/stream markers) happens loop-side under
|
|
86
|
-
* `hooks.step`, so a durable retry only re-runs the model call and re-streams its tokens — never
|
|
87
|
-
* double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
|
|
88
|
-
* across however many turns/retries it takes.
|
|
89
|
-
*/
|
|
90
|
-
async llm(input) {
|
|
91
|
-
const deps = this.factory.forAgent(input.agentName);
|
|
92
|
-
const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList);
|
|
93
|
-
const opened = await deps.sink.open(input.sinkRunId);
|
|
94
|
-
const writer = input.childSink ? childSinkWriter(opened) : opened;
|
|
95
|
-
return (0, import_nestjs_agent_core.traceLlmTurn)(input.runId, input.step, () => deps.model.runTurn({
|
|
96
|
-
system: input.system,
|
|
97
|
-
messages: input.messages,
|
|
98
|
-
tools,
|
|
99
|
-
sink: writer
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* NO DURABLE retries — a tool may not be idempotent (a bare `@Step()` carries none, unlike `llm`
|
|
104
|
-
* above). What it does retry, in place, is a CLASSIFIED-TRANSIENT error from the tool's own
|
|
105
|
-
* invocation (a DB deadlock, a lock-wait timeout — see `isTransientToolError`): the server rolled
|
|
106
|
-
* that work back, so re-invoking it is safe, and doing it here (inside this one step, never a new
|
|
107
|
-
* checkpoint) is exactly how the non-dispatched loop path handles it too. The timeout is applied
|
|
108
|
-
* HERE, handler-side, via `withToolTimeout` — never as a durable dispatch `timeoutMs`, which
|
|
109
|
-
* bounds engine liveness (retryable) rather than the tool's own business outcome — and per
|
|
110
|
-
* ATTEMPT, so a retried invocation gets its own fresh timeout window.
|
|
111
|
-
*/
|
|
112
|
-
async tool(input) {
|
|
113
|
-
const deps = this.factory.forAgent(input.ctx.agentName);
|
|
114
|
-
const ctx = {
|
|
115
|
-
...input.ctx
|
|
116
|
-
};
|
|
117
|
-
const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
|
|
118
|
-
const transientRetry = input.transientRetry === false ? false : {
|
|
119
|
-
attempts: input.transientRetry.attempts,
|
|
120
|
-
backoffMs: input.transientRetry.backoffMs,
|
|
121
|
-
...localClassify !== void 0 ? {
|
|
122
|
-
classify: localClassify
|
|
123
|
-
} : {}
|
|
124
|
-
};
|
|
125
|
-
return (0, import_nestjs_agent_core.traceToolExecution)(input.ctx.runId, {
|
|
126
|
-
toolCallId: input.toolCallId,
|
|
127
|
-
toolName: input.toolName,
|
|
128
|
-
toolType: input.toolType
|
|
129
|
-
}, () => (0, import_nestjs_agent_core.invokeWithTransientRetry)(() => {
|
|
130
|
-
const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
|
|
131
|
-
return input.timeoutMs !== void 0 ? (0, import_nestjs_agent_core.withToolTimeout)(invocation, input.timeoutMs, input.toolName) : invocation;
|
|
132
|
-
}, transientRetry, {
|
|
133
|
-
onRetry: /* @__PURE__ */ __name((attempt, error) => {
|
|
134
|
-
(0, import_nestjs_agent_core.publishAgentToolRetry)({
|
|
135
|
-
toolName: input.toolName,
|
|
136
|
-
toolCallId: input.toolCallId,
|
|
137
|
-
attempt,
|
|
138
|
-
message: error instanceof Error ? error.message : String(error)
|
|
139
|
-
});
|
|
140
|
-
}, "onRetry")
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
_ts_decorate([
|
|
145
|
-
(0, import_nestjs_durable.Step)({
|
|
146
|
-
retries: 3
|
|
147
|
-
}),
|
|
148
|
-
_ts_metadata("design:type", Function),
|
|
149
|
-
_ts_metadata("design:paramtypes", [
|
|
150
|
-
typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
|
|
151
|
-
]),
|
|
152
|
-
_ts_metadata("design:returntype", Promise)
|
|
153
|
-
], AgentRunSteps.prototype, "llm", null);
|
|
154
|
-
_ts_decorate([
|
|
155
|
-
(0, import_nestjs_durable.Step)(),
|
|
156
|
-
_ts_metadata("design:type", Function),
|
|
157
|
-
_ts_metadata("design:paramtypes", [
|
|
158
|
-
typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
|
|
159
|
-
]),
|
|
160
|
-
_ts_metadata("design:returntype", Promise)
|
|
161
|
-
], AgentRunSteps.prototype, "tool", null);
|
|
162
|
-
AgentRunSteps = _ts_decorate([
|
|
163
|
-
(0, import_common.Injectable)(),
|
|
164
|
-
_ts_param(0, (0, import_common.Inject)(import_nestjs_agent_core.AGENT_DEPS_FACTORY)),
|
|
165
|
-
_ts_metadata("design:type", Function),
|
|
166
|
-
_ts_metadata("design:paramtypes", [
|
|
167
|
-
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
|
|
168
|
-
])
|
|
169
|
-
], AgentRunSteps);
|
|
170
|
-
|
|
171
|
-
// src/durable/dispatched-steps.token.ts
|
|
172
|
-
var AGENT_DISPATCHED_STEPS = Symbol.for("@dudousxd/nestjs-agent:dispatched-steps");
|
|
173
|
-
|
|
174
|
-
// src/durable/agent-run.workflow.ts
|
|
175
|
-
function _ts_decorate2(decorators, target, key, desc) {
|
|
176
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
177
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
178
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
179
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
180
|
-
}
|
|
181
|
-
__name(_ts_decorate2, "_ts_decorate");
|
|
182
|
-
function _ts_metadata2(k, v) {
|
|
183
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
184
|
-
}
|
|
185
|
-
__name(_ts_metadata2, "_ts_metadata");
|
|
186
|
-
function _ts_param2(paramIndex, decorator) {
|
|
187
|
-
return function(target, key) {
|
|
188
|
-
decorator(target, key, paramIndex);
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
__name(_ts_param2, "_ts_param");
|
|
192
76
|
var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
193
77
|
static {
|
|
194
78
|
__name(this, "AgentRunWorkflow");
|
|
@@ -206,6 +90,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
206
90
|
async run(ctx, input) {
|
|
207
91
|
const day = input.day ?? utcDay();
|
|
208
92
|
const deps = this.factory.forAgent(input.agentName);
|
|
93
|
+
const dispatchSteps = this.dispatchedSteps ? this.steps : void 0;
|
|
209
94
|
if (input.sinkRunId !== void 0) {
|
|
210
95
|
await ctx.localStep("activate", () => this.store.setActiveStream(input.threadId, ctx.runId));
|
|
211
96
|
}
|
|
@@ -243,8 +128,8 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
243
128
|
// `ctx.localStep`s, so a turn isn't pinned to this workflow worker for the model call or a tool
|
|
244
129
|
// execution. `sinkRunId`/`childSink` are sink routing this workflow already resolved above —
|
|
245
130
|
// core's dispatchLlm signature stays sink-topology-agnostic, so we add them here, not in core.
|
|
246
|
-
...
|
|
247
|
-
dispatchLlm: /* @__PURE__ */ __name((index, envelope) => ctx.step(
|
|
131
|
+
...dispatchSteps !== void 0 ? {
|
|
132
|
+
dispatchLlm: /* @__PURE__ */ __name((index, envelope) => ctx.step(dispatchSteps.llm, {
|
|
248
133
|
...envelope,
|
|
249
134
|
runId: ctx.runId,
|
|
250
135
|
step: index,
|
|
@@ -256,7 +141,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
256
141
|
// ('agent'-kind delegations branch to runAgent earlier in the loop), so anything that
|
|
257
142
|
// isn't 'action' is defensively 'read' — the same posture core takes for an
|
|
258
143
|
// unresolvable `kind`.
|
|
259
|
-
dispatchTool: /* @__PURE__ */ __name((call, envelope) => ctx.step(
|
|
144
|
+
dispatchTool: /* @__PURE__ */ __name((call, envelope) => ctx.step(dispatchSteps.tool, {
|
|
260
145
|
...envelope,
|
|
261
146
|
toolCallId: call.id,
|
|
262
147
|
toolType: call.kind === "action" ? "action" : "read"
|
|
@@ -264,7 +149,7 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
264
149
|
} : {}
|
|
265
150
|
};
|
|
266
151
|
try {
|
|
267
|
-
const result = await (0,
|
|
152
|
+
const result = await (0, import_nestjs_agent_core.runAgentLoop)({
|
|
268
153
|
...deps,
|
|
269
154
|
day
|
|
270
155
|
}, input, hooks);
|
|
@@ -275,8 +160,8 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
275
160
|
throw error;
|
|
276
161
|
}
|
|
277
162
|
const message = error instanceof Error ? error.message : String(error);
|
|
278
|
-
const code = error instanceof
|
|
279
|
-
(0,
|
|
163
|
+
const code = error instanceof import_nestjs_agent_core.QuotaExceededError ? "quota_exceeded" : "run_failed";
|
|
164
|
+
(0, import_nestjs_agent_core.publishAgentRunFailed)({
|
|
280
165
|
runId: ctx.runId,
|
|
281
166
|
code,
|
|
282
167
|
message
|
|
@@ -297,24 +182,141 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
|
|
|
297
182
|
}
|
|
298
183
|
}
|
|
299
184
|
};
|
|
300
|
-
AgentRunWorkflow =
|
|
301
|
-
(0,
|
|
302
|
-
(0,
|
|
185
|
+
AgentRunWorkflow = _ts_decorate([
|
|
186
|
+
(0, import_common.Injectable)(),
|
|
187
|
+
(0, import_nestjs_durable.Workflow)({
|
|
303
188
|
name: "agent.run",
|
|
304
189
|
version: "1"
|
|
305
190
|
}),
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
191
|
+
_ts_param(0, (0, import_common.Inject)(import_nestjs_agent_core.AGENT_DEPS_FACTORY)),
|
|
192
|
+
_ts_param(1, (0, import_common.Inject)(import_nestjs_agent_core.AGENT_STORE)),
|
|
193
|
+
_ts_param(2, (0, import_common.Optional)()),
|
|
194
|
+
_ts_param(3, (0, import_common.Inject)(AGENT_DISPATCHED_STEPS)),
|
|
195
|
+
_ts_metadata("design:type", Function),
|
|
196
|
+
_ts_metadata("design:paramtypes", [
|
|
311
197
|
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
|
|
312
198
|
typeof AgentStore === "undefined" ? Object : AgentStore,
|
|
313
|
-
|
|
199
|
+
Object,
|
|
314
200
|
Boolean
|
|
315
201
|
])
|
|
316
202
|
], AgentRunWorkflow);
|
|
317
203
|
|
|
204
|
+
// src/durable/agent-run.steps.ts
|
|
205
|
+
var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
|
|
206
|
+
var import_nestjs_durable2 = require("@dudousxd/nestjs-durable");
|
|
207
|
+
var import_common2 = require("@nestjs/common");
|
|
208
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
209
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
210
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
211
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
212
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
213
|
+
}
|
|
214
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
215
|
+
function _ts_metadata2(k, v) {
|
|
216
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
217
|
+
}
|
|
218
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
219
|
+
function _ts_param2(paramIndex, decorator) {
|
|
220
|
+
return function(target, key) {
|
|
221
|
+
decorator(target, key, paramIndex);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
__name(_ts_param2, "_ts_param");
|
|
225
|
+
var AgentRunSteps = class {
|
|
226
|
+
static {
|
|
227
|
+
__name(this, "AgentRunSteps");
|
|
228
|
+
}
|
|
229
|
+
factory;
|
|
230
|
+
constructor(factory) {
|
|
231
|
+
this.factory = factory;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Retryable: every side effect (persist/quota/stream markers) happens loop-side under
|
|
235
|
+
* `hooks.step`, so a durable retry only re-runs the model call and re-streams its tokens — never
|
|
236
|
+
* double-writes. NEVER call `writer.end()`/`fail()` here: the workflow owns the stream's lifecycle,
|
|
237
|
+
* across however many turns/retries it takes.
|
|
238
|
+
*/
|
|
239
|
+
async llm(input) {
|
|
240
|
+
const deps = this.factory.forAgent(input.agentName);
|
|
241
|
+
const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, deps.toolAllowList);
|
|
242
|
+
const opened = await deps.sink.open(input.sinkRunId);
|
|
243
|
+
const writer = input.childSink ? childSinkWriter(opened) : opened;
|
|
244
|
+
return (0, import_nestjs_agent_core2.traceLlmTurn)(input.runId, input.step, () => deps.model.runTurn({
|
|
245
|
+
system: input.system,
|
|
246
|
+
messages: input.messages,
|
|
247
|
+
tools,
|
|
248
|
+
sink: writer
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* NO DURABLE retries — a tool may not be idempotent (a bare `@Step()` carries none, unlike `llm`
|
|
253
|
+
* above). What it does retry, in place, is a CLASSIFIED-TRANSIENT error from the tool's own
|
|
254
|
+
* invocation (a DB deadlock, a lock-wait timeout — see `isTransientToolError`): the server rolled
|
|
255
|
+
* that work back, so re-invoking it is safe, and doing it here (inside this one step, never a new
|
|
256
|
+
* checkpoint) is exactly how the non-dispatched loop path handles it too. The timeout is applied
|
|
257
|
+
* HERE, handler-side, via `withToolTimeout` — never as a durable dispatch `timeoutMs`, which
|
|
258
|
+
* bounds engine liveness (retryable) rather than the tool's own business outcome — and per
|
|
259
|
+
* ATTEMPT, so a retried invocation gets its own fresh timeout window.
|
|
260
|
+
*/
|
|
261
|
+
async tool(input) {
|
|
262
|
+
const deps = this.factory.forAgent(input.ctx.agentName);
|
|
263
|
+
const ctx = {
|
|
264
|
+
...input.ctx
|
|
265
|
+
};
|
|
266
|
+
const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
|
|
267
|
+
const transientRetry = input.transientRetry === false ? false : {
|
|
268
|
+
attempts: input.transientRetry.attempts,
|
|
269
|
+
backoffMs: input.transientRetry.backoffMs,
|
|
270
|
+
...localClassify !== void 0 ? {
|
|
271
|
+
classify: localClassify
|
|
272
|
+
} : {}
|
|
273
|
+
};
|
|
274
|
+
return (0, import_nestjs_agent_core2.traceToolExecution)(input.ctx.runId, {
|
|
275
|
+
toolCallId: input.toolCallId,
|
|
276
|
+
toolName: input.toolName,
|
|
277
|
+
toolType: input.toolType
|
|
278
|
+
}, () => (0, import_nestjs_agent_core2.invokeWithTransientRetry)(() => {
|
|
279
|
+
const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
|
|
280
|
+
return input.timeoutMs !== void 0 ? (0, import_nestjs_agent_core2.withToolTimeout)(invocation, input.timeoutMs, input.toolName) : invocation;
|
|
281
|
+
}, transientRetry, {
|
|
282
|
+
onRetry: /* @__PURE__ */ __name((attempt, error) => {
|
|
283
|
+
(0, import_nestjs_agent_core2.publishAgentToolRetry)({
|
|
284
|
+
toolName: input.toolName,
|
|
285
|
+
toolCallId: input.toolCallId,
|
|
286
|
+
attempt,
|
|
287
|
+
message: error instanceof Error ? error.message : String(error)
|
|
288
|
+
});
|
|
289
|
+
}, "onRetry")
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
_ts_decorate2([
|
|
294
|
+
(0, import_nestjs_durable2.Step)({
|
|
295
|
+
retries: 3
|
|
296
|
+
}),
|
|
297
|
+
_ts_metadata2("design:type", Function),
|
|
298
|
+
_ts_metadata2("design:paramtypes", [
|
|
299
|
+
typeof DispatchedLlmInput === "undefined" ? Object : DispatchedLlmInput
|
|
300
|
+
]),
|
|
301
|
+
_ts_metadata2("design:returntype", Promise)
|
|
302
|
+
], AgentRunSteps.prototype, "llm", null);
|
|
303
|
+
_ts_decorate2([
|
|
304
|
+
(0, import_nestjs_durable2.Step)(),
|
|
305
|
+
_ts_metadata2("design:type", Function),
|
|
306
|
+
_ts_metadata2("design:paramtypes", [
|
|
307
|
+
typeof DispatchedToolInput === "undefined" ? Object : DispatchedToolInput
|
|
308
|
+
]),
|
|
309
|
+
_ts_metadata2("design:returntype", Promise)
|
|
310
|
+
], AgentRunSteps.prototype, "tool", null);
|
|
311
|
+
AgentRunSteps = _ts_decorate2([
|
|
312
|
+
(0, import_common2.Injectable)(),
|
|
313
|
+
_ts_param2(0, (0, import_common2.Inject)(import_nestjs_agent_core2.AGENT_DEPS_FACTORY)),
|
|
314
|
+
_ts_metadata2("design:type", Function),
|
|
315
|
+
_ts_metadata2("design:paramtypes", [
|
|
316
|
+
typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory
|
|
317
|
+
])
|
|
318
|
+
], AgentRunSteps);
|
|
319
|
+
|
|
318
320
|
// src/durable/durable-agent-runner.ts
|
|
319
321
|
var import_node_crypto = require("crypto");
|
|
320
322
|
var import_nestjs_durable3 = require("@dudousxd/nestjs-durable");
|
|
@@ -452,16 +454,68 @@ function _ts_decorate4(decorators, target, key, desc) {
|
|
|
452
454
|
}
|
|
453
455
|
__name(_ts_decorate4, "_ts_decorate");
|
|
454
456
|
var logger = new import_common4.Logger("AgentDurableModule");
|
|
457
|
+
function runnerProviders() {
|
|
458
|
+
return [
|
|
459
|
+
DurableAgentRunner,
|
|
460
|
+
{
|
|
461
|
+
provide: import_nestjs_agent_core4.AGENT_DURABLE_RUNNER,
|
|
462
|
+
useExisting: DurableAgentRunner
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
provide: AGENT_DISPATCHED_STEPS,
|
|
466
|
+
useFactory: /* @__PURE__ */ __name((options, sink) => {
|
|
467
|
+
const enabled = options.durable === true && options.dispatchedSteps !== false;
|
|
468
|
+
if (enabled && sink instanceof InProcessTokenStreamSink) {
|
|
469
|
+
logger.warn("Dispatched steps are active (the default under durable: true) with the default InProcessTokenStreamSink, which only buffers tokens in-process. A dispatched `llm` step served by a different worker cannot stream into this buffer. Wire a cross-process TokenStreamSink (e.g. a Redis pub/sub sink) via AgentModule.forRoot({ sink }) before running multi-pod, or set dispatchedSteps: false to keep the turn in-process localSteps.");
|
|
470
|
+
}
|
|
471
|
+
return enabled;
|
|
472
|
+
}, "useFactory"),
|
|
473
|
+
inject: [
|
|
474
|
+
import_nestjs_agent_core4.AGENT_OPTIONS,
|
|
475
|
+
import_nestjs_agent_core4.AGENT_SINK
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
__name(runnerProviders, "runnerProviders");
|
|
481
|
+
var AgentDurableHttpModule = class AgentDurableHttpModule2 {
|
|
482
|
+
static {
|
|
483
|
+
__name(this, "AgentDurableHttpModule");
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
AgentDurableHttpModule = _ts_decorate4([
|
|
487
|
+
(0, import_common4.Global)(),
|
|
488
|
+
(0, import_common4.Module)({
|
|
489
|
+
providers: [
|
|
490
|
+
AgentRunWorkflow,
|
|
491
|
+
...runnerProviders()
|
|
492
|
+
],
|
|
493
|
+
exports: [
|
|
494
|
+
import_nestjs_agent_core4.AGENT_DURABLE_RUNNER,
|
|
495
|
+
AgentRunWorkflow,
|
|
496
|
+
AGENT_DISPATCHED_STEPS
|
|
497
|
+
]
|
|
498
|
+
})
|
|
499
|
+
], AgentDurableHttpModule);
|
|
455
500
|
var AgentDurableModule = class _AgentDurableModule {
|
|
456
501
|
static {
|
|
457
502
|
__name(this, "AgentDurableModule");
|
|
458
503
|
}
|
|
459
504
|
/**
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
505
|
+
* `surface: 'http'` returns the reduced `AgentDurableHttpModule` wiring (no `AgentRunSteps`);
|
|
506
|
+
* everything else (omitted, `'engine'`, `'both'`) returns this module's own full wiring — the
|
|
507
|
+
* class's `@Module` providers above merge in regardless of which DynamicModule shape a caller
|
|
508
|
+
* imports (Nest merges a class's static provider list with whatever a self-referencing
|
|
509
|
+
* `DynamicModule` adds), which is exactly why the reduced case has to be a DIFFERENT module class
|
|
510
|
+
* rather than a conditional subset of this one's own decorator.
|
|
463
511
|
*/
|
|
464
|
-
static forRoot() {
|
|
512
|
+
static forRoot(options) {
|
|
513
|
+
if (options?.surface === "http") {
|
|
514
|
+
return {
|
|
515
|
+
module: AgentDurableHttpModule,
|
|
516
|
+
global: true
|
|
517
|
+
};
|
|
518
|
+
}
|
|
465
519
|
return {
|
|
466
520
|
module: _AgentDurableModule,
|
|
467
521
|
global: true
|
|
@@ -474,25 +528,7 @@ AgentDurableModule = _ts_decorate4([
|
|
|
474
528
|
providers: [
|
|
475
529
|
AgentRunWorkflow,
|
|
476
530
|
AgentRunSteps,
|
|
477
|
-
|
|
478
|
-
{
|
|
479
|
-
provide: import_nestjs_agent_core4.AGENT_DURABLE_RUNNER,
|
|
480
|
-
useExisting: DurableAgentRunner
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
provide: AGENT_DISPATCHED_STEPS,
|
|
484
|
-
useFactory: /* @__PURE__ */ __name((options, sink) => {
|
|
485
|
-
const enabled = options.durable === true && options.dispatchedSteps !== false;
|
|
486
|
-
if (enabled && sink instanceof InProcessTokenStreamSink) {
|
|
487
|
-
logger.warn("Dispatched steps are active (the default under durable: true) with the default InProcessTokenStreamSink, which only buffers tokens in-process. A dispatched `llm` step served by a different worker cannot stream into this buffer. Wire a cross-process TokenStreamSink (e.g. a Redis pub/sub sink) via AgentModule.forRoot({ sink }) before running multi-pod, or set dispatchedSteps: false to keep the turn in-process localSteps.");
|
|
488
|
-
}
|
|
489
|
-
return enabled;
|
|
490
|
-
}, "useFactory"),
|
|
491
|
-
inject: [
|
|
492
|
-
import_nestjs_agent_core4.AGENT_OPTIONS,
|
|
493
|
-
import_nestjs_agent_core4.AGENT_SINK
|
|
494
|
-
]
|
|
495
|
-
}
|
|
531
|
+
...runnerProviders()
|
|
496
532
|
],
|
|
497
533
|
exports: [
|
|
498
534
|
import_nestjs_agent_core4.AGENT_DURABLE_RUNNER,
|
|
@@ -2279,6 +2315,10 @@ function controllersFor(mountAttachments) {
|
|
|
2279
2315
|
] : BASE_CONTROLLERS;
|
|
2280
2316
|
}
|
|
2281
2317
|
__name(controllersFor, "controllersFor");
|
|
2318
|
+
function controllersForSurface(surface, mountAttachments) {
|
|
2319
|
+
return surface === "engine" ? [] : controllersFor(mountAttachments);
|
|
2320
|
+
}
|
|
2321
|
+
__name(controllersForSurface, "controllersForSurface");
|
|
2282
2322
|
var GUARDS_METADATA = "__guards__";
|
|
2283
2323
|
function applyGuards(guards) {
|
|
2284
2324
|
const resolved = guards ?? [];
|
|
@@ -2324,7 +2364,7 @@ var AgentModule = class _AgentModule {
|
|
|
2324
2364
|
import_core3.DiscoveryModule,
|
|
2325
2365
|
routerFor(path)
|
|
2326
2366
|
],
|
|
2327
|
-
controllers:
|
|
2367
|
+
controllers: controllersForSurface(options.surface, options.attachments?.upload === true),
|
|
2328
2368
|
providers: [
|
|
2329
2369
|
{
|
|
2330
2370
|
provide: import_nestjs_agent_core17.AGENT_OPTIONS,
|
|
@@ -2349,17 +2389,21 @@ var AgentModule = class _AgentModule {
|
|
|
2349
2389
|
routerFor(path),
|
|
2350
2390
|
...options.imports ?? []
|
|
2351
2391
|
],
|
|
2352
|
-
controllers:
|
|
2392
|
+
controllers: controllersForSurface(options.surface, options.attachmentsUpload === true),
|
|
2353
2393
|
providers: [
|
|
2354
2394
|
{
|
|
2355
2395
|
provide: import_nestjs_agent_core17.AGENT_OPTIONS,
|
|
2356
|
-
// Stamp the STATIC wiring flags onto the factory result: `durable`/`dispatchedSteps
|
|
2357
|
-
// authoritative on the async config object (they decided the actual wiring
|
|
2358
|
-
// `AgentDurableModule` derives the effective dispatched-steps value from
|
|
2359
|
-
// without this stamp, an async host would get in-process localSteps
|
|
2396
|
+
// Stamp the STATIC wiring flags onto the factory result: `durable`/`dispatchedSteps`/
|
|
2397
|
+
// `surface` are authoritative on the async config object (they decided the actual wiring
|
|
2398
|
+
// above), and `AgentDurableModule` derives the effective dispatched-steps value from
|
|
2399
|
+
// AGENT_OPTIONS — without this stamp, an async host would get in-process localSteps
|
|
2400
|
+
// despite durable: true. `surface` isn't read back by anything today (AgentDurableModule
|
|
2401
|
+
// takes its own mirror option — see its doc for why), but it's stamped here too so the
|
|
2402
|
+
// EFFECTIVE value is always observable off AGENT_OPTIONS, sync or async alike.
|
|
2360
2403
|
useFactory: /* @__PURE__ */ __name(async (...args) => ({
|
|
2361
2404
|
...await options.useFactory(...args),
|
|
2362
2405
|
durable: options.durable ?? false,
|
|
2406
|
+
surface: options.surface ?? "both",
|
|
2363
2407
|
...options.dispatchedSteps !== void 0 ? {
|
|
2364
2408
|
dispatchedSteps: options.dispatchedSteps
|
|
2365
2409
|
} : {}
|
|
@@ -2385,7 +2429,9 @@ function agentDurable(options) {
|
|
|
2385
2429
|
...options,
|
|
2386
2430
|
durable: true
|
|
2387
2431
|
}),
|
|
2388
|
-
AgentDurableModule.forRoot(
|
|
2432
|
+
AgentDurableModule.forRoot(options.surface !== void 0 ? {
|
|
2433
|
+
surface: options.surface
|
|
2434
|
+
} : {})
|
|
2389
2435
|
];
|
|
2390
2436
|
}
|
|
2391
2437
|
__name(agentDurable, "agentDurable");
|