@trigger.dev/sdk 4.5.0 → 4.5.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.
Files changed (130) hide show
  1. package/dist/commonjs/v3/ai.d.ts +7 -10
  2. package/dist/commonjs/v3/ai.js +77 -44
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/auth.d.ts +1 -1
  5. package/dist/commonjs/v3/auth.js +3 -4
  6. package/dist/commonjs/v3/auth.js.map +1 -1
  7. package/dist/commonjs/v3/batch.d.ts +1 -1
  8. package/dist/commonjs/v3/batch.js.map +1 -1
  9. package/dist/commonjs/v3/chat-react.d.ts +1 -0
  10. package/dist/commonjs/v3/chat-react.js +5 -2
  11. package/dist/commonjs/v3/chat-react.js.map +1 -1
  12. package/dist/commonjs/v3/chat.d.ts +100 -0
  13. package/dist/commonjs/v3/chat.js +203 -19
  14. package/dist/commonjs/v3/chat.js.map +1 -1
  15. package/dist/commonjs/v3/chat.test.js +3 -30
  16. package/dist/commonjs/v3/chat.test.js.map +1 -1
  17. package/dist/commonjs/v3/deployments.d.ts +2 -3
  18. package/dist/commonjs/v3/deployments.js.map +1 -1
  19. package/dist/commonjs/v3/envvars.js.map +1 -1
  20. package/dist/commonjs/v3/index.d.ts +1 -1
  21. package/dist/commonjs/v3/index.js.map +1 -1
  22. package/dist/commonjs/v3/metadata.d.ts +2 -2
  23. package/dist/commonjs/v3/query.js.map +1 -1
  24. package/dist/commonjs/v3/queues.d.ts +1 -1
  25. package/dist/commonjs/v3/queues.js.map +1 -1
  26. package/dist/commonjs/v3/retry.d.ts +1 -1
  27. package/dist/commonjs/v3/retry.js +1 -1
  28. package/dist/commonjs/v3/retry.js.map +1 -1
  29. package/dist/commonjs/v3/runs-bulk.test.d.ts +1 -0
  30. package/dist/commonjs/v3/runs-bulk.test.js +141 -0
  31. package/dist/commonjs/v3/runs-bulk.test.js.map +1 -0
  32. package/dist/commonjs/v3/runs.d.ts +20 -4
  33. package/dist/commonjs/v3/runs.js +87 -0
  34. package/dist/commonjs/v3/runs.js.map +1 -1
  35. package/dist/commonjs/v3/schedules/index.d.ts +3 -3
  36. package/dist/commonjs/v3/schedules/index.js.map +1 -1
  37. package/dist/commonjs/v3/sessions.d.ts +3 -4
  38. package/dist/commonjs/v3/sessions.js +6 -3
  39. package/dist/commonjs/v3/sessions.js.map +1 -1
  40. package/dist/commonjs/v3/shared.d.ts +5 -6
  41. package/dist/commonjs/v3/shared.js +79 -16
  42. package/dist/commonjs/v3/shared.js.map +1 -1
  43. package/dist/commonjs/v3/shared.test.js +37 -0
  44. package/dist/commonjs/v3/shared.test.js.map +1 -1
  45. package/dist/commonjs/v3/streams.d.ts +1 -1
  46. package/dist/commonjs/v3/streams.js +2 -2
  47. package/dist/commonjs/v3/streams.js.map +1 -1
  48. package/dist/commonjs/v3/streams.test.js +3 -3
  49. package/dist/commonjs/v3/streams.test.js.map +1 -1
  50. package/dist/commonjs/v3/test/test-session-handle.d.ts +2 -1
  51. package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
  52. package/dist/commonjs/v3/triggerClient.types.test.js.map +1 -1
  53. package/dist/commonjs/v3/wait.d.ts +2 -1
  54. package/dist/commonjs/v3/wait.js.map +1 -1
  55. package/dist/commonjs/v3/webhooks.js +1 -1
  56. package/dist/commonjs/v3/webhooks.js.map +1 -1
  57. package/dist/commonjs/version.js +1 -1
  58. package/dist/esm/v3/ai.d.ts +7 -10
  59. package/dist/esm/v3/ai.js +75 -32
  60. package/dist/esm/v3/ai.js.map +1 -1
  61. package/dist/esm/v3/auth.d.ts +1 -1
  62. package/dist/esm/v3/auth.js +1 -2
  63. package/dist/esm/v3/auth.js.map +1 -1
  64. package/dist/esm/v3/batch.d.ts +1 -1
  65. package/dist/esm/v3/batch.js +1 -1
  66. package/dist/esm/v3/batch.js.map +1 -1
  67. package/dist/esm/v3/chat-react.d.ts +1 -0
  68. package/dist/esm/v3/chat-react.js +5 -2
  69. package/dist/esm/v3/chat-react.js.map +1 -1
  70. package/dist/esm/v3/chat.d.ts +100 -0
  71. package/dist/esm/v3/chat.js +204 -20
  72. package/dist/esm/v3/chat.js.map +1 -1
  73. package/dist/esm/v3/chat.test.js +3 -30
  74. package/dist/esm/v3/chat.test.js.map +1 -1
  75. package/dist/esm/v3/deployments.d.ts +2 -3
  76. package/dist/esm/v3/deployments.js +1 -1
  77. package/dist/esm/v3/deployments.js.map +1 -1
  78. package/dist/esm/v3/envvars.js.map +1 -1
  79. package/dist/esm/v3/index.d.ts +1 -1
  80. package/dist/esm/v3/index.js.map +1 -1
  81. package/dist/esm/v3/metadata.d.ts +2 -2
  82. package/dist/esm/v3/query.js.map +1 -1
  83. package/dist/esm/v3/queues.d.ts +1 -1
  84. package/dist/esm/v3/queues.js.map +1 -1
  85. package/dist/esm/v3/retry.d.ts +1 -1
  86. package/dist/esm/v3/retry.js +1 -1
  87. package/dist/esm/v3/retry.js.map +1 -1
  88. package/dist/esm/v3/runs-bulk.test.d.ts +1 -0
  89. package/dist/esm/v3/runs-bulk.test.js +139 -0
  90. package/dist/esm/v3/runs-bulk.test.js.map +1 -0
  91. package/dist/esm/v3/runs.d.ts +19 -3
  92. package/dist/esm/v3/runs.js +87 -0
  93. package/dist/esm/v3/runs.js.map +1 -1
  94. package/dist/esm/v3/schedules/index.d.ts +3 -3
  95. package/dist/esm/v3/schedules/index.js.map +1 -1
  96. package/dist/esm/v3/sessions.d.ts +3 -4
  97. package/dist/esm/v3/sessions.js +6 -3
  98. package/dist/esm/v3/sessions.js.map +1 -1
  99. package/dist/esm/v3/shared.d.ts +5 -6
  100. package/dist/esm/v3/shared.js +79 -17
  101. package/dist/esm/v3/shared.js.map +1 -1
  102. package/dist/esm/v3/shared.test.js +38 -1
  103. package/dist/esm/v3/shared.test.js.map +1 -1
  104. package/dist/esm/v3/streams.d.ts +1 -1
  105. package/dist/esm/v3/streams.js +2 -2
  106. package/dist/esm/v3/streams.js.map +1 -1
  107. package/dist/esm/v3/streams.test.js +4 -4
  108. package/dist/esm/v3/streams.test.js.map +1 -1
  109. package/dist/esm/v3/test/test-session-handle.d.ts +2 -1
  110. package/dist/esm/v3/test/test-session-handle.js +1 -1
  111. package/dist/esm/v3/test/test-session-handle.js.map +1 -1
  112. package/dist/esm/v3/triggerClient.types.test.js.map +1 -1
  113. package/dist/esm/v3/wait.d.ts +2 -1
  114. package/dist/esm/v3/wait.js.map +1 -1
  115. package/dist/esm/v3/webhooks.js +1 -1
  116. package/dist/esm/v3/webhooks.js.map +1 -1
  117. package/dist/esm/version.js +1 -1
  118. package/docs/ai-chat/frontend.mdx +46 -0
  119. package/docs/ai-chat/pending-messages.mdx +3 -1
  120. package/docs/ai-chat/reference.mdx +20 -0
  121. package/docs/billing-limits.mdx +73 -0
  122. package/docs/how-to-reduce-your-spend.mdx +1 -14
  123. package/docs/limits.mdx +5 -0
  124. package/docs/self-hosting/docker.mdx +24 -1
  125. package/docs/self-hosting/env/webapp.mdx +6 -1
  126. package/docs/self-hosting/kubernetes.mdx +34 -8
  127. package/docs/self-hosting/overview.mdx +4 -1
  128. package/docs/self-hosting/security.mdx +50 -0
  129. package/docs/troubleshooting.mdx +4 -0
  130. package/package.json +2 -2
@@ -1,5 +1,9 @@
1
- import { AnyTask, type MachinePresetName, type RealtimeDefinedInputStream, type RealtimeDefinedStream, type ApiClientConfiguration, Task, type inferSchemaIn, type inferSchemaOut, type TaskIdentifier, type TaskOptions, type TaskSchema, type TaskRunContext, type TaskWithSchema } from "@trigger.dev/core/v3";
1
+ import { type AnyTask, type Task, type ChatSnapshotV1, type ApiClientConfiguration, type inferSchemaIn, type inferSchemaOut, type MachinePresetName, type RealtimeDefinedInputStream, type RealtimeDefinedStream, type SessionTriggerConfig, type TaskIdentifier, type TaskOptions, type TaskRunContext, type TaskSchema, type TaskWithSchema } from "@trigger.dev/core/v3";
2
2
  import type { FinishReason, LanguageModelUsage, ModelMessage, ProviderMetadata, Tool, ToolSet, UIMessage, UIMessageChunk, UIMessageStreamOptions } from "ai";
3
+ import { type ChatInputChunk, type ChatTaskWirePayload, type InferChatClientData, type InferChatUIMessage, type InferChatUIMessageFromTools, PENDING_MESSAGE_INJECTED_TYPE, upsertIncomingMessage } from "./ai-shared.js";
4
+ import { locals } from "./locals.js";
5
+ import type { ResolvedPrompt } from "./prompt.js";
6
+ import type { ResolvedSkill } from "./skill.js";
3
7
  type ToolCallOptions = {
4
8
  toolCallId: string;
5
9
  messages?: ModelMessage[];
@@ -7,10 +11,6 @@ type ToolCallOptions = {
7
11
  experimental_context?: unknown;
8
12
  context?: unknown;
9
13
  };
10
- import { locals } from "./locals.js";
11
- import type { ResolvedPrompt } from "./prompt.js";
12
- import type { ResolvedSkill } from "./skill.js";
13
- import { type SessionTriggerConfig } from "@trigger.dev/core/v3";
14
14
  /** Re-export for typing `ctx` in `chat.agent` hooks without importing `@trigger.dev/core`. */
15
15
  export type { TaskRunContext } from "@trigger.dev/core/v3";
16
16
  export type ToolCallExecutionOptions = {
@@ -171,8 +171,6 @@ export type ChatWriter = {
171
171
  /** Merge another stream's chunks into the chat stream. */
172
172
  merge(stream: ReadableStream<UIMessageChunk>): void;
173
173
  };
174
- import type { ChatTaskWirePayload } from "./ai-shared.js";
175
- export type { ChatTaskWirePayload, ChatInputChunk } from "./ai-shared.js";
176
174
  /**
177
175
  * The payload shape passed to the `chatAgent` run function.
178
176
  *
@@ -520,7 +518,8 @@ export type PendingMessagesOptions<TUIM extends UIMessage = UIMessage> = {
520
518
  * between tool-call steps. The frontend can match on this to render
521
519
  * injection points inline in the assistant response.
522
520
  */
523
- export { PENDING_MESSAGE_INJECTED_TYPE, upsertIncomingMessage } from "./ai-shared.js";
521
+ export { PENDING_MESSAGE_INJECTED_TYPE, upsertIncomingMessage };
522
+ export type { InferChatClientData, InferChatUIMessage, InferChatUIMessageFromTools };
524
523
  /**
525
524
  * Event passed to the `prepareMessages` hook.
526
525
  */
@@ -2795,8 +2794,6 @@ declare function chatLocal<T extends Record<string, unknown>>(options: {
2795
2794
  * // { model?: string; userId: string }
2796
2795
  * ```
2797
2796
  */
2798
- import type { InferChatClientData } from "./ai-shared.js";
2799
- export type { InferChatClientData, InferChatUIMessage, InferChatUIMessageFromTools, } from "./ai-shared.js";
2800
2797
  /**
2801
2798
  * Options for {@link createChatStartSessionAction}.
2802
2799
  */
@@ -15,8 +15,11 @@ exports.buildSkillTools = buildSkillTools;
15
15
  const v3_1 = require("@trigger.dev/core/v3");
16
16
  // Runtime VALUES go through the ESM/CJS shim so the CJS build can `require`
17
17
  // ESM-only `ai@7` (see ../imports/ai-runtime.ts).
18
- const ai_runtime_js_1 = require("../imports/ai-runtime.js");
19
18
  const api_1 = require("@opentelemetry/api");
19
+ const ai_runtime_js_1 = require("../imports/ai-runtime.js");
20
+ const ai_shared_js_1 = require("./ai-shared.js");
21
+ Object.defineProperty(exports, "PENDING_MESSAGE_INJECTED_TYPE", { enumerable: true, get: function () { return ai_shared_js_1.PENDING_MESSAGE_INJECTED_TYPE; } });
22
+ Object.defineProperty(exports, "upsertIncomingMessage", { enumerable: true, get: function () { return ai_shared_js_1.upsertIncomingMessage; } });
20
23
  const auth_js_1 = require("./auth.js");
21
24
  const locals_js_1 = require("./locals.js");
22
25
  const metadata_js_1 = require("./metadata.js");
@@ -29,11 +32,10 @@ const metadata_js_1 = require("./metadata.js");
29
32
  // never touches `ai.ts`'s module graph, so the `node:*` builtins
30
33
  // pulled in transitively here never reach a client chunk.
31
34
  const agentSkillsRuntime_js_1 = require("./agentSkillsRuntime.js");
32
- const streams_js_1 = require("./streams.js");
35
+ const aiAutoTelemetry_js_1 = require("./aiAutoTelemetry.js");
33
36
  const sessions_js_1 = require("./sessions.js");
34
37
  const shared_js_1 = require("./shared.js");
35
- const aiAutoTelemetry_js_1 = require("./aiAutoTelemetry.js");
36
- const v3_2 = require("@trigger.dev/core/v3");
38
+ const streams_js_1 = require("./streams.js");
37
39
  const tracer_js_1 = require("./tracer.js");
38
40
  const METADATA_KEY = "tool.execute.options";
39
41
  /**
@@ -1899,19 +1901,6 @@ const chatUpgradeRequestedKey = locals_js_1.locals.create("chat.upgradeRequested
1899
1901
  const chatEndRunRequestedKey = locals_js_1.locals.create("chat.endRunRequested");
1900
1902
  /** @internal */
1901
1903
  const chatAgentCompactionKey = locals_js_1.locals.create("chat.agentCompaction");
1902
- /**
1903
- * The data part type used to signal that pending messages were injected
1904
- * between tool-call steps. The frontend can match on this to render
1905
- * injection points inline in the assistant response.
1906
- */
1907
- // `PENDING_MESSAGE_INJECTED_TYPE` lives in `./ai-shared.ts` so the chat
1908
- // React hooks (`@trigger.dev/sdk/chat/react`) can import it without
1909
- // dragging `ai.ts` into the browser graph. Re-exported here so
1910
- // `@trigger.dev/sdk/ai` consumers still see it.
1911
- var ai_shared_js_1 = require("./ai-shared.js");
1912
- Object.defineProperty(exports, "PENDING_MESSAGE_INJECTED_TYPE", { enumerable: true, get: function () { return ai_shared_js_1.PENDING_MESSAGE_INJECTED_TYPE; } });
1913
- Object.defineProperty(exports, "upsertIncomingMessage", { enumerable: true, get: function () { return ai_shared_js_1.upsertIncomingMessage; } });
1914
- const ai_shared_js_2 = require("./ai-shared.js");
1915
1904
  /** @internal */
1916
1905
  const chatPendingMessagesKey = locals_js_1.locals.create("chat.pendingMessages");
1917
1906
  /** @internal */
@@ -2303,7 +2292,7 @@ async function drainSteeringQueue(config, messages, steps, queueOverride) {
2303
2292
  .filter((p) => p.type === "text")
2304
2293
  .map((p) => p.text)
2305
2294
  .join("") || "");
2306
- const previewText = messageTexts.length === 1 ? messageTexts[0].slice(0, 80) : `${queue.length} messages`;
2295
+ const _previewText = messageTexts.length === 1 ? messageTexts[0].slice(0, 80) : `${queue.length} messages`;
2307
2296
  return tracer_js_1.tracer.startActiveSpan("pending message injected", async () => {
2308
2297
  // Transform the batch — default: concatenate all pre-converted model messages
2309
2298
  const injected = config.prepare
@@ -2324,7 +2313,7 @@ async function drainSteeringQueue(config, messages, steps, queueOverride) {
2324
2313
  collapsed: true,
2325
2314
  execute: ({ write }) => {
2326
2315
  write({
2327
- type: ai_shared_js_2.PENDING_MESSAGE_INJECTED_TYPE,
2316
+ type: ai_shared_js_1.PENDING_MESSAGE_INJECTED_TYPE,
2328
2317
  id: (0, ai_runtime_js_1.generateId)(),
2329
2318
  data: {
2330
2319
  messageIds: uiMessages.map((m) => m.id),
@@ -2817,7 +2806,7 @@ function chatCustomAgent(options) {
2817
2806
  // Register clientDataSchema so the CLI converts it to JSONSchema
2818
2807
  // and stores it as payloadSchema — used by the Playground UI
2819
2808
  if (clientDataSchema) {
2820
- v3_2.resourceCatalog.updateTaskMetadata(options.id, {
2809
+ v3_1.resourceCatalog.updateTaskMetadata(options.id, {
2821
2810
  schema: clientDataSchema,
2822
2811
  });
2823
2812
  }
@@ -2933,6 +2922,11 @@ function chatAgent(options) {
2933
2922
  // default, `inFlightUsers`). The turn-loop checks this queue ahead of
2934
2923
  // `messagesInput.waitWithIdleTimeout` so recovered turns fire first.
2935
2924
  const bootInjectedQueue = [];
2925
+ // Messages consumed by a turn's `messagesInput.on` handler, dispatched
2926
+ // one per turn by the end-of-turn pickup. Loop-level on purpose:
2927
+ // consuming a record advances the committed `.in` cursor, so entries
2928
+ // dropped with a turn-local buffer are lost permanently.
2929
+ const pendingWireMessages = [];
2936
2930
  const couldHavePriorState = payload.continuation === true || ctx.attempt.number > 1;
2937
2931
  // `.in` resume cursor, computed at most once per boot. The boot
2938
2932
  // block below resolves it (snapshot field or records scan) and the
@@ -3639,6 +3633,9 @@ function chatAgent(options) {
3639
3633
  } // end else (no boot-injected first turn)
3640
3634
  }
3641
3635
  for (let turn = 0; turn < maxTurns; turn++) {
3636
+ // Declared here so the finally can detach it — a handler leaked past
3637
+ // its turn duplicates every mid-stream message into the shared buffer.
3638
+ let turnMsgSub;
3642
3639
  try {
3643
3640
  // Extract turn-level context before entering the span. Slim
3644
3641
  // wire: at most one delta message per record. `headStartMessages`
@@ -3714,8 +3711,6 @@ function chatAgent(options) {
3714
3711
  const stopSignal = stopController.signal;
3715
3712
  const cancelSignal = runSignal;
3716
3713
  const combinedSignal = AbortSignal.any([runSignal, stopController.signal]);
3717
- // Buffer messages that arrive during streaming
3718
- const pendingMessages = [];
3719
3714
  const pmConfig = locals_js_1.locals.get(chatPendingMessagesKey);
3720
3715
  const msgSub = messagesInput.on(async (msg) => {
3721
3716
  // If pendingMessages is configured, route to the steering queue
@@ -3762,8 +3757,9 @@ function chatAgent(options) {
3762
3757
  return; // Don't add to wire buffer — frontend handles non-injected case
3763
3758
  }
3764
3759
  // No pendingMessages config — standard wire buffer for next turn
3765
- pendingMessages.push(msg);
3760
+ pendingWireMessages.push(msg);
3766
3761
  });
3762
+ turnMsgSub = msgSub;
3767
3763
  // Track new messages for this turn (user input + assistant response).
3768
3764
  const turnNewModelMessages = [];
3769
3765
  const turnNewUIMessages = [];
@@ -4818,9 +4814,10 @@ function chatAgent(options) {
4818
4814
  return "continue";
4819
4815
  }
4820
4816
  // If messages arrived during streaming (without pendingMessages config),
4821
- // use the first one immediately as the next turn.
4822
- if (pendingMessages.length > 0) {
4823
- currentWirePayload = pendingMessages[0];
4817
+ // dispatch the oldest as the next turn. The rest stay queued
4818
+ // and drain one per turn.
4819
+ if (pendingWireMessages.length > 0) {
4820
+ currentWirePayload = pendingWireMessages.shift();
4824
4821
  return "continue";
4825
4822
  }
4826
4823
  // chat.requestUpgrade() was called — exit the loop so the
@@ -4906,6 +4903,9 @@ function chatAgent(options) {
4906
4903
  // Turn error handler: write an error chunk + turn-complete to the stream
4907
4904
  // so the client sees the error, then wait for the next message instead
4908
4905
  // of killing the entire run. This keeps the conversation alive.
4906
+ // Detach the turn's message handler first — left attached it would
4907
+ // eat the very message the wait below is waiting for.
4908
+ turnMsgSub?.off();
4909
4909
  if (turnError instanceof Error &&
4910
4910
  turnError.name === "AbortError" &&
4911
4911
  runSignal.aborted) {
@@ -5025,6 +5025,11 @@ function chatAgent(options) {
5025
5025
  currentWirePayload = bootInjectedQueue.shift();
5026
5026
  continue;
5027
5027
  }
5028
+ // Same for messages buffered during the errored turn — already consumed, idling strands them.
5029
+ if (pendingWireMessages.length > 0) {
5030
+ currentWirePayload = pendingWireMessages.shift();
5031
+ continue;
5032
+ }
5028
5033
  // Wait for the next message — same as after a successful turn
5029
5034
  const effectiveIdleTimeout = metadata_js_1.metadata.get(IDLE_TIMEOUT_METADATA_KEY) ??
5030
5035
  idleTimeoutInSeconds;
@@ -5040,6 +5045,9 @@ function chatAgent(options) {
5040
5045
  currentWirePayload = next.output;
5041
5046
  // Continue to next iteration of the for loop
5042
5047
  }
5048
+ finally {
5049
+ turnMsgSub?.off();
5050
+ }
5043
5051
  }
5044
5052
  }
5045
5053
  finally {
@@ -5053,7 +5061,7 @@ function chatAgent(options) {
5053
5061
  // Register clientDataSchema so the CLI converts it to JSONSchema
5054
5062
  // and stores it as payloadSchema — used by the Playground UI
5055
5063
  if (clientDataSchema) {
5056
- v3_2.resourceCatalog.updateTaskMetadata(options.id, {
5064
+ v3_1.resourceCatalog.updateTaskMetadata(options.id, {
5057
5065
  schema: clientDataSchema,
5058
5066
  });
5059
5067
  }
@@ -5950,8 +5958,17 @@ function createChatSession(payload, options) {
5950
5958
  const accumulator = new ChatMessageAccumulator();
5951
5959
  let previousTurnUsage;
5952
5960
  let cumulativeUsage = emptyUsage();
5961
+ // Messages consumed mid-turn, dispatched one per next(). Iterator-level
5962
+ // for the same reason as the agent loop's `pendingWireMessages`:
5963
+ // consumed records never replay, so a turn-local buffer loses them.
5964
+ const sessionPendingWire = [];
5965
+ // The current turn's message subscription — detached defensively at the
5966
+ // top of next() in case user code threw without complete()/done().
5967
+ let activeMsgSub;
5953
5968
  return {
5954
5969
  async next() {
5970
+ activeMsgSub?.off();
5971
+ activeMsgSub = undefined;
5955
5972
  if (!booted) {
5956
5973
  booted = true;
5957
5974
  await seedSessionInResumeCursorForCustomLoop(currentPayload);
@@ -6011,23 +6028,29 @@ function createChatSession(payload, options) {
6011
6028
  currentPayload = { ...currentPayload, continuation: true };
6012
6029
  }
6013
6030
  }
6014
- // Subsequent turns: wait for the next message
6031
+ // Subsequent turns: drain buffered mid-turn messages first (they
6032
+ // were consumed and won't be re-delivered), then wait.
6015
6033
  if (turn > 0) {
6016
- // chat.requestUpgrade() / chat.endRun() — exit before waiting
6017
- if (locals_js_1.locals.get(chatUpgradeRequestedKey) || locals_js_1.locals.get(chatEndRunRequestedKey)) {
6018
- stop.cleanup();
6019
- return { done: true, value: undefined };
6034
+ if (sessionPendingWire.length > 0) {
6035
+ currentPayload = sessionPendingWire.shift();
6020
6036
  }
6021
- const next = await messagesInput.waitWithIdleTimeout({
6022
- idleTimeoutInSeconds,
6023
- timeout,
6024
- spanName: "waiting for next message",
6025
- });
6026
- if (!next.ok || runSignal.aborted) {
6027
- stop.cleanup();
6028
- return { done: true, value: undefined };
6037
+ else {
6038
+ // chat.requestUpgrade() / chat.endRun() — exit before waiting
6039
+ if (locals_js_1.locals.get(chatUpgradeRequestedKey) || locals_js_1.locals.get(chatEndRunRequestedKey)) {
6040
+ stop.cleanup();
6041
+ return { done: true, value: undefined };
6042
+ }
6043
+ const next = await messagesInput.waitWithIdleTimeout({
6044
+ idleTimeoutInSeconds,
6045
+ timeout,
6046
+ spanName: "waiting for next message",
6047
+ });
6048
+ if (!next.ok || runSignal.aborted) {
6049
+ stop.cleanup();
6050
+ return { done: true, value: undefined };
6051
+ }
6052
+ currentPayload = next.output;
6029
6053
  }
6030
- currentPayload = next.output;
6031
6054
  }
6032
6055
  // Check limits
6033
6056
  if (turn >= maxTurns || runSignal.aborted) {
@@ -6052,10 +6075,10 @@ function createChatSession(payload, options) {
6052
6075
  clientData: currentPayload.metadata,
6053
6076
  });
6054
6077
  // Listen for messages during streaming (steering + next-turn buffer)
6055
- const sessionPendingWire = [];
6056
6078
  const sessionMsgSub = messagesInput.on(async (msg) => {
6057
- sessionPendingWire.push(msg);
6058
6079
  if (sessionPendingMessages) {
6080
+ // Steering route — the frontend re-sends non-injected
6081
+ // messages on turn complete, so don't also buffer the wire.
6059
6082
  // Slim wire: at most one delta message per record. Read
6060
6083
  // `msg.message` directly — no array slicing needed.
6061
6084
  const lastUIMessage = msg.message;
@@ -6080,15 +6103,18 @@ function createChatSession(payload, options) {
6080
6103
  /* non-fatal */
6081
6104
  }
6082
6105
  }
6106
+ return;
6083
6107
  }
6108
+ sessionPendingWire.push(msg);
6084
6109
  });
6110
+ activeMsgSub = sessionMsgSub;
6085
6111
  // Accumulate messages. Slim wire: pass the single delta message as
6086
6112
  // a 0-or-1-length array. The accumulator's behavior is unchanged —
6087
6113
  // it still appends user messages and reconverts on regenerate.
6088
6114
  const incomingForAccumulator = currentPayload.message
6089
6115
  ? [currentPayload.message]
6090
6116
  : [];
6091
- const messages = await accumulator.addIncoming(incomingForAccumulator, currentPayload.trigger, turn);
6117
+ const _messages = await accumulator.addIncoming(incomingForAccumulator, currentPayload.trigger, turn);
6092
6118
  // Apply the head-start handover AFTER addIncoming — turn-0 addIncoming
6093
6119
  // replaces accumulator state, which would wipe a pre-applied splice.
6094
6120
  // Seed prior history first, then splice the warm step-1 partial.
@@ -6172,6 +6198,11 @@ function createChatSession(payload, options) {
6172
6198
  throw error;
6173
6199
  }
6174
6200
  }
6201
+ finally {
6202
+ // Detach at stream end (like the agent loop): the steering queue
6203
+ // can't inject anymore, so later arrivals must buffer for the next turn.
6204
+ sessionMsgSub.off();
6205
+ }
6175
6206
  if (response) {
6176
6207
  const cleaned = stop.signal.aborted && !runSignal.aborted
6177
6208
  ? cleanupAbortedParts(response)
@@ -6315,6 +6346,8 @@ function createChatSession(payload, options) {
6315
6346
  return { done: false, value: turnObj };
6316
6347
  },
6317
6348
  async return() {
6349
+ activeMsgSub?.off();
6350
+ activeMsgSub = undefined;
6318
6351
  // `stop` only exists once next() has booted the iterator.
6319
6352
  stop?.cleanup();
6320
6353
  return { done: true, value: undefined };