@dudousxd/nestjs-agent-core 0.33.0 → 0.35.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/dist/index.cjs CHANGED
@@ -76,6 +76,11 @@ __export(src_exports, {
76
76
  REMEMBER_TOOL_DESCRIPTION: () => REMEMBER_TOOL_DESCRIPTION,
77
77
  REMEMBER_TOOL_NAME: () => REMEMBER_TOOL_NAME,
78
78
  REQUESTER_APPROVER: () => REQUESTER_APPROVER,
79
+ RUN_ENDED_BEFORE_TOOL_CALL: () => RUN_ENDED_BEFORE_TOOL_CALL,
80
+ RUN_FAILED_MESSAGE: () => RUN_FAILED_MESSAGE,
81
+ RUN_NOT_ACTIVE_CODE: () => RUN_NOT_ACTIVE_CODE,
82
+ RUN_NOT_ACTIVE_MESSAGE: () => RUN_NOT_ACTIVE_MESSAGE,
83
+ RUN_NO_LONGER_RUNNING: () => RUN_NO_LONGER_RUNNING,
79
84
  RunCancelledError: () => RunCancelledError,
80
85
  SKILL_TOOL_DESCRIPTION: () => SKILL_TOOL_DESCRIPTION,
81
86
  SKILL_TOOL_NAME: () => SKILL_TOOL_NAME,
@@ -86,6 +91,7 @@ __export(src_exports, {
86
91
  ToolInputInvalidError: () => ToolInputInvalidError,
87
92
  ToolNotFoundError: () => ToolNotFoundError,
88
93
  ToolRegistry: () => ToolRegistry,
94
+ UNFINISHED_TOOL_CALL: () => UNFINISHED_TOOL_CALL,
89
95
  actorScope: () => actorScope,
90
96
  agentDiagnosticKey: () => agentDiagnosticKey,
91
97
  agentFailureCode: () => agentFailureCode,
@@ -103,6 +109,7 @@ __export(src_exports, {
103
109
  createIncrementalGate: () => createIncrementalGate,
104
110
  createNoopEmitUi: () => createNoopEmitUi,
105
111
  createUiCollector: () => createUiCollector,
112
+ danglingToolCallIds: () => danglingToolCallIds,
106
113
  dayBoundsUtc: () => dayBoundsUtc,
107
114
  decodeStreamEvent: () => decodeStreamEvent,
108
115
  defaultCanDecide: () => defaultCanDecide,
@@ -114,6 +121,7 @@ __export(src_exports, {
114
121
  estimateCost: () => estimateCost,
115
122
  estimateMessageTokens: () => estimateMessageTokens,
116
123
  exhaustedWindow: () => exhaustedWindow,
124
+ exposeStreamErrorDetails: () => exposeStreamErrorDetails,
117
125
  extractJson: () => extractJson,
118
126
  filterToolsByAllowList: () => filterToolsByAllowList,
119
127
  filterToolsByCanUse: () => filterToolsByCanUse,
@@ -176,6 +184,8 @@ __export(src_exports, {
176
184
  runOutputProcessors: () => runOutputProcessors,
177
185
  seedModelPrices: () => seedModelPrices,
178
186
  settleAll: () => settleAll,
187
+ settleDanglingToolCalls: () => settleDanglingToolCalls,
188
+ settleDeadRun: () => settleDeadRun,
179
189
  settleElicitation: () => settleElicitation,
180
190
  settleUnsettledDelegation: () => settleUnsettledDelegation,
181
191
  skillInputSchema: () => skillInputSchema,
@@ -184,9 +194,11 @@ __export(src_exports, {
184
194
  stampToolKinds: () => stampToolKinds,
185
195
  staticModelCatalog: () => staticModelCatalog,
186
196
  staticSkillProvider: () => staticSkillProvider,
197
+ streamFailure: () => streamFailure,
187
198
  summarizeWithModel: () => summarizeWithModel,
188
199
  tenantScope: () => tenantScope,
189
200
  toolCallApprovalFromRow: () => toolCallApprovalFromRow,
201
+ toolCallContext: () => toolCallContext,
190
202
  traceLlmTurn: () => traceLlmTurn,
191
203
  traceToolExecution: () => traceToolExecution,
192
204
  truncateDetailContent: () => truncateDetailContent,
@@ -2553,6 +2565,123 @@ function isControlFlowSignal(error) {
2553
2565
  }
2554
2566
  __name(isControlFlowSignal, "isControlFlowSignal");
2555
2567
 
2568
+ // src/dangling-tool-calls.ts
2569
+ var UNFINISHED_TOOL_CALL = "This tool call was never completed: the turn it belonged to ended unexpectedly before it was settled. Do not assume it ran, and do not assume it did not \u2014 check the current state with a read tool if one exists, tell the person plainly what is and is not confirmed, and do it again only if they still want it.";
2570
+ var RUN_ENDED_BEFORE_TOOL_CALL = "the run ended before this tool call was settled";
2571
+ var DECLINED = "The person was asked to approve this action and declined it. Nothing ran and nothing changed.";
2572
+ var EXPIRED = "This action needed approval, and the request expired before anyone decided. Nothing ran.";
2573
+ function danglingToolCallIds(messages) {
2574
+ const ids = [];
2575
+ for (const message of messages) {
2576
+ const calls = message.toolCalls ?? [];
2577
+ if (message.role !== "assistant" || calls.length === 0) {
2578
+ continue;
2579
+ }
2580
+ const settled = new Set((message.toolResults ?? []).map((result) => result.id));
2581
+ for (const call of calls) {
2582
+ if (!settled.has(call.id)) {
2583
+ ids.push(call.id);
2584
+ }
2585
+ }
2586
+ }
2587
+ return ids;
2588
+ }
2589
+ __name(danglingToolCallIds, "danglingToolCallIds");
2590
+ function resultFor(call, outcome) {
2591
+ const base = {
2592
+ id: call.id,
2593
+ name: call.name
2594
+ };
2595
+ switch (outcome?.status) {
2596
+ case "executed":
2597
+ case "auto_executed":
2598
+ return {
2599
+ ...base,
2600
+ output: outcome.output ?? null
2601
+ };
2602
+ case "failed":
2603
+ return {
2604
+ ...base,
2605
+ output: null,
2606
+ error: outcome.error ?? UNFINISHED_TOOL_CALL
2607
+ };
2608
+ case "rejected":
2609
+ return {
2610
+ ...base,
2611
+ output: {
2612
+ rejected: true
2613
+ },
2614
+ denied: true,
2615
+ error: DECLINED
2616
+ };
2617
+ case "expired":
2618
+ return {
2619
+ ...base,
2620
+ output: {
2621
+ rejected: true,
2622
+ expired: true
2623
+ },
2624
+ denied: true,
2625
+ expired: true,
2626
+ error: EXPIRED
2627
+ };
2628
+ default:
2629
+ return {
2630
+ ...base,
2631
+ output: null,
2632
+ error: UNFINISHED_TOOL_CALL
2633
+ };
2634
+ }
2635
+ }
2636
+ __name(resultFor, "resultFor");
2637
+ function settleDanglingToolCalls(messages, outcomes = []) {
2638
+ const known = new Map(outcomes.map((outcome) => [
2639
+ outcome.id,
2640
+ outcome
2641
+ ]));
2642
+ return messages.map((message) => {
2643
+ const calls = message.toolCalls ?? [];
2644
+ if (message.role !== "assistant" || calls.length === 0) {
2645
+ return message;
2646
+ }
2647
+ const results = message.toolResults ?? [];
2648
+ const settled = new Set(results.map((result) => result.id));
2649
+ const missing = calls.filter((call) => !settled.has(call.id));
2650
+ if (missing.length === 0) {
2651
+ return message;
2652
+ }
2653
+ return {
2654
+ ...message,
2655
+ toolResults: [
2656
+ ...results,
2657
+ ...missing.map((call) => resultFor(call, known.get(call.id)))
2658
+ ]
2659
+ };
2660
+ });
2661
+ }
2662
+ __name(settleDanglingToolCalls, "settleDanglingToolCalls");
2663
+
2664
+ // src/dead-run.ts
2665
+ var RUN_NOT_ACTIVE_CODE = "run_not_active";
2666
+ var RUN_NOT_ACTIVE_MESSAGE = "This request is no longer waiting for an answer: the turn it belonged to has ended. Send the message again.";
2667
+ var RUN_NO_LONGER_RUNNING = "the run is no longer running";
2668
+ async function settleDeadRun(store, input) {
2669
+ const failure = input.failure;
2670
+ if (failure !== void 0) {
2671
+ await Promise.resolve(store.recordRunEnd?.({
2672
+ runId: input.runId,
2673
+ status: "failed",
2674
+ errorCode: failure.code,
2675
+ errorMessage: failure.message
2676
+ })).catch(() => void 0);
2677
+ }
2678
+ await Promise.resolve(store.failUnsettledToolCalls?.(input.runId, RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
2679
+ if (input.threadId !== void 0) {
2680
+ await releaseThreadRun(store, input.threadId, input.runId).catch(() => void 0);
2681
+ }
2682
+ }
2683
+ __name(settleDeadRun, "settleDeadRun");
2684
+
2556
2685
  // src/delegation.ts
2557
2686
  function normalizeDelegation(entry) {
2558
2687
  return typeof entry === "string" ? {
@@ -3017,9 +3146,46 @@ function agentFailureCode(error) {
3017
3146
  if (error instanceof StructuredOutputError) {
3018
3147
  return "structured_output_invalid";
3019
3148
  }
3020
- return "run_failed";
3149
+ if (isReplayIntegrityError(error)) {
3150
+ return "replay_diverged";
3151
+ }
3152
+ return isNoOutputError(error) ? "model_no_output" : "run_failed";
3021
3153
  }
3022
3154
  __name(agentFailureCode, "agentFailureCode");
3155
+ function isNoOutputError(error) {
3156
+ if (!(error instanceof Error)) {
3157
+ return false;
3158
+ }
3159
+ return error.name === "AI_NoOutputGeneratedError" || error.name === "NoOutputGeneratedError" || error.message.startsWith("No output generated");
3160
+ }
3161
+ __name(isNoOutputError, "isNoOutputError");
3162
+ var RUN_FAILED_MESSAGE = "The assistant could not finish this answer. Please try again.";
3163
+ var streamErrorDetails;
3164
+ function exposeStreamErrorDetails(expose) {
3165
+ streamErrorDetails = expose;
3166
+ }
3167
+ __name(exposeStreamErrorDetails, "exposeStreamErrorDetails");
3168
+ var WORDED_CODES = /* @__PURE__ */ new Set([
3169
+ "quota_exceeded",
3170
+ "output_rejected",
3171
+ "structured_output_invalid"
3172
+ ]);
3173
+ function streamFailure(error) {
3174
+ const detail = error instanceof Error ? error.message : String(error);
3175
+ const code = agentFailureCode(error);
3176
+ if (WORDED_CODES.has(code)) {
3177
+ return {
3178
+ code,
3179
+ message: detail
3180
+ };
3181
+ }
3182
+ const exposed = streamErrorDetails ?? process.env.NODE_ENV !== "production";
3183
+ return {
3184
+ code,
3185
+ message: exposed ? detail : RUN_FAILED_MESSAGE
3186
+ };
3187
+ }
3188
+ __name(streamFailure, "streamFailure");
3023
3189
  var MAX_DELEGATION_DEPTH = 5;
3024
3190
  var DEFAULT_MAX_AGENT_APPEARANCES = 1;
3025
3191
  function delegationRefusal(args) {
@@ -3211,9 +3377,9 @@ function splitHistory(policy, input, messages) {
3211
3377
  }
3212
3378
  __name(splitHistory, "splitHistory");
3213
3379
  async function loadSelectedHistory(deps, input, hooks) {
3214
- return hooks.step("load:thread", async () => {
3380
+ const recorded = await hooks.step("load:thread", async () => {
3215
3381
  const thread = await readThreadForTurn(deps, input.threadId);
3216
- const { keep, drop } = splitHistory(deps.historyPolicy, input, toModelMessages(thread.messages));
3382
+ const { keep, drop } = splitHistory(deps.historyPolicy, input, await settleHistory(deps, toModelMessages(thread.messages)));
3217
3383
  return {
3218
3384
  messages: keep,
3219
3385
  dropped: deps.historyPolicy?.summarize === void 0 ? [] : drop,
@@ -3221,8 +3387,21 @@ async function loadSelectedHistory(deps, input, hooks) {
3221
3387
  hasAssistantMessage: thread.hasAssistantMessage
3222
3388
  };
3223
3389
  });
3390
+ return {
3391
+ ...recorded,
3392
+ messages: settleDanglingToolCalls(recorded.messages)
3393
+ };
3224
3394
  }
3225
3395
  __name(loadSelectedHistory, "loadSelectedHistory");
3396
+ async function settleHistory(deps, messages) {
3397
+ const dangling = danglingToolCallIds(messages);
3398
+ if (dangling.length === 0) {
3399
+ return messages;
3400
+ }
3401
+ const outcomes = await Promise.resolve(deps.store.toolCallOutcomes?.(dangling) ?? []).catch(() => []);
3402
+ return settleDanglingToolCalls(messages, outcomes);
3403
+ }
3404
+ __name(settleHistory, "settleHistory");
3226
3405
  async function readThreadForTurn(deps, threadId) {
3227
3406
  const windowing = deps.store;
3228
3407
  if (typeof windowing.loadThreadForTurn === "function") {
@@ -3262,7 +3441,7 @@ __name(turnMessageLimit, "turnMessageLimit");
3262
3441
  async function loadWholeThread(deps, input, hooks) {
3263
3442
  const thread = await hooks.step("load:thread", () => deps.store.getThread(input.threadId));
3264
3443
  const stored = thread?.messages ?? [];
3265
- const { keep, drop } = splitHistory(deps.historyPolicy, input, toModelMessages(stored));
3444
+ const { keep, drop } = splitHistory(deps.historyPolicy, input, settleDanglingToolCalls(toModelMessages(stored)));
3266
3445
  return {
3267
3446
  messages: keep,
3268
3447
  dropped: drop,
@@ -3445,6 +3624,14 @@ async function awaitElicitation(hooks, request, ctx) {
3445
3624
  }, ctx));
3446
3625
  }
3447
3626
  __name(awaitElicitation, "awaitElicitation");
3627
+ function toolCallContext(ctx, toolCallId) {
3628
+ return {
3629
+ ...ctx,
3630
+ toolCallId,
3631
+ idempotencyKey: `${ctx.runId}:${toolCallId}`
3632
+ };
3633
+ }
3634
+ __name(toolCallContext, "toolCallContext");
3448
3635
  function toolContext(deps, input, hooks) {
3449
3636
  return {
3450
3637
  actor: input.actor,
@@ -3884,7 +4071,7 @@ async function invokeClaimedTool(turn, claimed) {
3884
4071
  }, () => invokeWithTransientRetry(() => {
3885
4072
  ui2.restart();
3886
4073
  return deps.registry.invoke(call.name, call.input, {
3887
- ...ctx,
4074
+ ...toolCallContext(ctx, call.id),
3888
4075
  emitUi: ui2.emit
3889
4076
  }, deps.rolesPolicy);
3890
4077
  }, deps.toolTransientRetry ?? {}, {
@@ -4607,6 +4794,7 @@ ${block}`;
4607
4794
  };
4608
4795
  }
4609
4796
  const resolvedModelId = turn.modelId ?? deps.modelId ?? "unknown";
4797
+ const stepModel = turn.modelId ?? deps.modelId;
4610
4798
  const costUsd = resolveCostUsd(turn.usage, turn.costUsd, priceByModel.get(resolvedModelId));
4611
4799
  const toolCallsWithKind = turn.toolCalls.map((call) => ({
4612
4800
  ...call,
@@ -4808,6 +4996,9 @@ ${block}`;
4808
4996
  costUsd,
4809
4997
  ...turn.reasoningMs !== void 0 ? {
4810
4998
  reasoningMs: turn.reasoningMs
4999
+ } : {},
5000
+ ...stepModel !== void 0 ? {
5001
+ model: stepModel
4811
5002
  } : {}
4812
5003
  }));
4813
5004
  });
@@ -4872,6 +5063,9 @@ ${block}`;
4872
5063
  costUsd,
4873
5064
  ...turn.reasoningMs !== void 0 ? {
4874
5065
  reasoningMs: turn.reasoningMs
5066
+ } : {},
5067
+ ...stepModel !== void 0 ? {
5068
+ model: stepModel
4875
5069
  } : {}
4876
5070
  }));
4877
5071
  });
@@ -4947,7 +5141,10 @@ var InMemoryAgentStore = class {
4947
5141
  return (/* @__PURE__ */ new Date()).toISOString();
4948
5142
  }
4949
5143
  async createThread(input) {
4950
- const id = crypto.randomUUID();
5144
+ const id = input.id ?? crypto.randomUUID();
5145
+ if (this.threads.has(id)) {
5146
+ throw new Error(`thread ${id} already exists`);
5147
+ }
4951
5148
  const ts = this.now();
4952
5149
  const row = {
4953
5150
  id,
@@ -5482,6 +5679,36 @@ var InMemoryAgentStore = class {
5482
5679
  } : {}
5483
5680
  });
5484
5681
  }
5682
+ async toolCallOutcomes(toolCallIds) {
5683
+ const outcomes = [];
5684
+ for (const id of toolCallIds) {
5685
+ const row = this.toolCalls.get(id);
5686
+ if (row !== void 0) {
5687
+ outcomes.push({
5688
+ id,
5689
+ status: row.status,
5690
+ ...row.output !== void 0 ? {
5691
+ output: row.output
5692
+ } : {},
5693
+ ...row.error !== void 0 ? {
5694
+ error: row.error
5695
+ } : {}
5696
+ });
5697
+ }
5698
+ }
5699
+ return outcomes;
5700
+ }
5701
+ async failUnsettledToolCalls(runId, error) {
5702
+ let settled = 0;
5703
+ for (const row of this.toolCalls.values()) {
5704
+ if (row.runId === runId && row.status === "pending_approval") {
5705
+ row.status = "failed";
5706
+ row.error = error;
5707
+ settled += 1;
5708
+ }
5709
+ }
5710
+ return settled;
5711
+ }
5485
5712
  async updateToolCall(input) {
5486
5713
  const row = this.toolCalls.get(input.toolCallId);
5487
5714
  if (row === void 0) {
@@ -5809,6 +6036,11 @@ var InMemoryAgentStore = class {
5809
6036
  REMEMBER_TOOL_DESCRIPTION,
5810
6037
  REMEMBER_TOOL_NAME,
5811
6038
  REQUESTER_APPROVER,
6039
+ RUN_ENDED_BEFORE_TOOL_CALL,
6040
+ RUN_FAILED_MESSAGE,
6041
+ RUN_NOT_ACTIVE_CODE,
6042
+ RUN_NOT_ACTIVE_MESSAGE,
6043
+ RUN_NO_LONGER_RUNNING,
5812
6044
  RunCancelledError,
5813
6045
  SKILL_TOOL_DESCRIPTION,
5814
6046
  SKILL_TOOL_NAME,
@@ -5819,6 +6051,7 @@ var InMemoryAgentStore = class {
5819
6051
  ToolInputInvalidError,
5820
6052
  ToolNotFoundError,
5821
6053
  ToolRegistry,
6054
+ UNFINISHED_TOOL_CALL,
5822
6055
  actorScope,
5823
6056
  agentDiagnosticKey,
5824
6057
  agentFailureCode,
@@ -5836,6 +6069,7 @@ var InMemoryAgentStore = class {
5836
6069
  createIncrementalGate,
5837
6070
  createNoopEmitUi,
5838
6071
  createUiCollector,
6072
+ danglingToolCallIds,
5839
6073
  dayBoundsUtc,
5840
6074
  decodeStreamEvent,
5841
6075
  defaultCanDecide,
@@ -5847,6 +6081,7 @@ var InMemoryAgentStore = class {
5847
6081
  estimateCost,
5848
6082
  estimateMessageTokens,
5849
6083
  exhaustedWindow,
6084
+ exposeStreamErrorDetails,
5850
6085
  extractJson,
5851
6086
  filterToolsByAllowList,
5852
6087
  filterToolsByCanUse,
@@ -5909,6 +6144,8 @@ var InMemoryAgentStore = class {
5909
6144
  runOutputProcessors,
5910
6145
  seedModelPrices,
5911
6146
  settleAll,
6147
+ settleDanglingToolCalls,
6148
+ settleDeadRun,
5912
6149
  settleElicitation,
5913
6150
  settleUnsettledDelegation,
5914
6151
  skillInputSchema,
@@ -5917,9 +6154,11 @@ var InMemoryAgentStore = class {
5917
6154
  stampToolKinds,
5918
6155
  staticModelCatalog,
5919
6156
  staticSkillProvider,
6157
+ streamFailure,
5920
6158
  summarizeWithModel,
5921
6159
  tenantScope,
5922
6160
  toolCallApprovalFromRow,
6161
+ toolCallContext,
5923
6162
  traceLlmTurn,
5924
6163
  traceToolExecution,
5925
6164
  truncateDetailContent,