Skip to content

Commit 67d563f

Browse files
committed
feat(sdk): ctx on chat.task hooks; ai-chat E2B sandbox; docs patterns
- Pass TaskRunContext through chat lifecycle events, CompactedEvent, and ChatTaskRunPayload; use ctx.run.id for chat access tokens - Export TaskRunContext from @trigger.dev/sdk - ai-chat reference: executeCode via E2B, code-sandbox module, warm on onTurnStart, dispose on token onWait and onComplete; chat.local run id - Docs: database persistence + code sandbox pattern pages; reference and backend updates for ctx; chat.defer anchor; navigation
1 parent 6e56fbc commit 67d563f

File tree

8 files changed

+299
-38
lines changed

8 files changed

+299
-38
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Add `TaskRunContext` (`ctx`) to all `chat.task` lifecycle events, `CompactedEvent`, and `ChatTaskRunPayload`. Export `TaskRunContext` from `@trigger.dev/sdk`.
6+

packages/trigger-sdk/src/v3/ai.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
type TaskIdentifier,
1414
type TaskOptions,
1515
type TaskSchema,
16+
type TaskRunContext,
1617
type TaskWithSchema,
1718
} from "@trigger.dev/core/v3";
1819
import type {
@@ -44,6 +45,9 @@ import type { ResolvedPrompt } from "./prompt.js";
4445
import { streams } from "./streams.js";
4546
import { createTask } from "./shared.js";
4647
import { tracer } from "./tracer.js";
48+
49+
/** Re-export for typing `ctx` in `chat.task` hooks without importing `@trigger.dev/core`. */
50+
export type { TaskRunContext } from "@trigger.dev/core/v3";
4751
import {
4852
CHAT_STREAM_KEY as _CHAT_STREAM_KEY,
4953
CHAT_MESSAGES_STREAM_ID,
@@ -612,6 +616,11 @@ export type ChatTaskSignals = {
612616
*/
613617
export type ChatTaskRunPayload<TClientData = unknown> = ChatTaskPayload<TClientData> &
614618
ChatTaskSignals & {
619+
/**
620+
* Task run context — same object as the `ctx` passed to a standard `task({ run })` handler’s second argument.
621+
* Use for tags, metadata, parent run links, or any API that needs the full run record.
622+
*/
623+
ctx: TaskRunContext;
615624
/** Token usage from the previous turn. Undefined on turn 0. */
616625
previousTurnUsage?: LanguageModelUsage;
617626
/** Cumulative token usage across all completed turns so far. */
@@ -742,6 +751,8 @@ interface CompactionState {
742751
const chatCompactionStateKey = locals.create<CompactionState>("chat.compaction");
743752
const chatOnCompactedKey =
744753
locals.create<(event: CompactedEvent) => Promise<void> | void>("chat.onCompacted");
754+
/** @internal Full task `ctx` for the active `chat.task` run (for hooks invoked from nested compaction). */
755+
const chatTaskRunContextKey = locals.create<TaskRunContext>("chat.taskRunContext");
745756
const chatPrepareMessagesKey =
746757
locals.create<(event: PrepareMessagesEvent<unknown>) => ModelMessage[] | Promise<ModelMessage[]>>(
747758
"chat.prepareMessages"
@@ -980,6 +991,8 @@ export type CompactionChunkData = {
980991
* Event passed to the `onCompacted` callback.
981992
*/
982993
export type CompactedEvent = {
994+
/** Task run context — same as `task` lifecycle hooks and `chat.task` `run({ ctx })`. */
995+
ctx: TaskRunContext;
983996
/** The generated summary text. */
984997
summary: string;
985998
/** The messages that were compacted (pre-compaction). */
@@ -1280,6 +1293,7 @@ async function chatCompact(
12801293
const onCompactedHook = locals.get(chatOnCompactedKey);
12811294
if (onCompactedHook) {
12821295
await onCompactedHook({
1296+
ctx: locals.get(chatTaskRunContextKey)!,
12831297
summary,
12841298
messages,
12851299
messageCount: messages.length,
@@ -1863,6 +1877,8 @@ async function pipeChat(
18631877
* Event passed to the `onPreload` callback.
18641878
*/
18651879
export type PreloadEvent<TClientData = unknown> = {
1880+
/** Task run context — same as `task({ run })` second-argument `ctx`. */
1881+
ctx: TaskRunContext;
18661882
/** The unique identifier for the chat session. */
18671883
chatId: string;
18681884
/** The Trigger.dev run ID for this conversation. */
@@ -1879,6 +1895,8 @@ export type PreloadEvent<TClientData = unknown> = {
18791895
* Event passed to the `onChatStart` callback.
18801896
*/
18811897
export type ChatStartEvent<TClientData = unknown> = {
1898+
/** Task run context — same as `task({ run })` second-argument `ctx`. */
1899+
ctx: TaskRunContext;
18821900
/** The unique identifier for the chat session. */
18831901
chatId: string;
18841902
/** The initial model-ready messages for this conversation. */
@@ -1903,6 +1921,8 @@ export type ChatStartEvent<TClientData = unknown> = {
19031921
* Event passed to the `onTurnStart` callback.
19041922
*/
19051923
export type TurnStartEvent<TClientData = unknown, TUIM extends UIMessage = UIMessage> = {
1924+
/** Task run context — same as `task({ run })` second-argument `ctx`. */
1925+
ctx: TaskRunContext;
19061926
/** The unique identifier for the chat session. */
19071927
chatId: string;
19081928
/** The accumulated model-ready messages (all turns so far, including new user message). */
@@ -1935,6 +1955,8 @@ export type TurnStartEvent<TClientData = unknown, TUIM extends UIMessage = UIMes
19351955
* Event passed to the `onTurnComplete` callback.
19361956
*/
19371957
export type TurnCompleteEvent<TClientData = unknown, TUIM extends UIMessage = UIMessage> = {
1958+
/** Task run context — same as `task({ run })` second-argument `ctx`. */
1959+
ctx: TaskRunContext;
19381960
/** The unique identifier for the chat session. */
19391961
chatId: string;
19401962
/** The full accumulated conversation in model format (all turns so far). */
@@ -2022,8 +2044,9 @@ export type ChatTaskOptions<
20222044
* chat.task({
20232045
* id: "my-chat",
20242046
* clientDataSchema: z.object({ model: z.string().optional(), userId: z.string() }),
2025-
* run: async ({ messages, clientData, signal }) => {
2047+
* run: async ({ messages, clientData, ctx, signal }) => {
20262048
* // clientData is typed as { model?: string; userId: string }
2049+
* // ctx is the same TaskRunContext as in task({ run: (payload, { ctx }) => ... })
20272050
* },
20282051
* });
20292052
* ```
@@ -2034,7 +2057,8 @@ export type ChatTaskOptions<
20342057
* The run function for the chat task.
20352058
*
20362059
* Receives a `ChatTaskRunPayload` with the conversation messages, chat session ID,
2037-
* trigger type, and abort signals (`signal`, `cancelSignal`, `stopSignal`).
2060+
* trigger type, task `ctx` (same as `task({ run })`’s second argument), and abort signals
2061+
* (`signal`, `cancelSignal`, `stopSignal`).
20382062
*
20392063
* **Auto-piping:** If this function returns a value with `.toUIMessageStream()`,
20402064
* the stream is automatically piped to the frontend.
@@ -2049,7 +2073,7 @@ export type ChatTaskOptions<
20492073
*
20502074
* @example
20512075
* ```ts
2052-
* onPreload: async ({ chatId, clientData }) => {
2076+
* onPreload: async ({ ctx, chatId, clientData }) => {
20532077
* await db.chat.create({ data: { id: chatId } });
20542078
* userContext.init(await loadUser(clientData.userId));
20552079
* }
@@ -2064,7 +2088,7 @@ export type ChatTaskOptions<
20642088
*
20652089
* @example
20662090
* ```ts
2067-
* onChatStart: async ({ chatId, messages, clientData }) => {
2091+
* onChatStart: async ({ ctx, chatId, messages, clientData }) => {
20682092
* await db.chat.create({ data: { id: chatId, userId: clientData.userId } });
20692093
* }
20702094
* ```
@@ -2080,7 +2104,7 @@ export type ChatTaskOptions<
20802104
*
20812105
* @example
20822106
* ```ts
2083-
* onTurnStart: async ({ chatId, uiMessages }) => {
2107+
* onTurnStart: async ({ ctx, chatId, uiMessages }) => {
20842108
* await db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } });
20852109
* }
20862110
* ```
@@ -2097,7 +2121,7 @@ export type ChatTaskOptions<
20972121
*
20982122
* @example
20992123
* ```ts
2100-
* onBeforeTurnComplete: async ({ writer, usage }) => {
2124+
* onBeforeTurnComplete: async ({ ctx, writer, usage }) => {
21012125
* if (usage?.inputTokens && usage.inputTokens > 5000) {
21022126
* writer.write({ type: "data-compaction", id: generateId(), data: { status: "compacting" } });
21032127
* // ... compact messages ...
@@ -2117,7 +2141,7 @@ export type ChatTaskOptions<
21172141
*
21182142
* @example
21192143
* ```ts
2120-
* onCompacted: async ({ summary, totalTokens, chatId }) => {
2144+
* onCompacted: async ({ ctx, summary, totalTokens, chatId }) => {
21212145
* logger.info("Compacted", { totalTokens, chatId });
21222146
* await db.compactionLog.create({ data: { chatId, summary } });
21232147
* }
@@ -2177,7 +2201,7 @@ export type ChatTaskOptions<
21772201
*
21782202
* @example
21792203
* ```ts
2180-
* onTurnComplete: async ({ chatId, messages }) => {
2204+
* onTurnComplete: async ({ ctx, chatId, messages }) => {
21812205
* await db.chat.update({ where: { id: chatId }, data: { messages } });
21822206
* }
21832207
* ```
@@ -2366,8 +2390,10 @@ function chatTask<
23662390
...restOptions,
23672391
run: async (
23682392
payload: ChatTaskWirePayload<TUIMessage, inferSchemaIn<TClientDataSchema>>,
2369-
{ signal: runSignal }
2393+
{ signal: runSignal, ctx }
23702394
) => {
2395+
locals.set(chatTaskRunContextKey, ctx);
2396+
23712397
// Set gen_ai.conversation.id on the run-level span for dashboard context
23722398
const activeSpan = trace.getActiveSpan();
23732399
if (activeSpan) {
@@ -2433,7 +2459,7 @@ function chatTask<
24332459
activeSpan.setAttribute("chat.preloaded", true);
24342460
}
24352461

2436-
const currentRunId = taskContext.ctx?.run.id ?? "";
2462+
const currentRunId = ctx.run.id;
24372463
let preloadAccessToken = "";
24382464
if (currentRunId) {
24392465
try {
@@ -2461,6 +2487,7 @@ function chatTask<
24612487
async () => {
24622488
await withChatWriter(async (writer) => {
24632489
await onPreload({
2490+
ctx,
24642491
chatId: payload.chatId,
24652492
runId: currentRunId,
24662493
chatAccessToken: preloadAccessToken,
@@ -2671,7 +2698,7 @@ function chatTask<
26712698

26722699
// Mint a scoped public access token once per turn, reused for
26732700
// onChatStart, onTurnStart, onTurnComplete, and the turn-complete chunk.
2674-
const currentRunId = taskContext.ctx?.run.id ?? "";
2701+
const currentRunId = ctx.run.id;
26752702
let turnAccessToken = "";
26762703
if (currentRunId) {
26772704
try {
@@ -2694,6 +2721,7 @@ function chatTask<
26942721
async () => {
26952722
await withChatWriter(async (writer) => {
26962723
await onChatStart({
2724+
ctx,
26972725
chatId: currentWirePayload.chatId,
26982726
messages: accumulatedMessages,
26992727
clientData,
@@ -2728,6 +2756,7 @@ function chatTask<
27282756
async () => {
27292757
await withChatWriter(async (writer) => {
27302758
await onTurnStart({
2759+
ctx,
27312760
chatId: currentWirePayload.chatId,
27322761
messages: accumulatedMessages,
27332762
uiMessages: accumulatedUIMessages,
@@ -2799,6 +2828,7 @@ function chatTask<
27992828
preloaded,
28002829
previousTurnUsage,
28012830
totalUsage: cumulativeUsage,
2831+
ctx,
28022832
signal: combinedSignal,
28032833
cancelSignal,
28042834
stopSignal,
@@ -3057,6 +3087,7 @@ function chatTask<
30573087
const onCompactedHook = locals.get(chatOnCompactedKey);
30583088
if (onCompactedHook) {
30593089
await onCompactedHook({
3090+
ctx,
30603091
summary,
30613092
messages: accumulatedMessages,
30623093
messageCount: accumulatedMessages.length,
@@ -3108,6 +3139,7 @@ function chatTask<
31083139
}
31093140

31103141
const turnCompleteEvent = {
3142+
ctx,
31113143
chatId: currentWirePayload.chatId,
31123144
messages: accumulatedMessages,
31133145
uiMessages: accumulatedUIMessages,

packages/trigger-sdk/src/v3/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ export type { Context };
2222

2323
import type { Context } from "./shared.js";
2424

25-
import type { ApiClientConfiguration } from "@trigger.dev/core/v3";
25+
import type { ApiClientConfiguration, TaskRunContext } from "@trigger.dev/core/v3";
2626

27-
export type { ApiClientConfiguration };
27+
export type { ApiClientConfiguration, TaskRunContext };
2828

2929
export {
3030
ApiError,

0 commit comments

Comments
 (0)