@trigger.dev/sdk 4.5.1 → 4.5.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 (68) hide show
  1. package/dist/commonjs/v3/ai-shared.d.ts +3 -127
  2. package/dist/commonjs/v3/ai-shared.js.map +1 -1
  3. package/dist/commonjs/v3/ai.js +66 -22
  4. package/dist/commonjs/v3/ai.js.map +1 -1
  5. package/dist/commonjs/v3/chat-react.d.ts +1 -0
  6. package/dist/commonjs/v3/chat-react.js +4 -1
  7. package/dist/commonjs/v3/chat-react.js.map +1 -1
  8. package/dist/commonjs/v3/chat.d.ts +204 -1
  9. package/dist/commonjs/v3/chat.js +203 -19
  10. package/dist/commonjs/v3/chat.js.map +1 -1
  11. package/dist/commonjs/v3/index.d.ts +1 -1
  12. package/dist/commonjs/v3/index.js.map +1 -1
  13. package/dist/commonjs/v3/runs-bulk.test.d.ts +1 -0
  14. package/dist/commonjs/v3/runs-bulk.test.js +141 -0
  15. package/dist/commonjs/v3/runs-bulk.test.js.map +1 -0
  16. package/dist/commonjs/v3/runs.d.ts +23 -6
  17. package/dist/commonjs/v3/runs.js +87 -0
  18. package/dist/commonjs/v3/runs.js.map +1 -1
  19. package/dist/commonjs/v3/sessions.js +5 -2
  20. package/dist/commonjs/v3/sessions.js.map +1 -1
  21. package/dist/commonjs/v3/shared.d.ts +2 -2
  22. package/dist/commonjs/v3/shared.js +79 -4
  23. package/dist/commonjs/v3/shared.js.map +1 -1
  24. package/dist/commonjs/v3/shared.test.js +37 -0
  25. package/dist/commonjs/v3/shared.test.js.map +1 -1
  26. package/dist/commonjs/version.js +1 -1
  27. package/dist/esm/v3/ai-shared.d.ts +3 -127
  28. package/dist/esm/v3/ai-shared.js.map +1 -1
  29. package/dist/esm/v3/ai.js +66 -22
  30. package/dist/esm/v3/ai.js.map +1 -1
  31. package/dist/esm/v3/chat-react.d.ts +1 -0
  32. package/dist/esm/v3/chat-react.js +4 -1
  33. package/dist/esm/v3/chat-react.js.map +1 -1
  34. package/dist/esm/v3/chat.d.ts +204 -1
  35. package/dist/esm/v3/chat.js +204 -20
  36. package/dist/esm/v3/chat.js.map +1 -1
  37. package/dist/esm/v3/index.d.ts +1 -1
  38. package/dist/esm/v3/index.js.map +1 -1
  39. package/dist/esm/v3/runs-bulk.test.d.ts +1 -0
  40. package/dist/esm/v3/runs-bulk.test.js +139 -0
  41. package/dist/esm/v3/runs-bulk.test.js.map +1 -0
  42. package/dist/esm/v3/runs.d.ts +18 -1
  43. package/dist/esm/v3/runs.js +87 -0
  44. package/dist/esm/v3/runs.js.map +1 -1
  45. package/dist/esm/v3/sessions.js +5 -2
  46. package/dist/esm/v3/sessions.js.map +1 -1
  47. package/dist/esm/v3/shared.d.ts +2 -2
  48. package/dist/esm/v3/shared.js +79 -5
  49. package/dist/esm/v3/shared.js.map +1 -1
  50. package/dist/esm/v3/shared.test.js +38 -1
  51. package/dist/esm/v3/shared.test.js.map +1 -1
  52. package/dist/esm/version.js +1 -1
  53. package/docs/ai-chat/frontend.mdx +46 -0
  54. package/docs/ai-chat/pending-messages.mdx +3 -1
  55. package/docs/ai-chat/reference.mdx +20 -0
  56. package/docs/billing-limits.mdx +73 -0
  57. package/docs/github-repo.mdx +1 -1
  58. package/docs/how-to-reduce-your-spend.mdx +1 -14
  59. package/docs/limits.mdx +5 -0
  60. package/docs/quick-start.mdx +1 -1
  61. package/docs/self-hosting/docker.mdx +24 -1
  62. package/docs/self-hosting/env/webapp.mdx +6 -1
  63. package/docs/self-hosting/kubernetes.mdx +26 -0
  64. package/docs/self-hosting/overview.mdx +4 -1
  65. package/docs/self-hosting/security.mdx +50 -0
  66. package/docs/troubleshooting.mdx +4 -0
  67. package/package.json +2 -2
  68. package/docs/open-source-self-hosting.mdx +0 -541
@@ -15,138 +15,14 @@
15
15
  * import from `ai.ts`.
16
16
  */
17
17
  import type { Task, AnyTask } from "@trigger.dev/core/v3";
18
- import type { InferUITools, ModelMessage, ToolSet, UIDataTypes, UIMessage } from "ai";
18
+ import type { InferUITools, ToolSet, UIDataTypes, UIMessage } from "ai";
19
19
  /**
20
20
  * Message-part `type` value for the pending-message data part the agent
21
21
  * injects when a follow-up message arrives mid-turn.
22
22
  */
23
23
  export declare const PENDING_MESSAGE_INJECTED_TYPE: "data-pending-message-injected";
24
- /**
25
- * The wire payload shape sent by `TriggerChatTransport`.
26
- * Uses `metadata` to match the AI SDK's `ChatRequestOptions` field name.
27
- *
28
- * Slim wire: at most ONE message per record. The agent runtime
29
- * reconstructs prior history at run boot from a durable S3 snapshot +
30
- * `session.out` replay (or `hydrateMessages` if registered). The wire is
31
- * delta-only — see plan `vivid-humming-bonbon.md`.
32
- */
33
- export type ChatTaskWirePayload<TMessage extends UIMessage = UIMessage, TMetadata = unknown> = {
34
- /**
35
- * The single message being delivered on this trigger. Set for:
36
- * - `submit-message`: the new user message OR a tool-approval-responded
37
- * assistant message (with `state: "approval-responded"` tool parts).
38
- * - `regenerate-message`: omitted (the agent slices its own history).
39
- * - `preload` / `close` / `action`: omitted.
40
- * - `handover-prepare`: omitted (use `headStartMessages` instead).
41
- */
42
- message?: TMessage;
43
- /**
44
- * Bespoke escape hatch for `chat.headStart`. The customer's HTTP route
45
- * handler ships full `UIMessage[]` history at the very first turn — before
46
- * any snapshot exists. The route handler isn't subject to the
47
- * `MAX_APPEND_BODY_BYTES` cap on `/in/append` because it goes through the
48
- * customer's own HTTP endpoint. Used ONLY by `trigger: "handover-prepare"`.
49
- * Ignored on every other trigger.
50
- */
51
- headStartMessages?: TMessage[];
52
- chatId: string;
53
- trigger: "submit-message" | "regenerate-message" | "preload" | "close" | "action"
54
- /**
55
- * The customer's `chat.handover` route handler kicked us off in
56
- * parallel with the first-turn `streamText` running in the warm
57
- * Next.js process. The run sits idle on `session.in` waiting for
58
- * a `kind: "handover"` (continue from tool execution) or
59
- * `kind: "handover-skip"` (handler finished pure-text, exit
60
- * cleanly). See `chat.handover` in `@trigger.dev/sdk/chat-server`.
61
- */
62
- | "handover-prepare";
63
- messageId?: string;
64
- metadata?: TMetadata;
65
- /** Custom action payload when `trigger` is `"action"`. Validated against `actionSchema` on the backend. */
66
- action?: unknown;
67
- /** Whether this run is continuing an existing chat whose previous run ended. */
68
- continuation?: boolean;
69
- /** The run ID of the previous run (only set when `continuation` is true). */
70
- previousRunId?: string;
71
- /** Override idle timeout for this run (seconds). Set by transport.preload(). */
72
- idleTimeoutInSeconds?: number;
73
- /**
74
- * The friendlyId of the Session primitive backing this chat. The
75
- * transport opens (or lazy-creates) the session with
76
- * `externalId = chatId` on first message, then sends this friendlyId
77
- * through to the run so the agent can attach to `.in` / `.out`
78
- * without needing to round-trip through the control plane again.
79
- * Optional for backward-compat while the migration is in flight;
80
- * required once the legacy run-scoped stream path is removed.
81
- */
82
- sessionId?: string;
83
- };
84
- /**
85
- * One chunk on the chat input stream. `kind` discriminates the variants —
86
- * a single ordered stream now carries all the signals the old three-stream
87
- * split did (`chat-messages`, `chat-stop`, plus action messages piggybacked
88
- * on `chat-messages`).
89
- */
90
- export type ChatInputChunk<TMessage extends UIMessage = UIMessage, TMetadata = unknown> = {
91
- kind: "message";
92
- /**
93
- * Full wire payload for a new user message or regeneration. Mirrors
94
- * what the legacy `chat-messages` input stream carried.
95
- */
96
- payload: ChatTaskWirePayload<TMessage, TMetadata>;
97
- } | {
98
- kind: "stop";
99
- /** Optional human-readable reason. Maps to the legacy `chat-stop` record. */
100
- message?: string;
101
- } | {
102
- /**
103
- * Sent by `chat.headStart` when the customer's first-turn
104
- * `streamText` finishes. The agent run (currently parked in
105
- * `handover-prepare`) wakes, seeds its accumulators with
106
- * `partialAssistantMessage`, and runs the normal turn loop
107
- * (`onChatStart` → `onTurnStart` → … → `onTurnComplete`).
108
- *
109
- * What happens after that depends on `isFinal`:
110
- *
111
- * - `isFinal: false` — step 1 ended with `finishReason:
112
- * "tool-calls"`. The partial carries the assistant's
113
- * tool-call(s) wrapped in AI SDK's tool-approval round. The
114
- * agent's `streamText` runs the approved tools and continues
115
- * from step 2.
116
- * - `isFinal: true` — step 1 ended pure-text (no tool calls).
117
- * The partial carries the final assistant text. The agent
118
- * skips the LLM call entirely (the response is already
119
- * complete on the customer side) and runs `onTurnComplete`
120
- * with the partial as `responseMessage` so persistence and
121
- * any post-turn work fire normally.
122
- */
123
- kind: "handover";
124
- /** Customer's step-1 response messages (ModelMessage form). */
125
- partialAssistantMessage: ModelMessage[];
126
- /**
127
- * The UI messageId the customer's handler used for its step-1
128
- * assistant message. The agent reuses this so any post-handover
129
- * chunks (tool-output-available, step-2 text, data-* parts
130
- * written by hooks) merge into the SAME assistant message on
131
- * the browser side instead of starting a new one.
132
- */
133
- messageId?: string;
134
- /**
135
- * Whether the customer's step 1 is the final response. See
136
- * `kind` description above for the two branches.
137
- */
138
- isFinal: boolean;
139
- } | {
140
- /**
141
- * Sent by `chat.headStart` only when the customer's handler
142
- * ABORTS before producing a finishReason (e.g., dispatch error,
143
- * stream cancelled before any tokens). The agent run exits
144
- * cleanly without firing turn hooks. Normal pure-text and
145
- * tool-call finishes go through `kind: "handover"` with the
146
- * appropriate `isFinal` flag.
147
- */
148
- kind: "handover-skip";
149
- };
24
+ import type { ChatTaskWirePayload } from "./chat.js";
25
+ export type { ChatTaskWirePayload, ChatInputChunk } from "./chat.js";
150
26
  /**
151
27
  * Extracts the client-data (`metadata`) type from a chat task.
152
28
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ai-shared.js","sourceRoot":"","sources":["../../../src/v3/ai-shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai-shared.js","sourceRoot":"","sources":["../../../src/v3/ai-shared.ts"],"names":[],"mappings":"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"}
package/dist/esm/v3/ai.js CHANGED
@@ -2916,6 +2916,11 @@ function chatAgent(options) {
2916
2916
  // default, `inFlightUsers`). The turn-loop checks this queue ahead of
2917
2917
  // `messagesInput.waitWithIdleTimeout` so recovered turns fire first.
2918
2918
  const bootInjectedQueue = [];
2919
+ // Messages consumed by a turn's `messagesInput.on` handler, dispatched
2920
+ // one per turn by the end-of-turn pickup. Loop-level on purpose:
2921
+ // consuming a record advances the committed `.in` cursor, so entries
2922
+ // dropped with a turn-local buffer are lost permanently.
2923
+ const pendingWireMessages = [];
2919
2924
  const couldHavePriorState = payload.continuation === true || ctx.attempt.number > 1;
2920
2925
  // `.in` resume cursor, computed at most once per boot. The boot
2921
2926
  // block below resolves it (snapshot field or records scan) and the
@@ -3622,6 +3627,9 @@ function chatAgent(options) {
3622
3627
  } // end else (no boot-injected first turn)
3623
3628
  }
3624
3629
  for (let turn = 0; turn < maxTurns; turn++) {
3630
+ // Declared here so the finally can detach it — a handler leaked past
3631
+ // its turn duplicates every mid-stream message into the shared buffer.
3632
+ let turnMsgSub;
3625
3633
  try {
3626
3634
  // Extract turn-level context before entering the span. Slim
3627
3635
  // wire: at most one delta message per record. `headStartMessages`
@@ -3697,8 +3705,6 @@ function chatAgent(options) {
3697
3705
  const stopSignal = stopController.signal;
3698
3706
  const cancelSignal = runSignal;
3699
3707
  const combinedSignal = AbortSignal.any([runSignal, stopController.signal]);
3700
- // Buffer messages that arrive during streaming
3701
- const pendingMessages = [];
3702
3708
  const pmConfig = locals.get(chatPendingMessagesKey);
3703
3709
  const msgSub = messagesInput.on(async (msg) => {
3704
3710
  // If pendingMessages is configured, route to the steering queue
@@ -3745,8 +3751,9 @@ function chatAgent(options) {
3745
3751
  return; // Don't add to wire buffer — frontend handles non-injected case
3746
3752
  }
3747
3753
  // No pendingMessages config — standard wire buffer for next turn
3748
- pendingMessages.push(msg);
3754
+ pendingWireMessages.push(msg);
3749
3755
  });
3756
+ turnMsgSub = msgSub;
3750
3757
  // Track new messages for this turn (user input + assistant response).
3751
3758
  const turnNewModelMessages = [];
3752
3759
  const turnNewUIMessages = [];
@@ -4801,9 +4808,10 @@ function chatAgent(options) {
4801
4808
  return "continue";
4802
4809
  }
4803
4810
  // If messages arrived during streaming (without pendingMessages config),
4804
- // use the first one immediately as the next turn.
4805
- if (pendingMessages.length > 0) {
4806
- currentWirePayload = pendingMessages[0];
4811
+ // dispatch the oldest as the next turn. The rest stay queued
4812
+ // and drain one per turn.
4813
+ if (pendingWireMessages.length > 0) {
4814
+ currentWirePayload = pendingWireMessages.shift();
4807
4815
  return "continue";
4808
4816
  }
4809
4817
  // chat.requestUpgrade() was called — exit the loop so the
@@ -4889,6 +4897,9 @@ function chatAgent(options) {
4889
4897
  // Turn error handler: write an error chunk + turn-complete to the stream
4890
4898
  // so the client sees the error, then wait for the next message instead
4891
4899
  // of killing the entire run. This keeps the conversation alive.
4900
+ // Detach the turn's message handler first — left attached it would
4901
+ // eat the very message the wait below is waiting for.
4902
+ turnMsgSub?.off();
4892
4903
  if (turnError instanceof Error &&
4893
4904
  turnError.name === "AbortError" &&
4894
4905
  runSignal.aborted) {
@@ -5008,6 +5019,11 @@ function chatAgent(options) {
5008
5019
  currentWirePayload = bootInjectedQueue.shift();
5009
5020
  continue;
5010
5021
  }
5022
+ // Same for messages buffered during the errored turn — already consumed, idling strands them.
5023
+ if (pendingWireMessages.length > 0) {
5024
+ currentWirePayload = pendingWireMessages.shift();
5025
+ continue;
5026
+ }
5011
5027
  // Wait for the next message — same as after a successful turn
5012
5028
  const effectiveIdleTimeout = metadata.get(IDLE_TIMEOUT_METADATA_KEY) ??
5013
5029
  idleTimeoutInSeconds;
@@ -5023,6 +5039,9 @@ function chatAgent(options) {
5023
5039
  currentWirePayload = next.output;
5024
5040
  // Continue to next iteration of the for loop
5025
5041
  }
5042
+ finally {
5043
+ turnMsgSub?.off();
5044
+ }
5026
5045
  }
5027
5046
  }
5028
5047
  finally {
@@ -5933,8 +5952,17 @@ function createChatSession(payload, options) {
5933
5952
  const accumulator = new ChatMessageAccumulator();
5934
5953
  let previousTurnUsage;
5935
5954
  let cumulativeUsage = emptyUsage();
5955
+ // Messages consumed mid-turn, dispatched one per next(). Iterator-level
5956
+ // for the same reason as the agent loop's `pendingWireMessages`:
5957
+ // consumed records never replay, so a turn-local buffer loses them.
5958
+ const sessionPendingWire = [];
5959
+ // The current turn's message subscription — detached defensively at the
5960
+ // top of next() in case user code threw without complete()/done().
5961
+ let activeMsgSub;
5936
5962
  return {
5937
5963
  async next() {
5964
+ activeMsgSub?.off();
5965
+ activeMsgSub = undefined;
5938
5966
  if (!booted) {
5939
5967
  booted = true;
5940
5968
  await seedSessionInResumeCursorForCustomLoop(currentPayload);
@@ -5994,23 +6022,29 @@ function createChatSession(payload, options) {
5994
6022
  currentPayload = { ...currentPayload, continuation: true };
5995
6023
  }
5996
6024
  }
5997
- // Subsequent turns: wait for the next message
6025
+ // Subsequent turns: drain buffered mid-turn messages first (they
6026
+ // were consumed and won't be re-delivered), then wait.
5998
6027
  if (turn > 0) {
5999
- // chat.requestUpgrade() / chat.endRun() — exit before waiting
6000
- if (locals.get(chatUpgradeRequestedKey) || locals.get(chatEndRunRequestedKey)) {
6001
- stop.cleanup();
6002
- return { done: true, value: undefined };
6028
+ if (sessionPendingWire.length > 0) {
6029
+ currentPayload = sessionPendingWire.shift();
6003
6030
  }
6004
- const next = await messagesInput.waitWithIdleTimeout({
6005
- idleTimeoutInSeconds,
6006
- timeout,
6007
- spanName: "waiting for next message",
6008
- });
6009
- if (!next.ok || runSignal.aborted) {
6010
- stop.cleanup();
6011
- return { done: true, value: undefined };
6031
+ else {
6032
+ // chat.requestUpgrade() / chat.endRun() — exit before waiting
6033
+ if (locals.get(chatUpgradeRequestedKey) || locals.get(chatEndRunRequestedKey)) {
6034
+ stop.cleanup();
6035
+ return { done: true, value: undefined };
6036
+ }
6037
+ const next = await messagesInput.waitWithIdleTimeout({
6038
+ idleTimeoutInSeconds,
6039
+ timeout,
6040
+ spanName: "waiting for next message",
6041
+ });
6042
+ if (!next.ok || runSignal.aborted) {
6043
+ stop.cleanup();
6044
+ return { done: true, value: undefined };
6045
+ }
6046
+ currentPayload = next.output;
6012
6047
  }
6013
- currentPayload = next.output;
6014
6048
  }
6015
6049
  // Check limits
6016
6050
  if (turn >= maxTurns || runSignal.aborted) {
@@ -6035,10 +6069,10 @@ function createChatSession(payload, options) {
6035
6069
  clientData: currentPayload.metadata,
6036
6070
  });
6037
6071
  // Listen for messages during streaming (steering + next-turn buffer)
6038
- const sessionPendingWire = [];
6039
6072
  const sessionMsgSub = messagesInput.on(async (msg) => {
6040
- sessionPendingWire.push(msg);
6041
6073
  if (sessionPendingMessages) {
6074
+ // Steering route — the frontend re-sends non-injected
6075
+ // messages on turn complete, so don't also buffer the wire.
6042
6076
  // Slim wire: at most one delta message per record. Read
6043
6077
  // `msg.message` directly — no array slicing needed.
6044
6078
  const lastUIMessage = msg.message;
@@ -6063,8 +6097,11 @@ function createChatSession(payload, options) {
6063
6097
  /* non-fatal */
6064
6098
  }
6065
6099
  }
6100
+ return;
6066
6101
  }
6102
+ sessionPendingWire.push(msg);
6067
6103
  });
6104
+ activeMsgSub = sessionMsgSub;
6068
6105
  // Accumulate messages. Slim wire: pass the single delta message as
6069
6106
  // a 0-or-1-length array. The accumulator's behavior is unchanged —
6070
6107
  // it still appends user messages and reconverts on regenerate.
@@ -6155,6 +6192,11 @@ function createChatSession(payload, options) {
6155
6192
  throw error;
6156
6193
  }
6157
6194
  }
6195
+ finally {
6196
+ // Detach at stream end (like the agent loop): the steering queue
6197
+ // can't inject anymore, so later arrivals must buffer for the next turn.
6198
+ sessionMsgSub.off();
6199
+ }
6158
6200
  if (response) {
6159
6201
  const cleaned = stop.signal.aborted && !runSignal.aborted
6160
6202
  ? cleanupAbortedParts(response)
@@ -6298,6 +6340,8 @@ function createChatSession(payload, options) {
6298
6340
  return { done: false, value: turnObj };
6299
6341
  },
6300
6342
  async return() {
6343
+ activeMsgSub?.off();
6344
+ activeMsgSub = undefined;
6301
6345
  // `stop` only exists once next() has booted the iterator.
6302
6346
  stop?.cleanup();
6303
6347
  return { done: true, value: undefined };