@trigger.dev/sdk 4.6.1 → 4.6.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 (37) hide show
  1. package/dist/commonjs/v3/ai.d.ts +32 -10
  2. package/dist/commonjs/v3/ai.js +122 -36
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/auth.js +8 -2
  5. package/dist/commonjs/v3/auth.js.map +1 -1
  6. package/dist/commonjs/v3/compactionResponse.d.ts +9 -0
  7. package/dist/commonjs/v3/compactionResponse.js +32 -0
  8. package/dist/commonjs/v3/compactionResponse.js.map +1 -0
  9. package/dist/commonjs/v3/runs.d.ts +5 -0
  10. package/dist/commonjs/v3/sessionTracing.d.ts +7 -0
  11. package/dist/commonjs/v3/sessionTracing.js +44 -0
  12. package/dist/commonjs/v3/sessionTracing.js.map +1 -0
  13. package/dist/commonjs/v3/sessions.js +31 -34
  14. package/dist/commonjs/v3/sessions.js.map +1 -1
  15. package/dist/commonjs/v3/shared.js +8 -5
  16. package/dist/commonjs/v3/shared.js.map +1 -1
  17. package/dist/commonjs/version.js +1 -1
  18. package/dist/esm/v3/ai.d.ts +32 -10
  19. package/dist/esm/v3/ai.js +122 -36
  20. package/dist/esm/v3/ai.js.map +1 -1
  21. package/dist/esm/v3/auth.js +8 -2
  22. package/dist/esm/v3/auth.js.map +1 -1
  23. package/dist/esm/v3/compactionResponse.d.ts +9 -0
  24. package/dist/esm/v3/compactionResponse.js +29 -0
  25. package/dist/esm/v3/compactionResponse.js.map +1 -0
  26. package/dist/esm/v3/runs.d.ts +5 -0
  27. package/dist/esm/v3/sessionTracing.d.ts +7 -0
  28. package/dist/esm/v3/sessionTracing.js +40 -0
  29. package/dist/esm/v3/sessionTracing.js.map +1 -0
  30. package/dist/esm/v3/sessions.js +31 -34
  31. package/dist/esm/v3/sessions.js.map +1 -1
  32. package/dist/esm/v3/shared.js +9 -6
  33. package/dist/esm/v3/shared.js.map +1 -1
  34. package/dist/esm/version.js +1 -1
  35. package/docs/ai-chat/lifecycle-hooks.mdx +4 -2
  36. package/docs/ai-chat/reference.mdx +4 -2
  37. package/package.json +2 -2
package/dist/esm/v3/ai.js CHANGED
@@ -2,8 +2,10 @@ import { accessoryAttributes, apiClientManager, controlSubtype, generateJWT, get
2
2
  // Runtime VALUES go through the ESM/CJS shim so the CJS build can `require`
3
3
  // ESM-only `ai@7` (see ../imports/ai-runtime.ts).
4
4
  import { trace } from "@opentelemetry/api";
5
+ import { traceSessionIdle } from "./sessionTracing.js";
5
6
  import { tool as aiTool, convertToModelMessages, dynamicTool, generateId as generateMessageId, getToolName, isToolUIPart, jsonSchema, readUIMessageStream, streamText as aiStreamText, zodSchema, } from "../imports/ai-runtime.js";
6
7
  import { createTranscriptShadow, defaultStorage, diffTranscript, fingerprintMessage, parseTranscriptRuntimeState, restoreModelLane, } from "./transcriptStorage.js";
8
+ import { responseAfterCompaction } from "./compactionResponse.js";
7
9
  let transcriptStorageOverride;
8
10
  /**
9
11
  * Test-only override for the storage `chat.agent` persists through, so a
@@ -1087,7 +1089,7 @@ async function waitOnChatRoute(route, options) {
1087
1089
  return tracer.startActiveSpan(options.spanName ?? `chat.${route}.wait()`, async (span) => {
1088
1090
  const idleMs = (options.idleTimeoutInSeconds ?? 0) * 1000;
1089
1091
  if (idleMs > 0) {
1090
- const warm = await router.next(route, { timeoutMs: idleMs });
1092
+ const warm = await traceSessionIdle(session.id, idleMs / 1000, () => router.next(route, { timeoutMs: idleMs }));
1091
1093
  if (warm) {
1092
1094
  span.setAttribute("wait.resolved", "idle");
1093
1095
  return { ok: true, output: warm.data, record: warm };
@@ -1141,10 +1143,6 @@ async function waitOnChatRoute(route, options) {
1141
1143
  session: session.id,
1142
1144
  io: "in",
1143
1145
  route,
1144
- ...accessoryAttributes({
1145
- items: [{ text: `${session.id}.in:${route}`, variant: "normal" }],
1146
- style: "codepath",
1147
- }),
1148
1146
  },
1149
1147
  });
1150
1148
  }
@@ -1820,6 +1818,14 @@ const chatHandoverPartialKey = locals.create("chat.handoverPartial");
1820
1818
  * @internal
1821
1819
  */
1822
1820
  const chatHandoverMessageIdKey = locals.create("chat.handoverMessageId");
1821
+ /**
1822
+ * The model messages a turn-0 head-start splice contributed to the model lane,
1823
+ * keyed by the UI message id it was synthesized under. When the agent's response
1824
+ * completes that message under the same id, this is the run to replace: the UI
1825
+ * form alone converts to something else (its pending tool calls drop out, the
1826
+ * approval round was never on it), so it cannot locate the run itself.
1827
+ */
1828
+ const chatHandoverSplicedRunKey = locals.create("chat.handoverSplicedRun");
1823
1829
  /**
1824
1830
  * Run-scoped slot indicating that the customer's step-1 head-start
1825
1831
  * response is the FINAL turn response. When true, turn 0 runs through
@@ -1903,16 +1909,19 @@ function synthesizeHandoverUIMessage(partial, messageId) {
1903
1909
  */
1904
1910
  function spliceHandoverPartial(modelMessages, uiMessages, signal) {
1905
1911
  if (!signal.partialAssistantMessage || signal.partialAssistantMessage.length === 0) {
1906
- return;
1912
+ return undefined;
1907
1913
  }
1908
1914
  // Skip if the hydrated chain already persisted the partial under this id.
1909
1915
  const alreadyInChain = signal.messageId !== undefined && uiMessages.some((m) => m.id === signal.messageId);
1910
1916
  if (alreadyInChain)
1911
- return;
1912
- modelMessages.push(...signal.partialAssistantMessage);
1917
+ return undefined;
1918
+ const run = [...signal.partialAssistantMessage];
1919
+ modelMessages.push(...run);
1913
1920
  const partialUI = synthesizeHandoverUIMessage(signal.partialAssistantMessage, signal.messageId);
1914
- if (partialUI)
1915
- uiMessages.push(partialUI);
1921
+ if (!partialUI)
1922
+ return undefined;
1923
+ uiMessages.push(partialUI);
1924
+ return { id: partialUI.id, run };
1916
1925
  }
1917
1926
  /**
1918
1927
  * Per-turn background context queue. Messages added via `chat.backgroundWork.inject()`
@@ -2782,6 +2791,7 @@ async function chatCompact(messages, steps, options) {
2782
2791
  locals.set(chatCompactionStateKey, {
2783
2792
  summary,
2784
2793
  baseResponseMessageCount: currentStep.response.messages.length,
2794
+ baseResponseStepCount: steps.length,
2785
2795
  });
2786
2796
  // Set model-only override — UI messages stay intact for persistence.
2787
2797
  // The summary becomes the model message history for the next turn,
@@ -3652,8 +3662,8 @@ function isActionTurn(value) {
3652
3662
  * tail does not match the old message's conversion, nothing is changed and
3653
3663
  * `false` is returned so the caller can fall back to a full reconversion.
3654
3664
  */
3655
- async function replaceModelRun(lane, oldUi, newUi, tailAfter) {
3656
- const oldRun = await toModelMessages([stripProviderMetadata(oldUi)]);
3665
+ async function replaceModelRun(lane, oldUi, newUi, tailAfter, knownOldRun) {
3666
+ const oldRun = knownOldRun ?? (await toModelMessages([stripProviderMetadata(oldUi)]));
3657
3667
  const newRun = await toModelMessages([stripProviderMetadata(newUi)]);
3658
3668
  // A message that converts to nothing (a pending tool call with no output yet,
3659
3669
  // which `ignoreIncompleteToolCalls` drops) locates no run in the lane. Matching
@@ -4785,7 +4795,7 @@ function chatAgent(options) {
4785
4795
  const preloadResult = await messagesInput.waitWithIdleTimeout({
4786
4796
  idleTimeoutInSeconds: effectivePreloadIdleTimeout,
4787
4797
  timeout: effectivePreloadTimeout,
4788
- spanName: "waiting for first message",
4798
+ spanName: "first message",
4789
4799
  skipSuspend: exitAfterPreloadIdle,
4790
4800
  onSuspend: onChatSuspend
4791
4801
  ? async () => {
@@ -4937,7 +4947,7 @@ function chatAgent(options) {
4937
4947
  const continuationResult = await messagesInput.waitWithIdleTimeout({
4938
4948
  idleTimeoutInSeconds: effectiveIdleTimeout,
4939
4949
  timeout: effectiveTurnTimeout,
4940
- spanName: "waiting for first message (continuation)",
4950
+ spanName: "first message (continuation)",
4941
4951
  onSuspend: onChatSuspend
4942
4952
  ? async () => {
4943
4953
  await tracer.startActiveSpan("onChatSuspend()", async () => {
@@ -5466,10 +5476,12 @@ function chatAgent(options) {
5466
5476
  // `UIMessageStreamError: No tool invocation found`.
5467
5477
  const pendingHandoverPartial = locals.get(chatHandoverPartialKey);
5468
5478
  if (pendingHandoverPartial && pendingHandoverPartial.length > 0) {
5469
- spliceHandoverPartial(accumulatedMessages, accumulatedUIMessages, {
5479
+ const spliced = spliceHandoverPartial(accumulatedMessages, accumulatedUIMessages, {
5470
5480
  partialAssistantMessage: pendingHandoverPartial,
5471
5481
  messageId: locals.get(chatHandoverMessageIdKey),
5472
5482
  });
5483
+ if (spliced)
5484
+ locals.set(chatHandoverSplicedRunKey, spliced);
5473
5485
  locals.set(chatHandoverPartialKey, []); // consume once
5474
5486
  splicedHandoverPartial = true;
5475
5487
  }
@@ -5814,6 +5826,7 @@ function chatAgent(options) {
5814
5826
  // never reports final usage), which would block the turn loop
5815
5827
  // from ever firing onTurnComplete / writeTurnComplete.
5816
5828
  let turnUsage;
5829
+ let lastStepUsage;
5817
5830
  if (runResult != null &&
5818
5831
  typeof runResult.totalUsage?.then === "function") {
5819
5832
  try {
@@ -5826,6 +5839,18 @@ function chatAgent(options) {
5826
5839
  /* non-fatal — usage capture failed */
5827
5840
  }
5828
5841
  }
5842
+ const lastStepUsagePromise = runResult != null ? runResult.usage : undefined;
5843
+ if (typeof lastStepUsagePromise?.then === "function") {
5844
+ try {
5845
+ lastStepUsage = (await Promise.race([
5846
+ lastStepUsagePromise,
5847
+ new Promise((r) => setTimeout(() => r(undefined), 2_000)),
5848
+ ]));
5849
+ }
5850
+ catch {
5851
+ /* non-fatal — usage capture failed */
5852
+ }
5853
+ }
5829
5854
  if (turnUsage) {
5830
5855
  cumulativeUsage = addUsage(cumulativeUsage, turnUsage);
5831
5856
  previousTurnUsage = turnUsage;
@@ -5876,6 +5901,13 @@ function chatAgent(options) {
5876
5901
  // Check if compaction set a model-only override (preserves UI messages).
5877
5902
  // Apply compactUIMessages/compactModelMessages callbacks if configured.
5878
5903
  const modelOnlyOverride = locals.get(chatOverrideModelMessagesKey);
5904
+ const responseCompaction = modelOnlyOverride
5905
+ ? locals.get(chatCompactionStateKey)
5906
+ : undefined;
5907
+ // Capture the original assistant before compactUIMessages can remove it.
5908
+ const originalResponse = responseCompaction && capturedResponseMessage
5909
+ ? accumulatedUIMessages.find((m) => m.id === capturedResponseMessage?.id)
5910
+ : undefined;
5879
5911
  if (modelOnlyOverride) {
5880
5912
  const compactionSummary = locals.get(chatCompactionStateKey)?.summary ?? "";
5881
5913
  const taskCompactionConfig = locals.get(chatAgentCompactionKey);
@@ -5967,12 +5999,39 @@ function chatAgent(options) {
5967
5999
  // rationale (TRI-9137).
5968
6000
  recordToolCallIdsFromMessage(capturedResponseMessage);
5969
6001
  try {
6002
+ const responseForModel = responseAfterCompaction(capturedResponseMessage, responseCompaction?.baseResponseStepCount, originalResponse);
6003
+ // Preserve the complete persistence response, including same-ID
6004
+ // replacements whose old tool parts can contain new results.
6005
+ // Convert prefix and suffix separately so each tool output is
6006
+ // converted once, while only the suffix enters model context.
6007
+ const responsePrefixMessages = responseCompaction
6008
+ ? await toModelMessages([
6009
+ stripProviderMetadata({
6010
+ ...capturedResponseMessage,
6011
+ parts: capturedResponseMessage.parts.slice(0, capturedResponseMessage.parts.length -
6012
+ responseForModel.parts.length),
6013
+ }),
6014
+ ])
6015
+ : [];
5970
6016
  const responseModelMessages = await toModelMessages([
5971
- stripProviderMetadata(capturedResponseMessage),
6017
+ stripProviderMetadata(responseForModel),
5972
6018
  ]);
5973
- if (existingIdx !== -1) {
6019
+ if (responseCompaction) {
6020
+ // The summary already replaced the original response, including
6021
+ // a same-ID approval/handover prefix. Replacing its old model run
6022
+ // would miss and fall back to the full, uncompacted UI history.
6023
+ accumulatedMessages.push(...responseModelMessages);
6024
+ locals.set(chatHandoverSplicedRunKey, undefined);
6025
+ }
6026
+ else if (existingIdx !== -1) {
6027
+ const spliced = locals.get(chatHandoverSplicedRunKey);
6028
+ const splicedRun = spliced && previousAtIdx && spliced.id === previousAtIdx.id
6029
+ ? spliced.run
6030
+ : undefined;
5974
6031
  const ok = previousAtIdx !== undefined &&
5975
- (await replaceModelRun(accumulatedMessages, previousAtIdx, capturedResponseMessage, steerTailThisTurn));
6032
+ (await replaceModelRun(accumulatedMessages, previousAtIdx, capturedResponseMessage, steerTailThisTurn, splicedRun));
6033
+ if (splicedRun)
6034
+ locals.set(chatHandoverSplicedRunKey, undefined);
5976
6035
  if (!ok) {
5977
6036
  logger.warn("chat.agent: replaced response not found at the model lane tail; reconverting the lane");
5978
6037
  accumulatedMessages = await toModelMessages(accumulatedUIMessages);
@@ -5983,7 +6042,7 @@ function chatAgent(options) {
5983
6042
  else {
5984
6043
  accumulatedMessages.push(...responseModelMessages);
5985
6044
  }
5986
- turnNewModelMessages.push(...responseModelMessages);
6045
+ turnNewModelMessages.push(...responsePrefixMessages, ...responseModelMessages);
5987
6046
  }
5988
6047
  catch {
5989
6048
  // Conversion failed — skip accumulation for this turn
@@ -6032,12 +6091,14 @@ function chatAgent(options) {
6032
6091
  const outerCompaction = locals.get(chatAgentCompactionKey);
6033
6092
  const innerCompactionState = locals.get(chatCompactionStateKey);
6034
6093
  if (outerCompaction && !innerCompactionState && turnUsage && !wasStopped) {
6094
+ const contextUsage = lastStepUsage ?? turnUsage;
6035
6095
  const shouldTrigger = await outerCompaction.shouldCompact({
6036
6096
  messages: accumulatedMessages,
6037
- totalTokens: turnUsage.totalTokens,
6038
- inputTokens: turnUsage.inputTokens,
6039
- outputTokens: turnUsage.outputTokens,
6040
- usage: turnUsage,
6097
+ totalTokens: contextUsage.totalTokens,
6098
+ inputTokens: contextUsage.inputTokens,
6099
+ outputTokens: contextUsage.outputTokens,
6100
+ usage: contextUsage,
6101
+ turnUsage,
6041
6102
  totalUsage: cumulativeUsage,
6042
6103
  chatId: currentWirePayload.chatId,
6043
6104
  turn,
@@ -6362,7 +6423,7 @@ function chatAgent(options) {
6362
6423
  const next = await messagesInput.waitWithIdleTimeout({
6363
6424
  idleTimeoutInSeconds: effectiveIdleTimeout,
6364
6425
  timeout: effectiveTurnTimeout,
6365
- spanName: "waiting for next message",
6426
+ spanName: "next message",
6366
6427
  onSuspend: onChatSuspend
6367
6428
  ? async () => {
6368
6429
  await tracer.startActiveSpan("onChatSuspend()", async () => {
@@ -6699,7 +6760,7 @@ function chatAgent(options) {
6699
6760
  const next = await messagesInput.waitWithIdleTimeout({
6700
6761
  idleTimeoutInSeconds: effectiveIdleTimeout,
6701
6762
  timeout: effectiveTurnTimeout,
6702
- spanName: "waiting for next message (after error)",
6763
+ spanName: "next message (after error)",
6703
6764
  });
6704
6765
  if (!next.ok) {
6705
6766
  return; // Timed out — end run gracefully
@@ -7739,6 +7800,8 @@ class ChatMessageAccumulator {
7739
7800
  modelMessages = [];
7740
7801
  uiMessages = [];
7741
7802
  _compaction;
7803
+ /** The run a spliced head-start partial contributed, until its response replaces it. */
7804
+ _handoverRun;
7742
7805
  _pendingMessages;
7743
7806
  _steeringQueue = [];
7744
7807
  constructor(options) {
@@ -7782,7 +7845,7 @@ class ChatMessageAccumulator {
7782
7845
  * `consumeHandover` for the wait+seed+apply convenience.
7783
7846
  */
7784
7847
  applyHandover(signal) {
7785
- spliceHandoverPartial(this.modelMessages, this.uiMessages, signal);
7848
+ this._handoverRun = spliceHandoverPartial(this.modelMessages, this.uiMessages, signal);
7786
7849
  }
7787
7850
  /**
7788
7851
  * One-call `chat.headStart` handover for a custom-agent loop: waits for the
@@ -7825,8 +7888,13 @@ class ChatMessageAccumulator {
7825
7888
  if (existingIdx !== -1) {
7826
7889
  const previous = this.uiMessages[existingIdx];
7827
7890
  this.uiMessages[existingIdx] = response;
7891
+ const handoverRun = this._handoverRun && this._handoverRun.id === previous.id
7892
+ ? this._handoverRun.run
7893
+ : undefined;
7894
+ if (handoverRun)
7895
+ this._handoverRun = undefined;
7828
7896
  try {
7829
- if (!(await replaceModelRun(this.modelMessages, previous, response, 0))) {
7897
+ if (!(await replaceModelRun(this.modelMessages, previous, response, 0, handoverRun))) {
7830
7898
  this.modelMessages = await toModelMessages(this.uiMessages.map((m) => stripProviderMetadata(m)));
7831
7899
  }
7832
7900
  }
@@ -7929,8 +7997,10 @@ class ChatMessageAccumulator {
7929
7997
  }
7930
7998
  /**
7931
7999
  * Run outer-loop compaction if needed. Call after adding the response
7932
- * and capturing usage. Applies `compactModelMessages` and `compactUIMessages`
7933
- * callbacks if configured.
8000
+ * and capturing usage. Pass the LAST step's usage (`result.usage`), which is
8001
+ * the context the model held on its final call; `result.totalUsage` sums every
8002
+ * step of a tool-using turn and belongs in `context.turnUsage`. Applies
8003
+ * `compactModelMessages` and `compactUIMessages` callbacks if configured.
7934
8004
  *
7935
8005
  * @returns `true` if compaction was performed, `false` otherwise.
7936
8006
  */
@@ -7943,6 +8013,7 @@ class ChatMessageAccumulator {
7943
8013
  inputTokens: usage.inputTokens,
7944
8014
  outputTokens: usage.outputTokens,
7945
8015
  usage,
8016
+ turnUsage: context?.turnUsage,
7946
8017
  totalUsage: context?.totalUsage,
7947
8018
  chatId: context?.chatId,
7948
8019
  turn: context?.turn,
@@ -8175,8 +8246,8 @@ function createChatSession(payload, options) {
8175
8246
  idleTimeoutInSeconds: sessionIdleTimeoutOpt ?? currentPayload.idleTimeoutInSeconds ?? 30,
8176
8247
  timeout,
8177
8248
  spanName: currentPayload.trigger === "preload"
8178
- ? "waiting for first message"
8179
- : "waiting for first message (continuation)",
8249
+ ? "first message"
8250
+ : "first message (continuation)",
8180
8251
  });
8181
8252
  if (!result.ok || runSignal.aborted) {
8182
8253
  stop.cleanup();
@@ -8213,7 +8284,7 @@ function createChatSession(payload, options) {
8213
8284
  const next = await messagesInput.waitWithIdleTimeout({
8214
8285
  idleTimeoutInSeconds,
8215
8286
  timeout,
8216
- spanName: "waiting for next message",
8287
+ spanName: "next message",
8217
8288
  });
8218
8289
  if (!next.ok || runSignal.aborted) {
8219
8290
  stop.cleanup();
@@ -8424,6 +8495,7 @@ function createChatSession(payload, options) {
8424
8495
  // indefinitely, which would wedge the turn loop (same guard as
8425
8496
  // chat.agent's turn loop).
8426
8497
  let turnUsage;
8498
+ let lastStepUsage;
8427
8499
  if (typeof source.totalUsage?.then === "function") {
8428
8500
  try {
8429
8501
  const usage = (await Promise.race([
@@ -8440,14 +8512,28 @@ function createChatSession(payload, options) {
8440
8512
  /* non-fatal */
8441
8513
  }
8442
8514
  }
8515
+ const lastStepUsagePromise = source.usage;
8516
+ if (typeof lastStepUsagePromise?.then === "function") {
8517
+ try {
8518
+ lastStepUsage = (await Promise.race([
8519
+ lastStepUsagePromise,
8520
+ new Promise((r) => setTimeout(() => r(undefined), 2_000)),
8521
+ ]));
8522
+ }
8523
+ catch {
8524
+ /* non-fatal */
8525
+ }
8526
+ }
8443
8527
  // Outer-loop compaction (same logic as chat.agent)
8444
8528
  if (sessionCompaction && turnUsage && !turnObj.stopped) {
8529
+ const contextUsage = lastStepUsage ?? turnUsage;
8445
8530
  const shouldTrigger = await sessionCompaction.shouldCompact({
8446
8531
  messages: accumulator.modelMessages,
8447
- totalTokens: turnUsage.totalTokens,
8448
- inputTokens: turnUsage.inputTokens,
8449
- outputTokens: turnUsage.outputTokens,
8450
- usage: turnUsage,
8532
+ totalTokens: contextUsage.totalTokens,
8533
+ inputTokens: contextUsage.inputTokens,
8534
+ outputTokens: contextUsage.outputTokens,
8535
+ usage: contextUsage,
8536
+ turnUsage,
8451
8537
  totalUsage: cumulativeUsage,
8452
8538
  chatId: currentPayload.chatId,
8453
8539
  turn,