diff --git a/packages/trigger-sdk/src/v3/ai.ts b/packages/trigger-sdk/src/v3/ai.ts index 8bec798e9..f9ba01861 100644 --- a/packages/trigger-sdk/src/v3/ai.ts +++ b/packages/trigger-sdk/src/v3/ai.ts @@ -15,7 +15,11 @@ import { auth } from "./auth.js"; import { metadata } from "./metadata.js"; import { streams } from "./streams.js"; import { createTask } from "./shared.js"; -import { wait } from "./wait.js"; +import { + CHAT_STREAM_KEY as _CHAT_STREAM_KEY, + CHAT_MESSAGES_STREAM_ID, + CHAT_STOP_STREAM_ID, +} from "./chat-constants.js"; const METADATA_KEY = "tool.execute.options"; @@ -136,13 +140,13 @@ export const ai = { * ```ts * // actions.ts * "use server"; - * import { createChatAccessToken } from "@trigger.dev/sdk/ai"; - * import type { chat } from "@/trigger/chat"; + * import { chat } from "@trigger.dev/sdk/ai"; + * import type { myChat } from "@/trigger/chat"; * - * export const getChatToken = () => createChatAccessToken("ai-chat"); + * export const getChatToken = () => chat.createAccessToken("my-chat"); * ``` */ -export async function createChatAccessToken( +function createChatAccessToken( taskId: TaskIdentifier ): Promise { return auth.createTriggerPublicToken(taskId as string, { multipleUse: true }); @@ -157,7 +161,10 @@ export async function createChatAccessToken( * Both `TriggerChatTransport` (frontend) and `pipeChat`/`chatTask` (backend) * use this key by default. */ -export const CHAT_STREAM_KEY = "chat"; +export const CHAT_STREAM_KEY = _CHAT_STREAM_KEY; + +// Re-export input stream IDs for advanced usage +export { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID }; /** * The payload shape that the chat transport sends to the triggered task. @@ -187,6 +194,28 @@ export type ChatTaskPayload = { metadata?: unknown; }; +/** + * Abort signals provided to the `chatTask` run function. + */ +export type ChatTaskSignals = { + /** Combined signal — fires on run cancel OR stop generation. Pass to `streamText`. */ + signal: AbortSignal; + /** Fires only when the run is cancelled, expired, or exceeds maxDuration. */ + cancelSignal: AbortSignal; + /** Fires only when the frontend stops generation for this turn (per-turn, reset each turn). */ + stopSignal: AbortSignal; +}; + +/** + * The full payload passed to a `chatTask` run function. + * Extends `ChatTaskPayload` (the wire payload) with abort signals. + */ +export type ChatTaskRunPayload = ChatTaskPayload & ChatTaskSignals; + +// Input streams for bidirectional chat communication +const messagesInput = streams.input({ id: CHAT_MESSAGES_STREAM_ID }); +const stopInput = streams.input<{ stop: true; message?: string }>({ id: CHAT_STOP_STREAM_ID }); + /** * Tracks how many times `pipeChat` has been called in the current `chatTask` run. * Used to prevent double-piping when a user both calls `pipeChat()` manually @@ -253,7 +282,7 @@ function isReadableStream(value: unknown): value is ReadableStream { * @example * ```ts * import { task } from "@trigger.dev/sdk"; - * import { pipeChat, type ChatTaskPayload } from "@trigger.dev/sdk/ai"; + * import { chat, type ChatTaskPayload } from "@trigger.dev/sdk/ai"; * import { streamText, convertToModelMessages } from "ai"; * * export const myChatTask = task({ @@ -264,7 +293,7 @@ function isReadableStream(value: unknown): value is ReadableStream { * messages: convertToModelMessages(payload.messages), * }); * - * await pipeChat(result); + * await chat.pipe(result); * }, * }); * ``` @@ -274,11 +303,11 @@ function isReadableStream(value: unknown): value is ReadableStream { * // Works from anywhere inside a task — even deep in your agent code * async function runAgentLoop(messages: CoreMessage[]) { * const result = streamText({ model, messages }); - * await pipeChat(result); + * await chat.pipe(result); * } * ``` */ -export async function pipeChat( +async function pipeChat( source: UIMessageStreamable | AsyncIterable | ReadableStream, options?: PipeChatOptions ): Promise { @@ -314,16 +343,15 @@ export async function pipeChat( * Options for defining a chat task. * * Extends the standard `TaskOptions` but pre-types the payload as `ChatTaskPayload` - * and overrides `run` to accept `ChatTaskPayload` directly. + * and overrides `run` to accept `ChatTaskRunPayload` (with abort signals). * * **Auto-piping:** If the `run` function returns a value with `.toUIMessageStream()` * (like a `StreamTextResult`), the stream is automatically piped to the frontend. - * For complex flows, use `pipeChat()` manually from anywhere in your code. * - * **Single-run mode:** By default, the task runs a waitpoint loop so that the + * **Single-run mode:** By default, the task uses input streams so that the * entire conversation lives inside one run. After each AI response, the task - * emits a control chunk and pauses via `wait.forToken`. The frontend transport - * resumes the same run by completing the token with the next set of messages. + * emits a control chunk and suspends via `messagesInput.wait()`. The frontend + * transport resumes the same run by sending the next message via input streams. */ export type ChatTaskOptions = Omit< TaskOptions, @@ -332,13 +360,13 @@ export type ChatTaskOptions = Omit< /** * The run function for the chat task. * - * Receives a `ChatTaskPayload` with the conversation messages, chat session ID, - * and trigger type. + * Receives a `ChatTaskRunPayload` with the conversation messages, chat session ID, + * trigger type, and abort signals (`signal`, `cancelSignal`, `stopSignal`). * * **Auto-piping:** If this function returns a value with `.toUIMessageStream()`, * the stream is automatically piped to the frontend. */ - run: (payload: ChatTaskPayload) => Promise; + run: (payload: ChatTaskRunPayload) => Promise; /** * Maximum number of conversational turns (message round-trips) a single run @@ -351,7 +379,7 @@ export type ChatTaskOptions = Omit< /** * How long to wait for the next message before timing out and ending the run. - * Accepts any duration string recognised by `wait.createToken` (e.g. `"1h"`, `"30m"`). + * Accepts any duration string (e.g. `"1h"`, `"30m"`). * * @default "1h" */ @@ -361,87 +389,164 @@ export type ChatTaskOptions = Omit< /** * Creates a Trigger.dev task pre-configured for AI SDK chat. * - * - **Pre-types the payload** as `ChatTaskPayload` — no manual typing needed + * - **Pre-types the payload** as `ChatTaskRunPayload` — includes abort signals * - **Auto-pipes the stream** if `run` returns a `StreamTextResult` + * - **Multi-turn**: keeps the conversation in a single run using input streams + * - **Stop support**: frontend can stop generation mid-stream via the stop input stream * - For complex flows, use `pipeChat()` from anywhere inside your task code * * @example * ```ts - * import { chatTask } from "@trigger.dev/sdk/ai"; + * import { chat } from "@trigger.dev/sdk/ai"; * import { streamText, convertToModelMessages } from "ai"; * import { openai } from "@ai-sdk/openai"; * - * // Simple: return streamText result — auto-piped to the frontend - * export const myChatTask = chatTask({ - * id: "my-chat-task", - * run: async ({ messages }) => { + * export const myChat = chat.task({ + * id: "my-chat", + * run: async ({ messages, signal }) => { * return streamText({ * model: openai("gpt-4o"), * messages: convertToModelMessages(messages), + * abortSignal: signal, // fires on stop or run cancel * }); * }, * }); * ``` - * - * @example - * ```ts - * import { chatTask, pipeChat } from "@trigger.dev/sdk/ai"; - * - * // Complex: pipeChat() from deep in your agent code - * export const myAgentTask = chatTask({ - * id: "my-agent-task", - * run: async ({ messages }) => { - * await runComplexAgentLoop(messages); - * }, - * }); - * ``` */ -export function chatTask( +function chatTask( options: ChatTaskOptions ): Task { const { run: userRun, maxTurns = 100, turnTimeout = "1h", ...restOptions } = options; return createTask({ ...restOptions, - run: async (payload: ChatTaskPayload) => { + run: async (payload: ChatTaskPayload, { signal: runSignal }) => { let currentPayload = payload; - for (let turn = 0; turn < maxTurns; turn++) { - _chatPipeCount = 0; + // Mutable reference to the current turn's stop controller so the + // stop input stream listener (registered once) can abort the right turn. + let currentStopController: AbortController | undefined; - const result = await userRun(currentPayload); + // Listen for stop signals for the lifetime of the run + const stopSub = stopInput.on((data) => { + currentStopController?.abort(data?.message || "stopped"); + }); - // Auto-pipe if the run function returned a StreamTextResult or similar, - // but only if pipeChat() wasn't already called manually during this turn - if (_chatPipeCount === 0 && isUIMessageStreamable(result)) { - await pipeChat(result); - } + try { + for (let turn = 0; turn < maxTurns; turn++) { + _chatPipeCount = 0; - // Create a waitpoint token and emit a control chunk so the frontend - // knows to resume this run instead of triggering a new one. - const token = await wait.createToken({ timeout: turnTimeout }); + // Per-turn stop controller (reset each turn) + const stopController = new AbortController(); + currentStopController = stopController; - const { waitUntilComplete } = streams.writer(CHAT_STREAM_KEY, { - execute: ({ write }) => { - write({ - type: "__trigger_waitpoint_ready", - tokenId: token.id, - publicAccessToken: token.publicAccessToken, + // Three signals for the user's run function + const stopSignal = stopController.signal; + const cancelSignal = runSignal; + const combinedSignal = AbortSignal.any([runSignal, stopController.signal]); + + // Buffer messages that arrive during streaming + const pendingMessages: ChatTaskPayload[] = []; + const msgSub = messagesInput.on((msg) => { + pendingMessages.push(msg as ChatTaskPayload); + }); + + try { + const result = await userRun({ + ...currentPayload, + signal: combinedSignal, + cancelSignal, + stopSignal, }); - }, - }); - await waitUntilComplete(); - // Pause until the frontend completes the token with the next message - const next = await wait.forToken(token); + // Auto-pipe if the run function returned a StreamTextResult or similar, + // but only if pipeChat() wasn't already called manually during this turn + if (_chatPipeCount === 0 && isUIMessageStreamable(result)) { + await pipeChat(result, { signal: combinedSignal }); + } + } catch (error) { + // Handle AbortError from streamText gracefully + if (error instanceof Error && error.name === "AbortError") { + if (runSignal.aborted) { + return; // Full run cancellation — exit + } + // Stop generation — fall through to continue the loop + } else { + throw error; + } + } finally { + msgSub.off(); + } - if (!next.ok) { - // Timed out waiting for the next message — end the conversation - return; + if (runSignal.aborted) return; + + // Write turn-complete control chunk so frontend closes its stream + await writeTurnCompleteChunk(); + + // If messages arrived during streaming, use the first one immediately + if (pendingMessages.length > 0) { + currentPayload = pendingMessages[0]!; + continue; + } + + // Suspend the task (frees compute) until the next message arrives + const next = await messagesInput.wait({ timeout: turnTimeout }); + + if (!next.ok) { + // Timed out waiting for the next message — end the conversation + return; + } + + currentPayload = next.output as ChatTaskPayload; } - - currentPayload = next.output; + } finally { + stopSub.off(); } }, }); } + +/** + * Namespace for AI SDK chat integration. + * + * @example + * ```ts + * import { chat } from "@trigger.dev/sdk/ai"; + * + * // Define a chat task + * export const myChat = chat.task({ + * id: "my-chat", + * run: async ({ messages, signal }) => { + * return streamText({ model, messages, abortSignal: signal }); + * }, + * }); + * + * // Pipe a stream manually (from inside a task) + * await chat.pipe(streamTextResult); + * + * // Create an access token (from a server action) + * const token = await chat.createAccessToken("my-chat"); + * ``` + */ +export const chat = { + /** Create a chat task. See {@link chatTask}. */ + task: chatTask, + /** Pipe a stream to the chat transport. See {@link pipeChat}. */ + pipe: pipeChat, + /** Create a public access token for a chat task. See {@link createChatAccessToken}. */ + createAccessToken: createChatAccessToken, +}; + +/** + * Writes a turn-complete control chunk to the chat output stream. + * The frontend transport intercepts this to close the ReadableStream for the current turn. + * @internal + */ +async function writeTurnCompleteChunk(): Promise { + const { waitUntilComplete } = streams.writer(CHAT_STREAM_KEY, { + execute: ({ write }) => { + write({ type: "__trigger_turn_complete" }); + }, + }); + await waitUntilComplete(); +} diff --git a/packages/trigger-sdk/src/v3/chat-constants.ts b/packages/trigger-sdk/src/v3/chat-constants.ts new file mode 100644 index 000000000..dcd170f02 --- /dev/null +++ b/packages/trigger-sdk/src/v3/chat-constants.ts @@ -0,0 +1,13 @@ +/** + * Stream IDs used for bidirectional chat communication. + * Shared between backend (ai.ts) and frontend (chat.ts). + */ + +/** The output stream key where UIMessageChunks are written. */ +export const CHAT_STREAM_KEY = "chat"; + +/** Input stream ID for sending chat messages to the running task. */ +export const CHAT_MESSAGES_STREAM_ID = "chat-messages"; + +/** Input stream ID for sending stop signals to abort the current generation. */ +export const CHAT_STOP_STREAM_ID = "chat-stop"; diff --git a/packages/trigger-sdk/src/v3/chat-react.ts b/packages/trigger-sdk/src/v3/chat-react.ts index a62496463..e37e2e8e5 100644 --- a/packages/trigger-sdk/src/v3/chat-react.ts +++ b/packages/trigger-sdk/src/v3/chat-react.ts @@ -51,7 +51,7 @@ export type UseTriggerChatTransportOptions = Om * * The transport is created once on first render and reused for the lifetime * of the component. This avoids the need for `useMemo` and ensures the - * transport's internal session state (waitpoint tokens, lastEventId, etc.) + * transport's internal session state (run IDs, lastEventId, etc.) * is preserved across re-renders. * * For dynamic access tokens, pass a function — it will be called on each diff --git a/packages/trigger-sdk/src/v3/chat.ts b/packages/trigger-sdk/src/v3/chat.ts index e36c57618..7fd620ab9 100644 --- a/packages/trigger-sdk/src/v3/chat.ts +++ b/packages/trigger-sdk/src/v3/chat.ts @@ -24,6 +24,7 @@ import type { ChatTransport, UIMessage, UIMessageChunk, ChatRequestOptions } from "ai"; import { ApiClient, SSEStreamSubscription } from "@trigger.dev/core/v3"; +import { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID } from "./chat-constants.js"; const DEFAULT_STREAM_KEY = "chat"; const DEFAULT_BASE_URL = "https://api.trigger.dev"; @@ -88,10 +89,6 @@ export type TriggerChatTransportOptions = { type ChatSessionState = { runId: string; publicAccessToken: string; - /** Token ID from the `__trigger_waitpoint_ready` control chunk. */ - waitpointTokenId?: string; - /** Access token scoped to complete the waitpoint (separate from the run's PAT). */ - waitpointAccessToken?: string; /** Last SSE event ID — used to resume the stream without replaying old events. */ lastEventId?: string; }; @@ -100,44 +97,29 @@ type ChatSessionState = { * A custom AI SDK `ChatTransport` that runs chat completions as durable Trigger.dev tasks. * * When `sendMessages` is called, the transport: - * 1. Triggers a Trigger.dev task with the chat messages as payload + * 1. Triggers a Trigger.dev task (or sends to an existing run via input streams) * 2. Subscribes to the task's realtime stream to receive `UIMessageChunk` data * 3. Returns a `ReadableStream` that the AI SDK processes natively * + * Calling `stop()` from `useChat` sends a stop signal via input streams, which + * aborts the current `streamText` call in the task without ending the run. + * * @example * ```tsx * import { useChat } from "@ai-sdk/react"; * import { TriggerChatTransport } from "@trigger.dev/sdk/chat"; * * function Chat({ accessToken }: { accessToken: string }) { - * const { messages, sendMessage, status } = useChat({ + * const { messages, sendMessage, stop, status } = useChat({ * transport: new TriggerChatTransport({ * task: "my-chat-task", * accessToken, * }), * }); * - * // ... render messages + * // stop() sends a stop signal — the task aborts streamText but keeps the run alive * } * ``` - * - * On the backend, define the task using `chatTask` from `@trigger.dev/sdk/ai`: - * - * @example - * ```ts - * import { chatTask } from "@trigger.dev/sdk/ai"; - * import { streamText, convertToModelMessages } from "ai"; - * - * export const myChatTask = chatTask({ - * id: "my-chat-task", - * run: async ({ messages }) => { - * return streamText({ - * model: openai("gpt-4o"), - * messages: convertToModelMessages(messages), - * }); - * }, - * }); - * ``` */ export class TriggerChatTransport implements ChatTransport { private readonly taskId: string; @@ -183,19 +165,12 @@ export class TriggerChatTransport implements ChatTransport { const session = this.sessions.get(chatId); - // If we have a waitpoint token from a previous turn, complete it to - // resume the existing run instead of triggering a new one. - if (session?.waitpointTokenId && session.waitpointAccessToken) { - const tokenId = session.waitpointTokenId; - const tokenAccessToken = session.waitpointAccessToken; - - // Clear the used waitpoint so we don't try to reuse it - session.waitpointTokenId = undefined; - session.waitpointAccessToken = undefined; - + // If we have an existing run, send the message via input stream + // to resume the conversation in the same run. + if (session?.runId) { try { - const wpClient = new ApiClient(this.baseURL, tokenAccessToken); - await wpClient.completeWaitpointToken(tokenId, { data: payload }); + const apiClient = new ApiClient(this.baseURL, session.publicAccessToken); + await apiClient.sendInputStream(session.runId, CHAT_MESSAGES_STREAM_ID, payload); return this.subscribeToStream( session.runId, @@ -204,12 +179,12 @@ export class TriggerChatTransport implements ChatTransport { chatId ); } catch { - // If completing the waitpoint fails (run died, token expired, etc.), - // fall through to trigger a new run. + // If sending fails (run died, etc.), fall through to trigger a new run. this.sessions.delete(chatId); } } + // First message or run has ended — trigger a new run const currentToken = await this.resolveAccessToken(); const apiClient = new ApiClient(this.baseURL, currentToken); @@ -263,7 +238,7 @@ export class TriggerChatTransport implements ChatTransport { ...this.extraHeaders, }; - // When resuming a run via waitpoint, skip past previously-seen events + // When resuming a run, skip past previously-seen events // so we only receive the new turn's response. const session = chatId ? this.sessions.get(chatId) : undefined; @@ -275,6 +250,24 @@ export class TriggerChatTransport implements ChatTransport { ? AbortSignal.any([abortSignal, internalAbort.signal]) : internalAbort.signal; + // When the caller aborts (user calls stop()), send a stop signal to the + // running task via input streams, then close the SSE connection. + if (abortSignal) { + abortSignal.addEventListener( + "abort", + () => { + if (session?.runId) { + const api = new ApiClient(this.baseURL, session.publicAccessToken); + api + .sendInputStream(session.runId, CHAT_STOP_STREAM_ID, { stop: true }) + .catch(() => {}); // Best-effort + } + internalAbort.abort(); + }, + { once: true } + ); + } + const subscription = new SSEStreamSubscription( `${this.baseURL}/realtime/v1/streams/${runId}/${this.streamKey}`, { @@ -300,11 +293,7 @@ export class TriggerChatTransport implements ChatTransport { // ended (or was killed). Clear the session so that the // next message triggers a fresh run. if (chatId) { - const s = this.sessions.get(chatId); - if (s) { - s.waitpointTokenId = undefined; - s.waitpointAccessToken = undefined; - } + this.sessions.delete(chatId); } controller.close(); return; @@ -326,16 +315,10 @@ export class TriggerChatTransport implements ChatTransport { if (value.chunk != null && typeof value.chunk === "object") { const chunk = value.chunk as Record; - // Intercept the waitpoint-ready control chunk emitted by + // Intercept the turn-complete control chunk emitted by // `chatTask` after the AI response stream completes. This // chunk is never forwarded to the AI SDK consumer. - if (chunk.type === "__trigger_waitpoint_ready" && chatId) { - const s = this.sessions.get(chatId); - if (s) { - s.waitpointTokenId = chunk.tokenId as string; - s.waitpointAccessToken = chunk.publicAccessToken as string; - } - + if (chunk.type === "__trigger_turn_complete" && chatId) { // Abort the underlying fetch to close the SSE connection internalAbort.abort(); try { @@ -391,4 +374,3 @@ export class TriggerChatTransport implements ChatTransport { export function createChatTransport(options: TriggerChatTransportOptions): TriggerChatTransport { return new TriggerChatTransport(options); } - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3514ce7f0..7a41ad4d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -477,8 +477,8 @@ importers: specifier: ^0.1.3 version: 0.1.3(@remix-run/react@2.17.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.5.4))(@remix-run/server-runtime@2.17.4(typescript@5.5.4)) '@s2-dev/streamstore': - specifier: ^0.17.2 - version: 0.17.3(typescript@5.5.4) + specifier: ^0.22.5 + version: 0.22.5(supports-color@10.0.0) '@sentry/remix': specifier: 9.46.0 version: 9.46.0(patch_hash=146126b032581925294aaed63ab53ce3f5e0356a755f1763d7a9a76b9846943b)(@remix-run/node@2.17.4(typescript@5.5.4))(@remix-run/react@2.17.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.5.4))(@remix-run/server-runtime@2.17.4(typescript@5.5.4))(encoding@0.1.13)(react@18.2.0) @@ -1138,7 +1138,7 @@ importers: version: 18.3.1 react-email: specifier: ^2.1.1 - version: 2.1.2(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.15)(bufferutil@4.0.9)(eslint@8.31.0) + version: 2.1.2(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.15)(eslint@8.31.0) resend: specifier: ^3.2.0 version: 3.2.0 @@ -1505,8 +1505,8 @@ importers: specifier: 1.36.0 version: 1.36.0 '@s2-dev/streamstore': - specifier: ^0.17.6 - version: 0.17.6 + specifier: ^0.22.5 + version: 0.22.5(supports-color@10.0.0) '@trigger.dev/build': specifier: workspace:4.4.4 version: link:../build @@ -1782,8 +1782,8 @@ importers: specifier: 1.36.0 version: 1.36.0 '@s2-dev/streamstore': - specifier: 0.17.3 - version: 0.17.3(typescript@5.5.4) + specifier: 0.22.5 + version: 0.22.5(supports-color@10.0.0) dequal: specifier: ^2.0.3 version: 2.0.3 @@ -2108,6 +2108,9 @@ importers: evt: specifier: ^2.4.13 version: 2.4.13 + react: + specifier: ^18.0 || ^19.0 + version: 18.3.1 slug: specifier: ^6.0.0 version: 6.1.0 @@ -9554,13 +9557,8 @@ packages: '@rushstack/eslint-patch@1.2.0': resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} - '@s2-dev/streamstore@0.17.3': - resolution: {integrity: sha512-UeXL5+MgZQfNkbhCgEDVm7PrV5B3bxh6Zp4C5pUzQQwaoA+iGh2QiiIptRZynWgayzRv4vh0PYfnKpTzJEXegQ==} - peerDependencies: - typescript: 5.5.4 - - '@s2-dev/streamstore@0.17.6': - resolution: {integrity: sha512-ocjZfKaPKmo2yhudM58zVNHv3rBLSbTKkabVoLFn9nAxU6iLrR2CO3QmSo7/waohI3EZHAWxF/Pw8kA8d6QH2g==} + '@s2-dev/streamstore@0.22.5': + resolution: {integrity: sha512-GqdOKIbIoIxT+40fnKzHbrsHB6gBqKdECmFe7D3Ojk4FoN1Hu0LhFzZv6ZmVMjoHHU+55debS1xSWjZwQmbIyQ==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -25817,7 +25815,7 @@ snapshots: '@puppeteer/browsers@2.10.6': dependencies: - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) extract-zip: 2.0.1 progress: 2.0.3 proxy-agent: 6.5.0 @@ -29487,14 +29485,12 @@ snapshots: '@rushstack/eslint-patch@1.2.0': {} - '@s2-dev/streamstore@0.17.3(typescript@5.5.4)': - dependencies: - '@protobuf-ts/runtime': 2.11.1 - typescript: 5.5.4 - - '@s2-dev/streamstore@0.17.6': + '@s2-dev/streamstore@0.22.5(supports-color@10.0.0)': dependencies: '@protobuf-ts/runtime': 2.11.1 + debug: 4.4.3(supports-color@10.0.0) + transitivePeerDependencies: + - supports-color '@sec-ant/readable-stream@0.4.1': {} @@ -31579,7 +31575,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 5.59.6(typescript@5.5.4) '@typescript-eslint/utils': 5.59.6(eslint@8.31.0)(typescript@5.5.4) - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) eslint: 8.31.0 tsutils: 3.21.0(typescript@5.5.4) optionalDependencies: @@ -31593,7 +31589,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.59.6 '@typescript-eslint/visitor-keys': 5.59.6 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.3 @@ -33621,11 +33617,9 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1(supports-color@10.0.0): + debug@4.4.1: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 10.0.0 debug@4.4.3(supports-color@10.0.0): dependencies: @@ -34972,7 +34966,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -35398,7 +35392,7 @@ snapshots: dependencies: basic-ftp: 5.0.3 data-uri-to-buffer: 5.0.1 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -35557,7 +35551,7 @@ snapshots: '@types/node': 20.14.14 '@types/semver': 7.5.1 chalk: 4.1.2 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) interpret: 3.1.1 semver: 7.7.3 tslib: 2.8.1 @@ -35841,7 +35835,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) transitivePeerDependencies: - supports-color @@ -35861,7 +35855,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) transitivePeerDependencies: - supports-color @@ -38459,7 +38453,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) get-uri: 6.0.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 @@ -39210,7 +39204,7 @@ snapshots: proxy-agent@6.5.0: dependencies: agent-base: 7.1.4 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 lru-cache: 7.18.3 @@ -39250,7 +39244,7 @@ snapshots: dependencies: '@puppeteer/browsers': 2.10.6 chromium-bidi: 7.2.0(devtools-protocol@0.0.1464554) - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.1 devtools-protocol: 0.0.1464554 typed-query-selector: 2.12.0 ws: 8.18.3(bufferutil@4.0.9) @@ -39465,7 +39459,7 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-email@2.1.2(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.15)(bufferutil@4.0.9)(eslint@8.31.0): + react-email@2.1.2(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.15)(eslint@8.31.0): dependencies: '@babel/parser': 7.24.1 '@radix-ui/colors': 1.0.1 @@ -39502,8 +39496,8 @@ snapshots: react: 18.3.1 react-dom: 18.2.0(react@18.3.1) shelljs: 0.8.5 - socket.io: 4.7.3(bufferutil@4.0.9) - socket.io-client: 4.7.3(bufferutil@4.0.9) + socket.io: 4.7.3 + socket.io-client: 4.7.3 sonner: 1.3.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) source-map-js: 1.0.2 stacktrace-parser: 0.1.10 @@ -40162,7 +40156,7 @@ snapshots: require-in-the-middle@7.1.1(supports-color@10.0.0): dependencies: - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -40728,7 +40722,7 @@ snapshots: - supports-color - utf-8-validate - socket.io-client@4.7.3(bufferutil@4.0.9): + socket.io-client@4.7.3: dependencies: '@socket.io/component-emitter': 3.1.0 debug: 4.3.7(supports-color@10.0.0) @@ -40757,7 +40751,7 @@ snapshots: transitivePeerDependencies: - supports-color - socket.io@4.7.3(bufferutil@4.0.9): + socket.io@4.7.3: dependencies: accepts: 1.3.8 base64id: 2.0.0 @@ -40788,7 +40782,7 @@ snapshots: socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.4 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) socks: 2.8.3 transitivePeerDependencies: - supports-color @@ -41162,7 +41156,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.3(supports-color@10.0.0) fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.1 @@ -42398,7 +42392,7 @@ snapshots: '@vitest/spy': 3.1.4 '@vitest/utils': 3.1.4 chai: 5.2.0 - debug: 4.4.1(supports-color@10.0.0) + debug: 4.4.1 expect-type: 1.2.1 magic-string: 0.30.21 pathe: 2.0.3 diff --git a/references/ai-chat/src/app/actions.ts b/references/ai-chat/src/app/actions.ts index 6d230e271..08657dd1a 100644 --- a/references/ai-chat/src/app/actions.ts +++ b/references/ai-chat/src/app/actions.ts @@ -1,6 +1,6 @@ "use server"; -import { createChatAccessToken } from "@trigger.dev/sdk/ai"; -import type { chat } from "@/trigger/chat"; +import { chat } from "@trigger.dev/sdk/ai"; +import type { aiChat } from "@/trigger/chat"; -export const getChatToken = async () => createChatAccessToken("ai-chat"); +export const getChatToken = async () => chat.createAccessToken("ai-chat"); diff --git a/references/ai-chat/src/components/chat.tsx b/references/ai-chat/src/components/chat.tsx index 9755e15f6..c5eac1c34 100644 --- a/references/ai-chat/src/components/chat.tsx +++ b/references/ai-chat/src/components/chat.tsx @@ -4,7 +4,7 @@ import { useChat } from "@ai-sdk/react"; import { useTriggerChatTransport } from "@trigger.dev/sdk/chat/react"; import { useState } from "react"; import { getChatToken } from "@/app/actions"; -import type { chat } from "@/trigger/chat"; +import type { aiChat } from "@/trigger/chat"; function ToolInvocation({ part }: { part: any }) { const [expanded, setExpanded] = useState(false); @@ -73,7 +73,7 @@ function ToolInvocation({ part }: { part: any }) { export function Chat() { const [input, setInput] = useState(""); - const transport = useTriggerChatTransport({ + const transport = useTriggerChatTransport({ task: "ai-chat", accessToken: getChatToken, baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL, diff --git a/references/ai-chat/src/trigger/chat.ts b/references/ai-chat/src/trigger/chat.ts index b600977c5..a798d35bd 100644 --- a/references/ai-chat/src/trigger/chat.ts +++ b/references/ai-chat/src/trigger/chat.ts @@ -1,4 +1,4 @@ -import { chatTask } from "@trigger.dev/sdk/ai"; +import { chat } from "@trigger.dev/sdk/ai"; import { streamText, convertToModelMessages, tool } from "ai"; import { openai } from "@ai-sdk/openai"; import { z } from "zod"; @@ -62,15 +62,16 @@ const inspectEnvironment = tool({ declare const Bun: unknown; declare const Deno: unknown; -export const chat = chatTask({ +export const aiChat = chat.task({ id: "ai-chat", - run: async ({ messages }) => { + run: async ({ messages, signal }) => { return streamText({ model: openai("gpt-4o-mini"), system: "You are a helpful assistant. Be concise and friendly.", messages: await convertToModelMessages(messages), tools: { inspectEnvironment }, maxSteps: 3, + abortSignal: signal, }); }, });