@dudousxd/nestjs-agent 1.3.0 → 1.5.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.
@@ -83,6 +83,13 @@ interface DispatchedLlmInput extends LlmStepEnvelope {
83
83
  interface DispatchedToolInput extends ToolStepEnvelope {
84
84
  toolCallId: string;
85
85
  toolType: 'read' | 'action';
86
+ /**
87
+ * Where a component the tool pushes (`ctx.emitUi`) streams live — the same routing as
88
+ * {@link DispatchedLlmInput}. Absent on a task enqueued by a workflow that predates it: the
89
+ * components are then still collected and persisted, just not streamed.
90
+ */
91
+ sinkRunId?: string;
92
+ childSink?: boolean;
86
93
  }
87
94
  /**
88
95
  * The two long steps every turn dispatches as routed remote steps (`AgentRunSteps.llm` /
@@ -83,6 +83,13 @@ interface DispatchedLlmInput extends LlmStepEnvelope {
83
83
  interface DispatchedToolInput extends ToolStepEnvelope {
84
84
  toolCallId: string;
85
85
  toolType: 'read' | 'action';
86
+ /**
87
+ * Where a component the tool pushes (`ctx.emitUi`) streams live — the same routing as
88
+ * {@link DispatchedLlmInput}. Absent on a task enqueued by a workflow that predates it: the
89
+ * components are then still collected and persisted, just not streamed.
90
+ */
91
+ sinkRunId?: string;
92
+ childSink?: boolean;
86
93
  }
87
94
  /**
88
95
  * The two long steps every turn dispatches as routed remote steps (`AgentRunSteps.llm` /
@@ -24,7 +24,7 @@ function childSinkWriter(writer) {
24
24
  __name(childSinkWriter, "childSinkWriter");
25
25
 
26
26
  // src/durable/agent-run.steps.ts
27
- import { AGENT_DEPS_FACTORY, createFrameBuffer, invokeWithTransientRetry, observeTurnFrames, publishAgentToolRetry, stampToolKinds, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames } from "@dudousxd/nestjs-agent-core";
27
+ import { AGENT_DEPS_FACTORY, createFrameBuffer, createUiCollector, encodeStreamEvent, invokeWithTransientRetry, observeTurnFrames, publishAgentToolRetry, stampToolKinds, traceLlmTurn, traceToolExecution, withAskTool, withMemoryTool, withSkillTool, withToolTimeout, withTurnFrames, wrapToolStepOutput } from "@dudousxd/nestjs-agent-core";
28
28
  import { Step } from "@dudousxd/nestjs-durable";
29
29
  import { Inject, Injectable } from "@nestjs/common";
30
30
  function _ts_decorate(decorators, target, key, desc) {
@@ -115,8 +115,15 @@ var AgentRunSteps = class {
115
115
  if (deps.registry.spec(input.toolName)?.kind === "action" && input.toolType !== "action") {
116
116
  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`);
117
117
  }
118
+ const sinkRunId = input.sinkRunId;
119
+ let opened;
120
+ const ui = createUiCollector(input.toolCallId, sinkRunId === void 0 ? void 0 : async (event) => {
121
+ opened ??= Promise.resolve(deps.sink.open(sinkRunId)).then((writer) => input.childSink === true ? childSinkWriter(writer) : writer);
122
+ await (await opened).write(encodeStreamEvent(event));
123
+ });
118
124
  const ctx = {
119
- ...input.ctx
125
+ ...input.ctx,
126
+ emitUi: ui.emit
120
127
  };
121
128
  const localClassify = deps.toolTransientRetry !== false ? deps.toolTransientRetry?.classify : void 0;
122
129
  const transientRetry = input.transientRetry === false ? false : {
@@ -126,11 +133,12 @@ var AgentRunSteps = class {
126
133
  classify: localClassify
127
134
  } : {}
128
135
  };
129
- return traceToolExecution(input.ctx.runId, {
136
+ const output = await traceToolExecution(input.ctx.runId, {
130
137
  toolCallId: input.toolCallId,
131
138
  toolName: input.toolName,
132
139
  toolType: input.toolType
133
140
  }, () => invokeWithTransientRetry(() => {
141
+ ui.restart();
134
142
  const invocation = deps.registry.invoke(input.toolName, input.input, ctx, deps.rolesPolicy);
135
143
  return input.timeoutMs !== void 0 ? withToolTimeout(invocation, input.timeoutMs, input.toolName) : invocation;
136
144
  }, transientRetry, {
@@ -143,6 +151,7 @@ var AgentRunSteps = class {
143
151
  });
144
152
  }, "onRetry")
145
153
  }));
154
+ return input.collectUi === true ? wrapToolStepOutput(output, ui.components()) : output;
146
155
  }
147
156
  };
148
157
  _ts_decorate([
@@ -386,7 +395,10 @@ var AgentRunWorkflow = class _AgentRunWorkflow {
386
395
  dispatchTool: /* @__PURE__ */ __name((call, envelope) => ctx.step(AgentRunSteps.prototype.tool, {
387
396
  ...envelope,
388
397
  toolCallId: call.id,
389
- toolType: call.kind === "action" ? "action" : "read"
398
+ toolType: call.kind === "action" ? "action" : "read",
399
+ // Where `ctx.emitUi` streams from the worker — the llm step's routing.
400
+ sinkRunId,
401
+ childSink: input.sinkRunId !== void 0
390
402
  }), "dispatchTool")
391
403
  } : {}
392
404
  };
@@ -471,7 +483,7 @@ __name(isSignalTimeout, "isSignalTimeout");
471
483
 
472
484
  // src/durable/durable-agent-runner.ts
473
485
  import { randomUUID } from "crypto";
474
- import { AGENT_SINK, AGENT_STORE as AGENT_STORE2, encodeStreamEvent } from "@dudousxd/nestjs-agent-core";
486
+ import { AGENT_SINK, AGENT_STORE as AGENT_STORE2, encodeStreamEvent as encodeStreamEvent2 } from "@dudousxd/nestjs-agent-core";
475
487
  import { RUN_GATEWAY as RUN_GATEWAY2, WorkflowService } from "@dudousxd/nestjs-durable";
476
488
  import { isWorkflowControlFlowSignal as isWorkflowControlFlowSignal2 } from "@dudousxd/nestjs-durable-core";
477
489
  import { Inject as Inject3, Injectable as Injectable3, Logger } from "@nestjs/common";
@@ -650,7 +662,7 @@ var DurableAgentRunner = class _DurableAgentRunner {
650
662
  await this.store.setActiveStream(threadId, null);
651
663
  }
652
664
  const writer = await this.sink.open(runId);
653
- await writer.write(encodeStreamEvent({
665
+ await writer.write(encodeStreamEvent2({
654
666
  kind: "cancelled"
655
667
  }));
656
668
  await writer.end();
@@ -1048,7 +1060,7 @@ AgentDepsFactory2 = _ts_decorate5([
1048
1060
  ], AgentDepsFactory2);
1049
1061
 
1050
1062
  // src/agent.service.ts
1051
- import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_QUOTA_STORE as AGENT_QUOTA_STORE2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE4, REQUESTER_APPROVER, mayDecideApproval } from "@dudousxd/nestjs-agent-core";
1063
+ import { AGENT_ATTACHMENT_STAGING, AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY3, AGENT_QUOTA_STORE as AGENT_QUOTA_STORE2, AGENT_RUNNER, AGENT_STORE as AGENT_STORE4, REQUESTER_APPROVER, mayDecideApproval, readElicitationQuestions, validateElicitationAnswer } from "@dudousxd/nestjs-agent-core";
1052
1064
  import { BadRequestException, ForbiddenException, GoneException, Inject as Inject5, Injectable as Injectable5, NotFoundException, NotImplementedException, Optional as Optional3 } from "@nestjs/common";
1053
1065
  function _ts_decorate6(decorators, target, key, desc) {
1054
1066
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -1224,6 +1236,7 @@ var AgentService = class {
1224
1236
  */
1225
1237
  async answer(actor, toolCallId2, answers2 = {}) {
1226
1238
  await this.assertOwnsToolCall(actor, toolCallId2);
1239
+ await this.assertAnswersFit(toolCallId2, answers2);
1227
1240
  const reply = {
1228
1241
  answers: answers2,
1229
1242
  answeredByRef: actor.id
@@ -1492,6 +1505,27 @@ var AgentService = class {
1492
1505
  }
1493
1506
  }
1494
1507
  /**
1508
+ * Check a reply against the questions it answers before it is signalled: every submitted value a
1509
+ * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
1510
+ * options), and every `required` question answered — by the reply, or by the defaults an omitted
1511
+ * question falls back to. A reply the loop would have to drop parts of is refused here instead, so
1512
+ * the person who typed it hears why. Needs the store to hand back the call's recorded questions;
1513
+ * without that seam the loop's own filtering is the only check.
1514
+ */
1515
+ async assertAnswersFit(toolCallId2, answers2) {
1516
+ if (this.store.toolCallInput === void 0) {
1517
+ return;
1518
+ }
1519
+ const questions = readElicitationQuestions(await this.store.toolCallInput(toolCallId2));
1520
+ for (const question of questions) {
1521
+ const submitted = Object.hasOwn(answers2, question.id) ? answers2[question.id] : void 0;
1522
+ const problem = validateElicitationAnswer(question, submitted ?? question.defaults ?? []);
1523
+ if (problem !== null) {
1524
+ throw new BadRequestException(`answers["${question.id}"] ${problem}`);
1525
+ }
1526
+ }
1527
+ }
1528
+ /**
1495
1529
  * Authorization seam for approve/reject. The call's recorded approver decides who may settle it:
1496
1530
  * the requester (the default, and every call recorded before approvers existed) is the thread's
1497
1531
  * own actor — the ownership check this always was; any other approver goes through the policy's
@@ -3096,6 +3130,9 @@ var AiToolDiscoveryService = class _AiToolDiscoveryService {
3096
3130
  } : {},
3097
3131
  ...meta.presentation !== void 0 ? {
3098
3132
  presentation: meta.presentation
3133
+ } : {},
3134
+ ...meta.terminal === true ? {
3135
+ terminal: true
3099
3136
  } : {}
3100
3137
  }, {
3101
3138
  execute: /* @__PURE__ */ __name((input, ctx) => instance.execute(input, ctx), "execute"),
@@ -3305,7 +3342,7 @@ var LedgerQuotaStore = class {
3305
3342
  };
3306
3343
 
3307
3344
  // src/runner/inline-agent-runner.ts
3308
- import { AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY6, AGENT_STORE as AGENT_STORE5, RunCancelledError as RunCancelledError2, agentFailureCode as agentFailureCode2, encodeStreamEvent as encodeStreamEvent2, publishAgentRunFailed as publishAgentRunFailed2, runAgentLoop as runAgentLoop2, settleAll as settleAll2, settleUnsettledDelegation as settleUnsettledDelegation2 } from "@dudousxd/nestjs-agent-core";
3345
+ import { AGENT_DEPS_FACTORY as AGENT_DEPS_FACTORY6, AGENT_STORE as AGENT_STORE5, RunCancelledError as RunCancelledError2, agentFailureCode as agentFailureCode2, encodeStreamEvent as encodeStreamEvent3, publishAgentRunFailed as publishAgentRunFailed2, runAgentLoop as runAgentLoop2, settleAll as settleAll2, settleUnsettledDelegation as settleUnsettledDelegation2 } from "@dudousxd/nestjs-agent-core";
3309
3346
  import { Inject as Inject18, Injectable as Injectable10, Logger as Logger5 } from "@nestjs/common";
3310
3347
  function _ts_decorate20(decorators, target, key, desc) {
3311
3348
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3443,7 +3480,7 @@ var InlineAgentRunner = class _InlineAgentRunner {
3443
3480
  this.logger.log(`agent run ${runId} cancelled`);
3444
3481
  await this.store.setActiveStream(threadId, null);
3445
3482
  const writer = await deps.sink.open(runId);
3446
- await writer.write(encodeStreamEvent2({
3483
+ await writer.write(encodeStreamEvent3({
3447
3484
  kind: "cancelled"
3448
3485
  }));
3449
3486
  await writer.end();