use input streams and rename chatTask and chatState to chat.task and chat.state

This commit is contained in:
Eric Allam
2026-03-03 19:09:51 +00:00
parent bf229d6faa
commit a0bc91a21d
8 changed files with 268 additions and 173 deletions
+172 -67
View File
@@ -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<typeof chat>("ai-chat");
* export const getChatToken = () => chat.createAccessToken<typeof myChat>("my-chat");
* ```
*/
export async function createChatAccessToken<TTask extends AnyTask>(
function createChatAccessToken<TTask extends AnyTask>(
taskId: TaskIdentifier<TTask>
): Promise<string> {
return auth.createTriggerPublicToken(taskId as string, { multipleUse: true });
@@ -157,7 +161,10 @@ export async function createChatAccessToken<TTask extends AnyTask>(
* 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<TMessage extends UIMessage = UIMessage> = {
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<ChatTaskPayload>({ 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<unknown> {
* @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<unknown> {
* messages: convertToModelMessages(payload.messages),
* });
*
* await pipeChat(result);
* await chat.pipe(result);
* },
* });
* ```
@@ -274,11 +303,11 @@ function isReadableStream(value: unknown): value is ReadableStream<unknown> {
* // 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<unknown> | ReadableStream<unknown>,
options?: PipeChatOptions
): Promise<void> {
@@ -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<TIdentifier extends string> = Omit<
TaskOptions<TIdentifier, ChatTaskPayload, unknown>,
@@ -332,13 +360,13 @@ export type ChatTaskOptions<TIdentifier extends string> = 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<unknown>;
run: (payload: ChatTaskRunPayload) => Promise<unknown>;
/**
* Maximum number of conversational turns (message round-trips) a single run
@@ -351,7 +379,7 @@ export type ChatTaskOptions<TIdentifier extends string> = 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<TIdentifier extends string> = 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<TIdentifier extends string>(
function chatTask<TIdentifier extends string>(
options: ChatTaskOptions<TIdentifier>
): Task<TIdentifier, ChatTaskPayload, unknown> {
const { run: userRun, maxTurns = 100, turnTimeout = "1h", ...restOptions } = options;
return createTask<TIdentifier, ChatTaskPayload, unknown>({
...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<ChatTaskPayload>(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<void> {
const { waitUntilComplete } = streams.writer(CHAT_STREAM_KEY, {
execute: ({ write }) => {
write({ type: "__trigger_turn_complete" });
},
});
await waitUntilComplete();
}
@@ -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";
+1 -1
View File
@@ -51,7 +51,7 @@ export type UseTriggerChatTransportOptions<TTask extends AnyTask = AnyTask> = 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
+36 -54
View File
@@ -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<UIMessageChunk>` 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<UIMessage> {
private readonly taskId: string;
@@ -183,19 +165,12 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
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<UIMessage> {
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<UIMessage> {
...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<UIMessage> {
? 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<UIMessage> {
// 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<UIMessage> {
if (value.chunk != null && typeof value.chunk === "object") {
const chunk = value.chunk as Record<string, unknown>;
// 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<UIMessage> {
export function createChatTransport(options: TriggerChatTransportOptions): TriggerChatTransport {
return new TriggerChatTransport(options);
}
+37 -43
View File
@@ -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
+3 -3
View File
@@ -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<typeof chat>("ai-chat");
export const getChatToken = async () => chat.createAccessToken<typeof aiChat>("ai-chat");
+2 -2
View File
@@ -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<typeof chat>({
const transport = useTriggerChatTransport<typeof aiChat>({
task: "ai-chat",
accessToken: getChatToken,
baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,
+4 -3
View File
@@ -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,
});
},
});