@dudousxd/nestjs-agent-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/index.cjs +813 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +848 -0
- package/dist/index.d.ts +848 -0
- package/dist/index.js +753 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/tokens.ts
|
|
5
|
+
var AGENT_OPTIONS = Symbol.for("@dudousxd/nestjs-agent:options");
|
|
6
|
+
var AGENT_STORE = Symbol.for("@dudousxd/nestjs-agent:store");
|
|
7
|
+
var AGENT_RUNNER = Symbol.for("@dudousxd/nestjs-agent:runner");
|
|
8
|
+
var AGENT_DURABLE_RUNNER = Symbol.for("@dudousxd/nestjs-agent:durable-runner");
|
|
9
|
+
var AGENT_SINK = Symbol.for("@dudousxd/nestjs-agent:sink");
|
|
10
|
+
var AGENT_MODEL = Symbol.for("@dudousxd/nestjs-agent:model");
|
|
11
|
+
var AGENT_ROLES_POLICY = Symbol.for("@dudousxd/nestjs-agent:roles-policy");
|
|
12
|
+
var AGENT_QUOTA_STORE = Symbol.for("@dudousxd/nestjs-agent:quota-store");
|
|
13
|
+
var AGENT_TOOL_REGISTRY = Symbol.for("@dudousxd/nestjs-agent:tool-registry");
|
|
14
|
+
var AGENT_REGISTRY = Symbol.for("@dudousxd/nestjs-agent:agent-registry");
|
|
15
|
+
var AGENT_ACTOR_RESOLVER = Symbol.for("@dudousxd/nestjs-agent:actor-resolver");
|
|
16
|
+
var AGENT_GOVERNANCE_QUERIES = Symbol.for("@dudousxd/nestjs-agent:governance-queries");
|
|
17
|
+
var AGENT_PRICING_STORE = Symbol.for("@dudousxd/nestjs-agent:pricing-store");
|
|
18
|
+
var AGENT_RETRIEVER = Symbol.for("@dudousxd/nestjs-agent:retriever");
|
|
19
|
+
var AGENT_EMBEDDING_PROVIDER = Symbol.for("@dudousxd/nestjs-agent:embedding-provider");
|
|
20
|
+
var AGENT_DEPS_FACTORY = Symbol.for("@dudousxd/nestjs-agent:deps-factory");
|
|
21
|
+
|
|
22
|
+
// src/spi/token-stream-sink.ts
|
|
23
|
+
var AgentStreamError = class extends Error {
|
|
24
|
+
static {
|
|
25
|
+
__name(this, "AgentStreamError");
|
|
26
|
+
}
|
|
27
|
+
code;
|
|
28
|
+
constructor(error) {
|
|
29
|
+
super(error.message);
|
|
30
|
+
this.name = "AgentStreamError";
|
|
31
|
+
this.code = error.code;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/spi/pricing-store.ts
|
|
36
|
+
async function seedModelPrices(store, prices) {
|
|
37
|
+
for (const price of prices) {
|
|
38
|
+
await store.upsertModelPrice(price);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
__name(seedModelPrices, "seedModelPrices");
|
|
42
|
+
|
|
43
|
+
// src/personas.ts
|
|
44
|
+
async function filterToolsByRole(tools, actor, policy) {
|
|
45
|
+
const checked = await Promise.all(tools.map(async (tool) => ({
|
|
46
|
+
tool,
|
|
47
|
+
allowed: await policy.can(actor, tool)
|
|
48
|
+
})));
|
|
49
|
+
return checked.filter((entry) => entry.allowed).map((entry) => entry.tool);
|
|
50
|
+
}
|
|
51
|
+
__name(filterToolsByRole, "filterToolsByRole");
|
|
52
|
+
function personaFilterTools(tools, allowedTools) {
|
|
53
|
+
if (allowedTools === void 0) {
|
|
54
|
+
return tools;
|
|
55
|
+
}
|
|
56
|
+
const allowed = new Set(allowedTools);
|
|
57
|
+
return tools.filter((tool) => allowed.has(tool.name));
|
|
58
|
+
}
|
|
59
|
+
__name(personaFilterTools, "personaFilterTools");
|
|
60
|
+
|
|
61
|
+
// src/agent-registry.ts
|
|
62
|
+
var AgentRegistry = class {
|
|
63
|
+
static {
|
|
64
|
+
__name(this, "AgentRegistry");
|
|
65
|
+
}
|
|
66
|
+
definitions = /* @__PURE__ */ new Map();
|
|
67
|
+
register(definition) {
|
|
68
|
+
this.definitions.set(definition.name, definition);
|
|
69
|
+
}
|
|
70
|
+
get(name) {
|
|
71
|
+
return this.definitions.get(name);
|
|
72
|
+
}
|
|
73
|
+
has(name) {
|
|
74
|
+
return this.definitions.has(name);
|
|
75
|
+
}
|
|
76
|
+
list() {
|
|
77
|
+
return [
|
|
78
|
+
...this.definitions.values()
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/tool-registry.ts
|
|
84
|
+
var ToolForbiddenError = class extends Error {
|
|
85
|
+
static {
|
|
86
|
+
__name(this, "ToolForbiddenError");
|
|
87
|
+
}
|
|
88
|
+
toolName;
|
|
89
|
+
constructor(toolName) {
|
|
90
|
+
super(`Tool "${toolName}" is not allowed for this role`), this.toolName = toolName;
|
|
91
|
+
this.name = "ToolForbiddenError";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var ToolNotFoundError = class extends Error {
|
|
95
|
+
static {
|
|
96
|
+
__name(this, "ToolNotFoundError");
|
|
97
|
+
}
|
|
98
|
+
toolName;
|
|
99
|
+
constructor(toolName) {
|
|
100
|
+
super(`Tool "${toolName}" is not registered`), this.toolName = toolName;
|
|
101
|
+
this.name = "ToolNotFoundError";
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var ToolInputInvalidError = class extends Error {
|
|
105
|
+
static {
|
|
106
|
+
__name(this, "ToolInputInvalidError");
|
|
107
|
+
}
|
|
108
|
+
toolName;
|
|
109
|
+
issues;
|
|
110
|
+
constructor(toolName, issues) {
|
|
111
|
+
super(`Invalid input for tool "${toolName}": ${issues.map((issue) => issue.message).join("; ")}`), this.toolName = toolName, this.issues = issues;
|
|
112
|
+
this.name = "ToolInputInvalidError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var ToolRegistry = class {
|
|
116
|
+
static {
|
|
117
|
+
__name(this, "ToolRegistry");
|
|
118
|
+
}
|
|
119
|
+
entries = /* @__PURE__ */ new Map();
|
|
120
|
+
register(spec, handler) {
|
|
121
|
+
this.entries.set(spec.name, {
|
|
122
|
+
spec,
|
|
123
|
+
handler
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
has(name) {
|
|
127
|
+
return this.entries.has(name);
|
|
128
|
+
}
|
|
129
|
+
spec(name) {
|
|
130
|
+
return this.entries.get(name)?.spec;
|
|
131
|
+
}
|
|
132
|
+
allSpecs() {
|
|
133
|
+
return [
|
|
134
|
+
...this.entries.values()
|
|
135
|
+
].map((entry) => entry.spec);
|
|
136
|
+
}
|
|
137
|
+
/** The tools to offer the model for this actor+persona, after the two filter layers. */
|
|
138
|
+
async definitionsFor(actor, policy, allowedTools) {
|
|
139
|
+
const roleScoped = await filterToolsByRole(this.allSpecs(), actor, policy);
|
|
140
|
+
const personaScoped = personaFilterTools(roleScoped, allowedTools);
|
|
141
|
+
return personaScoped.map((spec) => ({
|
|
142
|
+
name: spec.name,
|
|
143
|
+
kind: spec.kind,
|
|
144
|
+
description: spec.description,
|
|
145
|
+
inputSchema: spec.inputSchema
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
/** Run a tool. Re-checks the role (defense-in-depth) and re-parses the input via Zod. */
|
|
149
|
+
async invoke(name, input, ctx, policy) {
|
|
150
|
+
const entry = this.entries.get(name);
|
|
151
|
+
if (entry === void 0) {
|
|
152
|
+
throw new ToolNotFoundError(name);
|
|
153
|
+
}
|
|
154
|
+
if (!await policy.can(ctx.actor, entry.spec)) {
|
|
155
|
+
throw new ToolForbiddenError(name);
|
|
156
|
+
}
|
|
157
|
+
const validation = await entry.spec.inputSchema["~standard"].validate(input);
|
|
158
|
+
if (validation.issues !== void 0) {
|
|
159
|
+
throw new ToolInputInvalidError(name, validation.issues);
|
|
160
|
+
}
|
|
161
|
+
return entry.handler.execute(validation.value, ctx);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var DefaultRolesPolicy = class {
|
|
165
|
+
static {
|
|
166
|
+
__name(this, "DefaultRolesPolicy");
|
|
167
|
+
}
|
|
168
|
+
defaultRoles;
|
|
169
|
+
constructor(defaultRoles = [
|
|
170
|
+
"ADMIN"
|
|
171
|
+
]) {
|
|
172
|
+
this.defaultRoles = defaultRoles;
|
|
173
|
+
}
|
|
174
|
+
can(actor, tool) {
|
|
175
|
+
const allowed = tool.roles ?? this.defaultRoles;
|
|
176
|
+
return (actor.roles ?? []).some((role) => allowed.includes(role));
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/diagnostics.ts
|
|
181
|
+
import { emit } from "@dudousxd/nestjs-diagnostics";
|
|
182
|
+
function publishAgentRunStarted(payload) {
|
|
183
|
+
emit("agent", "run.started", payload);
|
|
184
|
+
}
|
|
185
|
+
__name(publishAgentRunStarted, "publishAgentRunStarted");
|
|
186
|
+
function publishAgentMessage(payload) {
|
|
187
|
+
emit("agent", "message", payload);
|
|
188
|
+
}
|
|
189
|
+
__name(publishAgentMessage, "publishAgentMessage");
|
|
190
|
+
function publishAgentToolCall(payload) {
|
|
191
|
+
emit("agent", "tool-call", payload);
|
|
192
|
+
}
|
|
193
|
+
__name(publishAgentToolCall, "publishAgentToolCall");
|
|
194
|
+
function publishAgentQuotaExceeded(payload) {
|
|
195
|
+
emit("agent", "quota.exceeded", payload);
|
|
196
|
+
}
|
|
197
|
+
__name(publishAgentQuotaExceeded, "publishAgentQuotaExceeded");
|
|
198
|
+
function publishAgentRunFinished(payload) {
|
|
199
|
+
emit("agent", "run.finished", payload);
|
|
200
|
+
}
|
|
201
|
+
__name(publishAgentRunFinished, "publishAgentRunFinished");
|
|
202
|
+
function publishAgentRunFailed(payload) {
|
|
203
|
+
emit("agent", "run.failed", payload);
|
|
204
|
+
}
|
|
205
|
+
__name(publishAgentRunFailed, "publishAgentRunFailed");
|
|
206
|
+
function publishAgentDelegated(payload) {
|
|
207
|
+
emit("agent", "delegated", payload);
|
|
208
|
+
}
|
|
209
|
+
__name(publishAgentDelegated, "publishAgentDelegated");
|
|
210
|
+
function publishAgentRetrieved(payload) {
|
|
211
|
+
emit("agent", "retrieved", payload);
|
|
212
|
+
}
|
|
213
|
+
__name(publishAgentRetrieved, "publishAgentRetrieved");
|
|
214
|
+
|
|
215
|
+
// src/agent-loop.ts
|
|
216
|
+
function buildContextBlock(passages) {
|
|
217
|
+
const items = passages.map((passage, index) => {
|
|
218
|
+
const label = passage.source !== void 0 ? ` (${passage.source})` : "";
|
|
219
|
+
return `[${index + 1}]${label} ${passage.text}`;
|
|
220
|
+
}).join("\n\n");
|
|
221
|
+
return `<retrieved_context>
|
|
222
|
+
${items}
|
|
223
|
+
</retrieved_context>
|
|
224
|
+
Use the retrieved context above to answer when relevant, and cite sources by their bracket number.`;
|
|
225
|
+
}
|
|
226
|
+
__name(buildContextBlock, "buildContextBlock");
|
|
227
|
+
function intersectAllow(a, b) {
|
|
228
|
+
if (a === void 0) {
|
|
229
|
+
return b;
|
|
230
|
+
}
|
|
231
|
+
if (b === void 0) {
|
|
232
|
+
return a;
|
|
233
|
+
}
|
|
234
|
+
const second = new Set(b);
|
|
235
|
+
return a.filter((name) => second.has(name));
|
|
236
|
+
}
|
|
237
|
+
__name(intersectAllow, "intersectAllow");
|
|
238
|
+
var QuotaExceededError = class extends Error {
|
|
239
|
+
static {
|
|
240
|
+
__name(this, "QuotaExceededError");
|
|
241
|
+
}
|
|
242
|
+
constructor() {
|
|
243
|
+
super("Daily token quota exceeded");
|
|
244
|
+
this.name = "QuotaExceededError";
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
var MAX_DELEGATION_DEPTH = 5;
|
|
248
|
+
async function resolvePrompt(prompt, ctx) {
|
|
249
|
+
return typeof prompt === "function" ? prompt(ctx) : prompt;
|
|
250
|
+
}
|
|
251
|
+
__name(resolvePrompt, "resolvePrompt");
|
|
252
|
+
async function resolveSystemPrompt(deps, input) {
|
|
253
|
+
const base = {
|
|
254
|
+
actor: input.actor,
|
|
255
|
+
...input.persona !== void 0 ? {
|
|
256
|
+
persona: input.persona
|
|
257
|
+
} : {},
|
|
258
|
+
...input.pageContext !== void 0 ? {
|
|
259
|
+
pageContext: input.pageContext
|
|
260
|
+
} : {}
|
|
261
|
+
};
|
|
262
|
+
const basePrompt = await resolvePrompt(deps.systemPrompt, {
|
|
263
|
+
...base,
|
|
264
|
+
basePrompt: ""
|
|
265
|
+
});
|
|
266
|
+
if (input.persona === void 0) {
|
|
267
|
+
return basePrompt;
|
|
268
|
+
}
|
|
269
|
+
return resolvePrompt(input.persona.systemPrompt, {
|
|
270
|
+
...base,
|
|
271
|
+
basePrompt
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
__name(resolveSystemPrompt, "resolveSystemPrompt");
|
|
275
|
+
function extractTask(input) {
|
|
276
|
+
if (typeof input === "object" && input !== null && "task" in input) {
|
|
277
|
+
const task = input.task;
|
|
278
|
+
if (typeof task === "string") {
|
|
279
|
+
return task;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return JSON.stringify(input);
|
|
283
|
+
}
|
|
284
|
+
__name(extractTask, "extractTask");
|
|
285
|
+
function deriveTitle(userText) {
|
|
286
|
+
const trimmed = userText.trim().replace(/\s+/g, " ");
|
|
287
|
+
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
|
|
288
|
+
}
|
|
289
|
+
__name(deriveTitle, "deriveTitle");
|
|
290
|
+
var ToolTimeoutError = class ToolTimeoutError2 extends Error {
|
|
291
|
+
static {
|
|
292
|
+
__name(this, "ToolTimeoutError");
|
|
293
|
+
}
|
|
294
|
+
constructor(toolName, ms) {
|
|
295
|
+
super(`Tool "${toolName}" exceeded its ${ms}ms timeout`);
|
|
296
|
+
this.name = "ToolTimeoutError";
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
function withTimeout(work, ms, toolName) {
|
|
300
|
+
return new Promise((resolve, reject) => {
|
|
301
|
+
const timer = setTimeout(() => reject(new ToolTimeoutError(toolName, ms)), ms);
|
|
302
|
+
work.then((value) => {
|
|
303
|
+
clearTimeout(timer);
|
|
304
|
+
resolve(value);
|
|
305
|
+
}, (error) => {
|
|
306
|
+
clearTimeout(timer);
|
|
307
|
+
reject(error);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
__name(withTimeout, "withTimeout");
|
|
312
|
+
function parseFollowUps(text, count) {
|
|
313
|
+
const source = text.match(/\[[\s\S]*\]/)?.[0] ?? text;
|
|
314
|
+
try {
|
|
315
|
+
const parsed = JSON.parse(source);
|
|
316
|
+
if (Array.isArray(parsed)) {
|
|
317
|
+
return parsed.filter((item) => typeof item === "string").slice(0, count);
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
__name(parseFollowUps, "parseFollowUps");
|
|
324
|
+
async function generateFollowUps(model, messages, count) {
|
|
325
|
+
const discard = {
|
|
326
|
+
write: /* @__PURE__ */ __name(() => {
|
|
327
|
+
}, "write"),
|
|
328
|
+
end: /* @__PURE__ */ __name(() => {
|
|
329
|
+
}, "end"),
|
|
330
|
+
fail: /* @__PURE__ */ __name(() => {
|
|
331
|
+
}, "fail")
|
|
332
|
+
};
|
|
333
|
+
const turn = await model.runTurn({
|
|
334
|
+
system: `Based on the conversation so far, propose up to ${count} short, distinct follow-up questions the user is likely to ask next. Respond with ONLY a JSON array of strings \u2014 no prose, no code fences.`,
|
|
335
|
+
messages,
|
|
336
|
+
tools: [],
|
|
337
|
+
sink: discard
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
followUps: parseFollowUps(turn.text, count),
|
|
341
|
+
usage: turn.usage,
|
|
342
|
+
...turn.modelId !== void 0 ? {
|
|
343
|
+
modelId: turn.modelId
|
|
344
|
+
} : {}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
__name(generateFollowUps, "generateFollowUps");
|
|
348
|
+
async function runAgentLoop(deps, input, hooks) {
|
|
349
|
+
const maxSteps = deps.maxSteps ?? 8;
|
|
350
|
+
const persona = input.persona;
|
|
351
|
+
let system = await resolveSystemPrompt(deps, input);
|
|
352
|
+
if (deps.quota !== void 0) {
|
|
353
|
+
const quota = deps.quota;
|
|
354
|
+
const state = await hooks.step("quota:check", () => quota.check(input.actor.id, deps.day));
|
|
355
|
+
if (!state.withinLimit) {
|
|
356
|
+
publishAgentQuotaExceeded({
|
|
357
|
+
actorId: input.actor.id,
|
|
358
|
+
usedTokens: state.usedTokens,
|
|
359
|
+
limitTokens: state.limitTokens
|
|
360
|
+
});
|
|
361
|
+
throw new QuotaExceededError();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (input.regenerate === true) {
|
|
365
|
+
await hooks.step("regenerate:truncate", async () => {
|
|
366
|
+
const existing = await deps.store.getThread(input.threadId);
|
|
367
|
+
const messages = existing?.messages ?? [];
|
|
368
|
+
let lastUserIndex = -1;
|
|
369
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
370
|
+
if (messages[index]?.role === "user") {
|
|
371
|
+
lastUserIndex = index;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const firstDropped = messages[lastUserIndex + 1];
|
|
376
|
+
if (firstDropped !== void 0) {
|
|
377
|
+
await deps.store.truncateFrom(input.threadId, firstDropped.id);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
} else {
|
|
381
|
+
await hooks.step("persist:user", () => deps.store.appendMessage({
|
|
382
|
+
threadId: input.threadId,
|
|
383
|
+
role: "user",
|
|
384
|
+
content: input.userText,
|
|
385
|
+
...persona !== void 0 ? {
|
|
386
|
+
persona: persona.id
|
|
387
|
+
} : {}
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
const thread = await hooks.step("load:thread", () => deps.store.getThread(input.threadId));
|
|
391
|
+
const modelMessages = (thread?.messages ?? []).map((message) => ({
|
|
392
|
+
role: message.role,
|
|
393
|
+
content: message.content,
|
|
394
|
+
...message.toolCalls !== void 0 ? {
|
|
395
|
+
toolCalls: message.toolCalls
|
|
396
|
+
} : {},
|
|
397
|
+
...message.toolResults !== void 0 ? {
|
|
398
|
+
toolResults: message.toolResults
|
|
399
|
+
} : {}
|
|
400
|
+
}));
|
|
401
|
+
const writer = await hooks.openSink();
|
|
402
|
+
let lastText = "";
|
|
403
|
+
let steps = 0;
|
|
404
|
+
let totalInput = 0;
|
|
405
|
+
let totalOutput = 0;
|
|
406
|
+
publishAgentRunStarted({
|
|
407
|
+
runId: hooks.runId,
|
|
408
|
+
threadId: input.threadId,
|
|
409
|
+
actorId: input.actor.id,
|
|
410
|
+
...persona !== void 0 ? {
|
|
411
|
+
persona: persona.id
|
|
412
|
+
} : {}
|
|
413
|
+
});
|
|
414
|
+
let injectedPassages;
|
|
415
|
+
if (deps.retriever !== void 0) {
|
|
416
|
+
const retriever = deps.retriever;
|
|
417
|
+
const passages = await hooks.step("retrieve", () => retriever.retrieve(input.userText, {
|
|
418
|
+
topK: deps.retrievalTopK ?? 5
|
|
419
|
+
}));
|
|
420
|
+
if (passages.length > 0) {
|
|
421
|
+
injectedPassages = passages;
|
|
422
|
+
system = `${system}
|
|
423
|
+
|
|
424
|
+
${buildContextBlock(passages)}`;
|
|
425
|
+
}
|
|
426
|
+
publishAgentRetrieved({
|
|
427
|
+
runId: hooks.runId,
|
|
428
|
+
query: input.userText,
|
|
429
|
+
count: passages.length
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
for (let i = 0; i < maxSteps; i += 1) {
|
|
433
|
+
const tools = await deps.registry.definitionsFor(input.actor, deps.rolesPolicy, intersectAllow(persona?.allowedTools, deps.toolAllowList));
|
|
434
|
+
const turn = await hooks.step(`llm:${i}`, () => deps.model.runTurn({
|
|
435
|
+
system,
|
|
436
|
+
messages: modelMessages,
|
|
437
|
+
tools,
|
|
438
|
+
sink: writer
|
|
439
|
+
}));
|
|
440
|
+
await hooks.step(`persist:usage:${i}`, () => deps.store.recordUsage({
|
|
441
|
+
threadId: input.threadId,
|
|
442
|
+
actorRef: input.actor.id,
|
|
443
|
+
// provider-reported model wins over the configured fallback, so cost can't misattribute
|
|
444
|
+
modelId: turn.modelId ?? deps.modelId ?? "unknown",
|
|
445
|
+
purpose: "chat",
|
|
446
|
+
usage: turn.usage,
|
|
447
|
+
// persist the provider's actual cost when reported; the read-model prefers it over pricing
|
|
448
|
+
...turn.costUsd !== void 0 ? {
|
|
449
|
+
costUsd: turn.costUsd
|
|
450
|
+
} : {}
|
|
451
|
+
}));
|
|
452
|
+
if (deps.quota !== void 0) {
|
|
453
|
+
const quota = deps.quota;
|
|
454
|
+
await hooks.step(`quota:bump:${i}`, () => quota.bump(input.actor.id, deps.day, turn.usage.inputTokens + turn.usage.outputTokens));
|
|
455
|
+
}
|
|
456
|
+
steps += 1;
|
|
457
|
+
totalInput += turn.usage.inputTokens;
|
|
458
|
+
totalOutput += turn.usage.outputTokens;
|
|
459
|
+
lastText = turn.text;
|
|
460
|
+
publishAgentMessage({
|
|
461
|
+
runId: hooks.runId,
|
|
462
|
+
threadId: input.threadId,
|
|
463
|
+
role: "assistant",
|
|
464
|
+
textLength: turn.text.length
|
|
465
|
+
});
|
|
466
|
+
const isFinalTurn = turn.toolCalls.length === 0;
|
|
467
|
+
let followUps;
|
|
468
|
+
if (isFinalTurn && deps.followUpsCount !== void 0 && deps.followUpsCount > 0) {
|
|
469
|
+
const count = deps.followUpsCount;
|
|
470
|
+
const generated = await hooks.step(`followups:${i}`, () => generateFollowUps(deps.model, [
|
|
471
|
+
...modelMessages,
|
|
472
|
+
{
|
|
473
|
+
role: "assistant",
|
|
474
|
+
content: turn.text
|
|
475
|
+
}
|
|
476
|
+
], count));
|
|
477
|
+
if (generated.followUps.length > 0) {
|
|
478
|
+
followUps = generated.followUps;
|
|
479
|
+
}
|
|
480
|
+
await hooks.step(`persist:usage:followups:${i}`, () => deps.store.recordUsage({
|
|
481
|
+
threadId: input.threadId,
|
|
482
|
+
actorRef: input.actor.id,
|
|
483
|
+
modelId: generated.modelId ?? deps.modelId ?? "unknown",
|
|
484
|
+
purpose: "follow_ups",
|
|
485
|
+
usage: generated.usage
|
|
486
|
+
}));
|
|
487
|
+
}
|
|
488
|
+
const assistant = await hooks.step(`persist:assistant:${i}`, () => deps.store.appendMessage({
|
|
489
|
+
threadId: input.threadId,
|
|
490
|
+
role: "assistant",
|
|
491
|
+
content: turn.text,
|
|
492
|
+
usage: turn.usage,
|
|
493
|
+
...persona !== void 0 ? {
|
|
494
|
+
persona: persona.id
|
|
495
|
+
} : {},
|
|
496
|
+
...turn.toolCalls.length > 0 ? {
|
|
497
|
+
toolCalls: turn.toolCalls
|
|
498
|
+
} : {},
|
|
499
|
+
...followUps !== void 0 ? {
|
|
500
|
+
followUps
|
|
501
|
+
} : {}
|
|
502
|
+
}));
|
|
503
|
+
modelMessages.push({
|
|
504
|
+
role: "assistant",
|
|
505
|
+
content: turn.text,
|
|
506
|
+
...turn.toolCalls.length > 0 ? {
|
|
507
|
+
toolCalls: turn.toolCalls
|
|
508
|
+
} : {}
|
|
509
|
+
});
|
|
510
|
+
if (i === 0 && injectedPassages !== void 0) {
|
|
511
|
+
const passages = injectedPassages;
|
|
512
|
+
const toolCallId = `retrieve-${assistant.id}`;
|
|
513
|
+
await hooks.step(`persist:retrieval:${assistant.id}`, async () => {
|
|
514
|
+
await deps.store.recordToolCall({
|
|
515
|
+
toolCallId,
|
|
516
|
+
messageId: assistant.id,
|
|
517
|
+
toolName: "retrieve",
|
|
518
|
+
toolType: "read",
|
|
519
|
+
input: {
|
|
520
|
+
query: input.userText
|
|
521
|
+
},
|
|
522
|
+
status: "auto_executed"
|
|
523
|
+
});
|
|
524
|
+
await deps.store.updateToolCall({
|
|
525
|
+
toolCallId,
|
|
526
|
+
status: "executed",
|
|
527
|
+
output: {
|
|
528
|
+
passages
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (isFinalTurn) {
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
const results = [];
|
|
537
|
+
for (const call of turn.toolCalls) {
|
|
538
|
+
const spec = deps.registry.spec(call.name);
|
|
539
|
+
const toolType = spec?.kind ?? "read";
|
|
540
|
+
const ctx = {
|
|
541
|
+
actor: input.actor,
|
|
542
|
+
threadId: input.threadId,
|
|
543
|
+
runId: hooks.runId,
|
|
544
|
+
requestId: hooks.runId,
|
|
545
|
+
...persona !== void 0 ? {
|
|
546
|
+
persona
|
|
547
|
+
} : {},
|
|
548
|
+
...input.pageContext !== void 0 ? {
|
|
549
|
+
pageContext: input.pageContext
|
|
550
|
+
} : {},
|
|
551
|
+
...deps.host !== void 0 ? {
|
|
552
|
+
host: deps.host
|
|
553
|
+
} : {}
|
|
554
|
+
};
|
|
555
|
+
if (toolType === "agent") {
|
|
556
|
+
const targetAgent = spec?.targetAgent ?? call.name;
|
|
557
|
+
const task = extractTask(call.input);
|
|
558
|
+
await hooks.step(`persist:toolcall:${call.id}`, () => deps.store.recordToolCall({
|
|
559
|
+
toolCallId: call.id,
|
|
560
|
+
messageId: assistant.id,
|
|
561
|
+
toolName: call.name,
|
|
562
|
+
toolType: "read",
|
|
563
|
+
input: call.input,
|
|
564
|
+
status: "auto_executed"
|
|
565
|
+
}));
|
|
566
|
+
const overDepth = (input.delegationDepth ?? 0) >= MAX_DELEGATION_DEPTH;
|
|
567
|
+
publishAgentDelegated({
|
|
568
|
+
runId: hooks.runId,
|
|
569
|
+
toAgent: targetAgent,
|
|
570
|
+
...input.agentName !== void 0 ? {
|
|
571
|
+
fromAgent: input.agentName
|
|
572
|
+
} : {}
|
|
573
|
+
});
|
|
574
|
+
let sub;
|
|
575
|
+
if (overDepth) {
|
|
576
|
+
sub = {
|
|
577
|
+
text: `(delegation depth limit of ${MAX_DELEGATION_DEPTH} reached)`
|
|
578
|
+
};
|
|
579
|
+
} else if (hooks.runAgent) {
|
|
580
|
+
sub = await hooks.runAgent(targetAgent, task);
|
|
581
|
+
} else {
|
|
582
|
+
sub = {
|
|
583
|
+
text: `(no multi-agent support wired; cannot reach "${targetAgent}")`
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
|
|
587
|
+
toolCallId: call.id,
|
|
588
|
+
status: "executed",
|
|
589
|
+
output: sub
|
|
590
|
+
}));
|
|
591
|
+
results.push({
|
|
592
|
+
id: call.id,
|
|
593
|
+
name: call.name,
|
|
594
|
+
output: sub
|
|
595
|
+
});
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (toolType === "action") {
|
|
599
|
+
await hooks.step(`persist:toolcall:${call.id}`, () => deps.store.recordToolCall({
|
|
600
|
+
toolCallId: call.id,
|
|
601
|
+
messageId: assistant.id,
|
|
602
|
+
toolName: call.name,
|
|
603
|
+
toolType: "action",
|
|
604
|
+
input: call.input,
|
|
605
|
+
status: "pending_approval"
|
|
606
|
+
}));
|
|
607
|
+
const decision = await hooks.awaitApproval(call, ctx);
|
|
608
|
+
if (!decision.approved) {
|
|
609
|
+
await hooks.step(`persist:toolreject:${call.id}`, () => deps.store.updateToolCall({
|
|
610
|
+
toolCallId: call.id,
|
|
611
|
+
status: "rejected",
|
|
612
|
+
...decision.reason !== void 0 ? {
|
|
613
|
+
error: decision.reason
|
|
614
|
+
} : {}
|
|
615
|
+
}));
|
|
616
|
+
results.push({
|
|
617
|
+
id: call.id,
|
|
618
|
+
name: call.name,
|
|
619
|
+
output: {
|
|
620
|
+
rejected: true,
|
|
621
|
+
reason: decision.reason ?? "rejected by user"
|
|
622
|
+
},
|
|
623
|
+
error: "rejected"
|
|
624
|
+
});
|
|
625
|
+
publishAgentToolCall({
|
|
626
|
+
runId: hooks.runId,
|
|
627
|
+
toolName: call.name,
|
|
628
|
+
toolType,
|
|
629
|
+
status: "rejected"
|
|
630
|
+
});
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
await hooks.step(`persist:toolcall:${call.id}`, () => deps.store.recordToolCall({
|
|
635
|
+
toolCallId: call.id,
|
|
636
|
+
messageId: assistant.id,
|
|
637
|
+
toolName: call.name,
|
|
638
|
+
toolType: "read",
|
|
639
|
+
input: call.input,
|
|
640
|
+
status: "auto_executed"
|
|
641
|
+
}));
|
|
642
|
+
}
|
|
643
|
+
const startedAt = Date.now();
|
|
644
|
+
try {
|
|
645
|
+
const invocation = hooks.step(`tool:${call.id}`, () => deps.registry.invoke(call.name, call.input, ctx, deps.rolesPolicy));
|
|
646
|
+
const output = deps.toolTimeoutMs !== void 0 ? await withTimeout(invocation, deps.toolTimeoutMs, call.name) : await invocation;
|
|
647
|
+
const executionMs = Date.now() - startedAt;
|
|
648
|
+
await hooks.step(`persist:toolexec:${call.id}`, () => deps.store.updateToolCall({
|
|
649
|
+
toolCallId: call.id,
|
|
650
|
+
status: "executed",
|
|
651
|
+
output,
|
|
652
|
+
executionMs,
|
|
653
|
+
...toolType === "action" ? {
|
|
654
|
+
executedByRef: input.actor.id
|
|
655
|
+
} : {}
|
|
656
|
+
}));
|
|
657
|
+
results.push({
|
|
658
|
+
id: call.id,
|
|
659
|
+
name: call.name,
|
|
660
|
+
output
|
|
661
|
+
});
|
|
662
|
+
publishAgentToolCall({
|
|
663
|
+
runId: hooks.runId,
|
|
664
|
+
toolName: call.name,
|
|
665
|
+
toolType,
|
|
666
|
+
status: "executed",
|
|
667
|
+
durationMs: executionMs
|
|
668
|
+
});
|
|
669
|
+
} catch (error) {
|
|
670
|
+
const executionMs = Date.now() - startedAt;
|
|
671
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
672
|
+
await hooks.step(`persist:toolfail:${call.id}`, () => deps.store.updateToolCall({
|
|
673
|
+
toolCallId: call.id,
|
|
674
|
+
status: "failed",
|
|
675
|
+
error: message,
|
|
676
|
+
executionMs
|
|
677
|
+
}));
|
|
678
|
+
results.push({
|
|
679
|
+
id: call.id,
|
|
680
|
+
name: call.name,
|
|
681
|
+
output: null,
|
|
682
|
+
error: message
|
|
683
|
+
});
|
|
684
|
+
publishAgentToolCall({
|
|
685
|
+
runId: hooks.runId,
|
|
686
|
+
toolName: call.name,
|
|
687
|
+
toolType,
|
|
688
|
+
status: "failed",
|
|
689
|
+
durationMs: executionMs
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
modelMessages.push({
|
|
694
|
+
role: "user",
|
|
695
|
+
content: "",
|
|
696
|
+
toolResults: results
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
|
|
700
|
+
await hooks.step("persist:title", () => deps.store.setTitle(input.threadId, deriveTitle(input.userText)));
|
|
701
|
+
}
|
|
702
|
+
await writer.end();
|
|
703
|
+
publishAgentRunFinished({
|
|
704
|
+
runId: hooks.runId,
|
|
705
|
+
threadId: input.threadId,
|
|
706
|
+
steps,
|
|
707
|
+
inputTokens: totalInput,
|
|
708
|
+
outputTokens: totalOutput
|
|
709
|
+
});
|
|
710
|
+
return {
|
|
711
|
+
text: lastText
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
__name(runAgentLoop, "runAgentLoop");
|
|
715
|
+
export {
|
|
716
|
+
AGENT_ACTOR_RESOLVER,
|
|
717
|
+
AGENT_DEPS_FACTORY,
|
|
718
|
+
AGENT_DURABLE_RUNNER,
|
|
719
|
+
AGENT_EMBEDDING_PROVIDER,
|
|
720
|
+
AGENT_GOVERNANCE_QUERIES,
|
|
721
|
+
AGENT_MODEL,
|
|
722
|
+
AGENT_OPTIONS,
|
|
723
|
+
AGENT_PRICING_STORE,
|
|
724
|
+
AGENT_QUOTA_STORE,
|
|
725
|
+
AGENT_REGISTRY,
|
|
726
|
+
AGENT_RETRIEVER,
|
|
727
|
+
AGENT_ROLES_POLICY,
|
|
728
|
+
AGENT_RUNNER,
|
|
729
|
+
AGENT_SINK,
|
|
730
|
+
AGENT_STORE,
|
|
731
|
+
AGENT_TOOL_REGISTRY,
|
|
732
|
+
AgentRegistry,
|
|
733
|
+
AgentStreamError,
|
|
734
|
+
DefaultRolesPolicy,
|
|
735
|
+
QuotaExceededError,
|
|
736
|
+
ToolForbiddenError,
|
|
737
|
+
ToolInputInvalidError,
|
|
738
|
+
ToolNotFoundError,
|
|
739
|
+
ToolRegistry,
|
|
740
|
+
filterToolsByRole,
|
|
741
|
+
personaFilterTools,
|
|
742
|
+
publishAgentDelegated,
|
|
743
|
+
publishAgentMessage,
|
|
744
|
+
publishAgentQuotaExceeded,
|
|
745
|
+
publishAgentRetrieved,
|
|
746
|
+
publishAgentRunFailed,
|
|
747
|
+
publishAgentRunFinished,
|
|
748
|
+
publishAgentRunStarted,
|
|
749
|
+
publishAgentToolCall,
|
|
750
|
+
runAgentLoop,
|
|
751
|
+
seedModelPrices
|
|
752
|
+
};
|
|
753
|
+
//# sourceMappingURL=index.js.map
|