@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.js CHANGED
@@ -2348,6 +2348,123 @@ function isControlFlowSignal(error) {
2348
2348
  }
2349
2349
  __name(isControlFlowSignal, "isControlFlowSignal");
2350
2350
 
2351
+ // src/dangling-tool-calls.ts
2352
+ 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.";
2353
+ var RUN_ENDED_BEFORE_TOOL_CALL = "the run ended before this tool call was settled";
2354
+ var DECLINED = "The person was asked to approve this action and declined it. Nothing ran and nothing changed.";
2355
+ var EXPIRED = "This action needed approval, and the request expired before anyone decided. Nothing ran.";
2356
+ function danglingToolCallIds(messages) {
2357
+ const ids = [];
2358
+ for (const message of messages) {
2359
+ const calls = message.toolCalls ?? [];
2360
+ if (message.role !== "assistant" || calls.length === 0) {
2361
+ continue;
2362
+ }
2363
+ const settled = new Set((message.toolResults ?? []).map((result) => result.id));
2364
+ for (const call of calls) {
2365
+ if (!settled.has(call.id)) {
2366
+ ids.push(call.id);
2367
+ }
2368
+ }
2369
+ }
2370
+ return ids;
2371
+ }
2372
+ __name(danglingToolCallIds, "danglingToolCallIds");
2373
+ function resultFor(call, outcome) {
2374
+ const base = {
2375
+ id: call.id,
2376
+ name: call.name
2377
+ };
2378
+ switch (outcome?.status) {
2379
+ case "executed":
2380
+ case "auto_executed":
2381
+ return {
2382
+ ...base,
2383
+ output: outcome.output ?? null
2384
+ };
2385
+ case "failed":
2386
+ return {
2387
+ ...base,
2388
+ output: null,
2389
+ error: outcome.error ?? UNFINISHED_TOOL_CALL
2390
+ };
2391
+ case "rejected":
2392
+ return {
2393
+ ...base,
2394
+ output: {
2395
+ rejected: true
2396
+ },
2397
+ denied: true,
2398
+ error: DECLINED
2399
+ };
2400
+ case "expired":
2401
+ return {
2402
+ ...base,
2403
+ output: {
2404
+ rejected: true,
2405
+ expired: true
2406
+ },
2407
+ denied: true,
2408
+ expired: true,
2409
+ error: EXPIRED
2410
+ };
2411
+ default:
2412
+ return {
2413
+ ...base,
2414
+ output: null,
2415
+ error: UNFINISHED_TOOL_CALL
2416
+ };
2417
+ }
2418
+ }
2419
+ __name(resultFor, "resultFor");
2420
+ function settleDanglingToolCalls(messages, outcomes = []) {
2421
+ const known = new Map(outcomes.map((outcome) => [
2422
+ outcome.id,
2423
+ outcome
2424
+ ]));
2425
+ return messages.map((message) => {
2426
+ const calls = message.toolCalls ?? [];
2427
+ if (message.role !== "assistant" || calls.length === 0) {
2428
+ return message;
2429
+ }
2430
+ const results = message.toolResults ?? [];
2431
+ const settled = new Set(results.map((result) => result.id));
2432
+ const missing = calls.filter((call) => !settled.has(call.id));
2433
+ if (missing.length === 0) {
2434
+ return message;
2435
+ }
2436
+ return {
2437
+ ...message,
2438
+ toolResults: [
2439
+ ...results,
2440
+ ...missing.map((call) => resultFor(call, known.get(call.id)))
2441
+ ]
2442
+ };
2443
+ });
2444
+ }
2445
+ __name(settleDanglingToolCalls, "settleDanglingToolCalls");
2446
+
2447
+ // src/dead-run.ts
2448
+ var RUN_NOT_ACTIVE_CODE = "run_not_active";
2449
+ 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.";
2450
+ var RUN_NO_LONGER_RUNNING = "the run is no longer running";
2451
+ async function settleDeadRun(store, input) {
2452
+ const failure = input.failure;
2453
+ if (failure !== void 0) {
2454
+ await Promise.resolve(store.recordRunEnd?.({
2455
+ runId: input.runId,
2456
+ status: "failed",
2457
+ errorCode: failure.code,
2458
+ errorMessage: failure.message
2459
+ })).catch(() => void 0);
2460
+ }
2461
+ await Promise.resolve(store.failUnsettledToolCalls?.(input.runId, RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
2462
+ if (input.threadId !== void 0) {
2463
+ await releaseThreadRun(store, input.threadId, input.runId).catch(() => void 0);
2464
+ }
2465
+ }
2466
+ __name(settleDeadRun, "settleDeadRun");
2467
+
2351
2468
  // src/delegation.ts
2352
2469
  function normalizeDelegation(entry) {
2353
2470
  return typeof entry === "string" ? {
@@ -2812,9 +2929,46 @@ function agentFailureCode(error) {
2812
2929
  if (error instanceof StructuredOutputError) {
2813
2930
  return "structured_output_invalid";
2814
2931
  }
2815
- return "run_failed";
2932
+ if (isReplayIntegrityError(error)) {
2933
+ return "replay_diverged";
2934
+ }
2935
+ return isNoOutputError(error) ? "model_no_output" : "run_failed";
2816
2936
  }
2817
2937
  __name(agentFailureCode, "agentFailureCode");
2938
+ function isNoOutputError(error) {
2939
+ if (!(error instanceof Error)) {
2940
+ return false;
2941
+ }
2942
+ return error.name === "AI_NoOutputGeneratedError" || error.name === "NoOutputGeneratedError" || error.message.startsWith("No output generated");
2943
+ }
2944
+ __name(isNoOutputError, "isNoOutputError");
2945
+ var RUN_FAILED_MESSAGE = "The assistant could not finish this answer. Please try again.";
2946
+ var streamErrorDetails;
2947
+ function exposeStreamErrorDetails(expose) {
2948
+ streamErrorDetails = expose;
2949
+ }
2950
+ __name(exposeStreamErrorDetails, "exposeStreamErrorDetails");
2951
+ var WORDED_CODES = /* @__PURE__ */ new Set([
2952
+ "quota_exceeded",
2953
+ "output_rejected",
2954
+ "structured_output_invalid"
2955
+ ]);
2956
+ function streamFailure(error) {
2957
+ const detail = error instanceof Error ? error.message : String(error);
2958
+ const code = agentFailureCode(error);
2959
+ if (WORDED_CODES.has(code)) {
2960
+ return {
2961
+ code,
2962
+ message: detail
2963
+ };
2964
+ }
2965
+ const exposed = streamErrorDetails ?? process.env.NODE_ENV !== "production";
2966
+ return {
2967
+ code,
2968
+ message: exposed ? detail : RUN_FAILED_MESSAGE
2969
+ };
2970
+ }
2971
+ __name(streamFailure, "streamFailure");
2818
2972
  var MAX_DELEGATION_DEPTH = 5;
2819
2973
  var DEFAULT_MAX_AGENT_APPEARANCES = 1;
2820
2974
  function delegationRefusal(args) {
@@ -3006,9 +3160,9 @@ function splitHistory(policy, input, messages) {
3006
3160
  }
3007
3161
  __name(splitHistory, "splitHistory");
3008
3162
  async function loadSelectedHistory(deps, input, hooks) {
3009
- return hooks.step("load:thread", async () => {
3163
+ const recorded = await hooks.step("load:thread", async () => {
3010
3164
  const thread = await readThreadForTurn(deps, input.threadId);
3011
- const { keep, drop } = splitHistory(deps.historyPolicy, input, toModelMessages(thread.messages));
3165
+ const { keep, drop } = splitHistory(deps.historyPolicy, input, await settleHistory(deps, toModelMessages(thread.messages)));
3012
3166
  return {
3013
3167
  messages: keep,
3014
3168
  dropped: deps.historyPolicy?.summarize === void 0 ? [] : drop,
@@ -3016,8 +3170,21 @@ async function loadSelectedHistory(deps, input, hooks) {
3016
3170
  hasAssistantMessage: thread.hasAssistantMessage
3017
3171
  };
3018
3172
  });
3173
+ return {
3174
+ ...recorded,
3175
+ messages: settleDanglingToolCalls(recorded.messages)
3176
+ };
3019
3177
  }
3020
3178
  __name(loadSelectedHistory, "loadSelectedHistory");
3179
+ async function settleHistory(deps, messages) {
3180
+ const dangling = danglingToolCallIds(messages);
3181
+ if (dangling.length === 0) {
3182
+ return messages;
3183
+ }
3184
+ const outcomes = await Promise.resolve(deps.store.toolCallOutcomes?.(dangling) ?? []).catch(() => []);
3185
+ return settleDanglingToolCalls(messages, outcomes);
3186
+ }
3187
+ __name(settleHistory, "settleHistory");
3021
3188
  async function readThreadForTurn(deps, threadId) {
3022
3189
  const windowing = deps.store;
3023
3190
  if (typeof windowing.loadThreadForTurn === "function") {
@@ -3057,7 +3224,7 @@ __name(turnMessageLimit, "turnMessageLimit");
3057
3224
  async function loadWholeThread(deps, input, hooks) {
3058
3225
  const thread = await hooks.step("load:thread", () => deps.store.getThread(input.threadId));
3059
3226
  const stored = thread?.messages ?? [];
3060
- const { keep, drop } = splitHistory(deps.historyPolicy, input, toModelMessages(stored));
3227
+ const { keep, drop } = splitHistory(deps.historyPolicy, input, settleDanglingToolCalls(toModelMessages(stored)));
3061
3228
  return {
3062
3229
  messages: keep,
3063
3230
  dropped: drop,
@@ -3240,6 +3407,14 @@ async function awaitElicitation(hooks, request, ctx) {
3240
3407
  }, ctx));
3241
3408
  }
3242
3409
  __name(awaitElicitation, "awaitElicitation");
3410
+ function toolCallContext(ctx, toolCallId) {
3411
+ return {
3412
+ ...ctx,
3413
+ toolCallId,
3414
+ idempotencyKey: `${ctx.runId}:${toolCallId}`
3415
+ };
3416
+ }
3417
+ __name(toolCallContext, "toolCallContext");
3243
3418
  function toolContext(deps, input, hooks) {
3244
3419
  return {
3245
3420
  actor: input.actor,
@@ -3679,7 +3854,7 @@ async function invokeClaimedTool(turn, claimed) {
3679
3854
  }, () => invokeWithTransientRetry(() => {
3680
3855
  ui2.restart();
3681
3856
  return deps.registry.invoke(call.name, call.input, {
3682
- ...ctx,
3857
+ ...toolCallContext(ctx, call.id),
3683
3858
  emitUi: ui2.emit
3684
3859
  }, deps.rolesPolicy);
3685
3860
  }, deps.toolTransientRetry ?? {}, {
@@ -4402,6 +4577,7 @@ ${block}`;
4402
4577
  };
4403
4578
  }
4404
4579
  const resolvedModelId = turn.modelId ?? deps.modelId ?? "unknown";
4580
+ const stepModel = turn.modelId ?? deps.modelId;
4405
4581
  const costUsd = resolveCostUsd(turn.usage, turn.costUsd, priceByModel.get(resolvedModelId));
4406
4582
  const toolCallsWithKind = turn.toolCalls.map((call) => ({
4407
4583
  ...call,
@@ -4603,6 +4779,9 @@ ${block}`;
4603
4779
  costUsd,
4604
4780
  ...turn.reasoningMs !== void 0 ? {
4605
4781
  reasoningMs: turn.reasoningMs
4782
+ } : {},
4783
+ ...stepModel !== void 0 ? {
4784
+ model: stepModel
4606
4785
  } : {}
4607
4786
  }));
4608
4787
  });
@@ -4667,6 +4846,9 @@ ${block}`;
4667
4846
  costUsd,
4668
4847
  ...turn.reasoningMs !== void 0 ? {
4669
4848
  reasoningMs: turn.reasoningMs
4849
+ } : {},
4850
+ ...stepModel !== void 0 ? {
4851
+ model: stepModel
4670
4852
  } : {}
4671
4853
  }));
4672
4854
  });
@@ -4742,7 +4924,10 @@ var InMemoryAgentStore = class {
4742
4924
  return (/* @__PURE__ */ new Date()).toISOString();
4743
4925
  }
4744
4926
  async createThread(input) {
4745
- const id = crypto.randomUUID();
4927
+ const id = input.id ?? crypto.randomUUID();
4928
+ if (this.threads.has(id)) {
4929
+ throw new Error(`thread ${id} already exists`);
4930
+ }
4746
4931
  const ts = this.now();
4747
4932
  const row = {
4748
4933
  id,
@@ -5277,6 +5462,36 @@ var InMemoryAgentStore = class {
5277
5462
  } : {}
5278
5463
  });
5279
5464
  }
5465
+ async toolCallOutcomes(toolCallIds) {
5466
+ const outcomes = [];
5467
+ for (const id of toolCallIds) {
5468
+ const row = this.toolCalls.get(id);
5469
+ if (row !== void 0) {
5470
+ outcomes.push({
5471
+ id,
5472
+ status: row.status,
5473
+ ...row.output !== void 0 ? {
5474
+ output: row.output
5475
+ } : {},
5476
+ ...row.error !== void 0 ? {
5477
+ error: row.error
5478
+ } : {}
5479
+ });
5480
+ }
5481
+ }
5482
+ return outcomes;
5483
+ }
5484
+ async failUnsettledToolCalls(runId, error) {
5485
+ let settled = 0;
5486
+ for (const row of this.toolCalls.values()) {
5487
+ if (row.runId === runId && row.status === "pending_approval") {
5488
+ row.status = "failed";
5489
+ row.error = error;
5490
+ settled += 1;
5491
+ }
5492
+ }
5493
+ return settled;
5494
+ }
5280
5495
  async updateToolCall(input) {
5281
5496
  const row = this.toolCalls.get(input.toolCallId);
5282
5497
  if (row === void 0) {
@@ -5603,6 +5818,11 @@ export {
5603
5818
  REMEMBER_TOOL_DESCRIPTION,
5604
5819
  REMEMBER_TOOL_NAME,
5605
5820
  REQUESTER_APPROVER,
5821
+ RUN_ENDED_BEFORE_TOOL_CALL,
5822
+ RUN_FAILED_MESSAGE,
5823
+ RUN_NOT_ACTIVE_CODE,
5824
+ RUN_NOT_ACTIVE_MESSAGE,
5825
+ RUN_NO_LONGER_RUNNING,
5606
5826
  RunCancelledError,
5607
5827
  SKILL_TOOL_DESCRIPTION,
5608
5828
  SKILL_TOOL_NAME,
@@ -5613,6 +5833,7 @@ export {
5613
5833
  ToolInputInvalidError,
5614
5834
  ToolNotFoundError,
5615
5835
  ToolRegistry,
5836
+ UNFINISHED_TOOL_CALL,
5616
5837
  actorScope,
5617
5838
  agentDiagnosticKey,
5618
5839
  agentFailureCode,
@@ -5630,6 +5851,7 @@ export {
5630
5851
  createIncrementalGate,
5631
5852
  createNoopEmitUi,
5632
5853
  createUiCollector,
5854
+ danglingToolCallIds,
5633
5855
  dayBoundsUtc,
5634
5856
  decodeStreamEvent,
5635
5857
  defaultCanDecide,
@@ -5641,6 +5863,7 @@ export {
5641
5863
  estimateCost,
5642
5864
  estimateMessageTokens,
5643
5865
  exhaustedWindow,
5866
+ exposeStreamErrorDetails,
5644
5867
  extractJson,
5645
5868
  filterToolsByAllowList,
5646
5869
  filterToolsByCanUse,
@@ -5703,6 +5926,8 @@ export {
5703
5926
  runOutputProcessors,
5704
5927
  seedModelPrices,
5705
5928
  settleAll,
5929
+ settleDanglingToolCalls,
5930
+ settleDeadRun,
5706
5931
  settleElicitation,
5707
5932
  settleUnsettledDelegation,
5708
5933
  skillInputSchema,
@@ -5711,9 +5936,11 @@ export {
5711
5936
  stampToolKinds,
5712
5937
  staticModelCatalog,
5713
5938
  staticSkillProvider,
5939
+ streamFailure,
5714
5940
  summarizeWithModel,
5715
5941
  tenantScope,
5716
5942
  toolCallApprovalFromRow,
5943
+ toolCallContext,
5717
5944
  traceLlmTurn,
5718
5945
  traceToolExecution,
5719
5946
  truncateDetailContent,