@trigger.dev/sdk 4.8.0-webhooks.20261015065339 → 4.8.0-webhooks.20261020092920

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 (53) hide show
  1. package/dist/commonjs/v3/ai.d.ts +67 -1
  2. package/dist/commonjs/v3/ai.js +102 -29
  3. package/dist/commonjs/v3/ai.js.map +1 -1
  4. package/dist/commonjs/v3/query.d.ts +434 -1
  5. package/dist/commonjs/v3/query.js +115 -0
  6. package/dist/commonjs/v3/query.js.map +1 -1
  7. package/dist/commonjs/v3/webhooks.d.ts +8 -2
  8. package/dist/commonjs/v3/webhooks.js +2 -1
  9. package/dist/commonjs/v3/webhooks.js.map +1 -1
  10. package/dist/commonjs/version.js +1 -1
  11. package/dist/esm/v3/ai.d.ts +67 -1
  12. package/dist/esm/v3/ai.js +101 -28
  13. package/dist/esm/v3/ai.js.map +1 -1
  14. package/dist/esm/v3/query.d.ts +434 -1
  15. package/dist/esm/v3/query.js +115 -0
  16. package/dist/esm/v3/query.js.map +1 -1
  17. package/dist/esm/v3/webhooks.d.ts +8 -2
  18. package/dist/esm/v3/webhooks.js +2 -1
  19. package/dist/esm/v3/webhooks.js.map +1 -1
  20. package/dist/esm/version.js +1 -1
  21. package/docs/ai-chat/backend.mdx +6 -0
  22. package/docs/ai-chat/patterns/redacting-telemetry.mdx +117 -0
  23. package/docs/ai-chat/pending-messages.mdx +1 -1
  24. package/docs/ai-chat/reference.mdx +7 -4
  25. package/docs/ai-chat/transcript-storage.mdx +1 -1
  26. package/docs/apikeys.mdx +6 -4
  27. package/docs/cli-runs-commands.mdx +2 -2
  28. package/docs/cli-tasks-commands.mdx +95 -0
  29. package/docs/config/config-file.mdx +58 -0
  30. package/docs/config/extensions/prismaExtension.mdx +4 -4
  31. package/docs/deploy-environment-variables.mdx +4 -0
  32. package/docs/deployment/atomic-deployment.mdx +3 -3
  33. package/docs/deployment/overview.mdx +1 -1
  34. package/docs/deployment/preview-branches.mdx +1 -1
  35. package/docs/github-actions.mdx +3 -3
  36. package/docs/limits.mdx +6 -0
  37. package/docs/manual-setup.mdx +1 -1
  38. package/docs/mcp-tools.mdx +23 -3
  39. package/docs/observability/dashboards.mdx +62 -0
  40. package/docs/runs/max-duration.mdx +4 -0
  41. package/docs/self-hosting/env/supervisor.mdx +1 -0
  42. package/docs/webhooks/channels.mdx +6 -2
  43. package/docs/webhooks/connect.mdx +4 -0
  44. package/docs/webhooks/deliveries.mdx +4 -0
  45. package/docs/webhooks/filters.mdx +35 -2
  46. package/docs/webhooks/human-in-the-loop.mdx +6 -0
  47. package/docs/webhooks/linear.mdx +7 -3
  48. package/docs/webhooks/overview.mdx +4 -0
  49. package/docs/webhooks/session-routing.mdx +7 -1
  50. package/docs/webhooks/slack.mdx +12 -4
  51. package/docs/webhooks/sources.mdx +4 -0
  52. package/docs/webhooks/waiters.mdx +26 -2
  53. package/package.json +3 -2
@@ -34,6 +34,7 @@ type ChatTurnContext<TClientData = unknown> = {
34
34
  turn: number;
35
35
  continuation: boolean;
36
36
  clientData?: TClientData;
37
+ recordUserMessage?: boolean;
37
38
  };
38
39
  export { __readChatSnapshotProductionPathForTests, __setReadChatSnapshotImplForTests, __setWriteChatSnapshotImplForTests, __writeChatSnapshotProductionPathForTests, } from "./chatSnapshotIo.js";
39
40
  export { defaultStorage, memoryTranscriptStorage, reduceTranscriptChanges, snapshotTranscriptStorage, type LoadContextEvent, type MemoryTranscriptStorage, type TranscriptChange, type TranscriptChangeReason, type TranscriptChangeset, type TranscriptCursors, type TranscriptLoadOptions, type TranscriptLoadResult, type TranscriptScope, type TranscriptState, type TranscriptStorage, type TranscriptStorageContext, } from "./transcriptStorage.js";
@@ -1862,21 +1863,25 @@ export type ChannelControl = {
1862
1863
  /**
1863
1864
  * What a connector's `progress()` hook observes while a turn streams: each tool call as its input is
1864
1865
  * complete, and each result or error as it lands. Text deltas are not reported (use `delivery: "stream"`).
1866
+ * `title` is the tool's display title (`tool({ title })`), when it declares one.
1865
1867
  */
1866
1868
  export type ChannelProgressEvent = {
1867
1869
  type: "tool-call";
1868
1870
  toolCallId: string;
1869
1871
  toolName: string;
1872
+ title?: string;
1870
1873
  input: unknown;
1871
1874
  } | {
1872
1875
  type: "tool-result";
1873
1876
  toolCallId: string;
1874
1877
  toolName: string;
1878
+ title?: string;
1875
1879
  output: unknown;
1876
1880
  } | {
1877
1881
  type: "tool-error";
1878
1882
  toolCallId: string;
1879
1883
  toolName: string;
1884
+ title?: string;
1880
1885
  errorText: string;
1881
1886
  };
1882
1887
  /**
@@ -1900,7 +1905,7 @@ export type ChannelReplyStreamEnd = {
1900
1905
  stopped: boolean;
1901
1906
  /** The turn paused on a human decision; the controls follow as their own message. */
1902
1907
  pending: boolean;
1903
- /** The sanitized error text when the turn ended on a model or stream error. */
1908
+ /** The connector's error line (`errorMessage`) when the turn ended on a model or stream error. */
1904
1909
  errorText?: string;
1905
1910
  };
1906
1911
  /**
@@ -2026,6 +2031,12 @@ export interface ChannelConnector<TEvent = unknown, THandle = unknown> {
2026
2031
  cancelInteraction?: (cancelled: ChannelCancelledToolCall[], ctx: ChannelTurnCtx<TEvent, THandle>) => Promise<void> | void;
2027
2032
  react?: (reaction: ChannelReaction, ctx: ChannelReactCtx<TEvent>) => Promise<void>;
2028
2033
  reactions?: ChannelReactions<TEvent>;
2034
+ /**
2035
+ * What the channel shows when a turn fails, in place of the error text: a fixed line, or a
2036
+ * function of the failure that returns one. Defaults to {@link CHANNEL_TURN_ERROR_MESSAGE}. The
2037
+ * error itself is logged on the run, and `outbound()` still receives it as `reply.error`.
2038
+ */
2039
+ errorMessage?: ChannelErrorMessage<TEvent>;
2029
2040
  /**
2030
2041
  * Observe tool calls and results as the turn streams, to post progress into the surface (a Linear
2031
2042
  * `action` activity, a Slack status). Called in order, awaited one at a time, independent of
@@ -2045,6 +2056,34 @@ export interface ChannelConnector<TEvent = unknown, THandle = unknown> {
2045
2056
  readonly [channelEventPhantom]?: TEvent;
2046
2057
  }
2047
2058
  export type AnyChannelConnector = ChannelConnector<any, any>;
2059
+ /** The line a channel shows when a turn fails, unless the connector sets its own `errorMessage`. */
2060
+ export declare const CHANNEL_TURN_ERROR_MESSAGE = "Something went wrong answering this. Try again.";
2061
+ /** What an `errorMessage` function receives when a channel turn fails. */
2062
+ export type ChannelErrorContext<TEvent = unknown> = {
2063
+ /** The failure: the model or stream error, or what the turn threw. */
2064
+ error: Error;
2065
+ /** The text the turn streamed before it failed; empty when it failed before answering. */
2066
+ partialText: string;
2067
+ /** The event that started the turn; undefined on a turn the channel did not start (an action turn, a browser message). */
2068
+ event: TEvent | undefined;
2069
+ /** The run the turn failed in, for a support reference. */
2070
+ runId: string | undefined;
2071
+ /** The session's chat id. */
2072
+ chatId: string;
2073
+ /** {@link CHANNEL_TURN_ERROR_MESSAGE}, to extend rather than replace. */
2074
+ defaultMessage: string;
2075
+ };
2076
+ /**
2077
+ * A channel's error line: a fixed string, or a function of the failure. A function that returns
2078
+ * nothing, or throws, falls back to {@link CHANNEL_TURN_ERROR_MESSAGE}.
2079
+ *
2080
+ * @example
2081
+ * ```ts
2082
+ * errorMessage: ({ error, runId }) =>
2083
+ * error.name === "RateLimitError" ? "I'm a bit busy, try again in a minute." : `That failed (ref ${runId}).`,
2084
+ * ```
2085
+ */
2086
+ export type ChannelErrorMessage<TEvent = unknown> = string | ((ctx: ChannelErrorContext<TEvent>) => string | null | undefined | Promise<string | null | undefined>);
2048
2087
  /**
2049
2088
  * A generic chat-frontend channel over any verified source. Unlike `slack()`, you supply the egress
2050
2089
  * `send` yourself (post/edit the reply back to your surface), so the whole round-trip is under your
@@ -2088,6 +2127,8 @@ export declare function chatChannelCustom<TEndpoint extends AnyWebhookEndpoint,
2088
2127
  filter?: ValidateWebhookFilter<InferWebhookEndpointEvent<TEndpoint>, TFilter>;
2089
2128
  startOn?: ValidateWebhookFilter<InferWebhookEndpointEvent<TEndpoint>, TStartOn>;
2090
2129
  delivery?: "final" | "stream";
2130
+ /** What the channel shows when a turn fails: a line, or a function of the failure. Defaults to {@link CHANNEL_TURN_ERROR_MESSAGE}. */
2131
+ errorMessage?: ChannelErrorMessage<NarrowWebhookEvent<InferWebhookEndpointEvent<TEndpoint>, TFilter>>;
2091
2132
  }): ChannelConnector<NarrowWebhookEvent<InferWebhookEndpointEvent<TEndpoint>, TFilter>, THandle>;
2092
2133
  export declare function resolveReactionChoice(choice: ChannelReactionChoice | undefined, event: unknown): Promise<string | undefined>;
2093
2134
  /**
@@ -2184,6 +2225,31 @@ export type ChatAgentOptions<TIdentifier extends string, TClientDataSchema exten
2184
2225
  * Ignored for sessions with no pin, and for sessions using `lockToVersion`.
2185
2226
  */
2186
2227
  versionSkew?: ChatVersionSkewPolicy;
2228
+ /**
2229
+ * Controls which chat content the chat runtime records on its spans in the run trace.
2230
+ *
2231
+ * - `recordUserMessage` (default `true`) records the last user message as `chat.user_message`
2232
+ * and as the turn span's preview text, and messages sent mid-turn as `pending.messages`.
2233
+ * - `recordClientData` (default `true`) records the raw `clientData` sent by the frontend as
2234
+ * `chat.client_data`.
2235
+ *
2236
+ * Set either to `false` to keep that content out of telemetry. Content recorded by the AI SDK's
2237
+ * own spans is controlled separately, through `experimental_telemetry` on the model call.
2238
+ *
2239
+ * @example
2240
+ * ```ts
2241
+ * chat.agent({
2242
+ * id: "my-chat",
2243
+ * telemetry: { recordUserMessage: false, recordClientData: false },
2244
+ * run: async ({ messages, signal, streamText }) =>
2245
+ * streamText({ model, messages, abortSignal: signal }),
2246
+ * });
2247
+ * ```
2248
+ */
2249
+ telemetry?: {
2250
+ recordUserMessage?: boolean;
2251
+ recordClientData?: boolean;
2252
+ };
2187
2253
  /**
2188
2254
  * Schema for validating `clientData` from the frontend.
2189
2255
  * Accepts Zod, ArkType, Valibot, or any supported schema library.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.chat = exports.__buildManagedStreamTextOptionsForTests = exports.upsertIncomingMessage = exports.PENDING_MESSAGE_INJECTED_TYPE = exports.ai = exports.snapshotTranscriptStorage = exports.reduceTranscriptChanges = exports.memoryTranscriptStorage = exports.defaultStorage = exports.__writeChatSnapshotProductionPathForTests = exports.__setWriteChatSnapshotImplForTests = exports.__setReadChatSnapshotImplForTests = exports.__readChatSnapshotProductionPathForTests = void 0;
3
+ exports.chat = exports.CHANNEL_TURN_ERROR_MESSAGE = exports.__buildManagedStreamTextOptionsForTests = exports.upsertIncomingMessage = exports.PENDING_MESSAGE_INJECTED_TYPE = exports.ai = exports.snapshotTranscriptStorage = exports.reduceTranscriptChanges = exports.memoryTranscriptStorage = exports.defaultStorage = exports.__writeChatSnapshotProductionPathForTests = exports.__setWriteChatSnapshotImplForTests = exports.__setReadChatSnapshotImplForTests = exports.__readChatSnapshotProductionPathForTests = void 0;
4
4
  exports.__setTranscriptStorageForTests = __setTranscriptStorageForTests;
5
5
  exports.__mergeByIdReplaceWinsForTests = __mergeByIdReplaceWinsForTests;
6
6
  exports.__setReplaySessionOutTailImplForTests = __setReplaySessionOutTailImplForTests;
@@ -2954,7 +2954,6 @@ async function drainSteeringQueue(config, messages, steps, queueOverride) {
2954
2954
  // Span attributes describe the offered batch; the chunk and callback below
2955
2955
  // describe what was actually claimed and injected.
2956
2956
  const messageTexts = uiMessages.map(textOfUIMessage);
2957
- const _previewText = messageTexts.length === 1 ? messageTexts[0].slice(0, 80) : `${queue.length} messages`;
2958
2957
  return tracer_js_1.tracer.startActiveSpan("pending message injected", async () => {
2959
2958
  /**
2960
2959
  * Claim before transforming, and inject only what was claimed.
@@ -3093,7 +3092,7 @@ async function drainSteeringQueue(config, messages, steps, queueOverride) {
3093
3092
  [v3_1.SemanticInternalAttributes.STYLE_ICON]: "tabler-message-forward",
3094
3093
  "pending.message_count": uiMessages.length,
3095
3094
  "pending.step_number": stepNumber,
3096
- "pending.messages": messageTexts,
3095
+ ...(ctx?.recordUserMessage === false ? {} : { "pending.messages": messageTexts }),
3097
3096
  ...(ctx?.chatId ? { "pending.chat_id": ctx.chatId } : {}),
3098
3097
  ...(ctx?.turn != null ? { "pending.turn": ctx.turn } : {}),
3099
3098
  ...(0, v3_1.accessoryAttributes)({
@@ -3839,13 +3838,39 @@ async function pipeChat(source, options, capture) {
3839
3838
  await waitUntilComplete();
3840
3839
  }
3841
3840
  }
3841
+ /** The AI SDK's default `toUIMessageStream` `onError`: the error's message, as the client sees it. */
3842
+ function uiStreamErrorText(error) {
3843
+ if (error == null)
3844
+ return "unknown error";
3845
+ if (typeof error === "string")
3846
+ return error;
3847
+ if (error instanceof Error)
3848
+ return error.message;
3849
+ return JSON.stringify(error);
3850
+ }
3851
+ /** The line a channel shows when a turn fails, unless the connector sets its own `errorMessage`. */
3852
+ exports.CHANNEL_TURN_ERROR_MESSAGE = "Something went wrong answering this. Try again.";
3853
+ async function channelTurnErrorMessage(connector, ctx) {
3854
+ const option = connector?.errorMessage;
3855
+ if (typeof option !== "function")
3856
+ return option || exports.CHANNEL_TURN_ERROR_MESSAGE;
3857
+ try {
3858
+ const line = await option({ ...ctx, defaultMessage: exports.CHANNEL_TURN_ERROR_MESSAGE });
3859
+ if (line)
3860
+ return line;
3861
+ }
3862
+ catch (error) {
3863
+ v3_1.logger.warn("chat.agent: channel errorMessage threw; posting the default line", { error });
3864
+ }
3865
+ return exports.CHANNEL_TURN_ERROR_MESSAGE;
3866
+ }
3842
3867
  /**
3843
3868
  * A generic chat-frontend channel over any verified source. Unlike `slack()`, you supply the egress
3844
3869
  * `send` yourself (post/edit the reply back to your surface), so the whole round-trip is under your
3845
3870
  * control. Use for providers without a preset, or to test the channel round-trip end to end.
3846
3871
  */
3847
3872
  function chatChannelCustom(options) {
3848
- const { id, endpoint, key, inbound, bind, seed, outbound, ack, send, renderInteraction, onInteraction, finalizeInteraction, react, reactions, progress, surface, onControl, stream, endInterruptedStream, download, received, toAction, lifecycle, cancelInteraction, filter, startOn, delivery, } = options;
3873
+ const { id, endpoint, key, inbound, bind, seed, outbound, ack, send, renderInteraction, onInteraction, finalizeInteraction, react, reactions, progress, surface, onControl, stream, endInterruptedStream, download, received, toAction, lifecycle, cancelInteraction, filter, startOn, delivery, errorMessage, } = options;
3849
3874
  if (!endpoint?.id) {
3850
3875
  throw new Error(`chat.channels.custom("${id}"): an endpoint is required. Declare one with webhooks.endpoint.define() and pass it as { endpoint }.`);
3851
3876
  }
@@ -3879,6 +3904,7 @@ function chatChannelCustom(options) {
3879
3904
  lifecycle,
3880
3905
  cancelInteraction,
3881
3906
  delivery: delivery ?? "final",
3907
+ errorMessage,
3882
3908
  };
3883
3909
  }
3884
3910
  const chatChannels = {
@@ -4376,6 +4402,7 @@ function makeChannelProgressObserver(connector, wireEvent, handle) {
4376
4402
  const progress = connector.progress;
4377
4403
  const turnCtx = channelTurnCtx(wireEvent, handle);
4378
4404
  const toolNames = new Map();
4405
+ const toolTitles = new Map();
4379
4406
  let chain = Promise.resolve();
4380
4407
  let stopped = false;
4381
4408
  const emit = (event) => {
@@ -4398,6 +4425,10 @@ function makeChannelProgressObserver(connector, wireEvent, handle) {
4398
4425
  const c = chunk;
4399
4426
  if (!c?.type || !c.toolCallId)
4400
4427
  return;
4428
+ if (c.title)
4429
+ toolTitles.set(c.toolCallId, c.title);
4430
+ const title = toolTitles.get(c.toolCallId);
4431
+ const titled = title ? { title } : {};
4401
4432
  if (c.type === "tool-input-start" && c.toolName) {
4402
4433
  toolNames.set(c.toolCallId, c.toolName);
4403
4434
  return;
@@ -4405,7 +4436,7 @@ function makeChannelProgressObserver(connector, wireEvent, handle) {
4405
4436
  if (c.type === "tool-input-available") {
4406
4437
  const toolName = c.toolName ?? toolNames.get(c.toolCallId) ?? "tool";
4407
4438
  toolNames.set(c.toolCallId, toolName);
4408
- emit({ type: "tool-call", toolCallId: c.toolCallId, toolName, input: c.input });
4439
+ emit({ type: "tool-call", toolCallId: c.toolCallId, toolName, ...titled, input: c.input });
4409
4440
  return;
4410
4441
  }
4411
4442
  if (c.type === "tool-output-available" && !c.preliminary) {
@@ -4413,6 +4444,7 @@ function makeChannelProgressObserver(connector, wireEvent, handle) {
4413
4444
  type: "tool-result",
4414
4445
  toolCallId: c.toolCallId,
4415
4446
  toolName: toolNames.get(c.toolCallId) ?? "tool",
4447
+ ...titled,
4416
4448
  output: c.output,
4417
4449
  });
4418
4450
  return;
@@ -4422,6 +4454,7 @@ function makeChannelProgressObserver(connector, wireEvent, handle) {
4422
4454
  type: "tool-error",
4423
4455
  toolCallId: c.toolCallId,
4424
4456
  toolName: toolNames.get(c.toolCallId) ?? "tool",
4457
+ ...titled,
4425
4458
  errorText: c.errorText ?? "tool failed",
4426
4459
  });
4427
4460
  }
@@ -4513,6 +4546,7 @@ function stopPendingToolCalls(message, toolCallIds, errorText) {
4513
4546
  */
4514
4547
  function makeChannelReplyStreamObserver(stream) {
4515
4548
  const toolNames = new Map();
4549
+ const toolTitles = new Map();
4516
4550
  let stopped = false;
4517
4551
  const safe = (fn) => {
4518
4552
  try {
@@ -4535,22 +4569,32 @@ function makeChannelReplyStreamObserver(stream) {
4535
4569
  if (!stream.progress || !c?.type || !c.toolCallId)
4536
4570
  return;
4537
4571
  const toolCallId = c.toolCallId;
4572
+ if (c.title)
4573
+ toolTitles.set(toolCallId, c.title);
4574
+ const title = toolTitles.get(toolCallId);
4575
+ const titled = title ? { title } : {};
4538
4576
  if (c.type === "tool-input-start" && c.toolName) {
4539
4577
  toolNames.set(toolCallId, c.toolName);
4540
4578
  }
4541
4579
  else if (c.type === "tool-input-available") {
4542
4580
  const toolName = c.toolName ?? toolNames.get(toolCallId) ?? "tool";
4543
4581
  toolNames.set(toolCallId, toolName);
4544
- safe(() => stream.progress({ type: "tool-call", toolCallId, toolName, input: c.input }));
4582
+ safe(() => stream.progress({ type: "tool-call", toolCallId, toolName, ...titled, input: c.input }));
4545
4583
  }
4546
4584
  else if (c.type === "tool-output-available" && !c.preliminary) {
4547
4585
  const toolName = toolNames.get(toolCallId) ?? "tool";
4548
- safe(() => stream.progress({ type: "tool-result", toolCallId, toolName, output: c.output }));
4586
+ safe(() => stream.progress({
4587
+ type: "tool-result",
4588
+ toolCallId,
4589
+ toolName,
4590
+ ...titled,
4591
+ output: c.output,
4592
+ }));
4549
4593
  }
4550
4594
  else if (c.type === "tool-output-error") {
4551
4595
  const toolName = toolNames.get(toolCallId) ?? "tool";
4552
4596
  const errorText = c.errorText ?? "tool failed";
4553
- safe(() => stream.progress({ type: "tool-error", toolCallId, toolName, errorText }));
4597
+ safe(() => stream.progress({ type: "tool-error", toolCallId, toolName, ...titled, errorText }));
4554
4598
  }
4555
4599
  },
4556
4600
  stop() {
@@ -4831,7 +4875,9 @@ function chatCustomAgent(options) {
4831
4875
  return task;
4832
4876
  }
4833
4877
  function chatAgent(options) {
4834
- const { run: userRun, clientDataSchema, onBoot, onRecoveryBoot, onPreload, onChatStart, onValidateMessages, hydrateMessages, storage, actionSchema, events, channels, onAction, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, tools: toolsOption, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, onChatSuspend, onChatResume, exitAfterPreloadIdle = false, oomMachine, registry: promptRegistry, system: agentSystem, cacheControl: agentCacheControl, systemProviderOptions: agentSystemProviderOptions, versionSkew, ...restOptions } = options;
4878
+ const { run: userRun, clientDataSchema, onBoot, onRecoveryBoot, onPreload, onChatStart, onValidateMessages, hydrateMessages, storage, actionSchema, events, channels, onAction, onTurnStart, onBeforeTurnComplete, onCompacted, compaction, pendingMessages: pendingMessagesConfig, prepareMessages, tools: toolsOption, onTurnComplete, maxTurns = 100, turnTimeout = "1h", idleTimeoutInSeconds = 30, chatAccessTokenTTL = "1h", preloadIdleTimeoutInSeconds, preloadTimeout, uiMessageStreamOptions, onChatSuspend, onChatResume, exitAfterPreloadIdle = false, oomMachine, registry: promptRegistry, system: agentSystem, cacheControl: agentCacheControl, systemProviderOptions: agentSystemProviderOptions, versionSkew, telemetry: telemetryOptions, ...restOptions } = options;
4879
+ const recordUserMessage = telemetryOptions?.recordUserMessage ?? true;
4880
+ const recordClientData = telemetryOptions?.recordClientData ?? true;
4835
4881
  if (hydrateMessages) {
4836
4882
  if (storage) {
4837
4883
  throw new Error(`chat.agent: "${options.id}" sets both \`hydrateMessages\` and \`storage\`. ` +
@@ -6113,6 +6159,8 @@ function chatAgent(options) {
6113
6159
  let channelFinalSendFailed = false;
6114
6160
  /** The stream-level error (an `error` chunk) this turn ended on, if any; posted to the channel in place of an empty reply. */
6115
6161
  let channelStreamErrorText;
6162
+ /** The original error behind that chunk, captured from `toUIMessageStream`'s `onError` when auto-piping. */
6163
+ let channelStreamError;
6116
6164
  /** The text this turn streamed, so the channel reply is this turn's answer and not the whole message. */
6117
6165
  let channelTurnText;
6118
6166
  try {
@@ -6520,7 +6568,7 @@ function chatAgent(options) {
6520
6568
  : "tabler-message-chatbot",
6521
6569
  [v3_1.SemanticInternalAttributes.ENTITY_TYPE]: isAction ? "chat-action" : "chat-turn",
6522
6570
  };
6523
- if (lastUserMessage) {
6571
+ if (lastUserMessage && recordUserMessage) {
6524
6572
  turnAttributes["chat.user_message"] = lastUserMessage;
6525
6573
  // Show a truncated preview of the user message as an accessory
6526
6574
  const preview = lastUserMessage.length > 80
@@ -6531,7 +6579,7 @@ function chatAgent(options) {
6531
6579
  style: "codepath",
6532
6580
  }));
6533
6581
  }
6534
- if (wireMetadata !== undefined) {
6582
+ if (wireMetadata !== undefined && recordClientData) {
6535
6583
  turnAttributes["chat.client_data"] =
6536
6584
  typeof wireMetadata === "string" ? wireMetadata : JSON.stringify(wireMetadata);
6537
6585
  }
@@ -6556,6 +6604,7 @@ function chatAgent(options) {
6556
6604
  turn,
6557
6605
  continuation,
6558
6606
  clientData,
6607
+ recordUserMessage,
6559
6608
  });
6560
6609
  // Resolve the per-turn `tools` set now that turn context
6561
6610
  // (incl. parsed clientData) exists, so every toModelMessages
@@ -7456,6 +7505,12 @@ function chatAgent(options) {
7456
7505
  // messageId. Without this, the frontend and backend generate IDs
7457
7506
  // independently and they won't match for ID-based dedup.
7458
7507
  generateMessageId: resolvedOptions.generateMessageId ?? ai_runtime_js_1.generateId,
7508
+ onError: (error) => {
7509
+ channelStreamError ??= error;
7510
+ return resolvedOptions.onError
7511
+ ? resolvedOptions.onError(error)
7512
+ : uiStreamErrorText(error);
7513
+ },
7459
7514
  onFinish: ({ responseMessage, finishReason, }) => {
7460
7515
  capturedResponseMessage = responseMessage;
7461
7516
  capturedPartialResponse = responseMessage;
@@ -7980,6 +8035,21 @@ function chatAgent(options) {
7980
8035
  const renderControls = !!((wireChannelEvent || boundTurn) &&
7981
8036
  pendingToolCalls.length > 0 &&
7982
8037
  channelConn?.renderInteraction);
8038
+ let channelErrorLine;
8039
+ if (channelStreamErrorText && (wireChannelEvent || boundTurn)) {
8040
+ v3_1.logger.warn("chat.agent: channel turn ended on a model or stream error", {
8041
+ errorText: channelStreamErrorText,
8042
+ });
8043
+ channelErrorLine = await channelTurnErrorMessage(channelConn, {
8044
+ error: channelStreamError instanceof Error
8045
+ ? channelStreamError
8046
+ : new Error(channelStreamErrorText),
8047
+ partialText: channelTurnText?.trim() ?? "",
8048
+ event: channelWireEvent?.event,
8049
+ runId: ctx.run.id,
8050
+ chatId: currentWirePayload.chatId,
8051
+ });
8052
+ }
7983
8053
  let channelReplyDelivered = false;
7984
8054
  if (channelReplyStream) {
7985
8055
  const replyStream = channelReplyStream;
@@ -7988,7 +8058,9 @@ function chatAgent(options) {
7988
8058
  const ended = await replyStream.end({
7989
8059
  stopped: turnCompleteEvent.stopped,
7990
8060
  pending: renderControls,
7991
- ...(channelStreamErrorText ? { errorText: channelStreamErrorText } : {}),
8061
+ ...(channelStreamErrorText
8062
+ ? { errorText: channelErrorLine ?? exports.CHANNEL_TURN_ERROR_MESSAGE }
8063
+ : {}),
7992
8064
  });
7993
8065
  channelReplyDelivered = ended.delivered;
7994
8066
  }
@@ -8018,13 +8090,16 @@ function chatAgent(options) {
8018
8090
  ? channelTurnText
8019
8091
  : channelReplyText(turnCompleteEvent.responseMessage);
8020
8092
  // A model or stream error surfaces as an `error` chunk and an empty reply, not as a
8021
- // thrown turn. The mapper gets it on this one call, with the error text standing in
8022
- // for an empty reply, so the placeholder never sits there as if the agent were
8023
- // still working.
8093
+ // thrown turn. The mapper gets it on this one call, with the connector's error line
8094
+ // standing in for an empty reply, so the placeholder never sits there as if the agent
8095
+ // were still working. The error text itself stays on the run, not in the channel.
8024
8096
  const finalReply = {
8025
8097
  // Prefer what this turn streamed; a resumed message carries earlier turns' text too.
8026
- text: channelStreamErrorText && replyText.trim().length === 0
8027
- ? channelStreamErrorText
8098
+ // A failed turn always ends on the connector's error line, after any partial text.
8099
+ text: channelErrorLine
8100
+ ? replyText.trim().length > 0
8101
+ ? `${replyText.trimEnd()}\n\n${channelErrorLine}`
8102
+ : channelErrorLine
8028
8103
  : replyText,
8029
8104
  message: turnCompleteEvent.responseMessage,
8030
8105
  final: true,
@@ -8393,18 +8468,16 @@ function chatAgent(options) {
8393
8468
  v3_1.logger.warn("chat.agent: channel error reaction failed", { error: reactionError });
8394
8469
  }
8395
8470
  }
8396
- let channelErrorText = "An unexpected error occurred";
8397
- if ((channelWireEvent || boundTurn) && channelConn) {
8398
- const sanitizeChannelError = resolveUIMessageStreamOptions().onError;
8399
- try {
8400
- channelErrorText = sanitizeChannelError
8401
- ? sanitizeChannelError(turnError)
8402
- : turnError instanceof Error
8403
- ? turnError.message
8404
- : channelErrorText;
8405
- }
8406
- catch { }
8407
- }
8471
+ // The channel gets the connector's error line, never the error itself: the run keeps that.
8472
+ const channelErrorText = channelReplyStream || ((channelWireEvent || boundTurn) && channelConn?.send)
8473
+ ? await channelTurnErrorMessage(channelConn, {
8474
+ error: turnError instanceof Error ? turnError : new Error(String(turnError)),
8475
+ partialText: channelTurnText?.trim() ?? "",
8476
+ event: channelWireEvent?.event,
8477
+ runId: ctx.run.id,
8478
+ chatId: currentWirePayload.chatId,
8479
+ })
8480
+ : exports.CHANNEL_TURN_ERROR_MESSAGE;
8408
8481
  let errorStreamDelivered = false;
8409
8482
  if (channelReplyStream) {
8410
8483
  const replyStream = channelReplyStream;