@rudderhq/server 0.7.16 → 0.7.18

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.
Files changed (153) hide show
  1. package/dist/bootstrap/create-http-app.d.ts.map +1 -1
  2. package/dist/bootstrap/create-http-app.js +3 -9
  3. package/dist/bootstrap/create-http-app.js.map +1 -1
  4. package/dist/routes/chats.d.ts.map +1 -1
  5. package/dist/routes/chats.js +16 -69
  6. package/dist/routes/chats.js.map +1 -1
  7. package/dist/routes/chats.message-query-routes.d.ts +9 -0
  8. package/dist/routes/chats.message-query-routes.d.ts.map +1 -0
  9. package/dist/routes/chats.message-query-routes.js +43 -0
  10. package/dist/routes/chats.message-query-routes.js.map +1 -0
  11. package/dist/routes/chats.stop-route.d.ts +3 -0
  12. package/dist/routes/chats.stop-route.d.ts.map +1 -0
  13. package/dist/routes/chats.stop-route.js +240 -0
  14. package/dist/routes/chats.stop-route.js.map +1 -0
  15. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  16. package/dist/routes/chats.stream-routes.js +94 -390
  17. package/dist/routes/chats.stream-routes.js.map +1 -1
  18. package/dist/routes/chats.stream-support.d.ts +41 -0
  19. package/dist/routes/chats.stream-support.d.ts.map +1 -1
  20. package/dist/routes/chats.stream-support.js +58 -0
  21. package/dist/routes/chats.stream-support.js.map +1 -1
  22. package/dist/routes/orgs.d.ts.map +1 -1
  23. package/dist/routes/orgs.js +0 -1
  24. package/dist/routes/orgs.js.map +1 -1
  25. package/dist/services/chat-generation-locks.test.js +0 -8
  26. package/dist/services/chat-generation-locks.test.js.map +1 -1
  27. package/dist/services/chat-inline-annotation-validation.d.ts.map +1 -1
  28. package/dist/services/chat-inline-annotation-validation.js +3 -10
  29. package/dist/services/chat-inline-annotation-validation.js.map +1 -1
  30. package/dist/services/chat-inline-annotations.test.js +9 -15
  31. package/dist/services/chat-inline-annotations.test.js.map +1 -1
  32. package/dist/services/chat-message-mutation-fingerprint.d.ts +29 -1
  33. package/dist/services/chat-message-mutation-fingerprint.d.ts.map +1 -1
  34. package/dist/services/chat-message-mutation-fingerprint.js +55 -0
  35. package/dist/services/chat-message-mutation-fingerprint.js.map +1 -1
  36. package/dist/services/chats.annotation-persistence.d.ts +5 -0
  37. package/dist/services/chats.annotation-persistence.d.ts.map +1 -1
  38. package/dist/services/chats.annotation-persistence.js +33 -1
  39. package/dist/services/chats.annotation-persistence.js.map +1 -1
  40. package/dist/services/chats.d.ts +2 -28
  41. package/dist/services/chats.d.ts.map +1 -1
  42. package/dist/services/chats.js +3 -29
  43. package/dist/services/chats.js.map +1 -1
  44. package/dist/services/organization-workspace-browser.d.ts.map +1 -1
  45. package/dist/services/organization-workspace-browser.js +1 -7
  46. package/dist/services/organization-workspace-browser.js.map +1 -1
  47. package/dist/services/product-analytics-collector.test.js +5 -1
  48. package/dist/services/product-analytics-collector.test.js.map +1 -1
  49. package/dist/services/rudder-plugin-catalog.d.ts.map +1 -1
  50. package/dist/services/rudder-plugin-catalog.js +10 -11
  51. package/dist/services/rudder-plugin-catalog.js.map +1 -1
  52. package/dist/services/rudder-plugins.d.ts +0 -2
  53. package/dist/services/rudder-plugins.d.ts.map +1 -1
  54. package/dist/services/rudder-plugins.js +11 -15
  55. package/dist/services/rudder-plugins.js.map +1 -1
  56. package/dist/services/rudder-plugins.test.js +1 -17
  57. package/dist/services/rudder-plugins.test.js.map +1 -1
  58. package/dist/services/runtime-kernel/heartbeat.core.d.ts +1 -0
  59. package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
  60. package/dist/services/runtime-kernel/heartbeat.core.js +11 -0
  61. package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
  62. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  63. package/dist/services/runtime-kernel/heartbeat.js +3 -33
  64. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  65. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts +1 -0
  66. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
  67. package/dist/services/runtime-kernel/heartbeat.recovery.js +23 -0
  68. package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
  69. package/package.json +14 -14
  70. package/ui-dist/assets/{LibraryLiveSurface-DnY0wZep.js → LibraryLiveSurface-BHneB__U.js} +1 -1
  71. package/ui-dist/assets/{abnfDiagram-VRR7QNED-CT-9m1Vc.js → abnfDiagram-VRR7QNED-GUoY9rTt.js} +1 -1
  72. package/ui-dist/assets/{addon-fit-C6aMSfRz.js → addon-fit-CqXXE0-1.js} +1 -1
  73. package/ui-dist/assets/{arc-1nExaM-y.js → arc-BD7m_r6l.js} +1 -1
  74. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-bILoPPs5.js → architectureDiagram-ZJ3FMSHR-nEufXAY_.js} +1 -1
  75. package/ui-dist/assets/{blockDiagram-677ZJIJ3--zRFciqi.js → blockDiagram-677ZJIJ3-D3qUEbL5.js} +1 -1
  76. package/ui-dist/assets/{c4Diagram-LMCZKHZV-CrtaX6A_.js → c4Diagram-LMCZKHZV-7SPUxKgx.js} +1 -1
  77. package/ui-dist/assets/channel-Czz1OwXA.js +1 -0
  78. package/ui-dist/assets/{chunk-2Q5K7J3B-DoBb7ZYS.js → chunk-2Q5K7J3B-6o3ETzOY.js} +1 -1
  79. package/ui-dist/assets/{chunk-32BRIVSS-DilGswdp.js → chunk-32BRIVSS-Efd566e8.js} +1 -1
  80. package/ui-dist/assets/{chunk-5VM5RSS4-D8q00d1k.js → chunk-5VM5RSS4-BA4X4gzU.js} +1 -1
  81. package/ui-dist/assets/{chunk-EX3LRPZG-Bl1KSAwQ.js → chunk-EX3LRPZG-BD7K-mPD.js} +1 -1
  82. package/ui-dist/assets/{chunk-JWPE2WC7-DtdgLB7Y.js → chunk-JWPE2WC7-DXv6iHxr.js} +1 -1
  83. package/ui-dist/assets/{chunk-MOJQB5TN-UaG7L1QI.js → chunk-MOJQB5TN-dQ6gk-_u.js} +1 -1
  84. package/ui-dist/assets/{chunk-RYQCIY6F-mkz9qbQF.js → chunk-RYQCIY6F-xEk6bTb7.js} +1 -1
  85. package/ui-dist/assets/{chunk-V7JOEXUC-FGET6Myl.js → chunk-V7JOEXUC-zlvz5Y5q.js} +1 -1
  86. package/ui-dist/assets/{chunk-VR4S4FIN-BgfUmupS.js → chunk-VR4S4FIN--zCSWk5F.js} +1 -1
  87. package/ui-dist/assets/{chunk-XXDRQBXY-B6I-gBFN.js → chunk-XXDRQBXY-CZ2QWPRe.js} +1 -1
  88. package/ui-dist/assets/classDiagram-OUVF2IWQ-C_ooAkuA.js +1 -0
  89. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-C_ooAkuA.js +1 -0
  90. package/ui-dist/assets/{cose-bilkent-JH36ORCC-DVGWBjbO.js → cose-bilkent-JH36ORCC-DrRNgFzR.js} +1 -1
  91. package/ui-dist/assets/{cynefin-VYW2F7L2-CWjLXAiA.js → cynefin-VYW2F7L2-T9PONuTh.js} +1 -1
  92. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-DWY5Jowv.js → cynefinDiagram-TSTJHNR4-Dkop4mS3.js} +1 -1
  93. package/ui-dist/assets/{dagre-VKFMJZFB-Bp0SVTqP.js → dagre-VKFMJZFB-Chc8Imxx.js} +1 -1
  94. package/ui-dist/assets/{diagram-FQU43EPY-EhsLuQCs.js → diagram-FQU43EPY-n4pgnJYP.js} +1 -1
  95. package/ui-dist/assets/{diagram-G47NLZAW-C_ifHw4Q.js → diagram-G47NLZAW-DnjvVsj5.js} +1 -1
  96. package/ui-dist/assets/{diagram-NH7WQ7WH-BCitqFNA.js → diagram-NH7WQ7WH-kHZ9astC.js} +1 -1
  97. package/ui-dist/assets/{diagram-OA4YK3LP-BRjomHnO.js → diagram-OA4YK3LP-BAMly3yv.js} +1 -1
  98. package/ui-dist/assets/{diagram-WEI45ONY-Cr_E92JE.js → diagram-WEI45ONY-BTFQhBrp.js} +1 -1
  99. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-0WJQMwqt.js → ebnfDiagram-CCIWWBDH-c2T4DORg.js} +1 -1
  100. package/ui-dist/assets/{erDiagram-Q63AITRT-B0EBivSu.js → erDiagram-Q63AITRT-BXLi1JHT.js} +1 -1
  101. package/ui-dist/assets/{flowDiagram-23GEKE2U-BwdRtCsd.js → flowDiagram-23GEKE2U-CHzX7naH.js} +1 -1
  102. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-KxlqXAqZ.js → ganttDiagram-NO4QXBWP-BqUKeoDF.js} +1 -1
  103. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-DeTCa-SS.js → gitGraphDiagram-IHSO6WYX-DM4K-DoE.js} +1 -1
  104. package/ui-dist/assets/{graph-rdRwl3us.js → graph-BDejhZe_.js} +1 -1
  105. package/ui-dist/assets/{index-CbEMZXO1.js → index-B4zWD5-i.js} +1 -1
  106. package/ui-dist/assets/{index-B2jHUYti.js → index-BJ-hTdyx.js} +1 -1
  107. package/ui-dist/assets/{index-CY1CWc3h.js → index-BN90mN5Z.js} +1 -1
  108. package/ui-dist/assets/{index-D3RtGD1i.js → index-Bni14R_J.js} +1 -1
  109. package/ui-dist/assets/index-BsB7Sc7m.css +32 -0
  110. package/ui-dist/assets/{index-CcLF8pix.js → index-BuCCxdIU.js} +1 -1
  111. package/ui-dist/assets/{index-NxbW73mN.js → index-CH8tOJZ9.js} +1 -1
  112. package/ui-dist/assets/{index-7Mb8iY3g.js → index-CIbvFYbV.js} +1 -1
  113. package/ui-dist/assets/{index-BzLCEJCf.js → index-CV0k_zvK.js} +1 -1
  114. package/ui-dist/assets/{index-CuP2P51w.js → index-DAM3EGbv.js} +1 -1
  115. package/ui-dist/assets/{index-s8ZAOPE2.js → index-DHzaZSPM.js} +641 -641
  116. package/ui-dist/assets/{index-DBPJ49Cn.js → index-DSAl1a-M.js} +1 -1
  117. package/ui-dist/assets/{index-Bi7KPtJ3.js → index-DVDu5Nwd.js} +1 -1
  118. package/ui-dist/assets/{index-C2DKaTLo.js → index-DZBZIqJI.js} +1 -1
  119. package/ui-dist/assets/{index-CtFA44ZZ.js → index-WbyFmgaS.js} +1 -1
  120. package/ui-dist/assets/{index-D5pdKmHf.js → index-XnCv8P5f.js} +1 -1
  121. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-Cun6yrS9.js → infoDiagram-FWYZ7A6U-PcFBrZTu.js} +1 -1
  122. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-SHuCvm_3.js → ishikawaDiagram-FXEZZL3T-BF2qBweH.js} +1 -1
  123. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-B2oa-ovF.js → journeyDiagram-5HDEW3XC-D3TqAoEB.js} +1 -1
  124. package/ui-dist/assets/{kanban-definition-HUTT4EX6-DtG3RFW0.js → kanban-definition-HUTT4EX6-CUiHrckR.js} +1 -1
  125. package/ui-dist/assets/{layout-DmSY_WKW.js → layout-DMKAzTyC.js} +1 -1
  126. package/ui-dist/assets/{linear-BiklTv-9.js → linear-D8C0WdTA.js} +1 -1
  127. package/ui-dist/assets/{map-B3RAMPoy.js → map-DUCHGq8e.js} +1 -1
  128. package/ui-dist/assets/{mermaid.core-CQj0CvWy.js → mermaid.core-C-eoqr77.js} +4 -4
  129. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BQrncLdk.js → mindmap-definition-LN4V7U3C-B54nV_Hu.js} +1 -1
  130. package/ui-dist/assets/{pegDiagram-2B236MQR-BmkmBhyS.js → pegDiagram-2B236MQR-CcIDNzTo.js} +1 -1
  131. package/ui-dist/assets/{pieDiagram-ENE6RG2P-B0dWJser.js → pieDiagram-ENE6RG2P-BU4swrSE.js} +1 -1
  132. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-RS2o06NX.js → quadrantDiagram-ABIIQ3AL-CnniDL18.js} +1 -1
  133. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-B4dlWUnB.js → railroadDiagram-RFXS5EU6-CiBY7wpV.js} +1 -1
  134. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-7M1vICZA.js → requirementDiagram-TGXJPOKE-C7c_tTqS.js} +1 -1
  135. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-LUpZrugG.js → sankeyDiagram-HTMAVEWB-DJuTS99d.js} +1 -1
  136. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-DB4ecUEg.js → sequenceDiagram-DBY2YBRQ-BxhtY70W.js} +1 -1
  137. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-CgLQGkEa.js → sizeCapture-X5ZJPWSS-0A0Kww03.js} +1 -1
  138. package/ui-dist/assets/{stateDiagram-2N3HPSRC-BxLcsZg0.js → stateDiagram-2N3HPSRC-DPw4bytW.js} +1 -1
  139. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-ANI7JZ72.js +1 -0
  140. package/ui-dist/assets/{swimlanes-5IMT3BWC-CFoyzQNM.js → swimlanes-5IMT3BWC-D6MMDWrs.js} +2 -2
  141. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CxjGCcz5.js +8 -0
  142. package/ui-dist/assets/{timeline-definition-FHXFAJF6-C1vajCzY.js → timeline-definition-FHXFAJF6-gNKDUxQx.js} +1 -1
  143. package/ui-dist/assets/{vennDiagram-L72KCM5P-C42rETOq.js → vennDiagram-L72KCM5P-BpZCM-ST.js} +1 -1
  144. package/ui-dist/assets/{wardleyDiagram-EHGQE667-BgzD6-QL.js → wardleyDiagram-EHGQE667-CvR0dYqn.js} +1 -1
  145. package/ui-dist/assets/{xterm-BeBaISmm.js → xterm-CEksOm0P.js} +1 -1
  146. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-BdoJT_55.js → xychartDiagram-FW5EYKEG-C6_4sxeI.js} +1 -1
  147. package/ui-dist/index.html +11 -7
  148. package/ui-dist/assets/channel-C3jj6nd_.js +0 -1
  149. package/ui-dist/assets/classDiagram-OUVF2IWQ-CNsW5NoJ.js +0 -1
  150. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CNsW5NoJ.js +0 -1
  151. package/ui-dist/assets/index-CYaeM_68.css +0 -32
  152. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DAlIlXqa.js +0 -1
  153. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DXfnihCA.js +0 -8
@@ -1,4 +1,4 @@
1
- import { addChatMessageSchema, chatClientCheckpointSchema, chatInlineAnnotationsFromStructuredPayload, convertChatToIssueSchema, createChatAttachmentMetadataSchema, createChatContextLinkSchema, createChatFirstTurnSchema, resolveChatOperationProposalSchema, setChatProjectContextSchema, stopChatGenerationSchema, } from "@rudderhq/shared";
1
+ import { addChatMessageSchema, chatClientCheckpointSchema, chatInlineAnnotationsFromStructuredPayload, convertChatToIssueSchema, createChatAttachmentMetadataSchema, createChatContextLinkSchema, createChatFirstTurnSchema, resolveChatOperationProposalSchema, setChatProjectContextSchema, } from "@rudderhq/shared";
2
2
  import { coalesceChatTranscriptTextEntries, withChatTranscriptGenerationProvenance, } from "@rudderhq/shared/chat-transcript-provenance";
3
3
  import multer from "multer";
4
4
  import { randomUUID } from "node:crypto";
@@ -8,14 +8,15 @@ import { logger } from "../middleware/logger.js";
8
8
  import { validate } from "../middleware/validate.js";
9
9
  import { chatAssistantErrorForLog } from "../services/chat-assistant.helpers.js";
10
10
  import { CHAT_ASSISTANT_USER_ERROR_MESSAGE, ChatAssistantStreamError, userVisiblePartialBodyFromError } from "../services/chat-assistant.js";
11
- import { cancelActiveChatGeneration, claimChatGeneration, createChatRuntimeControlCoordinator, getActiveChatGeneration, setActiveChatGenerationId, } from "../services/chat-generation-locks.js";
11
+ import { claimChatGeneration, createChatRuntimeControlCoordinator, getActiveChatGeneration, setActiveChatGenerationId, } from "../services/chat-generation-locks.js";
12
12
  import { hashChatGenerationBody } from "../services/chat-generation-protocol.js";
13
- import { chatMessageMutationFingerprint } from "../services/chat-message-mutation-fingerprint.js";
13
+ import { replayChatStreamMessage } from "../services/chat-message-mutation-fingerprint.js";
14
14
  import { logActivity } from "../services/index.js";
15
15
  import { assertCompanyAccess, getActorInfo } from "./authz.js";
16
16
  import { wakeIssueAssigneeAfterChatConversion } from "./chat-issue-assignment-wakeup.js";
17
17
  import { chatRuntimeSnapshot } from "./chats.runtime-controls.js";
18
- import { CHAT_ASSISTANT_RECOVERABLE_FAILURE_FALLBACK_MESSAGE, CHAT_ASSISTANT_STOPPED_FALLBACK_MESSAGE, createChatStreamFileStaging, createStartingChatGenerationGate, normalizeMultipartFirstTurnBody, normalizeMultipartMessageBody, outputAdmissionClosed, resolveStoppedAssistantState, startingChatGenerationGates, withMergedChatMessageAttachments, } from "./chats.stream-support.js";
18
+ import { registerChatStopRoute } from "./chats.stop-route.js";
19
+ import { CHAT_ASSISTANT_RECOVERABLE_FAILURE_FALLBACK_MESSAGE, CHAT_ASSISTANT_STOPPED_FALLBACK_MESSAGE, createChatStreamFileStaging, createStartingChatGenerationGate, normalizeMultipartFirstTurnBody, normalizeMultipartMessageBody, outputAdmissionClosed, persistChatStreamUserMessageBeforeGeneration, resolveStoppedAssistantState, startingChatGenerationGates, withMergedChatMessageAttachments, } from "./chats.stream-support.js";
19
20
  import { registerChatUserStateRoutes } from "./chats.user-state-routes.js";
20
21
  export function registerChatStreamRoutes(ctx) {
21
22
  const { router, db, storage, svc, assistantSvc, agentsSvc, issuesSvc, projectsSvc, goalsSvc, access, operatorProfiles, heartbeat, assertConversationAccess, assertChatLocalMutationAllowed, assertSideChatMutationAllowed, touchSideChat, boardUserId, assertCanAssignTasks, runSingleFileUpload, runMessageFileUpload, isMultipartRequest, uploadedMessageFiles, validateUploadedMessageFiles, preflightChatDraft, logChatMessagesAdded, assertContextLinksBelongToCompany, turnContextFromUserMessage, addUserMessage, inlineAnnotations, storeUserMessageFiles, cleanupStoredUserMessageFiles, storeQueuedAnnotationFiles, cleanupUncommittedQueuedAnnotationFiles, startChatTitleGeneration, attachFilesToUserMessage, loadAssistantInput, chatReplyingAgentId, assertCanConvertIssueProposal, persistAssistantReply, linkChatRunMessages, attachGeneratedFilesToPartialMessage, persistPartialAssistantMessage, recoverableFailurePayload, recoverableFailureBody, writeStreamEvent, queueRequestActor, wakeServerQueue, wakeTerminalProjector, } = ctx;
@@ -77,39 +78,24 @@ export function registerChatStreamRoutes(ctx) {
77
78
  return;
78
79
  }
79
80
  const clientMutationId = parsedBody.data.clientMutationId ?? null;
80
- const clientMutationFingerprint = clientMutationId
81
- ? chatMessageMutationFingerprint({
82
- body: parsedBody.data.body,
83
- editUserMessageId: parsedBody.data.editUserMessageId ?? null,
84
- inlineAnnotationsProvided,
85
- inlineAnnotations: parsedBody.data.inlineAnnotations,
86
- modelOverride: parsedBody.data.modelOverride ?? null,
87
- effortOverride: parsedBody.data.effortOverride ?? null,
88
- files: messageFiles,
89
- })
90
- : null;
91
- if (!atomicFirstTurn && clientMutationId) {
92
- const replayedMutation = await svc.getUserMessageMutationByClientMutationId(conversation.orgId, conversation.id, clientMutationId);
93
- if (replayedMutation) {
94
- const replayedUserMessage = replayedMutation.message;
95
- if (replayedUserMessage.body !== parsedBody.data.body
96
- || (replayedMutation.fingerprint !== null
97
- && replayedMutation.fingerprint !== clientMutationFingerprint)) {
98
- throw conflict("Chat mutation key was already used for different content");
99
- }
100
- res.status(200);
101
- res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
102
- res.setHeader("Cache-Control", "no-cache, no-transform");
103
- res.setHeader("X-Accel-Buffering", "no");
104
- writeStreamEvent(res, {
105
- type: "ack",
106
- userMessage: replayedUserMessage,
107
- });
108
- writeStreamEvent(res, { type: "final", messages: [] });
109
- res.end();
110
- return;
111
- }
112
- }
81
+ const clientMutationFingerprint = await replayChatStreamMessage({
82
+ atomicFirstTurn: Boolean(atomicFirstTurn),
83
+ clientMutationId,
84
+ body: parsedBody.data.body,
85
+ editUserMessageId: parsedBody.data.editUserMessageId,
86
+ inlineAnnotationsProvided,
87
+ inlineAnnotations: parsedBody.data.inlineAnnotations,
88
+ modelOverride: parsedBody.data.modelOverride,
89
+ effortOverride: parsedBody.data.effortOverride,
90
+ files: messageFiles,
91
+ orgId: conversation.orgId,
92
+ conversationId: conversation.id,
93
+ lookup: svc.getUserMessageMutationByClientMutationId,
94
+ response: res,
95
+ writeStreamEvent,
96
+ });
97
+ if (clientMutationFingerprint === undefined)
98
+ return;
113
99
  const preparedAnnotations = !atomicFirstTurn && inlineAnnotationsProvided
114
100
  ? await inlineAnnotations.prepare({
115
101
  orgId: conversation.orgId,
@@ -252,94 +238,45 @@ export function registerChatStreamRoutes(ctx) {
252
238
  }
253
239
  const startupGate = createStartingChatGenerationGate();
254
240
  startingChatGenerationGates.set(conversation.id, startupGate);
255
- let persistedUserMessage = atomicFirstTurn?.userMessage ?? null;
256
- let userMessagePersisted = Boolean(atomicFirstTurn);
257
- let committedUserMessageId = atomicFirstTurn?.userMessage.id ?? null;
258
- if (queuedMessageId) {
259
- try {
260
- await svc.assertQueuedMessageClaimedForDelivery({
261
- conversationId: conversation.id,
262
- itemId: queuedMessageId,
263
- body: parsedBody.data.body,
264
- });
265
- }
266
- catch (error) {
267
- startupGate.resolveGeneration(null);
268
- startingChatGenerationGates.delete(conversation.id);
269
- releaseGeneration();
270
- await stagedMessageFiles.cleanup();
271
- logger.warn({
272
- err: chatAssistantErrorForLog(error),
273
- conversationId: conversation.id,
274
- }, "chat user-message persistence failed before generation");
275
- res.status(201);
276
- res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
277
- res.setHeader("Cache-Control", "no-cache, no-transform");
278
- res.setHeader("X-Accel-Buffering", "no");
279
- writeStreamEvent(res, {
280
- type: "error",
281
- error: CHAT_ASSISTANT_USER_ERROR_MESSAGE,
282
- messageId: committedUserMessageId,
283
- });
284
- res.end();
285
- return;
286
- }
241
+ const messagePersistence = await persistChatStreamUserMessageBeforeGeneration({
242
+ atomicFirstTurn,
243
+ queuedMessageId,
244
+ conversation: conversation,
245
+ body: parsedBody.data.body,
246
+ editUserMessageId: parsedBody.data.editUserMessageId ?? null,
247
+ preparedAnnotations,
248
+ inlineAnnotationsProvided,
249
+ clientMutationId,
250
+ clientMutationFingerprint,
251
+ svc,
252
+ addUserMessage,
253
+ actor,
254
+ startupGate,
255
+ releaseGeneration,
256
+ stagedMessageFiles,
257
+ });
258
+ if (messagePersistence.kind === "error") {
259
+ logger.warn({ err: chatAssistantErrorForLog(messagePersistence.error), conversationId: conversation.id }, "chat user-message persistence failed before generation");
260
+ res.status(201);
261
+ res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
262
+ res.setHeader("Cache-Control", "no-cache, no-transform");
263
+ res.setHeader("X-Accel-Buffering", "no");
264
+ writeStreamEvent(res, { type: "error", error: CHAT_ASSISTANT_USER_ERROR_MESSAGE, messageId: messagePersistence.messageId });
265
+ res.end();
266
+ return;
287
267
  }
288
- if (!atomicFirstTurn) {
289
- try {
290
- const persistence = await addUserMessage(conversation, parsedBody.data.body, actor, parsedBody.data.editUserMessageId ?? null, {
291
- provided: inlineAnnotationsProvided,
292
- prepared: preparedAnnotations,
293
- storedAttachments: stagedMessageFiles.files,
294
- onPersisted: (messageId) => {
295
- userMessagePersisted = true;
296
- committedUserMessageId = messageId;
297
- stagedMessageFiles.markCommitted();
298
- },
299
- clientMutationId,
300
- clientMutationFingerprint,
301
- });
302
- persistedUserMessage = persistence.message;
303
- if (!persistence.accepted) {
304
- startupGate.resolveGeneration(null);
305
- startingChatGenerationGates.delete(conversation.id);
306
- releaseGeneration();
307
- await stagedMessageFiles.cleanup();
308
- res.status(200);
309
- res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
310
- res.setHeader("Cache-Control", "no-cache, no-transform");
311
- res.setHeader("X-Accel-Buffering", "no");
312
- writeStreamEvent(res, { type: "ack", userMessage: persistence.message });
313
- writeStreamEvent(res, { type: "final", messages: [] });
314
- res.end();
315
- return;
316
- }
317
- userMessagePersisted = true;
318
- committedUserMessageId = persistence.message.id;
319
- stagedMessageFiles.markCommitted();
320
- }
321
- catch (error) {
322
- startupGate.resolveGeneration(null);
323
- startingChatGenerationGates.delete(conversation.id);
324
- releaseGeneration();
325
- await stagedMessageFiles.cleanup();
326
- logger.warn({
327
- err: chatAssistantErrorForLog(error),
328
- conversationId: conversation.id,
329
- }, "chat user-message persistence failed before generation");
330
- res.status(201);
331
- res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
332
- res.setHeader("Cache-Control", "no-cache, no-transform");
333
- res.setHeader("X-Accel-Buffering", "no");
334
- writeStreamEvent(res, {
335
- type: "error",
336
- error: CHAT_ASSISTANT_USER_ERROR_MESSAGE,
337
- messageId: committedUserMessageId,
338
- });
339
- res.end();
340
- return;
341
- }
268
+ if (messagePersistence.kind === "replayed") {
269
+ res.status(200);
270
+ res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
271
+ res.setHeader("Cache-Control", "no-cache, no-transform");
272
+ res.setHeader("X-Accel-Buffering", "no");
273
+ writeStreamEvent(res, { type: "ack", userMessage: messagePersistence.message });
274
+ writeStreamEvent(res, { type: "final", messages: [] });
275
+ res.end();
276
+ return;
342
277
  }
278
+ let { userMessagePersisted, committedUserMessageId } = messagePersistence;
279
+ const persistedUserMessage = messagePersistence.userMessage;
343
280
  let generation = null;
344
281
  try {
345
282
  const createdGeneration = await svc.createGeneration(conversation.orgId, conversation.id);
@@ -352,42 +289,39 @@ export function registerChatStreamRoutes(ctx) {
352
289
  startingChatGenerationGates.delete(conversation.id);
353
290
  releaseGeneration();
354
291
  await stagedMessageFiles.cleanup();
355
- if (atomicFirstTurn) {
356
- const failurePayload = recoverableFailurePayload(error, null);
357
- const failureBody = recoverableFailureBody(failurePayload) || CHAT_ASSISTANT_RECOVERABLE_FAILURE_FALLBACK_MESSAGE;
358
- const failedMessage = await svc.addMessage(conversation.id, {
359
- orgId: conversation.orgId,
360
- role: "assistant",
361
- kind: "message",
362
- status: "failed",
363
- body: failureBody,
364
- structuredPayload: failurePayload,
365
- replyingAgentId: conversation.preferredAgentId,
366
- chatTurnId: atomicFirstTurn.userMessage.chatTurnId,
367
- });
368
- await logChatMessagesAdded(conversation, [failedMessage], {
369
- actorType: "system",
370
- actorId: "chat-assistant",
371
- agentId: conversation.preferredAgentId,
372
- });
373
- res.status(201);
374
- res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
375
- res.setHeader("Cache-Control", "no-cache, no-transform");
376
- res.setHeader("X-Accel-Buffering", "no");
377
- writeStreamEvent(res, {
378
- type: "ack",
379
- conversation: atomicFirstTurn.conversation,
380
- userMessage: atomicFirstTurn.userMessage,
381
- });
382
- writeStreamEvent(res, {
383
- type: "error",
384
- error: failureBody,
385
- messageId: failedMessage.id,
386
- });
387
- res.end();
388
- return;
389
- }
390
- throw error;
292
+ const failurePayload = recoverableFailurePayload(error, null);
293
+ const failureBody = recoverableFailureBody(failurePayload) || CHAT_ASSISTANT_RECOVERABLE_FAILURE_FALLBACK_MESSAGE;
294
+ const failedMessage = await svc.addMessage(conversation.id, {
295
+ orgId: conversation.orgId,
296
+ role: "assistant",
297
+ kind: "message",
298
+ status: "failed",
299
+ body: failureBody,
300
+ structuredPayload: failurePayload,
301
+ replyingAgentId: conversation.preferredAgentId,
302
+ chatTurnId: persistedUserMessage.chatTurnId,
303
+ });
304
+ await logChatMessagesAdded(conversation, [failedMessage], {
305
+ actorType: "system",
306
+ actorId: "chat-assistant",
307
+ agentId: conversation.preferredAgentId,
308
+ });
309
+ res.status(201);
310
+ res.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
311
+ res.setHeader("Cache-Control", "no-cache, no-transform");
312
+ res.setHeader("X-Accel-Buffering", "no");
313
+ writeStreamEvent(res, {
314
+ type: "ack",
315
+ ...(atomicFirstTurn ? { conversation: atomicFirstTurn.conversation } : {}),
316
+ userMessage: persistedUserMessage,
317
+ });
318
+ writeStreamEvent(res, {
319
+ type: "error",
320
+ error: failureBody,
321
+ messageId: failedMessage.id,
322
+ });
323
+ res.end();
324
+ return;
391
325
  }
392
326
  if (abortController.signal.aborted && startupGate.stopRequested) {
393
327
  await startupGate.stopApplied;
@@ -1061,237 +995,7 @@ export function registerChatStreamRoutes(ctx) {
1061
995
  advanced: checkpoint.advanced,
1062
996
  });
1063
997
  });
1064
- router.post("/chats/:id/messages/stream/stop", async (req, res) => {
1065
- const parsed = stopChatGenerationSchema.safeParse(req.body ?? {});
1066
- if (!parsed.success) {
1067
- res.status(400).json({ error: "Invalid chat Stop request", details: parsed.error.issues });
1068
- return;
1069
- }
1070
- const conversation = await assertConversationAccess(req, req.params.id);
1071
- if (!conversation) {
1072
- res.status(404).json({ error: "Chat conversation not found" });
1073
- return;
1074
- }
1075
- assertChatLocalMutationAllowed(conversation);
1076
- await assertSideChatMutationAllowed(req, conversation);
1077
- const controlActionId = parsed.data.controlActionId ?? randomUUID();
1078
- const startupGate = startingChatGenerationGates.get(conversation.id) ?? null;
1079
- let startupStopRequested = false;
1080
- let startupInterruptRequested = false;
1081
- try {
1082
- let active = getActiveChatGeneration(conversation.id);
1083
- if (active && !active.generationId && startupGate) {
1084
- startupStopRequested = true;
1085
- startupGate.stopRequested = true;
1086
- startupInterruptRequested = cancelActiveChatGeneration(conversation.id);
1087
- const startupGenerationId = await startupGate.generationReady;
1088
- active = startupGenerationId ? getActiveChatGeneration(conversation.id) : null;
1089
- if (!startupGenerationId) {
1090
- res.json({
1091
- stopped: startupInterruptRequested,
1092
- controlActionId,
1093
- generationId: null,
1094
- disposition: startupInterruptRequested ? "startup_cancelled" : "interrupted_unverified",
1095
- });
1096
- return;
1097
- }
1098
- }
1099
- const latestActiveGeneration = active?.generationId
1100
- ? null
1101
- : await svc.getLatestActiveGeneration(conversation.id);
1102
- const generationId = parsed.data.expectedGenerationId
1103
- ?? active?.generationId
1104
- ?? latestActiveGeneration?.id
1105
- ?? null;
1106
- if (!generationId) {
1107
- res.json({
1108
- stopped: false,
1109
- controlActionId,
1110
- generationId: null,
1111
- disposition: "no_active_generation",
1112
- });
1113
- return;
1114
- }
1115
- let durableCheckpoint = await svc.generationProtocol.getLatestVisibleCheckpoint({
1116
- orgId: conversation.orgId,
1117
- conversationId: conversation.id,
1118
- generationId,
1119
- });
1120
- if (durableCheckpoint.generation.runtimeTerminalAt && !parsed.data.controlActionId) {
1121
- wakeTerminalProjector();
1122
- res.json({
1123
- stopped: false,
1124
- controlActionId,
1125
- generationId,
1126
- disposition: "no_active_generation",
1127
- });
1128
- return;
1129
- }
1130
- const expectedAttemptEpoch = parsed.data.expectedAttemptEpoch
1131
- ?? durableCheckpoint.generation.attemptEpoch;
1132
- const requestedControlVersion = parsed.data.expectedControlVersion
1133
- ?? durableCheckpoint.generation.controlVersion;
1134
- const requestedRenderSeq = parsed.data.lastCommittedRenderSeq
1135
- ?? durableCheckpoint.generationSeq;
1136
- const requestedBodyHash = parsed.data.renderedBodyHash
1137
- ?? durableCheckpoint.bodyHash;
1138
- let stop = null;
1139
- for (let attempt = 0; attempt < 2 && !stop; attempt += 1) {
1140
- try {
1141
- stop = await svc.generationProtocol.beginStopAction({
1142
- orgId: conversation.orgId,
1143
- conversationId: conversation.id,
1144
- controlActionId,
1145
- expectedGenerationId: generationId,
1146
- expectedAttemptEpoch,
1147
- expectedControlVersion: requestedControlVersion,
1148
- admissionControlVersion: attempt === 0
1149
- ? requestedControlVersion
1150
- : durableCheckpoint.generation.controlVersion,
1151
- requestedRenderSeq,
1152
- requestedBodyHash,
1153
- });
1154
- }
1155
- catch (error) {
1156
- const status = error && typeof error === "object" && "status" in error
1157
- ? Number(error.status)
1158
- : null;
1159
- if (attempt > 0 || status !== 409)
1160
- throw error;
1161
- durableCheckpoint = await svc.generationProtocol.getLatestVisibleCheckpoint({
1162
- orgId: conversation.orgId,
1163
- conversationId: conversation.id,
1164
- generationId,
1165
- });
1166
- if (durableCheckpoint.generation.runtimeTerminalAt && !parsed.data.controlActionId) {
1167
- wakeTerminalProjector();
1168
- res.json({
1169
- stopped: false,
1170
- controlActionId,
1171
- generationId,
1172
- disposition: "no_active_generation",
1173
- });
1174
- return;
1175
- }
1176
- if (durableCheckpoint.generation.attemptEpoch !== expectedAttemptEpoch)
1177
- throw error;
1178
- }
1179
- }
1180
- if (!stop)
1181
- throw new Error("Failed to establish the chat Stop cutoff");
1182
- const wasWaitingForNetwork = durableCheckpoint.generation.status === "waiting_for_network";
1183
- if (stop.idempotent
1184
- && parsed.data.expectedControlVersion !== undefined
1185
- && stop.action.expectedControlVersion !== parsed.data.expectedControlVersion) {
1186
- throw conflict("Control action id was already used for a different Stop request");
1187
- }
1188
- if (stop.outcome === "completion_committed") {
1189
- res.json({
1190
- stopped: false,
1191
- controlActionId,
1192
- generationId,
1193
- disposition: "completion_committed",
1194
- });
1195
- return;
1196
- }
1197
- if (stop.outcome === "already_terminal") {
1198
- const alreadyStopped = stop.generation.status === "stopped"
1199
- || stop.action.localDisposition === "stopped";
1200
- wakeTerminalProjector();
1201
- res.json({
1202
- stopped: alreadyStopped,
1203
- controlActionId,
1204
- generationId,
1205
- disposition: alreadyStopped ? "stopped" : "no_active_generation",
1206
- acceptedThroughSeq: stop.action.acceptedThroughSeq,
1207
- frozenBodyHash: stop.action.frozenBodyHash,
1208
- });
1209
- return;
1210
- }
1211
- if (stop.outcome === "stop_in_progress" && !stop.generation.runtimeTerminalAt) {
1212
- const localInterruptRequested = startupInterruptRequested || cancelActiveChatGeneration(conversation.id);
1213
- res.json({
1214
- stopped: Boolean(localInterruptRequested),
1215
- controlActionId,
1216
- generationId,
1217
- disposition: "stopping",
1218
- acceptedThroughSeq: stop.action.acceptedThroughSeq,
1219
- frozenBodyHash: stop.action.frozenBodyHash,
1220
- });
1221
- return;
1222
- }
1223
- if (stop.idempotent && stop.generation.runtimeTerminalAt) {
1224
- const terminalDisposition = stop.generation.status === "stopped"
1225
- || stop.action.localDisposition === "stopped"
1226
- ? "stopped"
1227
- : "stopping";
1228
- wakeTerminalProjector();
1229
- res.json({
1230
- stopped: true,
1231
- controlActionId,
1232
- generationId,
1233
- disposition: terminalDisposition,
1234
- acceptedThroughSeq: stop.action.acceptedThroughSeq,
1235
- frozenBodyHash: stop.action.frozenBodyHash,
1236
- });
1237
- return;
1238
- }
1239
- const localInterruptRequested = startupInterruptRequested || cancelActiveChatGeneration(conversation.id);
1240
- let stopped = Boolean(localInterruptRequested);
1241
- let disposition = "stopping";
1242
- if (startupStopRequested) {
1243
- await svc.generationProtocol.recordRuntimeTerminal({
1244
- orgId: conversation.orgId,
1245
- conversationId: conversation.id,
1246
- generationId,
1247
- expectedAttemptEpoch: stop.generation.attemptEpoch,
1248
- finalStatus: "stopped",
1249
- terminalReason: "operator_stop",
1250
- controlActionId,
1251
- });
1252
- wakeTerminalProjector();
1253
- }
1254
- else if (wasWaitingForNetwork) {
1255
- stopped = true;
1256
- disposition = "stopped";
1257
- await svc.generationProtocol.recordRuntimeTerminal({
1258
- orgId: conversation.orgId,
1259
- conversationId: conversation.id,
1260
- generationId,
1261
- expectedAttemptEpoch: stop.generation.attemptEpoch,
1262
- finalStatus: "stopped",
1263
- terminalReason: "operator_stop",
1264
- controlActionId,
1265
- });
1266
- wakeTerminalProjector();
1267
- }
1268
- else if (!localInterruptRequested) {
1269
- disposition = "interrupted_unverified";
1270
- await svc.generationProtocol.recordRuntimeTerminal({
1271
- orgId: conversation.orgId,
1272
- conversationId: conversation.id,
1273
- generationId,
1274
- expectedAttemptEpoch: stop.generation.attemptEpoch,
1275
- finalStatus: "interrupted_unverified",
1276
- terminalReason: "stop_without_local_runtime_owner",
1277
- controlActionId,
1278
- });
1279
- wakeTerminalProjector();
1280
- }
1281
- res.json({
1282
- stopped,
1283
- controlActionId,
1284
- generationId,
1285
- disposition,
1286
- acceptedThroughSeq: stop.action.acceptedThroughSeq,
1287
- frozenBodyHash: stop.action.frozenBodyHash,
1288
- });
1289
- }
1290
- finally {
1291
- if (startupStopRequested)
1292
- startupGate?.resolveStopApplied();
1293
- }
1294
- });
998
+ registerChatStopRoute(ctx);
1295
999
  router.post("/orgs/:orgId/chats/:chatId/attachments", async (req, res) => {
1296
1000
  const orgId = req.params.orgId;
1297
1001
  const chatId = req.params.chatId;