@tangle-network/agent-app 0.43.53 → 0.43.55

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.
@@ -11,7 +11,7 @@ import {
11
11
  createSandboxFileIndexRoute,
12
12
  sanitizeAttachmentFileName,
13
13
  sniffBinary
14
- } from "../chunk-3EKOSBYL.js";
14
+ } from "../chunk-K63OWUDJ.js";
15
15
  import {
16
16
  DEFAULT_STALE_TURN_LOCK_GRACE_MS,
17
17
  DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
@@ -54,21 +54,25 @@ import {
54
54
  promptPartsByteSize,
55
55
  toChatMessageParts,
56
56
  validateSandboxMentionPath
57
- } from "../chunk-JGYOYY5D.js";
57
+ } from "../chunk-XKBIYSSM.js";
58
58
  import {
59
59
  asRecord,
60
60
  asString,
61
61
  finalizeAssistantParts,
62
+ finalizePendingInteractionParts,
62
63
  getPartKey,
63
64
  mergePersistedPart,
64
65
  normalizePersistedPart,
65
- normalizeToolEvent
66
+ normalizeToolEvent,
67
+ terminalizeDanglingAssistantToolUpdates
66
68
  } from "../chunk-5MG74GVQ.js";
67
69
  import {
68
70
  cancelStatusFor,
69
71
  interactionPartKey,
70
72
  interactionToPersistedPart,
71
73
  isRenderableInteractionKind,
74
+ noticePart,
75
+ noticePartKey,
72
76
  parseInteractionCancel,
73
77
  parseInteractionRequest
74
78
  } from "../chunk-XAWFPMAR.js";
@@ -573,6 +577,72 @@ function usageFromStepFinish(part, usage) {
573
577
  const cost = Number(part.cost);
574
578
  if (Number.isFinite(cost)) usage.costUsd = (usage.costUsd ?? 0) + cost;
575
579
  }
580
+ function toFiniteNumber(value) {
581
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
582
+ if (typeof value === "string" && value.trim() !== "") {
583
+ const parsed = Number(value);
584
+ return Number.isFinite(parsed) ? parsed : null;
585
+ }
586
+ return null;
587
+ }
588
+ function extractReportedTurnUsage(data) {
589
+ const tokenUsage = asRecord(data?.tokenUsage);
590
+ if (!tokenUsage) return null;
591
+ const inputTokens = toFiniteNumber(tokenUsage.inputTokens);
592
+ const outputTokens = toFiniteNumber(tokenUsage.outputTokens);
593
+ if (inputTokens === null || outputTokens === null) return null;
594
+ const reported = { inputTokens, outputTokens };
595
+ const reasoningTokens = toFiniteNumber(tokenUsage.reasoningTokens);
596
+ if (reasoningTokens !== null) reported.reasoningTokens = reasoningTokens;
597
+ const cacheReadTokens = toFiniteNumber(tokenUsage.cacheReadInputTokens);
598
+ if (cacheReadTokens !== null) reported.cacheReadTokens = cacheReadTokens;
599
+ const cacheWriteTokens = toFiniteNumber(tokenUsage.cacheCreationInputTokens);
600
+ if (cacheWriteTokens !== null) reported.cacheWriteTokens = cacheWriteTokens;
601
+ const costUsd = toFiniteNumber(data?.totalCostUsd) ?? toFiniteNumber(tokenUsage.cost);
602
+ if (costUsd !== null) reported.costUsd = costUsd;
603
+ return reported;
604
+ }
605
+ function applyTerminalUsage(reported, usage) {
606
+ usage.inputTokens = reported.inputTokens;
607
+ usage.outputTokens = reported.outputTokens;
608
+ if (reported.reasoningTokens !== void 0) usage.reasoningTokens = reported.reasoningTokens;
609
+ if (reported.cacheReadTokens !== void 0) usage.cacheReadTokens = reported.cacheReadTokens;
610
+ if (reported.cacheWriteTokens !== void 0) usage.cacheWriteTokens = reported.cacheWriteTokens;
611
+ if (reported.costUsd !== void 0) usage.costUsd = reported.costUsd;
612
+ }
613
+ function sandboxStreamErrorMessage(data) {
614
+ const record = asRecord(data);
615
+ const message = asString(record?.message) ?? asString(record?.error);
616
+ if (message) return message;
617
+ try {
618
+ return JSON.stringify(data) || "Sandbox stream returned an error event without details.";
619
+ } catch {
620
+ return "Sandbox stream returned an error event without details.";
621
+ }
622
+ }
623
+ function toProducerWireEvent(event) {
624
+ return event;
625
+ }
626
+ function sandboxStreamFailureDiagnostic(error) {
627
+ const streamMessage = error?.streamMessage;
628
+ const message = typeof streamMessage === "string" ? streamMessage : error instanceof Error ? error.message : "Sandbox stream failed";
629
+ const diagnostics = asRecord(error?.diagnostics);
630
+ let diagnosticText;
631
+ if (diagnostics) {
632
+ try {
633
+ diagnosticText = JSON.stringify(diagnostics);
634
+ } catch {
635
+ diagnosticText = "[unserializable diagnostics]";
636
+ }
637
+ }
638
+ const userMessage = [
639
+ "The sandbox model stream stopped before a clean completion.",
640
+ `Error: ${message}`,
641
+ "Please retry. If it repeats, send these support details to the team."
642
+ ].join("\n\n");
643
+ const failureNote = diagnosticText ? `sandbox-stream: ${message}; ${diagnosticText}` : `sandbox-stream: ${message}`;
644
+ return { userMessage, failureNote };
645
+ }
576
646
  function createSandboxChatProducer(options) {
577
647
  const log = options.log ?? ((message, meta) => console.error(message, meta ?? ""));
578
648
  const renderable = options.isRenderableInteraction ?? isRenderableInteractionKind;
@@ -584,6 +654,9 @@ function createSandboxChatProducer(options) {
584
654
  const announcedTools = /* @__PURE__ */ new Set();
585
655
  const settledTools = /* @__PURE__ */ new Set();
586
656
  let stepCounter = 0;
657
+ let danglingToolsTerminalized = false;
658
+ let interactionOutcome = "answered";
659
+ let warningCount = 0;
587
660
  const promotedFileParts = /* @__PURE__ */ new Map();
588
661
  function recordPersistedPart(part, delta, keyOverride) {
589
662
  const persisted = normalizePersistedPart(part);
@@ -592,179 +665,288 @@ function createSandboxChatProducer(options) {
592
665
  if (!partMap.has(key)) partOrder.push(key);
593
666
  partMap.set(key, mergePersistedPart(partMap.get(key), persisted, delta));
594
667
  }
668
+ function* emitTerminalizedTools() {
669
+ if (danglingToolsTerminalized) return;
670
+ danglingToolsTerminalized = true;
671
+ const updates = terminalizeDanglingAssistantToolUpdates(partOrder, partMap, fullText);
672
+ for (const part of updates) {
673
+ const toolId = asString(part.id);
674
+ if (!toolId || settledTools.has(toolId)) continue;
675
+ settledTools.add(toolId);
676
+ const state = asRecord(part.state);
677
+ yield {
678
+ type: "tool_result",
679
+ toolCallId: toolId,
680
+ toolName: asString(part.tool) ?? "tool",
681
+ outcome: {
682
+ ok: false,
683
+ ...asString(state?.error) ? { message: asString(state?.error) } : {}
684
+ }
685
+ };
686
+ }
687
+ }
688
+ function* emitFinalUsage() {
689
+ const promptTokens = usage.inputTokens ?? 0;
690
+ const completionTokens = usage.outputTokens ?? 0;
691
+ if (promptTokens || completionTokens) {
692
+ yield { type: "usage", usage: { promptTokens, completionTokens } };
693
+ }
694
+ }
595
695
  async function* stream() {
596
- for await (const raw of options.events) {
597
- const record = asRecord(raw);
598
- if (!record || typeof record.type !== "string") continue;
599
- const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) });
600
- const event = normalized.type === "message.part.updated" ? normalized : record;
601
- if (event.type === "message.part.updated") {
602
- const part = asRecord(event.data?.part);
603
- if (!part) continue;
604
- const rawDelta = event.data?.delta;
605
- const partType = String(part.type ?? "");
606
- if (partType === "text" || partType === "reasoning") {
607
- const key = getPartKey(part);
608
- const delta = textDelta(tracker, key, part, rawDelta);
609
- recordPersistedPart(part, delta || void 0);
610
- if (delta) {
611
- if (partType === "text") fullText += delta;
612
- yield { type: partType, text: delta };
696
+ try {
697
+ for await (const raw of options.events) {
698
+ const record = asRecord(raw);
699
+ if (!record || typeof record.type !== "string") continue;
700
+ const normalized = normalizeToolEvent({ type: record.type, data: asRecord(record.data) });
701
+ const event = normalized.type === "message.part.updated" ? normalized : { type: record.type, data: asRecord(record.data) };
702
+ if (event.type === "message.part.updated") {
703
+ const part = asRecord(event.data?.part);
704
+ if (!part) continue;
705
+ const rawDelta = event.data?.delta;
706
+ const partType = String(part.type ?? "");
707
+ if (partType === "text" || partType === "reasoning") {
708
+ const key = getPartKey(part);
709
+ const delta = textDelta(tracker, key, part, rawDelta);
710
+ recordPersistedPart(part, delta || void 0);
711
+ if (delta) {
712
+ if (partType === "text") fullText += delta;
713
+ yield { type: partType, text: delta };
714
+ }
715
+ continue;
613
716
  }
614
- continue;
615
- }
616
- if (partType === "tool") {
617
- recordPersistedPart(part, void 0);
618
- const persisted = partMap.get(getPartKey(part));
619
- const state = asRecord(persisted?.state);
620
- const toolId = String(persisted?.id ?? "");
621
- const toolName = String(persisted?.tool ?? "tool");
622
- if (toolId && !announcedTools.has(toolId)) {
623
- announcedTools.add(toolId);
624
- yield {
625
- type: "tool_call",
626
- call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} }
627
- };
717
+ if (partType === "tool") {
718
+ recordPersistedPart(part, void 0);
719
+ const persisted = partMap.get(getPartKey(part));
720
+ const state = asRecord(persisted?.state);
721
+ const toolId = String(persisted?.id ?? "");
722
+ const toolName = String(persisted?.tool ?? "tool");
723
+ if (toolId && !announcedTools.has(toolId)) {
724
+ announcedTools.add(toolId);
725
+ yield {
726
+ type: "tool_call",
727
+ call: { toolCallId: toolId, toolName, args: asRecord(state?.input) ?? {} }
728
+ };
729
+ }
730
+ const status = String(state?.status ?? "");
731
+ if (toolId && (status === "completed" || status === "error") && !settledTools.has(toolId)) {
732
+ settledTools.add(toolId);
733
+ yield {
734
+ type: "tool_result",
735
+ toolCallId: toolId,
736
+ toolName,
737
+ outcome: {
738
+ ok: status === "completed",
739
+ ...state?.output !== void 0 ? { result: state.output } : {},
740
+ ...asString(state?.error) ? { message: asString(state?.error) } : {}
741
+ }
742
+ };
743
+ }
744
+ continue;
628
745
  }
629
- const status = String(state?.status ?? "");
630
- if (toolId && (status === "completed" || status === "error") && !settledTools.has(toolId)) {
631
- settledTools.add(toolId);
632
- yield {
633
- type: "tool_result",
634
- toolCallId: toolId,
635
- toolName,
636
- outcome: {
637
- ok: status === "completed",
638
- ...state?.output !== void 0 ? { result: state.output } : {},
639
- ...asString(state?.error) ? { message: asString(state?.error) } : {}
640
- }
641
- };
746
+ if (partType === "step-finish") {
747
+ usageFromStepFinish(part, usage);
748
+ recordPersistedPart(part, void 0, `step-finish:#${stepCounter++}`);
749
+ const promptTokens = usage.inputTokens ?? 0;
750
+ const completionTokens = usage.outputTokens ?? 0;
751
+ if (promptTokens || completionTokens) {
752
+ yield { type: "usage", usage: { promptTokens, completionTokens } };
753
+ }
754
+ continue;
642
755
  }
643
- continue;
644
- }
645
- if (partType === "step-finish") {
646
- usageFromStepFinish(part, usage);
647
- recordPersistedPart(part, void 0, `step-finish:#${stepCounter++}`);
648
- const promptTokens = usage.inputTokens ?? 0;
649
- const completionTokens = usage.outputTokens ?? 0;
650
- if (promptTokens || completionTokens) {
651
- yield { type: "usage", usage: { promptTokens, completionTokens } };
756
+ if (partType === "step-start") {
757
+ recordPersistedPart(part, void 0, `step-start:#${stepCounter}`);
758
+ continue;
652
759
  }
760
+ if (partType === "file" && options.promoteFilePart) {
761
+ const promote = options.promoteFilePart;
762
+ const rawId = asString(part.id);
763
+ const rawUrl = asString(part.url);
764
+ const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : void 0;
765
+ const attempt = () => Promise.resolve().then(() => promote(part)).catch((err) => {
766
+ const reason = err instanceof Error ? err.message : String(err);
767
+ log("[chat-routes] file part promotion threw", { key: memoKey ?? "(keyless)", error: reason });
768
+ return { succeeded: false, reason };
769
+ });
770
+ let pending;
771
+ if (memoKey) {
772
+ pending = promotedFileParts.get(memoKey) ?? attempt();
773
+ promotedFileParts.set(memoKey, pending);
774
+ } else {
775
+ pending = attempt();
776
+ }
777
+ const outcome = await pending;
778
+ if (outcome.succeeded) {
779
+ recordPersistedPart(outcome.part, void 0, outcome.key);
780
+ } else if (outcome.part) {
781
+ recordPersistedPart(outcome.part, void 0, outcome.key);
782
+ } else {
783
+ recordPersistedPart(part, void 0);
784
+ }
785
+ continue;
786
+ }
787
+ recordPersistedPart(part, void 0);
653
788
  continue;
654
789
  }
655
- if (partType === "step-start") {
656
- recordPersistedPart(part, void 0, `step-start:#${stepCounter}`);
657
- continue;
658
- }
659
- if (partType === "file" && options.promoteFilePart) {
660
- const promote = options.promoteFilePart;
661
- const rawId = asString(part.id);
662
- const rawUrl = asString(part.url);
663
- const memoKey = rawId ? `id:${rawId}` : rawUrl ? `url:${rawUrl}` : void 0;
664
- const attempt = () => Promise.resolve().then(() => promote(part)).catch((err) => {
665
- const reason = err instanceof Error ? err.message : String(err);
666
- log("[chat-routes] file part promotion threw", { key: memoKey ?? "(keyless)", error: reason });
667
- return { succeeded: false, reason };
668
- });
669
- let pending;
670
- if (memoKey) {
671
- pending = promotedFileParts.get(memoKey) ?? attempt();
672
- promotedFileParts.set(memoKey, pending);
673
- } else {
674
- pending = attempt();
790
+ if (event.type === "interaction") {
791
+ const parsed = parseInteractionRequest(asRecord(record.data));
792
+ if (!parsed.succeeded) {
793
+ log("[chat-routes] dropping malformed interaction event", { error: parsed.error });
794
+ continue;
675
795
  }
676
- const outcome = await pending;
677
- if (outcome.succeeded) {
678
- recordPersistedPart(outcome.part, void 0, outcome.key);
679
- } else if (outcome.part) {
680
- recordPersistedPart(outcome.part, void 0, outcome.key);
796
+ if (renderable(parsed.value.kind)) {
797
+ recordPersistedPart(
798
+ interactionToPersistedPart(parsed.value, "pending"),
799
+ void 0,
800
+ interactionPartKey(parsed.value.id)
801
+ );
802
+ yield toProducerWireEvent(record);
803
+ continue;
804
+ }
805
+ let declineFailed = false;
806
+ if (options.declineInteraction) {
807
+ try {
808
+ await options.declineInteraction(parsed.value.id);
809
+ } catch (err) {
810
+ declineFailed = true;
811
+ log("[chat-routes] failed to auto-decline interaction", {
812
+ id: parsed.value.id,
813
+ error: err instanceof Error ? err.message : String(err)
814
+ });
815
+ }
681
816
  } else {
682
- recordPersistedPart(part, void 0);
817
+ declineFailed = true;
818
+ log("[chat-routes] non-renderable interaction with no declineInteraction wired", {
819
+ id: parsed.value.id,
820
+ kind: parsed.value.kind
821
+ });
683
822
  }
823
+ const text = declineFailed ? `The agent requested ${parsed.value.kind} approval; declining it failed \u2014 it will expire on its own.` : `The agent requested ${parsed.value.kind} approval \u2014 auto-declined by policy.`;
824
+ const notice = noticePart("auto-declined", `auto-declined-${parsed.value.id}`, text);
825
+ recordPersistedPart(notice, void 0, noticePartKey(notice.id));
826
+ yield { type: "notice", id: notice.id, noticeKind: "auto-declined", text };
684
827
  continue;
685
828
  }
686
- recordPersistedPart(part, void 0);
687
- continue;
688
- }
689
- if (event.type === "interaction") {
690
- const parsed = parseInteractionRequest(asRecord(record.data));
691
- if (!parsed.succeeded) {
692
- log("[chat-routes] dropping malformed interaction event", { error: parsed.error });
829
+ if (event.type === "interaction.cancel") {
830
+ const parsed = parseInteractionCancel(asRecord(record.data));
831
+ if (!parsed.succeeded) {
832
+ log("[chat-routes] dropping malformed interaction.cancel event", { error: parsed.error });
833
+ continue;
834
+ }
835
+ const key = interactionPartKey(parsed.value.id);
836
+ const existing = partMap.get(key);
837
+ if (existing?.type === "interaction" && existing.status === "pending") {
838
+ recordPersistedPart({
839
+ ...existing,
840
+ status: cancelStatusFor(parsed.value.reason),
841
+ ...parsed.value.reason ? { cancelReason: parsed.value.reason } : {}
842
+ }, void 0, key);
843
+ }
844
+ yield toProducerWireEvent(record);
693
845
  continue;
694
846
  }
695
- if (renderable(parsed.value.kind)) {
696
- recordPersistedPart(
697
- interactionToPersistedPart(parsed.value, "pending"),
698
- void 0,
699
- interactionPartKey(parsed.value.id)
700
- );
701
- yield event;
847
+ if (event.type === "plan.submitted") {
848
+ const parsed = parsePlanSubmittedEvent(record);
849
+ if (!parsed.succeeded) {
850
+ log("[chat-routes] dropping malformed plan.submitted event", { error: parsed.error });
851
+ continue;
852
+ }
853
+ recordPersistedPart(planToPersistedPart(parsed.value), void 0);
854
+ yield toProducerWireEvent(record);
702
855
  continue;
703
856
  }
704
- if (options.declineInteraction) {
705
- try {
706
- await options.declineInteraction(parsed.value.id);
707
- } catch (err) {
708
- log("[chat-routes] failed to auto-decline interaction", {
709
- id: parsed.value.id,
710
- error: err instanceof Error ? err.message : String(err)
711
- });
857
+ if (event.type === "warning") {
858
+ const message = asString(event.data?.message);
859
+ if (message) {
860
+ warningCount += 1;
861
+ const code = asString(event.data?.code);
862
+ const text = code ? `${code}: ${message}` : message;
863
+ const notice = noticePart("warning", `warning-${warningCount}`, text);
864
+ recordPersistedPart(notice, void 0, noticePartKey(notice.id));
865
+ yield { type: "notice", id: notice.id, noticeKind: "warning", text };
712
866
  }
713
- } else {
714
- log("[chat-routes] non-renderable interaction with no declineInteraction wired", {
715
- id: parsed.value.id,
716
- kind: parsed.value.kind
717
- });
867
+ yield toProducerWireEvent(record);
868
+ continue;
718
869
  }
719
- continue;
720
- }
721
- if (event.type === "interaction.cancel") {
722
- const parsed = parseInteractionCancel(asRecord(record.data));
723
- if (!parsed.succeeded) {
724
- log("[chat-routes] dropping malformed interaction.cancel event", { error: parsed.error });
870
+ if (event.type === "result") {
871
+ const finalText = asString(event.data?.finalText);
872
+ if (finalText) fullText = finalText;
873
+ const reported = extractReportedTurnUsage(asRecord(event.data));
874
+ if (reported) {
875
+ applyTerminalUsage(reported, usage);
876
+ yield* emitFinalUsage();
877
+ } else {
878
+ const resultUsage = asRecord(event.data?.usage);
879
+ if (resultUsage) {
880
+ const input = Number(resultUsage.inputTokens);
881
+ const output = Number(resultUsage.outputTokens);
882
+ if (Number.isFinite(input)) usage.inputTokens = input;
883
+ if (Number.isFinite(output)) usage.outputTokens = output;
884
+ }
885
+ }
886
+ yield* emitTerminalizedTools();
725
887
  continue;
726
888
  }
727
- const key = interactionPartKey(parsed.value.id);
728
- const existing = partMap.get(key);
729
- if (existing?.type === "interaction" && existing.status === "pending") {
730
- recordPersistedPart({
731
- ...existing,
732
- status: cancelStatusFor(parsed.value.reason),
733
- ...parsed.value.reason ? { cancelReason: parsed.value.reason } : {}
734
- }, void 0, key);
889
+ if (event.type === "done") {
890
+ const reported = extractReportedTurnUsage(asRecord(event.data));
891
+ if (reported) {
892
+ applyTerminalUsage(reported, usage);
893
+ yield* emitFinalUsage();
894
+ }
895
+ yield* emitTerminalizedTools();
896
+ yield toProducerWireEvent(record);
897
+ continue;
735
898
  }
736
- yield event;
737
- continue;
738
- }
739
- if (event.type === "plan.submitted") {
740
- const parsed = parsePlanSubmittedEvent(record);
741
- if (!parsed.succeeded) {
742
- log("[chat-routes] dropping malformed plan.submitted event", { error: parsed.error });
899
+ if (event.type === "error") {
900
+ const message = sandboxStreamErrorMessage(event.data);
901
+ const errorContent = fullText.trim() ? `The sandbox model stream stopped before a clean completion.
902
+
903
+ Error: ${message}` : `The sandbox agent returned an error before producing a visible answer.
904
+
905
+ Error: ${message}`;
906
+ const errorDelta = fullText ? `
907
+
908
+ ---
909
+ ${errorContent}` : errorContent;
910
+ fullText += errorDelta;
911
+ interactionOutcome = "expired";
912
+ yield { type: "text", text: errorDelta };
913
+ yield* emitTerminalizedTools();
914
+ yield toProducerWireEvent(record);
743
915
  continue;
744
916
  }
745
- recordPersistedPart(planToPersistedPart(parsed.value), void 0);
746
- yield event;
747
- continue;
917
+ yield toProducerWireEvent(record);
748
918
  }
749
- if (event.type === "result") {
750
- const finalText = asString(event.data?.finalText);
751
- if (finalText) fullText = finalText;
752
- const resultUsage = asRecord(event.data?.usage);
753
- if (resultUsage) {
754
- const input = Number(resultUsage.inputTokens);
755
- const output = Number(resultUsage.outputTokens);
756
- if (Number.isFinite(input)) usage.inputTokens = input;
757
- if (Number.isFinite(output)) usage.outputTokens = output;
919
+ } catch (streamErr) {
920
+ const diagnostic = sandboxStreamFailureDiagnostic(streamErr);
921
+ log("[chat-routes] sandbox stream failed", {
922
+ failureNote: diagnostic.failureNote,
923
+ error: streamErr instanceof Error ? streamErr.message : String(streamErr)
924
+ });
925
+ const errorDelta = fullText ? `
926
+
927
+ ---
928
+ ${diagnostic.userMessage}` : diagnostic.userMessage;
929
+ fullText += errorDelta;
930
+ interactionOutcome = "expired";
931
+ yield { type: "text", text: errorDelta };
932
+ yield* emitTerminalizedTools();
933
+ yield {
934
+ type: "error",
935
+ data: {
936
+ message: diagnostic.userMessage,
937
+ code: "sandbox.stream_failed",
938
+ details: { failureNote: diagnostic.failureNote }
758
939
  }
759
- continue;
760
- }
761
- yield event;
940
+ };
762
941
  }
763
942
  }
764
943
  return {
765
944
  stream: stream(),
766
945
  finalText: () => fullText,
767
- assistantParts: () => finalizeAssistantParts(partOrder, partMap, fullText),
946
+ assistantParts: () => finalizePendingInteractionParts(
947
+ finalizeAssistantParts(partOrder, partMap, fullText),
948
+ interactionOutcome
949
+ ),
768
950
  usage: () => usage,
769
951
  ...options.model ? { model: options.model } : {}
770
952
  };