@dudousxd/nestjs-agent-core 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,6 +36,8 @@ import type { ModelProvider, AgentStore, ToolSpec, RolesPolicy } from '@dudousxd
36
36
  - `isControlFlowSignal(error)` / `isReplayIntegrityError(error)` — recognize a durable suspend and a checkpoint refusal without importing the durable packages (a `Symbol.for` marker and a class name, respectively). Rethrow both untouched from any `catch` in a workflow body.
37
37
  - `settleAll(tasks)` / `SettledTask<T>` — the implementation behind the optional `AgentLoopHooks.parallel`, which is how a turn's `read` tool calls run concurrently. It invokes every task synchronously, in list order, before awaiting any of them (so a runner that takes checkpoint positions on the call keeps them in call order), and resolves only once all of them have settled (so a runner that unwinds a turn by throwing never abandons a sibling mid-dispatch). A runner that assigns positions anywhere else simply omits the hook and the loop stays sequential.
38
38
  - `AgentStreamEvent` / `encodeStreamEvent` / `decodeStreamEvent` — the live-stream vocabulary (text, reasoning, tool calls with optional `parentId` nesting, `elicitation`, `approval-requested` with `approver`/`expiresAt`, server-pushed `ui` components, `title`, `cancelled`). It is also the contract a runner that is not this loop writes to get the React client for free: see [docs/stream-protocol.md](../../docs/stream-protocol.md). `AgentUiComponent` and `AgentApprovalRequest` are the payload types.
39
+ - `observeTurnFrames` / `withTurnFrames` — what a model turn streamed that its result does not carry: the model's thinking (`reasoning` text, and `reasoningMs` summed over each burst of consecutive reasoning frames) and pushed `ui` components (first-seen order, last props per id). The loop and the dispatched `llm` step wrap the provider's sink with it inside the model checkpoint, so every provider that streams the vocabulary gets reasoning persisted (`StoredMessage.reasoning` / `reasoningMs` / `ui`) and a replay reads the journaled numbers back. A provider may set `ModelTurnResult.reasoning` / `reasoningMs` / `ui` itself; its values win. The step's `reasoningMs` also rides the `step-finish` frame.
40
+ - `ToolPresentation` / `ToolResultView` / `ToolCatalogEntry` — how a person-facing surface talks about a tool without naming it (`ToolSpec.presentation`, set by `@AiTool({ presentation })`; never shown to the model). `ToolRegistry.visibleSpecs(actor, policy, allowList)` returns the whole specs behind the same gates `definitionsFor` applies, which is what `GET /agent/tools` serves.
39
41
  - `runAgentLoop(deps, input, hooks)` — the loop; the NestJS package drives it from both runners
40
42
 
41
43
  ## Guardrails — `@dudousxd/nestjs-agent-core/guardrails`
@@ -1,4 +1,4 @@
1
- import { A as Actor, I as InputProcessor, O as OutputProcessor, T as ToolHandler } from '../tool-CL9oEytW.cjs';
1
+ import { A as Actor, I as InputProcessor, O as OutputProcessor, T as ToolHandler } from '../tool-Q2rmGeaG.cjs';
2
2
  import '@standard-schema/spec';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { A as Actor, I as InputProcessor, O as OutputProcessor, T as ToolHandler } from '../tool-CL9oEytW.js';
1
+ import { A as Actor, I as InputProcessor, O as OutputProcessor, T as ToolHandler } from '../tool-Q2rmGeaG.js';
2
2
  import '@standard-schema/spec';
3
3
 
4
4
  /**
package/dist/index.cjs CHANGED
@@ -119,6 +119,7 @@ __export(src_exports, {
119
119
  memoryWriteVerdict: () => memoryWriteVerdict,
120
120
  normalizeDelegation: () => normalizeDelegation,
121
121
  normalizeElicitationReply: () => normalizeElicitationReply,
122
+ observeTurnFrames: () => observeTurnFrames,
122
123
  offerMemories: () => offerMemories,
123
124
  offerSkills: () => offerSkills,
124
125
  publishAgentDelegated: () => publishAgentDelegated,
@@ -168,6 +169,7 @@ __export(src_exports, {
168
169
  withMemoryTool: () => withMemoryTool,
169
170
  withSkillTool: () => withSkillTool,
170
171
  withToolTimeout: () => withToolTimeout,
172
+ withTurnFrames: () => withTurnFrames,
171
173
  writeMemory: () => writeMemory
172
174
  });
173
175
  module.exports = __toCommonJS(src_exports);
@@ -229,6 +231,105 @@ function decodeStreamEvent(line) {
229
231
  }
230
232
  __name(decodeStreamEvent, "decodeStreamEvent");
231
233
 
234
+ // src/turn-frames.ts
235
+ function observeTurnFrames(writer, now = Date.now) {
236
+ const decoder2 = new TextDecoder();
237
+ let pending = "";
238
+ let reasoning = "";
239
+ let reasoningMs = 0;
240
+ let burstStartedAt;
241
+ const ui = /* @__PURE__ */ new Map();
242
+ function closeBurst() {
243
+ if (burstStartedAt !== void 0) {
244
+ reasoningMs += Math.max(0, now() - burstStartedAt);
245
+ burstStartedAt = void 0;
246
+ }
247
+ }
248
+ __name(closeBurst, "closeBurst");
249
+ function observeLine(line) {
250
+ if (line.trim().length === 0) {
251
+ return;
252
+ }
253
+ const event = decodeStreamEvent(line);
254
+ if (event === null) {
255
+ return;
256
+ }
257
+ if (event.kind === "reasoning") {
258
+ burstStartedAt ??= now();
259
+ reasoning += event.text;
260
+ return;
261
+ }
262
+ closeBurst();
263
+ if (event.kind === "ui") {
264
+ ui.set(event.id, {
265
+ id: event.id,
266
+ component: event.component,
267
+ props: event.props,
268
+ ...event.version !== void 0 ? {
269
+ version: event.version
270
+ } : {}
271
+ });
272
+ }
273
+ }
274
+ __name(observeLine, "observeLine");
275
+ function observe(chunk) {
276
+ pending += decoder2.decode(chunk, {
277
+ stream: true
278
+ });
279
+ let newline = pending.indexOf("\n");
280
+ while (newline !== -1) {
281
+ observeLine(pending.slice(0, newline));
282
+ pending = pending.slice(newline + 1);
283
+ newline = pending.indexOf("\n");
284
+ }
285
+ }
286
+ __name(observe, "observe");
287
+ return {
288
+ writer: {
289
+ write(chunk) {
290
+ observe(chunk);
291
+ return writer.write(chunk);
292
+ },
293
+ end: /* @__PURE__ */ __name(() => writer.end(), "end"),
294
+ fail: /* @__PURE__ */ __name((error) => writer.fail(error), "fail")
295
+ },
296
+ summary() {
297
+ if (pending.length > 0) {
298
+ observeLine(pending);
299
+ pending = "";
300
+ }
301
+ closeBurst();
302
+ return {
303
+ ...reasoning.length > 0 ? {
304
+ reasoning,
305
+ reasoningMs: Math.round(reasoningMs)
306
+ } : {},
307
+ ...ui.size > 0 ? {
308
+ ui: [
309
+ ...ui.values()
310
+ ]
311
+ } : {}
312
+ };
313
+ }
314
+ };
315
+ }
316
+ __name(observeTurnFrames, "observeTurnFrames");
317
+ function withTurnFrames(result, frames) {
318
+ return {
319
+ ...result,
320
+ ...result.reasoning === void 0 && frames.reasoning !== void 0 ? {
321
+ reasoning: frames.reasoning
322
+ } : {},
323
+ ...result.reasoningMs === void 0 && frames.reasoningMs !== void 0 ? {
324
+ reasoningMs: frames.reasoningMs
325
+ } : {},
326
+ ...result.ui === void 0 && frames.ui !== void 0 ? {
327
+ ui: frames.ui
328
+ } : {}
329
+ };
330
+ }
331
+ __name(withTurnFrames, "withTurnFrames");
332
+
232
333
  // src/spi/pricing-store.ts
233
334
  async function seedModelPrices(store, prices) {
234
335
  for (const price of prices) {
@@ -1949,6 +2050,19 @@ var ToolRegistry = class {
1949
2050
  * tool the allow-list has already excluded is a call whose answer nothing reads.
1950
2051
  */
1951
2052
  async definitionsFor(actor, policy, allowedTools) {
2053
+ return (await this.visibleSpecs(actor, policy, allowedTools)).map((spec) => ({
2054
+ name: spec.name,
2055
+ kind: spec.kind,
2056
+ description: spec.description,
2057
+ inputSchema: spec.inputSchema
2058
+ }));
2059
+ }
2060
+ /**
2061
+ * The specs {@link definitionsFor} offers the model, whole — the same four gates (allow-list,
2062
+ * enabled, role, `canUse`) in the same order. For a surface that lists what an actor can reach
2063
+ * (`GET <base>/tools`), which must never disagree with what the model is actually shown.
2064
+ */
2065
+ async visibleSpecs(actor, policy, allowedTools) {
1952
2066
  const pinnedNames = new Set(filterToolsByAllowList(this.allSpecs(), allowedTools).map((spec) => spec.name));
1953
2067
  const pinned = [
1954
2068
  ...this.entries.values()
@@ -1957,12 +2071,7 @@ var ToolRegistry = class {
1957
2071
  const allowedByRole = new Set((await filterToolsByRole(live.map((entry) => entry.spec), actor, policy)).map((spec) => spec.name));
1958
2072
  const roleScoped = live.filter((entry) => allowedByRole.has(entry.spec.name));
1959
2073
  const actorScoped = await filterToolsByCanUse(roleScoped, actor);
1960
- return actorScoped.map(({ spec }) => ({
1961
- name: spec.name,
1962
- kind: spec.kind,
1963
- description: spec.description,
1964
- inputSchema: spec.inputSchema
1965
- }));
2074
+ return actorScoped.map(({ spec }) => spec);
1966
2075
  }
1967
2076
  /**
1968
2077
  * Run a tool. Re-checks that the tool is enabled and that the role allows it (defense-in-depth —
@@ -3534,12 +3643,13 @@ ${block}`;
3534
3643
  writer
3535
3644
  }) : void 0;
3536
3645
  const buffer = gateMode === "whole" ? createFrameBuffer() : void 0;
3537
- const result = stampToolKinds(await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
3646
+ const frames = observeTurnFrames(incremental?.writer ?? buffer?.writer ?? writer);
3647
+ const result = stampToolKinds(withTurnFrames(await traceLlmTurn(hooks.runId, i, () => deps.model.runTurn({
3538
3648
  system: prompt.system,
3539
3649
  messages: prompt.messages,
3540
3650
  tools,
3541
- sink: incremental?.writer ?? buffer?.writer ?? writer
3542
- })), deps);
3651
+ sink: frames.writer
3652
+ })), frames.summary()), deps);
3543
3653
  if (incremental !== void 0) {
3544
3654
  await incremental.settled();
3545
3655
  const refusal = incremental.rejection();
@@ -3727,6 +3837,15 @@ ${block}`;
3727
3837
  ...input.agentName !== void 0 ? {
3728
3838
  agentName: input.agentName
3729
3839
  } : {},
3840
+ ...turn.reasoning !== void 0 ? {
3841
+ reasoning: turn.reasoning
3842
+ } : {},
3843
+ ...turn.reasoningMs !== void 0 ? {
3844
+ reasoningMs: turn.reasoningMs
3845
+ } : {},
3846
+ ...turn.ui !== void 0 && turn.ui.length > 0 ? {
3847
+ ui: turn.ui
3848
+ } : {},
3730
3849
  ...messageCalls.length > 0 ? {
3731
3850
  toolCalls: messageCalls
3732
3851
  } : {},
@@ -3784,7 +3903,10 @@ ${block}`;
3784
3903
  await writer.write(encodeStreamEvent({
3785
3904
  kind: "step-finish",
3786
3905
  usage: turn.usage,
3787
- costUsd
3906
+ costUsd,
3907
+ ...turn.reasoningMs !== void 0 ? {
3908
+ reasoningMs: turn.reasoningMs
3909
+ } : {}
3788
3910
  }));
3789
3911
  });
3790
3912
  break;
@@ -3837,7 +3959,10 @@ ${block}`;
3837
3959
  await writer.write(encodeStreamEvent({
3838
3960
  kind: "step-finish",
3839
3961
  usage: turn.usage,
3840
- costUsd
3962
+ costUsd,
3963
+ ...turn.reasoningMs !== void 0 ? {
3964
+ reasoningMs: turn.reasoningMs
3965
+ } : {}
3841
3966
  }));
3842
3967
  });
3843
3968
  }
@@ -3992,6 +4117,7 @@ __name(runAgentLoop, "runAgentLoop");
3992
4117
  memoryWriteVerdict,
3993
4118
  normalizeDelegation,
3994
4119
  normalizeElicitationReply,
4120
+ observeTurnFrames,
3995
4121
  offerMemories,
3996
4122
  offerSkills,
3997
4123
  publishAgentDelegated,
@@ -4041,6 +4167,7 @@ __name(runAgentLoop, "runAgentLoop");
4041
4167
  withMemoryTool,
4042
4168
  withSkillTool,
4043
4169
  withToolTimeout,
4170
+ withTurnFrames,
4044
4171
  writeMemory
4045
4172
  });
4046
4173
  //# sourceMappingURL=index.cjs.map