@nodetool-ai/websocket 0.8.2 → 0.8.3

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 (316) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/chat-turn-registry.d.ts +2 -1
  3. package/dist/chat-turn-registry.d.ts.map +1 -1
  4. package/dist/chat-turn-registry.js +8 -1
  5. package/dist/chat-turn-registry.js.map +1 -1
  6. package/dist/collection-api.d.ts.map +1 -1
  7. package/dist/collection-api.js +3 -5
  8. package/dist/collection-api.js.map +1 -1
  9. package/dist/custom-providers.d.ts.map +1 -1
  10. package/dist/custom-providers.js +6 -1
  11. package/dist/custom-providers.js.map +1 -1
  12. package/dist/example-workflows.d.ts.map +1 -1
  13. package/dist/example-workflows.js +6 -0
  14. package/dist/example-workflows.js.map +1 -1
  15. package/dist/fake-runtime.d.ts +41 -6
  16. package/dist/fake-runtime.d.ts.map +1 -1
  17. package/dist/fake-runtime.js +207 -13
  18. package/dist/fake-runtime.js.map +1 -1
  19. package/dist/file-api.d.ts.map +1 -1
  20. package/dist/file-api.js +6 -8
  21. package/dist/file-api.js.map +1 -1
  22. package/dist/generation-recovery.d.ts.map +1 -1
  23. package/dist/generation-recovery.js +7 -4
  24. package/dist/generation-recovery.js.map +1 -1
  25. package/dist/headless-job-runner.d.ts.map +1 -1
  26. package/dist/headless-job-runner.js +5 -1
  27. package/dist/headless-job-runner.js.map +1 -1
  28. package/dist/http-api.d.ts +3 -1
  29. package/dist/http-api.d.ts.map +1 -1
  30. package/dist/http-api.js +51 -9
  31. package/dist/http-api.js.map +1 -1
  32. package/dist/http-body-schemas.d.ts +2 -0
  33. package/dist/http-body-schemas.d.ts.map +1 -1
  34. package/dist/http-body-schemas.js +8 -1
  35. package/dist/http-body-schemas.js.map +1 -1
  36. package/dist/lib/admin.d.ts +7 -0
  37. package/dist/lib/admin.d.ts.map +1 -0
  38. package/dist/lib/admin.js +20 -0
  39. package/dist/lib/admin.js.map +1 -0
  40. package/dist/lib/app-instances-service.d.ts +35 -0
  41. package/dist/lib/app-instances-service.d.ts.map +1 -1
  42. package/dist/lib/app-instances-service.js +3 -4
  43. package/dist/lib/app-instances-service.js.map +1 -1
  44. package/dist/lib/app-project-export.d.ts +12 -0
  45. package/dist/lib/app-project-export.d.ts.map +1 -0
  46. package/dist/lib/app-project-export.js +42 -0
  47. package/dist/lib/app-project-export.js.map +1 -0
  48. package/dist/lib/applications-service.d.ts.map +1 -1
  49. package/dist/lib/applications-service.js +7 -2
  50. package/dist/lib/applications-service.js.map +1 -1
  51. package/dist/lib/asset-export.d.ts +6 -2
  52. package/dist/lib/asset-export.d.ts.map +1 -1
  53. package/dist/lib/asset-export.js +15 -7
  54. package/dist/lib/asset-export.js.map +1 -1
  55. package/dist/lib/asset-response.d.ts.map +1 -1
  56. package/dist/lib/asset-response.js +1 -0
  57. package/dist/lib/asset-response.js.map +1 -1
  58. package/dist/lib/cloud-profile.d.ts +10 -0
  59. package/dist/lib/cloud-profile.d.ts.map +1 -0
  60. package/dist/lib/cloud-profile.js +13 -0
  61. package/dist/lib/cloud-profile.js.map +1 -0
  62. package/dist/lib/http-user-id.d.ts +2 -0
  63. package/dist/lib/http-user-id.d.ts.map +1 -0
  64. package/dist/lib/http-user-id.js +4 -0
  65. package/dist/lib/http-user-id.js.map +1 -0
  66. package/dist/lib/json-body.d.ts +11 -0
  67. package/dist/lib/json-body.d.ts.map +1 -0
  68. package/dist/lib/json-body.js +13 -0
  69. package/dist/lib/json-body.js.map +1 -0
  70. package/dist/lib/public-routes.d.ts +15 -0
  71. package/dist/lib/public-routes.d.ts.map +1 -1
  72. package/dist/lib/public-routes.js +27 -0
  73. package/dist/lib/public-routes.js.map +1 -1
  74. package/dist/lib/public-workflow-share.d.ts +12 -1
  75. package/dist/lib/public-workflow-share.d.ts.map +1 -1
  76. package/dist/lib/public-workflow-share.js +45 -2
  77. package/dist/lib/public-workflow-share.js.map +1 -1
  78. package/dist/lib/python-metadata-roots.d.ts +27 -0
  79. package/dist/lib/python-metadata-roots.d.ts.map +1 -0
  80. package/dist/lib/python-metadata-roots.js +92 -0
  81. package/dist/lib/python-metadata-roots.js.map +1 -0
  82. package/dist/lib/runtime-detection.d.ts +1 -1
  83. package/dist/lib/runtime-detection.d.ts.map +1 -1
  84. package/dist/lib/runtime-detection.js +2 -1
  85. package/dist/lib/runtime-detection.js.map +1 -1
  86. package/dist/lib/server-auth-token.d.ts +18 -0
  87. package/dist/lib/server-auth-token.d.ts.map +1 -0
  88. package/dist/lib/server-auth-token.js +28 -0
  89. package/dist/lib/server-auth-token.js.map +1 -0
  90. package/dist/lib/timeline-bundle.d.ts +11 -0
  91. package/dist/lib/timeline-bundle.d.ts.map +1 -1
  92. package/dist/lib/timeline-bundle.js +22 -6
  93. package/dist/lib/timeline-bundle.js.map +1 -1
  94. package/dist/lib/timeline-media-export.d.ts +18 -0
  95. package/dist/lib/timeline-media-export.d.ts.map +1 -0
  96. package/dist/lib/timeline-media-export.js +66 -0
  97. package/dist/lib/timeline-media-export.js.map +1 -0
  98. package/dist/lib/timeline-premiere-import.d.ts +24 -0
  99. package/dist/lib/timeline-premiere-import.d.ts.map +1 -0
  100. package/dist/lib/timeline-premiere-import.js +102 -0
  101. package/dist/lib/timeline-premiere-import.js.map +1 -0
  102. package/dist/lib/timeline-premiere.d.ts +38 -0
  103. package/dist/lib/timeline-premiere.d.ts.map +1 -0
  104. package/dist/lib/timeline-premiere.js +87 -0
  105. package/dist/lib/timeline-premiere.js.map +1 -0
  106. package/dist/mcp-agent-tools.d.ts.map +1 -1
  107. package/dist/mcp-agent-tools.js +2 -0
  108. package/dist/mcp-agent-tools.js.map +1 -1
  109. package/dist/mcp-server.d.ts +7 -0
  110. package/dist/mcp-server.d.ts.map +1 -1
  111. package/dist/mcp-server.js +163 -16
  112. package/dist/mcp-server.js.map +1 -1
  113. package/dist/mcp-skill-prompts.d.ts +23 -0
  114. package/dist/mcp-skill-prompts.d.ts.map +1 -0
  115. package/dist/mcp-skill-prompts.js +91 -0
  116. package/dist/mcp-skill-prompts.js.map +1 -0
  117. package/dist/mcp-tool-deps.d.ts.map +1 -1
  118. package/dist/mcp-tool-deps.js +8 -1
  119. package/dist/mcp-tool-deps.js.map +1 -1
  120. package/dist/models-api.d.ts.map +1 -1
  121. package/dist/models-api.js +6 -2
  122. package/dist/models-api.js.map +1 -1
  123. package/dist/oauth-api.d.ts.map +1 -1
  124. package/dist/oauth-api.js +45 -23
  125. package/dist/oauth-api.js.map +1 -1
  126. package/dist/openai-api.d.ts.map +1 -1
  127. package/dist/openai-api.js +10 -2
  128. package/dist/openai-api.js.map +1 -1
  129. package/dist/plugins/websocket.d.ts.map +1 -1
  130. package/dist/plugins/websocket.js +7 -2
  131. package/dist/plugins/websocket.js.map +1 -1
  132. package/dist/routes/applications.d.ts +1 -0
  133. package/dist/routes/applications.d.ts.map +1 -1
  134. package/dist/routes/applications.js +24 -0
  135. package/dist/routes/applications.js.map +1 -1
  136. package/dist/routes/assets.d.ts.map +1 -1
  137. package/dist/routes/assets.js +31 -41
  138. package/dist/routes/assets.js.map +1 -1
  139. package/dist/routes/config.d.ts.map +1 -1
  140. package/dist/routes/config.js +6 -0
  141. package/dist/routes/config.js.map +1 -1
  142. package/dist/routes/documents.d.ts.map +1 -1
  143. package/dist/routes/documents.js +24 -0
  144. package/dist/routes/documents.js.map +1 -1
  145. package/dist/routes/fal-pricing-estimate.d.ts.map +1 -1
  146. package/dist/routes/fal-pricing-estimate.js +2 -0
  147. package/dist/routes/fal-pricing-estimate.js.map +1 -1
  148. package/dist/routes/files.d.ts.map +1 -1
  149. package/dist/routes/files.js +2 -0
  150. package/dist/routes/files.js.map +1 -1
  151. package/dist/routes/games.d.ts +38 -0
  152. package/dist/routes/games.d.ts.map +1 -0
  153. package/dist/routes/games.js +158 -0
  154. package/dist/routes/games.js.map +1 -0
  155. package/dist/routes/integrations.d.ts.map +1 -1
  156. package/dist/routes/integrations.js +22 -26
  157. package/dist/routes/integrations.js.map +1 -1
  158. package/dist/routes/js-scripts.d.ts +1 -1
  159. package/dist/routes/js-scripts.d.ts.map +1 -1
  160. package/dist/routes/js-scripts.js +1 -1
  161. package/dist/routes/js-scripts.js.map +1 -1
  162. package/dist/routes/nodes.d.ts.map +1 -1
  163. package/dist/routes/nodes.js +2 -0
  164. package/dist/routes/nodes.js.map +1 -1
  165. package/dist/routes/storage.d.ts.map +1 -1
  166. package/dist/routes/storage.js +2 -0
  167. package/dist/routes/storage.js.map +1 -1
  168. package/dist/routes/storyboards.d.ts.map +1 -1
  169. package/dist/routes/storyboards.js +6 -3
  170. package/dist/routes/storyboards.js.map +1 -1
  171. package/dist/routes/timelines.d.ts +8 -4
  172. package/dist/routes/timelines.d.ts.map +1 -1
  173. package/dist/routes/timelines.js +209 -16
  174. package/dist/routes/timelines.js.map +1 -1
  175. package/dist/screenshot-docs-seed.d.ts +24 -0
  176. package/dist/screenshot-docs-seed.d.ts.map +1 -0
  177. package/dist/screenshot-docs-seed.js +414 -0
  178. package/dist/screenshot-docs-seed.js.map +1 -0
  179. package/dist/screenshot-projects.d.ts.map +1 -1
  180. package/dist/screenshot-projects.js +9 -9
  181. package/dist/screenshot-projects.js.map +1 -1
  182. package/dist/screenshot-server.js +111 -16
  183. package/dist/screenshot-server.js.map +1 -1
  184. package/dist/server.js +87 -90
  185. package/dist/server.js.map +1 -1
  186. package/dist/session/chat-turn.d.ts +10 -0
  187. package/dist/session/chat-turn.d.ts.map +1 -1
  188. package/dist/session/chat-turn.js +68 -10
  189. package/dist/session/chat-turn.js.map +1 -1
  190. package/dist/session/commands.d.ts +14 -0
  191. package/dist/session/commands.d.ts.map +1 -1
  192. package/dist/session/commands.js +113 -14
  193. package/dist/session/commands.js.map +1 -1
  194. package/dist/session/inference.d.ts +25 -2
  195. package/dist/session/inference.d.ts.map +1 -1
  196. package/dist/session/inference.js +86 -8
  197. package/dist/session/inference.js.map +1 -1
  198. package/dist/session/job-execution.d.ts +0 -1
  199. package/dist/session/job-execution.d.ts.map +1 -1
  200. package/dist/session/job-execution.js +34 -37
  201. package/dist/session/job-execution.js.map +1 -1
  202. package/dist/storage-api.d.ts +0 -4
  203. package/dist/storage-api.d.ts.map +1 -1
  204. package/dist/storage-api.js +3 -26
  205. package/dist/storage-api.js.map +1 -1
  206. package/dist/test-ui-server.d.ts +11 -0
  207. package/dist/test-ui-server.d.ts.map +1 -1
  208. package/dist/test-ui-server.js +63 -3
  209. package/dist/test-ui-server.js.map +1 -1
  210. package/dist/triggers/dispatcher.d.ts.map +1 -1
  211. package/dist/triggers/dispatcher.js +24 -4
  212. package/dist/triggers/dispatcher.js.map +1 -1
  213. package/dist/triggers/file-watch.d.ts.map +1 -1
  214. package/dist/triggers/file-watch.js +35 -19
  215. package/dist/triggers/file-watch.js.map +1 -1
  216. package/dist/triggers/scheduler.d.ts.map +1 -1
  217. package/dist/triggers/scheduler.js +7 -5
  218. package/dist/triggers/scheduler.js.map +1 -1
  219. package/dist/trpc/error-formatter.d.ts.map +1 -1
  220. package/dist/trpc/error-formatter.js +2 -1
  221. package/dist/trpc/error-formatter.js.map +1 -1
  222. package/dist/trpc/routers/app-instances.d.ts +63 -0
  223. package/dist/trpc/routers/app-instances.d.ts.map +1 -1
  224. package/dist/trpc/routers/app-runs.d.ts +28 -0
  225. package/dist/trpc/routers/app-runs.d.ts.map +1 -1
  226. package/dist/trpc/routers/applications.d.ts +7 -0
  227. package/dist/trpc/routers/applications.d.ts.map +1 -1
  228. package/dist/trpc/routers/assets.d.ts +8 -0
  229. package/dist/trpc/routers/assets.d.ts.map +1 -1
  230. package/dist/trpc/routers/assets.js +16 -5
  231. package/dist/trpc/routers/assets.js.map +1 -1
  232. package/dist/trpc/routers/collections.d.ts.map +1 -1
  233. package/dist/trpc/routers/collections.js +16 -10
  234. package/dist/trpc/routers/collections.js.map +1 -1
  235. package/dist/trpc/routers/credits.d.ts.map +1 -1
  236. package/dist/trpc/routers/credits.js +11 -1
  237. package/dist/trpc/routers/credits.js.map +1 -1
  238. package/dist/trpc/routers/extension.d.ts +1 -1
  239. package/dist/trpc/routers/extension.d.ts.map +1 -1
  240. package/dist/trpc/routers/extension.js +8 -4
  241. package/dist/trpc/routers/extension.js.map +1 -1
  242. package/dist/trpc/routers/gameDraftHistory.d.ts +1 -2
  243. package/dist/trpc/routers/gameDraftHistory.d.ts.map +1 -1
  244. package/dist/trpc/routers/gameDraftHistory.js +1 -1
  245. package/dist/trpc/routers/gameDraftHistory.js.map +1 -1
  246. package/dist/trpc/routers/games.d.ts +40822 -5154
  247. package/dist/trpc/routers/games.d.ts.map +1 -1
  248. package/dist/trpc/routers/games.js +317 -140
  249. package/dist/trpc/routers/games.js.map +1 -1
  250. package/dist/trpc/routers/jobs.d.ts +7 -0
  251. package/dist/trpc/routers/jobs.d.ts.map +1 -1
  252. package/dist/trpc/routers/jobs.js +5 -3
  253. package/dist/trpc/routers/jobs.js.map +1 -1
  254. package/dist/trpc/routers/js-scripts.d.ts.map +1 -1
  255. package/dist/trpc/routers/js-scripts.js +18 -2
  256. package/dist/trpc/routers/js-scripts.js.map +1 -1
  257. package/dist/trpc/routers/messages.d.ts +10 -0
  258. package/dist/trpc/routers/messages.d.ts.map +1 -1
  259. package/dist/trpc/routers/messages.js +16 -2
  260. package/dist/trpc/routers/messages.js.map +1 -1
  261. package/dist/trpc/routers/models.d.ts.map +1 -1
  262. package/dist/trpc/routers/models.js +20 -30
  263. package/dist/trpc/routers/models.js.map +1 -1
  264. package/dist/trpc/routers/packs.d.ts.map +1 -1
  265. package/dist/trpc/routers/packs.js +18 -4
  266. package/dist/trpc/routers/packs.js.map +1 -1
  267. package/dist/trpc/routers/projects.d.ts +66 -2
  268. package/dist/trpc/routers/projects.d.ts.map +1 -1
  269. package/dist/trpc/routers/projects.js +19 -2
  270. package/dist/trpc/routers/projects.js.map +1 -1
  271. package/dist/trpc/routers/settings.d.ts.map +1 -1
  272. package/dist/trpc/routers/settings.js +30 -5
  273. package/dist/trpc/routers/settings.js.map +1 -1
  274. package/dist/trpc/routers/sketch.d.ts +30 -0
  275. package/dist/trpc/routers/sketch.d.ts.map +1 -1
  276. package/dist/trpc/routers/sketch.js +2 -2
  277. package/dist/trpc/routers/sketch.js.map +1 -1
  278. package/dist/trpc/routers/storyboards.d.ts +171 -1
  279. package/dist/trpc/routers/storyboards.d.ts.map +1 -1
  280. package/dist/trpc/routers/storyboards.js +8 -5
  281. package/dist/trpc/routers/storyboards.js.map +1 -1
  282. package/dist/trpc/routers/threads.d.ts.map +1 -1
  283. package/dist/trpc/routers/threads.js +8 -2
  284. package/dist/trpc/routers/threads.js.map +1 -1
  285. package/dist/trpc/routers/timeline.d.ts +15 -0
  286. package/dist/trpc/routers/timeline.d.ts.map +1 -1
  287. package/dist/trpc/routers/timeline.js +15 -2
  288. package/dist/trpc/routers/timeline.js.map +1 -1
  289. package/dist/trpc/routers/triggers.d.ts.map +1 -1
  290. package/dist/trpc/routers/triggers.js +18 -3
  291. package/dist/trpc/routers/triggers.js.map +1 -1
  292. package/dist/trpc/routers/users.d.ts +3 -2
  293. package/dist/trpc/routers/users.d.ts.map +1 -1
  294. package/dist/trpc/routers/users.js +25 -27
  295. package/dist/trpc/routers/users.js.map +1 -1
  296. package/dist/trpc/routers/worker.d.ts.map +1 -1
  297. package/dist/trpc/routers/worker.js +37 -14
  298. package/dist/trpc/routers/worker.js.map +1 -1
  299. package/dist/trpc/routers/workflows.d.ts +146 -0
  300. package/dist/trpc/routers/workflows.d.ts.map +1 -1
  301. package/dist/trpc/routers/workflows.js +52 -13
  302. package/dist/trpc/routers/workflows.js.map +1 -1
  303. package/dist/trpc/sandbox-coverage.d.ts.map +1 -1
  304. package/dist/trpc/sandbox-coverage.js +29 -0
  305. package/dist/trpc/sandbox-coverage.js.map +1 -1
  306. package/dist/websocket-client-session.d.ts +2 -2
  307. package/dist/websocket-client-session.d.ts.map +1 -1
  308. package/dist/websocket-client-session.js +9 -3
  309. package/dist/websocket-client-session.js.map +1 -1
  310. package/dist/worker-settings-env.d.ts +23 -0
  311. package/dist/worker-settings-env.d.ts.map +1 -0
  312. package/dist/worker-settings-env.js +47 -0
  313. package/dist/worker-settings-env.js.map +1 -0
  314. package/dist/workflow-interface-service.d.ts +7 -0
  315. package/dist/workflow-interface-service.d.ts.map +1 -1
  316. package/package.json +44 -42
@@ -1,6 +1,6 @@
1
1
  import { registerChatRunTrace, registerWorkflowRunTrace, withRegisteredRunTrace, settleRegisteredRunTrace } from "@nodetool-ai/execution";
2
2
  import { getRunTraceScope, suppressRunTraceContent } from "@nodetool-ai/runtime";
3
- import { TRACE_RESTRICTED_CAPABILITY_MODULES } from "@nodetool-ai/protocol";
3
+ import { TRACE_RESTRICTED_CAPABILITY_MODULES, isFullResourceId } from "@nodetool-ai/protocol";
4
4
  import { trace, SpanStatusCode } from "@opentelemetry/api";
5
5
  import { settleRunTrace, registerRunTraceParent, registerRunTraceParents, threadHasTraceContentTools } from "@nodetool-ai/models";
6
6
  import { capabilityModuleOf, listCapabilitySpecs } from "@nodetool-ai/agents";
@@ -8,6 +8,7 @@ import { createLogger } from "@nodetool-ai/config";
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { SUPERSEDED_TOOL_RESULT, repairOrphanedToolCalls } from "../chat-tool-call-repair.js";
10
10
  import { attachChatPredictionForwarder } from "../chat-prediction-forwarder.js";
11
+ import { admitSpend } from "../credit-gate.js";
11
12
  import { isGoogleWorkspaceEnabled } from "@nodetool-ai/config";
12
13
  import { isFunctionValue, isNonEmptyString, isNumber, isObjectLike, isRecord, isString } from "../lib/wire-values.js";
13
14
  import { storeAssetWithThumbnail } from "../lib/thumbnail.js";
@@ -248,6 +249,27 @@ const MEMORY_BLOCK_LIMIT = 100;
248
249
  * full thread load.
249
250
  */
250
251
  const SESSION_PROBE_WINDOW = 50;
252
+ /** The most rows of a thread a turn loads as its history. */
253
+ const THREAD_HISTORY_LIMIT = 1000;
254
+ /**
255
+ * The thread's newest rows, oldest first. An ascending page would return the
256
+ * oldest rows instead, and a thread past the limit would then lose its latest
257
+ * turns, the user message just sent among them.
258
+ *
259
+ * A window cut by the limit starts at its first user row, so it never opens on
260
+ * a tool result whose call fell outside it.
261
+ */
262
+ export async function loadRecentThreadRows(threadId, limit = THREAD_HISTORY_LIMIT) {
263
+ const [newestFirst] = await Message.paginate(threadId, {
264
+ reverse: true,
265
+ limit
266
+ });
267
+ const rows = newestFirst.reverse();
268
+ if (rows.length < limit)
269
+ return rows;
270
+ const firstUser = rows.findIndex((row) => row.role === "user");
271
+ return firstUser > 0 ? rows.slice(firstUser) : rows;
272
+ }
251
273
  /**
252
274
  * Output-token worst case assumed when reserving a chat turn. The chat loop
253
275
  * sends no `maxTokens`, so nothing bounds the answer from this side; the
@@ -505,6 +527,12 @@ export class ChatTurnHandler {
505
527
  const existing = await Thread.find(userId, threadId);
506
528
  if (existing)
507
529
  return existing.id;
530
+ // `create` upserts on the primary key, so creating under an id another
531
+ // user's thread already holds would reassign that thread (and its
532
+ // history) to this caller.
533
+ if (await Thread.get(threadId)) {
534
+ throw new Error("Thread not found");
535
+ }
508
536
  const thread = await Thread.create({
509
537
  id: threadId,
510
538
  user_id: userId,
@@ -518,14 +546,24 @@ export class ChatTurnHandler {
518
546
  * Save a message dict to the database.
519
547
  * Mirrors Python's _save_message_to_db_async: pops id, type, user_id before create.
520
548
  */
521
- async saveMessageToDb(messageData) {
549
+ async saveMessageToDb(messageData, options = {}) {
522
550
  const data = { ...messageData };
551
+ const clientId = data.id;
523
552
  delete data.id;
524
553
  delete data.type;
525
554
  const threadId = isString(data.thread_id) ? data.thread_id : "";
526
555
  delete data.thread_id;
527
556
  const userId = this.session.requireUserId();
528
557
  delete data.user_id;
558
+ // The user's own turn keeps the id the client minted for it, so the client
559
+ // can name that turn later (Regenerate, edit a sent message) without a
560
+ // round-trip. Anything that is not a fresh 32-hex id is replaced.
561
+ if (options.keepClientId &&
562
+ isString(clientId) &&
563
+ isFullResourceId(clientId) &&
564
+ !(await Message.find(clientId))) {
565
+ data.id = clientId;
566
+ }
529
567
  const message = await Message.create({ thread_id: threadId, user_id: userId, ...data });
530
568
  const scope = getRunTraceScope();
531
569
  if (scope) {
@@ -1083,7 +1121,7 @@ export class ChatTurnHandler {
1083
1121
  const userId = this.session.requireUserId();
1084
1122
  log.debug("Chat message", { threadId, model, provider: providerId });
1085
1123
  // Save user message to DB — matches Python's _save_message_to_db_async(data)
1086
- const turnMessage = await this.saveMessageToDb(data);
1124
+ const turnMessage = await this.saveMessageToDb(data, { keepClientId: true });
1087
1125
  const rootContext = createRuntimeContext({ jobId: turnMessage.id, threadId, workflowId, userId, workspace: null });
1088
1126
  await registerChatRunTrace(rootContext, { messageId: turnMessage.id, threadId });
1089
1127
  return withRegisteredRunTrace(rootContext, "chat.turn", async () => {
@@ -1117,6 +1155,21 @@ export class ChatTurnHandler {
1117
1155
  });
1118
1156
  return;
1119
1157
  }
1158
+ // The managed provider spends NodeTool's own keys against the user's
1159
+ // credits. Every other managed entry point admits against the balance and
1160
+ // the operator's model whitelist first, and so does a chat turn.
1161
+ if (providerId === "nodetool") {
1162
+ const decision = await admitSpend(userId, 0, [model]);
1163
+ if (!decision.allowed) {
1164
+ await settleRegisteredRunTrace(rootContext, "failed", decision.reason);
1165
+ await this.session.send({
1166
+ type: "error",
1167
+ message: decision.reason,
1168
+ thread_id: threadId
1169
+ });
1170
+ return;
1171
+ }
1172
+ }
1120
1173
  // Route to the workflow processor ONLY when the client explicitly opts in
1121
1174
  // via `workflow_target: "workflow"`. A bare `workflow_id` is context, not a
1122
1175
  // routing signal: the editor binds the open workflow so `ui_*` tools target
@@ -1127,7 +1180,7 @@ export class ChatTurnHandler {
1127
1180
  ? data.workflow_target
1128
1181
  : null;
1129
1182
  if (workflowTarget === "workflow") {
1130
- await this.handleWorkflowMessage(data, requestSeq, signal);
1183
+ await this.handleWorkflowMessage(data, turnMessage.id, requestSeq, signal);
1131
1184
  return;
1132
1185
  }
1133
1186
  // Route to media generation when the client requests a text-to-image or
@@ -1285,7 +1338,7 @@ export class ChatTurnHandler {
1285
1338
  sessionCheckpointOverride =
1286
1339
  probeSession.checkpoint + 1 + newTurns.length;
1287
1340
  loadFullHistory = async () => {
1288
- const [rows] = await Message.paginate(threadId, { limit: 1000 });
1341
+ const rows = await loadRecentThreadRows(threadId);
1289
1342
  const full = convertDbMessages(historySinceCompaction(rows));
1290
1343
  full.unshift(systemChatMessage());
1291
1344
  const scope = getRunTraceScope();
@@ -1306,7 +1359,7 @@ export class ChatTurnHandler {
1306
1359
  // all (a far-back session still resumes via the slice path). A session
1307
1360
  // older than the compaction cut is not one of them: searching only the
1308
1361
  // compacted slice is what keeps the newest marker winning here too.
1309
- const [rows] = await Message.paginate(threadId, { limit: 1000 });
1362
+ const rows = await loadRecentThreadRows(threadId);
1310
1363
  const kept = historySinceCompaction(rows);
1311
1364
  chatHistory = convertDbMessages(kept);
1312
1365
  priorSession = lastMatchingProviderSession(kept, providerId, model);
@@ -2063,7 +2116,7 @@ export class ChatTurnHandler {
2063
2116
  * turn that cannot run at all.
2064
2117
  */
2065
2118
  const compactThread = async (reason) => {
2066
- const [rows] = await Message.paginate(threadId, { limit: 1000 });
2119
+ const rows = await loadRecentThreadRows(threadId);
2067
2120
  const cut = chooseCompactionCut(historySinceCompaction(rows), compaction.keepUserTurns, {
2068
2121
  // Leave room for the summary, turn context and subsequent replies.
2069
2122
  maxTokens: compactionThresholdTokens * 0.75 -
@@ -3214,7 +3267,7 @@ export class ChatTurnHandler {
3214
3267
  * 6. Collect output_update results
3215
3268
  * 7. Send response message with typed content, then the terminal chunk
3216
3269
  */
3217
- async handleWorkflowMessage(data, requestSeq, signal) {
3270
+ async handleWorkflowMessage(data, turnMessageId, requestSeq, signal) {
3218
3271
  const threadId = isString(data.thread_id) ? data.thread_id : "";
3219
3272
  const workflowId = isString(data.workflow_id) ? data.workflow_id : null;
3220
3273
  const providerId = isString(data.provider)
@@ -3256,7 +3309,7 @@ export class ChatTurnHandler {
3256
3309
  messagesName ??
3257
3310
  "messages";
3258
3311
  // Build chat history for params — matches Python
3259
- const [dbMessages] = await Message.paginate(threadId, { limit: 1000 });
3312
+ const dbMessages = await loadRecentThreadRows(threadId);
3260
3313
  const inheritedTrace = getRunTraceScope();
3261
3314
  seedStoredToolProvenance(dbMessages);
3262
3315
  if (inheritedTrace) {
@@ -3280,7 +3333,12 @@ export class ChatTurnHandler {
3280
3333
  // Prepare params — matches Python's WorkflowMessageProcessor
3281
3334
  const params = {
3282
3335
  [messageInputName]: currentMessage,
3283
- [messagesInputName]: [...chatHistorySerialized, currentMessage]
3336
+ // The stored history already holds this turn's message; it is appended
3337
+ // once, as `currentMessage`.
3338
+ [messagesInputName]: [
3339
+ ...chatHistorySerialized.filter((_, i) => dbMessages[i].id !== turnMessageId),
3340
+ currentMessage
3341
+ ]
3284
3342
  };
3285
3343
  if (isObjectLike(data.params)) {
3286
3344
  Object.assign(params, data.params);