@dudousxd/nestjs-agent-core 0.45.0 → 0.45.2

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
@@ -256,6 +256,8 @@ __export(src_exports, {
256
256
  toActionProposalView: () => toActionProposalView,
257
257
  toolCallApprovalFromRow: () => toolCallApprovalFromRow,
258
258
  toolCallContext: () => toolCallContext,
259
+ toolCallUpdateForProposal: () => toolCallUpdateForProposal,
260
+ toolCallUpdateForTransition: () => toolCallUpdateForTransition,
259
261
  traceLlmTurn: () => traceLlmTurn,
260
262
  traceToolExecution: () => traceToolExecution,
261
263
  transitionActionProposalClaim: () => transitionActionProposalClaim,
@@ -5964,7 +5966,7 @@ function traceToolExecution(runId, call, run) {
5964
5966
  }
5965
5967
  __name(traceToolExecution, "traceToolExecution");
5966
5968
  function toModelMessages(messages) {
5967
- return messages.map((message) => ({
5969
+ return withoutBlankAssistantMessages(messages.map((message) => ({
5968
5970
  role: message.role,
5969
5971
  content: message.content,
5970
5972
  ...message.toolCalls !== void 0 ? {
@@ -5976,9 +5978,17 @@ function toModelMessages(messages) {
5976
5978
  ...message.attachments !== void 0 ? {
5977
5979
  attachments: message.attachments
5978
5980
  } : {}
5979
- }));
5981
+ })));
5980
5982
  }
5981
5983
  __name(toModelMessages, "toModelMessages");
5984
+ function isBlankAssistantMessage(message) {
5985
+ return message.role === "assistant" && message.content.trim().length === 0 && (message.toolCalls?.length ?? 0) === 0 && (message.toolResults?.length ?? 0) === 0;
5986
+ }
5987
+ __name(isBlankAssistantMessage, "isBlankAssistantMessage");
5988
+ function withoutBlankAssistantMessages(messages) {
5989
+ return messages.some(isBlankAssistantMessage) ? messages.filter((message) => !isBlankAssistantMessage(message)) : messages;
5990
+ }
5991
+ __name(withoutBlankAssistantMessages, "withoutBlankAssistantMessages");
5982
5992
  function historyContext(input) {
5983
5993
  return {
5984
5994
  threadId: input.threadId,
@@ -6010,7 +6020,7 @@ async function loadSelectedHistory(deps, input, hooks) {
6010
6020
  });
6011
6021
  return {
6012
6022
  ...recorded,
6013
- messages: settleDanglingToolCalls(recorded.messages)
6023
+ messages: settleDanglingToolCalls(withoutBlankAssistantMessages(recorded.messages))
6014
6024
  };
6015
6025
  }
6016
6026
  __name(loadSelectedHistory, "loadSelectedHistory");
@@ -7778,13 +7788,18 @@ ${block}`;
7778
7788
  runId: hooks.runId,
7779
7789
  step: i,
7780
7790
  count
7781
- }, () => generateFollowUps(deps.model, [
7782
- ...modelMessages,
7783
- {
7784
- role: "assistant",
7785
- content: turn.text
7786
- }
7787
- ], count), (result2) => ({
7791
+ }, () => generateFollowUps(
7792
+ deps.model,
7793
+ // A blank answer is left off: an empty assistant message gets the call refused.
7794
+ turn.text.trim().length > 0 ? [
7795
+ ...modelMessages,
7796
+ {
7797
+ role: "assistant",
7798
+ content: turn.text
7799
+ }
7800
+ ] : modelMessages,
7801
+ count
7802
+ ), (result2) => ({
7788
7803
  followUps: result2.followUps.length,
7789
7804
  inputTokens: result2.usage.inputTokens,
7790
7805
  outputTokens: result2.usage.outputTokens,
@@ -7852,7 +7867,8 @@ ${block}`;
7852
7867
  ...synthetic.map((entry) => entry.call)
7853
7868
  ];
7854
7869
  const syntheticResults = synthetic.map((entry) => entry.result);
7855
- const assistant = await hooks.step(`persist:assistant:${i}`, () => deps.store.appendMessage({
7870
+ const blankStep = turn.text.trim().length === 0 && messageCalls.length === 0 && syntheticResults.length === 0 && (turn.ui === void 0 || turn.ui.length === 0) && turn.reasoning === void 0 && followUps === void 0;
7871
+ const assistant = await hooks.step(`persist:assistant:${i}`, () => blankStep ? Promise.resolve(null) : deps.store.appendMessage({
7856
7872
  threadId: input.threadId,
7857
7873
  role: "assistant",
7858
7874
  content: turn.text,
@@ -7898,6 +7914,23 @@ ${block}`;
7898
7914
  toolResults: syntheticResults
7899
7915
  } : {}
7900
7916
  };
7917
+ const finishFinalStep = /* @__PURE__ */ __name(() => hooks.step(`stream:step-finish:${i}`, async () => {
7918
+ await writer.write(encodeStreamEvent({
7919
+ kind: "step-finish",
7920
+ usage: turn.usage,
7921
+ costUsd,
7922
+ ...turn.reasoningMs !== void 0 ? {
7923
+ reasoningMs: turn.reasoningMs
7924
+ } : {},
7925
+ ...stepModel !== void 0 ? {
7926
+ model: stepModel
7927
+ } : {}
7928
+ }));
7929
+ }), "finishFinalStep");
7930
+ if (assistant === null) {
7931
+ await finishFinalStep();
7932
+ break;
7933
+ }
7901
7934
  modelMessages.push(assistantMessage);
7902
7935
  for (const entry of synthetic) {
7903
7936
  const { call, result: result2 } = entry;
@@ -7931,19 +7964,7 @@ ${block}`;
7931
7964
  });
7932
7965
  }
7933
7966
  if (isFinalTurn) {
7934
- await hooks.step(`stream:step-finish:${i}`, async () => {
7935
- await writer.write(encodeStreamEvent({
7936
- kind: "step-finish",
7937
- usage: turn.usage,
7938
- costUsd,
7939
- ...turn.reasoningMs !== void 0 ? {
7940
- reasoningMs: turn.reasoningMs
7941
- } : {},
7942
- ...stepModel !== void 0 ? {
7943
- model: stepModel
7944
- } : {}
7945
- }));
7946
- });
7967
+ await finishFinalStep();
7947
7968
  break;
7948
7969
  }
7949
7970
  await haltIfCancelled(hooks, cancellable, `cancel:tools:${i}`);
@@ -8054,13 +8075,15 @@ ${toolText}`;
8054
8075
  return;
8055
8076
  }
8056
8077
  const agent = input.agentName ?? "default";
8057
- await deps.store.appendMessage({
8058
- threadId: delivery.threadId,
8059
- role: "assistant",
8060
- content: lastText,
8061
- agentName: agent,
8062
- runId: hooks.runId
8063
- });
8078
+ if (lastText.trim().length > 0) {
8079
+ await deps.store.appendMessage({
8080
+ threadId: delivery.threadId,
8081
+ role: "assistant",
8082
+ content: lastText,
8083
+ agentName: agent,
8084
+ runId: hooks.runId
8085
+ });
8086
+ }
8064
8087
  await deps.store.updateToolCall({
8065
8088
  toolCallId: delivery.toolCallId,
8066
8089
  status: "executed",
@@ -8096,6 +8119,98 @@ ${toolText}`;
8096
8119
  }
8097
8120
  __name(runAgentLoop, "runAgentLoop");
8098
8121
 
8122
+ // src/action-proposal-tool-call.ts
8123
+ function toolCallUpdateForProposal(proposal) {
8124
+ const audit = proposal.decisionAudit;
8125
+ const toolCallId = proposal.originToolCallId;
8126
+ const decidedBy = {
8127
+ ...audit?.actorRef !== void 0 ? {
8128
+ executedByRef: audit.actorRef
8129
+ } : {},
8130
+ ...audit?.via !== void 0 ? {
8131
+ decidedVia: audit.via
8132
+ } : {}
8133
+ };
8134
+ switch (proposal.decision) {
8135
+ case "rejected":
8136
+ return {
8137
+ toolCallId,
8138
+ status: "rejected",
8139
+ ...decidedBy,
8140
+ ...audit?.reason !== void 0 ? {
8141
+ error: storableText2(audit.reason)
8142
+ } : {}
8143
+ };
8144
+ case "expired":
8145
+ return {
8146
+ toolCallId,
8147
+ status: "expired",
8148
+ ...audit?.via !== void 0 ? {
8149
+ decidedVia: audit.via
8150
+ } : {}
8151
+ };
8152
+ case "superseded":
8153
+ return {
8154
+ toolCallId,
8155
+ status: "expired",
8156
+ decidedVia: audit?.via ?? "supersession",
8157
+ error: proposal.supersededBy !== void 0 ? `superseded by proposal ${proposal.supersededBy}` : "superseded by a newer proposal"
8158
+ };
8159
+ case "approved": {
8160
+ const execution = proposal.execution;
8161
+ const remember = audit?.remember !== void 0 ? {
8162
+ remember: audit.remember
8163
+ } : {};
8164
+ if (execution?.status === "succeeded") {
8165
+ return {
8166
+ toolCallId,
8167
+ status: "executed",
8168
+ ...storableOutput(execution.result) ? {
8169
+ output: execution.result
8170
+ } : {},
8171
+ ...decidedBy,
8172
+ ...remember
8173
+ };
8174
+ }
8175
+ if (execution?.status === "failed") {
8176
+ return {
8177
+ toolCallId,
8178
+ status: "failed",
8179
+ error: storableText2(execution.error ?? "the approved action failed"),
8180
+ ...decidedBy,
8181
+ ...remember
8182
+ };
8183
+ }
8184
+ return null;
8185
+ }
8186
+ default:
8187
+ return null;
8188
+ }
8189
+ }
8190
+ __name(toolCallUpdateForProposal, "toolCallUpdateForProposal");
8191
+ function toolCallUpdateForTransition(previous, next) {
8192
+ const update = toolCallUpdateForProposal(next);
8193
+ if (update === null) return null;
8194
+ const before = previous === void 0 ? null : toolCallUpdateForProposal(previous);
8195
+ return before?.status === update.status ? null : update;
8196
+ }
8197
+ __name(toolCallUpdateForTransition, "toolCallUpdateForTransition");
8198
+ function storableOutput(result2) {
8199
+ if (result2 === void 0) return false;
8200
+ let json;
8201
+ try {
8202
+ json = JSON.stringify(result2);
8203
+ } catch {
8204
+ return false;
8205
+ }
8206
+ return json !== void 0 && !/\\u0000|\\ud[89ab][0-9a-f]{2}(?!\\ud[c-f])|(?<!\\ud[89ab][0-9a-f]{2})\\ud[c-f][0-9a-f]{2}/i.test(json);
8207
+ }
8208
+ __name(storableOutput, "storableOutput");
8209
+ function storableText2(text) {
8210
+ return text.replaceAll("\0", "");
8211
+ }
8212
+ __name(storableText2, "storableText");
8213
+
8099
8214
  // src/in-memory-action-proposal-store.ts
8100
8215
  var import_node_crypto2 = require("crypto");
8101
8216
  var InMemoryActionProposalStore = class {
@@ -8142,9 +8257,19 @@ var InMemoryActionProposalStore = class {
8142
8257
  status: "not_found"
8143
8258
  };
8144
8259
  const result2 = transition(row2, this.clock());
8145
- if (result2.proposal) this.rows.set(id, snapshotActionProposal(result2.proposal));
8260
+ if (result2.proposal) {
8261
+ this.rows.set(id, snapshotActionProposal(result2.proposal));
8262
+ this.proposalChanged(row2, result2.proposal);
8263
+ }
8146
8264
  return result2;
8147
8265
  }
8266
+ /**
8267
+ * Called after every proposal transition, with the row before and after — synchronously, so no
8268
+ * reader sees one without the other. {@link InMemoryAgentStore} settles the proposal's tool-call
8269
+ * record here (see `toolCallUpdateForTransition`).
8270
+ */
8271
+ proposalChanged(_previous, _next) {
8272
+ }
8148
8273
  async decideActionProposal(scope, id, command) {
8149
8274
  return this.mutate(scope, id, (row2, now) => transitionActionProposalDecision(row2, command, now));
8150
8275
  }
@@ -8225,7 +8350,11 @@ var InMemoryActionProposalStore = class {
8225
8350
  if (result2.proposal) replacements.push(result2.proposal);
8226
8351
  }
8227
8352
  this.rows.set(proposal.id, proposal);
8228
- for (const row2 of replacements) this.rows.set(row2.id, row2);
8353
+ for (const row2 of replacements) {
8354
+ const previous = this.rows.get(row2.id);
8355
+ this.rows.set(row2.id, row2);
8356
+ if (previous) this.proposalChanged(previous, row2);
8357
+ }
8229
8358
  return {
8230
8359
  status: "created",
8231
8360
  proposal: snapshotActionProposal(proposal)
@@ -8946,6 +9075,15 @@ var InMemoryAgentStore = class extends InMemoryActionProposalStore {
8946
9075
  return settled;
8947
9076
  }
8948
9077
  async updateToolCall(input) {
9078
+ this.applyToolCallUpdate(input);
9079
+ }
9080
+ /** An independent proposal moved: its `proposed` tool-call record follows it. */
9081
+ proposalChanged(previous, next) {
9082
+ const update = toolCallUpdateForTransition(previous, next);
9083
+ if (update === null || this.toolCalls.get(update.toolCallId)?.proposalId !== next.id) return;
9084
+ this.applyToolCallUpdate(update);
9085
+ }
9086
+ applyToolCallUpdate(input) {
8949
9087
  const row2 = this.toolCalls.get(input.toolCallId);
8950
9088
  if (row2 === void 0) {
8951
9089
  return;
@@ -10488,6 +10626,8 @@ __name(createFunctionalTool, "createFunctionalTool");
10488
10626
  toActionProposalView,
10489
10627
  toolCallApprovalFromRow,
10490
10628
  toolCallContext,
10629
+ toolCallUpdateForProposal,
10630
+ toolCallUpdateForTransition,
10491
10631
  traceLlmTurn,
10492
10632
  traceToolExecution,
10493
10633
  transitionActionProposalClaim,