Update realtime docs for streaming and a couple tweaks to the SDK (#1486)

This commit is contained in:
Eric Allam
2024-11-20 14:10:38 +00:00
committed by GitHub
parent c044cb125e
commit af7a7681cf
16 changed files with 1435 additions and 349 deletions
+7
View File
@@ -0,0 +1,7 @@
---
"@trigger.dev/react-hooks": patch
"@trigger.dev/sdk": patch
---
React hooks now all accept accessToken and baseURL options so the use of the Provider is no longer necessary
+20 -93
View File
@@ -4,31 +4,16 @@ sidebarTitle: Overview & Auth
description: Using the Trigger.dev SDK from your frontend application.
---
You can use certain SDK functions in your frontend application to interact with the Trigger.dev API. This guide will show you how to authenticate your requests and use the SDK in your frontend application.
You can use our [React hooks](/frontend/react-hooks) in your frontend application to interact with the Trigger.dev API. This guide will show you how to generate Public Access Tokens that can be used to authenticate your requests.
## Authentication
You must authenticate your requests using a "Public Access Token" when using the SDK in your frontend application. To create a Public Access Token, you can use the `auth.createPublicToken` function in your backend code:
To create a Public Access Token, you can use the `auth.createPublicToken` function in your **backend** code:
```tsx
const publicToken = await auth.createPublicToken();
```
To use a Public Access Token in your frontend application, you can call the `auth.configure` function or the `auth.withAuth` function:
```ts
import { auth } from "@trigger.dev/sdk/v3";
auth.configure({
accessToken: publicToken,
});
// or
await auth.withAuth({ accessToken: publicToken }, async () => {
// Your code here will use the public token
});
```
### Scopes
By default a Public Access Token has limited permissions. You can specify the scopes you need when creating a Public Access Token:
@@ -104,6 +89,22 @@ const publicToken = await auth.createPublicToken({
});
```
### Write scopes
You can also specify write scopes, which is required for triggering tasks from your frontend application:
```ts
const publicToken = await auth.createPublicToken({
scopes: {
write: {
tasks: ["my-task-1", "my-task-2"],
},
},
});
```
This will allow the token to trigger the specified tasks. `tasks` is the only write scope available at the moment.
### Expiration
By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token:
@@ -163,80 +164,6 @@ const handle = await tasks.batchTrigger("my-task", [
console.log(handle.publicAccessToken);
```
## Available SDK functions
## Usage
Currently the following functions are available in the frontend SDK:
### runs.retrieve
The `runs.retrieve` function allows you to retrieve a run by its ID.
```ts
import { runs, auth } from "@trigger.dev/sdk/v3";
// Somewhere in your backend code
const handle = await tasks.trigger("my-task", { some: "data" });
// In your frontend code
auth.configure({
accessToken: handle.publicAccessToken,
});
const run = await runs.retrieve(handle.id);
```
Learn more about the `runs.retrieve` function in the [runs.retrieve doc](/management/runs/retrieve).
### runs.subscribeToRun
The `runs.subscribeToRun` function allows you to subscribe to a run by its ID, and receive updates in real-time when the run changes.
```ts
import { runs, auth } from "@trigger.dev/sdk/v3";
// Somewhere in your backend code
const handle = await tasks.trigger("my-task", { some: "data" });
// In your frontend code
auth.configure({
accessToken: handle.publicAccessToken,
});
for await (const run of runs.subscribeToRun(handle.id)) {
// This will log the run every time it changes
console.log(run);
}
```
See the [Realtime doc](/realtime) for more information.
### runs.subscribeToRunsWithTag
The `runs.subscribeToRunsWithTag` function allows you to subscribe to runs with a specific tag, and receive updates in real-time when the runs change.
```ts
import { runs, auth } from "@trigger.dev/sdk/v3";
// Somewhere in your backend code
const handle = await tasks.trigger("my-task", { some: "data" }, { tags: ["my-tag"] });
// In your frontend code
auth.configure({
accessToken: handle.publicAccessToken,
});
for await (const run of runs.subscribeToRunsWithTag("my-tag")) {
// This will log the run every time it changes
console.log(run);
}
```
See the [Realtime doc](/realtime) for more information.
## React hooks
We also provide React hooks to make it easier to use the SDK in your React application. See our [React hooks](/frontend/react-hooks) documentation for more information.
## Triggering tasks
We don't currently support triggering tasks from the frontend SDK. If this is something you need, please let us know by [upvoting the feature](https://feedback.trigger.dev/p/ability-to-trigger-tasks-from-frontend).
To learn how to use these Public Access Tokens, see our [React hooks](/frontend/react-hooks) guide.
+435 -84
View File
@@ -28,14 +28,35 @@ yarn install @trigger.dev/react-hooks
## Authentication
Before you can use the hooks, you need to provide a public access token to the `TriggerAuthContext` provider. Learn more about [authentication in the frontend guide](/frontend/overview).
All hooks accept an optional last argument `options` that accepts an `accessToken` param, which should be a valid Public Access Token. Learn more about [generating tokens in the frontend guide](/frontend/overview).
```tsx
import { useRealtimeRun } from "@trigger.dev/react-hooks";
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { run, error } = useRealtimeRun(runId, {
accessToken: publicAccessToken, // This is required
baseURL: "https://your-trigger-dev-instance.com", // optional, only needed if you are self-hosting Trigger.dev
});
// ...
}
```
Alternatively, you can use our `TriggerAuthContext` provider
```tsx
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
export function SetupTrigger() {
export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) {
return (
<TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
<TriggerAuthContext.Provider value={{ accessToken: publicAccessToken }}>
<MyComponent />
</TriggerAuthContext.Provider>
);
@@ -47,11 +68,11 @@ Now children components can use the hooks to interact with the Trigger.dev API.
```tsx
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
export function SetupTrigger() {
export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) {
return (
<TriggerAuthContext.Provider
value={{
accessToken: "your-access-token",
accessToken: publicAccessToken,
baseURL: "https://your-trigger-dev-instance.com",
}}
>
@@ -217,9 +238,7 @@ export async function generatePublicAccessToken(runId: string) {
</CodeGroup>
## Usage
### SWR vs Realtime hooks
## SWR vs Realtime hooks
We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](https://swr.vercel.app/) library to fetch data once and cache it. The Realtime hooks use [Trigger.dev realtime](/realtime) to subscribe to updates in real-time.
@@ -231,77 +250,7 @@ We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](ht
All hooks named `useRealtime*` are Realtime hooks, and all hooks named `use*` are SWR hooks.
#### Common SWR hook options
You can pass the following options to the all SWR hooks:
<ParamField path="revalidateOnFocus" type="boolean">
Revalidate the data when the window regains focus.
</ParamField>
<ParamField path="revalidateOnReconnect" type="boolean">
Revalidate the data when the browser regains a network connection.
</ParamField>
<ParamField path="refreshInterval" type="number">
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
use-cases. Use the Realtime hooks instead.
</ParamField>
#### Common SWR hook return values
<ResponseField name="error" type="Error">
An error object if an error occurred while fetching the data.
</ResponseField>
<ResponseField name="isLoading" type="boolean">
A boolean indicating if the data is currently being fetched.
</ResponseField>
<ResponseField name="isValidating" type="boolean">
A boolean indicating if the data is currently being revalidated.
</ResponseField>
<ResponseField name="isError" type="boolean">
A boolean indicating if an error occurred while fetching the data.
</ResponseField>
### useRun
The `useRun` hook allows you to fetch a run by its ID.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRun } from "@trigger.dev/react-hooks";
export function MyComponent({ runId }: { runId: string }) {
const { run, error, isLoading } = useRun(runId);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
The `run` object returned is the same as the [run object](/management/runs/retrieve) returned by the Trigger.dev API. To correctly type the run's payload and output, you can provide the type of your task to the `useRun` hook:
```tsx
import { useRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ runId }: { runId: string }) {
const { run, error, isLoading } = useRun<typeof myTask>(runId);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
// Now run.payload and run.output are correctly typed
return <div>Run: {run.id}</div>;
}
```
## Realtime hooks
### useRealtimeRun
@@ -312,8 +261,16 @@ The `useRealtimeRun` hook allows you to subscribe to a run by its ID.
import { useRealtimeRun } from "@trigger.dev/react-hooks";
export function MyComponent({ runId }: { runId: string }) {
const { run, error } = useRealtimeRun(runId);
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { run, error } = useRealtimeRun(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
@@ -327,8 +284,16 @@ To correctly type the run's payload and output, you can provide the type of your
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ runId }: { runId: string }) {
const { run, error } = useRealtimeRun<typeof myTask>(runId);
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { run, error } = useRealtimeRun<typeof myTask>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
@@ -338,7 +303,7 @@ export function MyComponent({ runId }: { runId: string }) {
}
```
See our [Realtime documentation](/realtime) for more information.
See our [Realtime documentation](/realtime) for more information about the type of the run object and more.
### useRealtimeRunsWithTag
@@ -444,3 +409,389 @@ export function MyComponent({ batchId }: { batchId: string }) {
```
See our [Realtime documentation](/realtime) for more information.
### useRealtimeRunWithStreams
The `useRealtimeRunWithStreams` hook allows you to subscribe to a run by its ID and also receive any streams that are emitted by the task. See our [Realtime documentation](/realtime#streams) for more information about emitting streams from a task.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { run, streams, error } = useRealtimeRunWithStreams(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<div>Run: {run.id}</div>
<div>
{Object.keys(streams).map((stream) => (
<div key={stream}>Stream: {stream}</div>
))}
</div>
</div>
);
}
```
You can provide the type of the streams to the `useRealtimeRunWithStreams` hook:
```tsx
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
type STREAMS = {
openai: string; // this is the type of each "part" of the stream
};
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { run, streams, error } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
const text = streams.openai?.map((part) => part).join("");
return (
<div>
<div>Run: {run.id}</div>
<div>{text}</div>
</div>
);
}
```
As you can see above, each stream is an array of the type you provided, keyed by the stream name. If instead of a pure text stream you have a stream of objects, you can provide the type of the object:
```tsx
import type { TextStreamPart } from "ai";
import type { myTask } from "@/trigger/myTask";
type STREAMS = { openai: TextStreamPart<{}> };
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { run, streams, error } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (error) return <div>Error: {error.message}</div>;
const text = streams.openai
?.filter((stream) => stream.type === "text-delta")
?.map((part) => part.text)
.join("");
return (
<div>
<div>Run: {run.id}</div>
<div>{text}</div>
</div>
);
}
```
### Common options
#### enabled
You can pass the `enabled` option to the Realtime hooks to enable or disable the subscription.
```tsx
import { useRealtimeRun } from "@trigger.dev/react-hooks";
export function MyComponent({
runId,
publicAccessToken,
enabled,
}: {
runId: string;
publicAccessToken: string;
enabled: boolean;
}) {
const { run, error } = useRealtimeRun(runId, {
accessToken: publicAccessToken,
enabled,
});
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
This allows you to conditionally disable using the hook based on some state.
#### id
You can pass the `id` option to the Realtime hooks to change the ID of the subscription.
```tsx
import { useRealtimeRun } from "@trigger.dev/react-hooks";
export function MyComponent({
id,
runId,
publicAccessToken,
enabled,
}: {
id: string;
runId: string;
publicAccessToken: string;
enabled: boolean;
}) {
const { run, error } = useRealtimeRun(runId, {
accessToken: publicAccessToken,
enabled,
id,
});
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
This allows you to change the ID of the subscription based on some state. Passing in a different ID will unsubscribe from the current subscription and subscribe to the new one (and remove any cached data).
#### experimental_throttleInMs
The `*withStreams` variants of the Realtime hooks accept an `experimental_throttleInMs` option to throttle the updates from the server. This can be useful if you are getting too many updates and want to reduce the number of updates.
```tsx
import { useRealtimeRunsWithStreams } from "@trigger.dev/react-hooks";
export function MyComponent({
runId,
publicAccessToken,
}: {
runId: string;
publicAccessToken: string;
}) {
const { runs, error } = useRealtimeRunsWithStreams(tag, {
accessToken: publicAccessToken,
experimental_throttleInMs: 1000, // Throttle updates to once per second
});
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
## SWR Hooks
### useRun
The `useRun` hook allows you to fetch a run by its ID.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRun } from "@trigger.dev/react-hooks";
export function MyComponent({ runId }: { runId: string }) {
const { run, error, isLoading } = useRun(runId);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
The `run` object returned is the same as the [run object](/management/runs/retrieve) returned by the Trigger.dev API. To correctly type the run's payload and output, you can provide the type of your task to the `useRun` hook:
```tsx
import { useRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ runId }: { runId: string }) {
const { run, error, isLoading } = useRun<typeof myTask>(runId, {
refreshInterval: 0, // Disable polling
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
// Now run.payload and run.output are correctly typed
return <div>Run: {run.id}</div>;
}
```
### Common options
You can pass the following options to the all SWR hooks:
<ParamField path="revalidateOnFocus" type="boolean">
Revalidate the data when the window regains focus.
</ParamField>
<ParamField path="revalidateOnReconnect" type="boolean">
Revalidate the data when the browser regains a network connection.
</ParamField>
<ParamField path="refreshInterval" type="number">
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
use-cases. Use the Realtime hooks instead.
</ParamField>
### Common return values
<ResponseField name="error" type="Error">
An error object if an error occurred while fetching the data.
</ResponseField>
<ResponseField name="isLoading" type="boolean">
A boolean indicating if the data is currently being fetched.
</ResponseField>
<ResponseField name="isValidating" type="boolean">
A boolean indicating if the data is currently being revalidated.
</ResponseField>
<ResponseField name="isError" type="boolean">
A boolean indicating if an error occurred while fetching the data.
</ResponseField>
## Trigger Hooks
We provide a set of hooks that can be used to trigger tasks from your frontend application. You'll need to generate a Public Access Token with `write` permissions to use these hooks. See our [frontend guide](/frontend/overview#write-scopes) for more information.
### useTaskTrigger
The `useTaskTrigger` hook allows you to trigger a task from your frontend application.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, handle, error, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (handle) {
return <div>Run ID: {handle.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
### useRealtimeTaskTrigger
The `useRealtimeTaskTrigger` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime:
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, run, error, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
// This is the realtime run object, which will automatically update when the run changes
if (run) {
return <div>Run ID: {run.id}</div>;
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
### useRealtimeTaskTriggerWithStreams
The `useRealtimeTaskTriggerWithStreams` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime, and also receive any streams that are emitted by the task.
```tsx
"use client"; // This is needed for Next.js App Router or other RSC frameworks
import { useRealtimeTaskTriggerWithStreams } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";
type STREAMS = {
openai: string; // this is the type of each "part" of the stream
};
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
const { submit, run, streams, error, isLoading } = useRealtimeTaskTriggerWithStreams<
typeof myTask,
STREAMS
>("my-task", {
accessToken: publicAccessToken,
});
if (error) {
return <div>Error: {error.message}</div>;
}
if (streams && run) {
const text = streams.openai?.map((part) => part).join("");
return (
<div>
<div>Run ID: {run.id}</div>
<div>{text}</div>
</div>
);
}
return (
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
{isLoading ? "Loading..." : "Trigger Task"}
</button>
);
}
```
+11 -1
View File
@@ -12,13 +12,23 @@ Trigger.dev v3 makes it easy to write reliable long-running tasks without timeou
- We provide an SDK and CLI for writing tasks in your existing codebase, inside [/trigger folders](/config/config-file).
- We provide different types of tasks: [regular](/tasks-regular) and [scheduled](/tasks/scheduled).
- We provide a dashboard for monitoring, debugging, and managing your tasks.
- We provide a [Realtime API](/realtime) for monitoring tasks in real-time, along with [React hooks](/frontend/react-hooks#realtime-hooks) for building custom dashboards.
We're [open source](https://github.com/triggerdotdev/trigger.dev) and you can choose to use the [Trigger.dev Cloud](https://cloud.trigger.dev) or [Self-host Trigger.dev](/open-source-self-hosting) on your own infrastructure.
## Getting started
<div className="w-full h-full aspect-video mb-3">
<iframe width="100%" height="100%" src="https://www.youtube.com/embed/YH_4c0K7fGM?si=5JzZmZseuqI5aciM" title="Trigger.dev walkthrough" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen/>
<iframe
width="100%"
height="100%"
src="https://www.youtube.com/embed/YH_4c0K7fGM?si=5JzZmZseuqI5aciM"
title="Trigger.dev walkthrough"
frameborder="0"
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
/>
</div>
<CardGroup>
+56 -17
View File
@@ -1,7 +1,10 @@
{
"$schema": "https://mintlify.com/schema.json",
"name": "Trigger.dev",
"openapi": ["/openapi.yml", "/v3-openapi.yaml"],
"openapi": [
"/openapi.yml",
"/v3-openapi.yaml"
],
"api": {
"playground": {
"mode": "simple"
@@ -133,20 +136,30 @@
"pages": [
{
"group": "Tasks",
"pages": ["tasks/overview", "tasks/schemaTask", "tasks/scheduled"]
"pages": [
"tasks/overview",
"tasks/schemaTask",
"tasks/scheduled"
]
},
"triggering",
"runs",
"apikeys",
{
"group": "Configuration",
"pages": ["config/config-file", "config/extensions/overview"]
"pages": [
"config/config-file",
"config/extensions/overview"
]
}
]
},
{
"group": "Development",
"pages": ["cli-dev", "run-tests"]
"pages": [
"cli-dev",
"run-tests"
]
},
{
"group": "Deployment",
@@ -156,7 +169,9 @@
"github-actions",
{
"group": "Deployment integrations",
"pages": ["vercel-integration"]
"pages": [
"vercel-integration"
]
}
]
},
@@ -168,7 +183,13 @@
"errors-retrying",
{
"group": "Wait",
"pages": ["wait", "wait-for", "wait-until", "wait-for-event", "wait-for-request"]
"pages": [
"wait",
"wait-for",
"wait-until",
"wait-for-event",
"wait-for-request"
]
},
"queue-concurrency",
"versioning",
@@ -184,16 +205,19 @@
},
{
"group": "Frontend usage",
"pages": ["frontend/overview", "frontend/react-hooks"]
"pages": [
"frontend/overview",
"frontend/react-hooks"
]
},
{
"group": "Realtime API",
"pages": [
"realtime/overview",
"realtime/streams",
"realtime/react-hooks",
"realtime/subscribe-to-run",
"realtime/subscribe-to-runs-with-tag",
"realtime/use-realtime-run",
"realtime/use-realtime-runs-with-tag"
"realtime/subscribe-to-runs-with-tag"
]
},
{
@@ -202,7 +226,10 @@
"management/overview",
{
"group": "Tasks API",
"pages": ["management/tasks/trigger", "management/tasks/batch-trigger"]
"pages": [
"management/tasks/trigger",
"management/tasks/batch-trigger"
]
},
{
"group": "Runs API",
@@ -241,7 +268,9 @@
},
{
"group": "Projects API",
"pages": ["management/projects/runs"]
"pages": [
"management/projects/runs"
]
}
]
},
@@ -287,11 +316,17 @@
},
{
"group": "Help",
"pages": ["community", "help-slack", "help-email"]
"pages": [
"community",
"help-slack",
"help-email"
]
},
{
"group": "",
"pages": ["guides/introduction"]
"pages": [
"guides/introduction"
]
},
{
"group": "Frameworks",
@@ -357,11 +392,15 @@
},
{
"group": "Dashboard",
"pages": ["guides/dashboard/creating-a-project"]
"pages": [
"guides/dashboard/creating-a-project"
]
},
{
"group": "Migrations",
"pages": ["guides/use-cases/upgrading-from-v2"]
"pages": [
"guides/use-cases/upgrading-from-v2"
]
}
],
"footerSocials": {
@@ -369,4 +408,4 @@
"github": "https://github.com/triggerdotdev",
"linkedin": "https://www.linkedin.com/company/triggerdotdev"
}
}
}
+4
View File
@@ -258,6 +258,10 @@ We suggest combining run metadata with the realtime API and our [React hooks](/f
We have a full demo app repo available [here](https://github.com/triggerdotdev/nextjs-realtime-simple-demo)
## Realtime streams
See our dedicated [Realtime streams](/realtime/streams) documentation for more information on how to use the Realtime streams API.
## Limits
The Realtime API in the Trigger.dev Cloud limits the number of concurrent subscriptions, depending on your plan. If you exceed the limit, you will receive an error when trying to subscribe to a run. For more information, see our [pricing page](https://trigger.dev/pricing).
+7
View File
@@ -0,0 +1,7 @@
---
title: Realtime React hooks
sidebarTitle: React hooks
description: Subscribes to all changes to a run in a React component.
---
See our [React hooks](/frontend/react-hooks) for more information about how to use the Realtime API from your frontend application.
+452
View File
@@ -0,0 +1,452 @@
---
title: Realtime streams
sidebarTitle: Streams
description: Stream data in realtime from inside your tasks
---
The world is going realtime, and so should your tasks. With the Streams API, you can stream data from your tasks to the outside world in realtime. This is useful for a variety of use cases, including AI.
## How it works
The Streams API is a simple API that allows you to send data from your tasks to the outside world in realtime using the [metadata](/runs/metadata) system. You can send any kind of data that is streamed in realtime, but the most common use case is to send streaming output from streaming LLM providers, like OpenAI.
## Usage
To use the Streams API, you need to register a stream with a specific key using `metadata.stream`. The following example uses the OpenAI SDK with `stream: true` to stream the output of the LLM model in realtime:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export type STREAMS = {
openai: OpenAI.ChatCompletionChunk; // The type of the chunk is determined by the provider
};
export const myTask = task({
id: "my-task",
run: async (payload: { prompt: string }) => {
const completion = await openai.chat.completions.create({
messages: [{ role: "user", content: payload.prompt }],
model: "gpt-3.5-turbo",
stream: true,
});
// Register the stream with the key "openai"
// This will "tee" the stream and send it to the metadata system
const stream = await metadata.stream("openai", completion);
let text = "";
// You can read the returned stream as an async iterator
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
// The type of the chunk is determined by the provider
text += chunk.choices.map((choice) => choice.delta?.content).join("");
}
return { text };
},
});
```
You can then subscribe to the stream using the `runs.subscribeToRun` method:
<Note>
`runs.subscribeToRun` should be used from your backend or another task. To subscribe to a run from
your frontend, you can use our [React hooks](/frontend/react-hooks).
</Note>
```ts
import { runs } from "@trigger.dev/sdk/v3";
import type { myTask, STREAMS } from "./trigger/my-task";
// Somewhere in your backend
async function subscribeToStream(runId: string) {
// Use a for-await loop to subscribe to the stream
for await (const part of runs.subscribeToRun<typeof myTask>(runId).withStreams<STREAMS>()) {
switch (part.type) {
case "run": {
console.log("Received run", part.run);
break;
}
case "openai": {
// part.chunk is of type OpenAI.ChatCompletionChunk
console.log("Received OpenAI chunk", part.chunk);
break;
}
}
}
}
```
You can register and subscribe to multiple streams in the same task. Let's add a stream from the response body of a fetch request:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export type STREAMS = {
openai: OpenAI.ChatCompletionChunk; // The type of the chunk is determined by the provider
fetch: string; // The response body will be an array of strings
};
export const myTask = task({
id: "my-task",
run: async (payload: { prompt: string }) => {
const completion = await openai.chat.completions.create({
messages: [{ role: "user", content: payload.prompt }],
model: "gpt-3.5-turbo",
stream: true,
});
// Register the stream with the key "openai"
await metadata.stream("openai", completion);
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!response.body) {
return;
}
// Register the stream with the key "fetch"
// Pipe the response.body through a TextDecoderStream to convert it to a string
await metadata.stream("fetch", response.body.pipeThrough(new TextDecoderStream()));
},
});
```
<Note>
You may notice above that we aren't consuming either of the streams in the task. In the
background, we'll wait until all streams are consumed before the task is considered complete (with
a max timeout of 60 seconds). If you have a longer running stream, make sure to consume it in the
task.
</Note>
And then subscribing to the streams:
```ts
import { runs } from "@trigger.dev/sdk/v3";
import type { myTask, STREAMS } from "./trigger/my-task";
// Somewhere in your backend
async function subscribeToStream(runId: string) {
// Use a for-await loop to subscribe to the stream
for await (const part of runs.subscribeToRun<typeof myTask>(runId).withStreams<STREAMS>()) {
switch (part.type) {
case "run": {
console.log("Received run", part.run);
break;
}
case "openai": {
// part.chunk is of type OpenAI.ChatCompletionChunk
console.log("Received OpenAI chunk", part.chunk);
break;
}
case "fetch": {
// part.chunk is a string
console.log("Received fetch chunk", part.chunk);
break;
}
}
}
}
```
## React hooks
If you're building a frontend application, you can use our React hooks to subscribe to streams. Here's an example of how you can use the `useRealtimeRunWithStreams` hook to subscribe to a stream:
```tsx
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
import type { myTask, STREAMS } from "./trigger/my-task";
// Somewhere in your React component
function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
const { run, streams } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (!run) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Run ID: {run.id}</h1>
<h2>Streams:</h2>
<ul>
{Object.entries(streams).map(([key, value]) => (
<li key={key}>
<strong>{key}</strong>: {JSON.stringify(value)}
</li>
))}
</ul>
</div>
);
}
```
Read more about using the React hooks in the [React hooks](/frontend/react-hooks) documentation.
## Usage with the `ai` SDK
The [ai SDK](https://sdk.vercel.ai/docs/introduction) provides a higher-level API for working with AI models. You can use the `ai` SDK with the Streams API by using the `streamText` method:
```ts
import { openai } from "@ai-sdk/openai";
import { logger, metadata, runs, schemaTask } from "@trigger.dev/sdk/v3";
import { streamText } from "ai";
import { z } from "zod";
export type STREAMS = {
openai: string;
};
export const aiStreaming = schemaTask({
id: "ai-streaming",
description: "Stream data from the AI sdk",
schema: z.object({
model: z.string().default("o1-preview"),
prompt: z.string().default("Hello, how are you?"),
}),
run: async ({ model, prompt }) => {
logger.info("Running OpenAI model", { model, prompt });
const result = streamText({
model: openai(model),
prompt,
});
// pass the textStream to the metadata system
const stream = await metadata.stream("openai", result.textStream);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
text += chunk; // chunk is a string
}
return { text };
},
});
```
And then render the stream in your frontend:
```tsx
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
import type { aiStreaming, STREAMS } from "./trigger/ai-streaming";
function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
const { streams } = useRealtimeRunWithStreams<typeof aiStreaming, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (!streams.openai) {
return <div>Loading...</div>;
}
const text = streams.openai.join(""); // `streams.openai` is an array of strings
return (
<div>
<h2>OpenAI response:</h2>
<p>{text}</p>
</div>
);
}
```
### Using tools and `fullStream`
When calling `streamText`, you can provide a `tools` object that allows the LLM to use additional tools. You can then access the tool call and results using the `fullStream` method:
```ts
import { openai } from "@ai-sdk/openai";
import { logger, metadata, runs, schemaTask } from "@trigger.dev/sdk/v3";
import { streamText, tool, type TextStreamPart } from "ai";
import { z } from "zod";
const tools = {
getWeather: tool({
description: "Get the weather in a location",
parameters: z.object({
location: z.string().describe("The location to get the weather for"),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
};
export type STREAMS = {
// Give the stream a type of TextStreamPart along with the tools
openai: TextStreamPart<{ getWeather: typeof tools.getWeather }>;
};
export const aiStreamingWithTools = schemaTask({
id: "ai-streaming-with-tools",
description: "Stream data from the AI SDK and use tools",
schema: z.object({
model: z.string().default("gpt-4o-mini"),
prompt: z
.string()
.default(
"Based on the temperature, will I need to wear extra clothes today in San Fransico? Please be detailed."
),
}),
run: async ({ model, prompt }) => {
logger.info("Running OpenAI model", { model, prompt });
const result = streamText({
model: openai(model),
prompt,
tools, // Pass in the tools to use
maxSteps: 5, // Allow streamText to repeatedly call the model
});
// pass the fullStream to the metadata system
const stream = await metadata.stream("openai", result.fullStream);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
// chunk is a TextStreamPart
if (chunk.type === "text-delta") {
text += chunk.textDelta;
}
}
return { text };
},
});
```
Now you can get access to the tool call and results in your frontend:
```tsx
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
import type { aiStreamingWithTools, STREAMS } from "./trigger/ai-streaming";
function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
const { streams } = useRealtimeRunWithStreams<typeof aiStreamingWithTools, STREAMS>(runId, {
accessToken: publicAccessToken,
});
if (!streams.openai) {
return <div>Loading...</div>;
}
// streams.openai is an array of TextStreamPart
const toolCall = streams.openai.find(
(stream) => stream.type === "tool-call" && stream.toolName === "getWeather"
);
const toolResult = streams.openai.find((stream) => stream.type === "tool-result");
const textDeltas = streams.openai.filter((stream) => stream.type === "text-delta");
const text = textDeltas.map((delta) => delta.textDelta).join("");
const weatherLocation = toolCall ? toolCall.args.location : undefined;
const weather = toolResult ? toolResult.result.temperature : undefined;
return (
<div>
<h2>OpenAI response:</h2>
<p>{text}</p>
<h2>Weather:</h2>
<p>
{weatherLocation
? `The weather in ${weatherLocation} is ${weather} degrees.`
: "No weather data"}
</p>
</div>
);
}
```
### Using `toolTask`
As you can see above, we defined a tool which will be used in the `aiStreamingWithTools` task. You can also define a Trigger.dev task that can be used as a tool, and will automatically be invoked with `triggerAndWait` when the tool is called. This is done using the `toolTask` function:
```ts
import { openai } from "@ai-sdk/openai";
import { logger, metadata, runs, schemaTask, toolTask } from "@trigger.dev/sdk/v3";
import { streamText, tool, type TextStreamPart } from "ai";
import { z } from "zod";
export const getWeather = toolTask({
id: "get-weather",
description: "Get the weather for a location",
// Define the parameters for the tool, which becomes the task payload
parameters: z.object({
location: z.string(),
}),
run: async ({ location }) => {
// return mock data
return {
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
};
},
});
export type STREAMS = {
// Give the stream a type of TextStreamPart along with the tools
openai: TextStreamPart<{ getWeather: typeof getWeather.tool }>;
};
export const aiStreamingWithTools = schemaTask({
id: "ai-streaming-with-tools",
description: "Stream data from the AI SDK and use tools",
schema: z.object({
model: z.string().default("gpt-4o-mini"),
prompt: z
.string()
.default(
"Based on the temperature, will I need to wear extra clothes today in San Fransico? Please be detailed."
),
}),
run: async ({ model, prompt }) => {
logger.info("Running OpenAI model", { model, prompt });
const result = streamText({
model: openai(model),
prompt,
tools: {
getWeather: getWeather.tool, // pass weatherTask.tool as a tool
},
maxSteps: 5, // Allow streamText to repeatedly call the model
});
// pass the fullStream to the metadata system
const stream = await metadata.stream("openai", result.fullStream);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
// chunk is a TextStreamPart
if (chunk.type === "text-delta") {
text += chunk.textDelta;
}
}
return { text };
},
});
```
-38
View File
@@ -1,38 +0,0 @@
---
title: useRealtimeRun
sidebarTitle: useRealtimeRun
description: Subscribes to all changes to a run in a React component.
---
import RunObject from "/snippets/realtime/run-object.mdx";
<RequestExample>
```ts Example
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "./trigger/tasks";
export function MyComponent({ runId }: { runId: string }) {
const { run, error } = useRealtimeRun<typeof myTask>(runId);
if (error) return <div>Error: {error.message}</div>;
return <div>Run: {run.id}</div>;
}
```
</RequestExample>
This react hook subscribes to all changes to a run. See the [React hooks doc](/frontend/react-hooks) for more information on how to use this hook.
### Response
The react hook returns an object with the following properties:
<ParamField path="run" type="object" required>
The run object. See the [Run object doc](realtime/overview#run-object) for more information.
</ParamField>
<ParamField path="error" type="Error">
An error object if an error occurred while subscribing to a run.
</ParamField>
@@ -1,45 +0,0 @@
---
title: useRealtimeRunsWithTag
sidebarTitle: useRealtimeRunsWithTag
description: Subscribes to all changes to runs with a specific tag in a React component.
---
import RunObject from "/snippets/realtime/run-object.mdx";
<RequestExample>
```ts Example
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
import type { myTask, myOtherTask } from "./trigger/tasks";
export function MyComponent({ tag }: { tag: string }) {
const { runs, error } = useRealtimeRunsWithTag<typeof myTask | typeof myOtherTask>(tag);
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{runs.map((run) => (
<div key={run.id}>Run: {run.id}</div>
))}
</div>
);
}
```
</RequestExample>
This react hook subscribes to all changes to runs with a specific tag. See the [React hooks doc](/frontend/react-hooks) for more information on how to use this hook.
### Response
The react hook returns an object with the following properties:
<ParamField path="runs" type="object[]" required>
An array of run objects. See the [Run object doc](/realtime/overview#run-object) for more
information.
</ParamField>
<ParamField path="error" type="Error">
An error object if an error occurred while subscribing.
</ParamField>
+281 -63
View File
@@ -17,42 +17,7 @@ const handle = await myTask.trigger(
);
```
Then inside your run function, you can access the metadata like this:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
const user = metadata.get("user");
console.log(user.name); // "Eric"
console.log(user.id); // "user_1234"
},
});
```
You can also update the metadata during the run:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
await metadata.set("progress", 0.1);
// Do some more work
await metadata.set("progress", 0.5);
// Do even more work
await metadata.set("progress", 1.0);
},
});
```
You can get the current metadata at any time by calling `metadata.get()` or `metadata.current()` (again, only inside a run):
You can get the current metadata at any time by calling `metadata.get()` or `metadata.current()` (only inside a run):
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
@@ -71,26 +36,6 @@ export const myTask = task({
});
```
You can update metadata inside a run using `metadata.set()`, `metadata.save()`, or `metadata.del()`:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Set a key
await metadata.set("progress", 0.5);
// Update the entire metadata object
await metadata.save({ progress: 0.6 });
// Delete a key
await metadata.del("progress");
},
});
```
Any of these methods can be called anywhere "inside" the run function, or a function called from the run function:
```ts
@@ -99,12 +44,12 @@ import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
await doSomeWork();
doSomeWork();
},
});
async function doSomeWork() {
await metadata.set("progress", 0.5);
metadata.set("progress", 0.5);
}
```
@@ -114,8 +59,8 @@ If you call any of the metadata methods outside of the run function, they will h
import { metadata } from "@trigger.dev/sdk/v3";
// Somewhere outside of the run function
async function doSomeWork() {
await metadata.set("progress", 0.5); // This will do nothing
function doSomeWork() {
metadata.set("progress", 0.5); // This will do nothing
}
```
@@ -139,10 +84,10 @@ export const myTask = task({
// Your run function work here
},
onStart: async () => {
await metadata.set("progress", 0.5);
metadata.set("progress", 0.5);
},
onSuccess: async () => {
await metadata.set("progress", 1.0);
metadata.set("progress", 1.0);
},
});
```
@@ -153,13 +98,286 @@ import { defineConfig, metadata } from "@trigger.dev/sdk/v3";
export default defineConfig({
project: "proj_1234",
onStart: async () => {
await metadata.set("progress", 0.5);
metadata.set("progress", 0.5);
},
});
```
</CodeGroup>
## Updates API
One of the more powerful features of metadata is the ability to update it as the run progresses. This is useful for tracking the progress of a run, storing intermediate results, or storing any other information that changes over time. (Combining metadata with [Realtime](/realtime) can give you a live view of the progress of your runs.)
All metadata update methods (accept for `flush` and `stream`) are synchronous and will not block the run function. We periodically flush metadata to the database in the background, so you can safely update the metadata inside a run as often as you need to, without worrying about impacting the run's performance.
### set
Set the value of a key in the metadata object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Do some more work
metadata.set("progress", 0.5);
// Do even more work
metadata.set("progress", 1.0);
},
});
```
### del
Delete a key from the metadata object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Do some more work
metadata.set("progress", 0.5);
// Remove the progress key
metadata.del("progress");
},
});
```
### replace
Replace the entire metadata object with a new object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Replace the metadata object
metadata.replace({ user: { name: "Eric", id: "user_1234" } });
},
});
```
### append
Append a value to an array in the metadata object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Append a value to an array
metadata.append("logs", "Step 1 complete");
console.log(metadata.get("logs")); // ["Step 1 complete"]
},
});
```
### remove
Remove a value from an array in the metadata object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Append a value to an array
metadata.append("logs", "Step 1 complete");
// Remove a value from the array
metadata.remove("logs", "Step 1 complete");
console.log(metadata.get("logs")); // []
},
});
```
### increment
Increment a numeric value in the metadata object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Increment a value
metadata.increment("progress", 0.4);
console.log(metadata.get("progress")); // 0.5
},
});
```
### decrement
Decrement a numeric value in the metadata object:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.5);
// Decrement a value
metadata.decrement("progress", 0.4);
console.log(metadata.get("progress")); // 0.1
},
});
```
### stream
Capture a stream of values and make the stream available when using Realtime. See our [Realtime streams](/realtime/streams) documentation for more information.
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
const readableStream = new ReadableStream({
start(controller) {
controller.enqueue("Step 1 complete");
controller.enqueue("Step 2 complete");
controller.enqueue("Step 3 complete");
controller.close();
},
});
// IMPORTANT: you must await the stream method
const stream = await metadata.stream("logs", readableStream);
// You can read from the returned stream locally
for await (const value of stream) {
console.log(value);
}
},
});
```
`metadata.stream` accepts any `AsyncIterable` or `ReadableStream` object. The stream will be captured and made available in the Realtime API. So for example, you could pass the body of a fetch response to `metadata.stream` to capture the response body and make it available in Realtime:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { url: string }) => {
logger.info("Streaming response", { url });
const response = await fetch(url);
if (!response.body) {
throw new Error("Response body is not readable");
}
const stream = await metadata.stream(
"fetch",
response.body.pipeThrough(new TextDecoderStream())
);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
text += chunk;
}
return { text };
},
});
```
Or the results of a streaming call to the OpenAI SDK:
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const myTask = task({
id: "my-task",
run: async (payload: { prompt: string }) => {
const completion = await openai.chat.completions.create({
messages: [{ role: "user", content: payload.prompt }],
model: "gpt-3.5-turbo",
stream: true,
});
const stream = await metadata.stream("openai", completion);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
text += chunk.choices.map((choice) => choice.delta?.content).join("");
}
return { text };
},
});
```
### flush
Flush the metadata to the database. The SDK will automatically flush the metadata periodically, so you don't need to call this method unless you need to ensure that the metadata is persisted immediately.
```ts
import { task, metadata } from "@trigger.dev/sdk/v3";
export const myTask = task({
id: "my-task",
run: async (payload: { message: string }) => {
// Do some work
metadata.set("progress", 0.1);
// Flush the metadata to the database
await metadata.flush();
},
});
```
## Metadata propagation
Metadata is NOT propagated to child tasks. If you want to pass metadata to a child task, you must do so explicitly:
+1 -1
View File
@@ -27,7 +27,7 @@ export function useRun<TTask extends AnyTask>(
isValidating: boolean;
isError: boolean;
} {
const apiClient = useApiClient();
const apiClient = useApiClient(options);
const {
data: run,
error,
@@ -1,11 +1,26 @@
"use client";
import { ApiRequestOptions } from "@trigger.dev/core/v3";
// eslint-disable-next-line import/export
export * from "swr";
// eslint-disable-next-line import/export
export { default as useSWR, SWRConfig } from "swr";
export type CommonTriggerHookOptions = {
/**
* Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most use-cases. Use the Realtime hooks instead.
*/
refreshInterval?: number;
/** Revalidate the data when the browser regains a network connection. */
revalidateOnReconnect?: boolean;
/** Revalidate the data when the window regains focus. */
revalidateOnFocus?: boolean;
/** Optional access token for authentication */
accessToken?: string;
/** Optional base URL for the API endpoints */
baseURL?: string;
/** Optional additional request configuration */
requestOptions?: ApiRequestOptions;
};
+2 -2
View File
@@ -66,7 +66,7 @@
"@types/slug": "^5.0.3",
"@types/uuid": "^9.0.0",
"@types/ws": "^8.5.3",
"ai": "^3.4.33",
"ai": "^4.0.1",
"encoding": "^0.1.13",
"rimraf": "^3.0.2",
"tshy": "^3.0.2",
@@ -109,4 +109,4 @@
"main": "./dist/commonjs/index.js",
"types": "./dist/commonjs/index.d.ts",
"module": "./dist/esm/index.js"
}
}
+76 -4
View File
@@ -1531,8 +1531,8 @@ importers:
specifier: ^8.5.3
version: 8.5.4
ai:
specifier: ^3.4.33
version: 3.4.33(react@18.3.1)(svelte@4.2.19)(vue@3.4.38)(zod@3.23.8)
specifier: ^4.0.1
version: 4.0.2(react@18.3.1)(zod@3.23.8)
encoding:
specifier: ^0.1.13
version: 0.1.13
@@ -1961,6 +1961,22 @@ packages:
zod: 3.23.8
dev: false
/@ai-sdk/provider-utils@2.0.1(zod@3.23.8):
resolution: {integrity: sha512-TNg7rPhRtETB2Z9F0JpOvpGii9Fs8EWM8nYy1jEkvSXkrPJ6b/9zVnDdaJsmLFDyrMbOsPJlkblYtmYEQou36w==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
dependencies:
'@ai-sdk/provider': 1.0.0
eventsource-parser: 3.0.0
nanoid: 3.3.7
secure-json-parse: 2.7.0
zod: 3.23.8
dev: true
/@ai-sdk/provider@0.0.22:
resolution: {integrity: sha512-smZ1/2jL/JSKnbhC6ama/PxI2D/psj+YAe0c0qpd5ComQCNFltg72VFf0rpUSFMmFuj1pCCNoBOCrvyl8HTZHQ==}
engines: {node: '>=18'}
@@ -1980,7 +1996,6 @@ packages:
engines: {node: '>=18'}
dependencies:
json-schema: 0.4.0
dev: false
/@ai-sdk/react@0.0.53(react@19.0.0-rc.0)(zod@3.23.8):
resolution: {integrity: sha512-sIsmTFoR/QHvUUkltmHwP4bPjwy2vko6j/Nj8ayxLhEHs04Ug+dwXQyfA7MwgimEE3BcDQpWL8ikVj0m3ZILWQ==}
@@ -2041,6 +2056,26 @@ packages:
zod: 3.23.8
dev: false
/@ai-sdk/react@1.0.1(react@18.3.1)(zod@3.23.8):
resolution: {integrity: sha512-vonKc5bcUQDkzWhqP/bBagT1Cam81gHuCAWPy52PHv1372OUrLmO2s9ZJPvfORE9ns8H84zT78MWaCf5pb/yiQ==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
zod: ^3.0.0
peerDependenciesMeta:
react:
optional: true
zod:
optional: true
dependencies:
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
'@ai-sdk/ui-utils': 1.0.1(zod@3.23.8)
react: 18.3.1
swr: 2.2.5(react@18.3.1)
throttleit: 2.1.0
zod: 3.23.8
dev: true
/@ai-sdk/solid@0.0.43(zod@3.23.8):
resolution: {integrity: sha512-7PlPLaeMAu97oOY2gjywvKZMYHF+GDfUxYNcuJ4AZ3/MRBatzs/U2r4ClT1iH8uMOcMg02RX6UKzP5SgnUBjVw==}
engines: {node: '>=18'}
@@ -2154,6 +2189,21 @@ packages:
zod-to-json-schema: 3.23.5(zod@3.23.8)
dev: false
/@ai-sdk/ui-utils@1.0.1(zod@3.23.8):
resolution: {integrity: sha512-zK7yNixtCve8ng/8+9jUFyLvI+1dPzSHuyIM56p3EeXwJECRt6e8xyk9AZJNskhmDN6jP+qucP2rWlkX3ZQ2gA==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
peerDependenciesMeta:
zod:
optional: true
dependencies:
'@ai-sdk/provider': 1.0.0
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
dev: true
/@ai-sdk/vue@0.0.45(vue@3.4.38)(zod@3.23.8):
resolution: {integrity: sha512-bqeoWZqk88TQmfoPgnFUKkrvhOIcOcSH5LMPgzZ8XwDqz5tHHrMHzpPfHCj7XyYn4ROTFK/2kKdC/ta6Ko0fMw==}
engines: {node: '>=18'}
@@ -16763,6 +16813,29 @@ packages:
zod-to-json-schema: 3.23.5(zod@3.23.8)
dev: false
/ai@4.0.2(react@18.3.1)(zod@3.23.8):
resolution: {integrity: sha512-Dj17cVKCM+FgsJIAhYv7zT4YsrK3noFsLUxJOnTCVIHhmiNrl6/M6ebwQUOukLR/P2PbVzUQpHzdTvGck2b05Q==}
engines: {node: '>=18'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
zod: ^3.0.0
peerDependenciesMeta:
react:
optional: true
zod:
optional: true
dependencies:
'@ai-sdk/provider': 1.0.0
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
'@ai-sdk/react': 1.0.1(react@18.3.1)(zod@3.23.8)
'@ai-sdk/ui-utils': 1.0.1(zod@3.23.8)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
react: 18.3.1
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
dev: true
/ajv-formats@2.1.1(ajv@8.12.0):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
@@ -20638,7 +20711,6 @@ packages:
/eventsource-parser@3.0.0:
resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==}
engines: {node: '>=18.0.0'}
dev: false
/evt@2.4.13:
resolution: {integrity: sha512-haTVOsmjzk+28zpzvVwan9Zw2rLQF2izgi7BKjAPRzZAfcv+8scL0TpM8MzvGNKFYHiy+Bq3r6FYIIUPl9kt3A==}
+68 -1
View File
@@ -3,6 +3,11 @@ import { logger, metadata, runs, schemaTask, task, toolTask, wait } from "@trigg
import { streamText, type TextStreamPart } from "ai";
import { setTimeout } from "node:timers/promises";
import { z } from "zod";
import OpenAI from "openai";
const openaiSDK = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export type STREAMS = { openai: TextStreamPart<{ getWeather: typeof weatherTask.tool }> };
@@ -90,7 +95,7 @@ export const openaiStreaming = schemaTask({
run: async ({ model, prompt }) => {
logger.info("Running OpenAI model", { model, prompt });
const result = await streamText({
const result = streamText({
model: openai(model),
prompt,
tools: {
@@ -144,3 +149,65 @@ export const openaiO1Model = schemaTask({
return { text };
},
});
export const fetchStream = schemaTask({
id: "fetch-stream",
description: "Stream data from fetch",
schema: z.object({
url: z.string().url(),
}),
run: async ({ url }) => {
logger.info("Streaming response", { url });
const response = await fetch(url);
if (!response.body) {
throw new Error("Response body is not readable");
}
const stream = await metadata.stream(
"fetch",
response.body.pipeThrough(new TextDecoderStream())
);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
text += chunk;
}
return { text };
},
});
export const openaiSDKStreaming = schemaTask({
id: "openai-sdk-streaming",
description: "Stream data from the OpenAI SDK",
schema: z.object({
model: z.string().default("gpt-3.5-turbo"),
prompt: z.string().default("Hello, how are you?"),
}),
run: async ({ model, prompt }) => {
logger.info("Running OpenAI model", { model, prompt });
const completion = await openaiSDK.chat.completions.create({
messages: [{ role: "user", content: prompt }],
model: "gpt-3.5-turbo",
stream: true,
});
const stream = await metadata.stream("openai", completion);
let text = "";
for await (const chunk of stream) {
logger.log("Received chunk", { chunk });
text += chunk.choices.map((choice) => choice.delta?.content).join("");
}
return { text };
},
});