Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
10467b3
feat: add @trigger.dev/ai package with TriggerChatTransport
cursoragent Feb 15, 2026
b91fb44
test: add comprehensive unit tests for TriggerChatTransport
cursoragent Feb 15, 2026
1836ab2
refactor: polish TriggerChatTransport implementation
cursoragent Feb 15, 2026
fbfa181
test: add abort signal, multiple sessions, and body merging tests
cursoragent Feb 15, 2026
9e10ecb
chore: add changeset for @trigger.dev/ai package
cursoragent Feb 15, 2026
0abfed5
refactor: remove internal ChatSessionState from public exports
cursoragent Feb 15, 2026
4cb5472
feat: support dynamic accessToken function for token refresh
cursoragent Feb 15, 2026
738fb51
refactor: avoid double-resolving accessToken in sendMessages
cursoragent Feb 15, 2026
a905825
feat: add chat transport and AI chat helpers to @trigger.dev/sdk
cursoragent Feb 15, 2026
e504caf
test: move chat transport tests to @trigger.dev/sdk
cursoragent Feb 15, 2026
008b4e2
refactor: delete packages/ai/ — moved to @trigger.dev/sdk subpaths
cursoragent Feb 15, 2026
3699f56
chore: update changeset to target @trigger.dev/sdk
cursoragent Feb 15, 2026
3400bb0
fix: address CodeRabbit review feedback
cursoragent Feb 15, 2026
ef3d440
docs(ai): add AI Chat with useChat guide
cursoragent Feb 15, 2026
71a355d
feat(reference): add ai-chat Next.js reference project
cursoragent Feb 15, 2026
e4cf142
fix(reference): use compatible @ai-sdk v3 packages, await convertToMo…
cursoragent Feb 15, 2026
da13674
Use a single run with iterative waitpoint token completions
ericallam Feb 21, 2026
e57f1f3
Added tool example
ericallam Feb 21, 2026
d1afbb7
expose a useTriggerChatTransport hook
ericallam Feb 21, 2026
b11bed4
use input streams and rename chatTask and chatState to chat.task and …
ericallam Mar 3, 2026
9aa70c1
add stopping support and fix issue with the OpenAI responses API and …
ericallam Mar 4, 2026
d4defd1
Add warmTimeoutInSeconds option
ericallam Mar 4, 2026
74089d7
Add clientData support
ericallam Mar 4, 2026
a1a4c49
provide already converted UIMessages to the run function for better dx
ericallam Mar 4, 2026
cc2b7f1
Added better telemetry support to view turns
ericallam Mar 4, 2026
5891f78
Fix double looping when resuming from an input stream waitpoint
ericallam Mar 4, 2026
953b2c8
Add some pending message support in the example
ericallam Mar 4, 2026
962446a
Accumulate messages in the task, allowing us to only have to send use…
ericallam Mar 5, 2026
991f70c
build full example with persisting messages, adding necessary hooks, …
ericallam Mar 5, 2026
edbda26
Add ai chat to the sidebar for now
ericallam Mar 5, 2026
869c375
remove postinstall hook
ericallam Mar 5, 2026
5b938c0
feat: add onTurnStart hook, lastEventId support, and stream resume de…
ericallam Mar 5, 2026
908f611
Minor fixes around reconnecting streams
ericallam Mar 6, 2026
2ab5cbb
update pnpm link file
ericallam Mar 6, 2026
91a5f81
fixed chat tests
ericallam Mar 6, 2026
aa925bd
use locals for the chat pipe counter instead of a module global
ericallam Mar 6, 2026
beec65c
Add triggerOptions to the transport, auto-tag with the chat ID
ericallam Mar 6, 2026
ed2b9c9
Make clientData typesafe and pass to all chat.task hooks
ericallam Mar 6, 2026
37edc90
feat: add chat.local for per-run typed data with Proxy access and dir…
ericallam Mar 6, 2026
93ea98c
feat(chat): add stop handling, abort cleanup, continuation support, a…
ericallam Mar 7, 2026
821da1e
Some improvements to the example ai-chat
ericallam Mar 7, 2026
2650230
feat(chat): expose typed chat.stream, add deepResearch subtask exampl…
ericallam Mar 8, 2026
1057ad0
feat(ai): pass chat context and toolCallId to subtasks, add typed ai.…
ericallam Mar 8, 2026
4b32f5c
feat(chat): add preload support, dynamic tools, and preload-specific …
ericallam Mar 9, 2026
a0fb759
docs: add mermaid architecture diagrams for ai-chat system
ericallam Mar 9, 2026
d0c2edb
docs: add sequence diagrams to ai-chat guide
ericallam Mar 9, 2026
60ad57c
feat(chat): auto-hydrate chat.local values in ai.tool subtasks
ericallam Mar 9, 2026
6e1bf06
feat(chat): add chat.defer(), preload toggle, TTFB measurement, and f…
ericallam Mar 9, 2026
e816ea6
fix(reference): replace hand-rolled HTML stripping with turndown
ericallam Mar 9, 2026
397ca7d
feat(streams): add inputStream.waitWithWarmup(), warm timeout config …
ericallam Mar 9, 2026
e07a3ec
feat(chat): add composable primitives, raw task example, and task mod…
ericallam Mar 10, 2026
1a4b9f7
Introduce the chat session API and better docs organization
ericallam Mar 10, 2026
dac41fe
Add support for toUIMessageStream() options
ericallam Mar 10, 2026
b6f938a
Add metadata to the streamText call
ericallam Mar 12, 2026
25405e2
feat(chat): add chat.prompt API with provider registry support
ericallam Mar 23, 2026
62fed51
refactor: rename warmTimeout to idleTimeout across chat APIs
ericallam Mar 23, 2026
b5bcd0f
feat: support message compaction
ericallam Mar 24, 2026
5746762
better compaction support in our other chat variants
ericallam Mar 24, 2026
cdcb43e
feat(chat): add compaction option, pendingMessages steering, and useP…
ericallam Mar 25, 2026
7027a13
Add a writer to easily write chunks in callbacks
ericallam Mar 26, 2026
3e8604b
feat(ai): add triggerAndSubscribe method and use it in ai.tool
ericallam Mar 26, 2026
968e1c3
feat(chat): add chat.inject() for background context injection and ch…
ericallam Mar 26, 2026
da62791
feat(sdk): ToolSet typing for toolFromTask, ai.toolExecute, deprecate…
ericallam Mar 27, 2026
6e56fbc
Add run-scoped PAT renewal for chat transport
ericallam Mar 27, 2026
67d563f
feat(sdk): ctx on chat.task hooks; ai-chat E2B sandbox; docs patterns
ericallam Mar 27, 2026
bf5c64b
feat(sdk): add onChatSuspend/onChatResume hooks, exitAfterPreloadIdle…
ericallam Mar 28, 2026
06c5bec
Add support for triggering from the backend
ericallam Mar 28, 2026
d5b472b
chat.task -> chat.agent
ericallam Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/ai-chat-sandbox-and-ctx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@trigger.dev/sdk": patch
---

Add `TaskRunContext` (`ctx`) to all `chat.task` lifecycle events, `CompactedEvent`, and `ChatTaskRunPayload`. Export `TaskRunContext` from `@trigger.dev/sdk`.

42 changes: 42 additions & 0 deletions .changeset/ai-sdk-chat-transport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
"@trigger.dev/sdk": minor
---

Add AI SDK chat transport integration via two new subpath exports:

**`@trigger.dev/sdk/chat`** (frontend, browser-safe):
- `TriggerChatTransport` — custom `ChatTransport` for the AI SDK's `useChat` hook that runs chat completions as durable Trigger.dev tasks
- `createChatTransport()` — factory function

```tsx
import { useChat } from "@ai-sdk/react";
import { TriggerChatTransport } from "@trigger.dev/sdk/chat";

const { messages, sendMessage } = useChat({
transport: new TriggerChatTransport({
task: "my-chat-task",
accessToken,
}),
});
```

**`@trigger.dev/sdk/ai`** (backend, extends existing `ai.tool`/`ai.currentToolOptions`):
- `chatTask()` — pre-typed task wrapper with auto-pipe support
- `pipeChat()` — pipe a `StreamTextResult` or stream to the frontend
- `CHAT_STREAM_KEY` — the default stream key constant
- `ChatTaskPayload` type

```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),
});
},
});
```
5 changes: 5 additions & 0 deletions .changeset/ai-tool-execute-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---

Add `ai.toolExecute(task)` so you can pass Trigger's subtask/metadata wiring as the `execute` handler to AI SDK `tool()` while defining `description` and `inputSchema` yourself. Refactors `ai.tool()` to share the same internal handler.
6 changes: 6 additions & 0 deletions .changeset/ai-tool-toolset-typing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@trigger.dev/sdk": patch
---

Align `ai.tool()` (`toolFromTask`) with the AI SDK `ToolSet` shape: Zod-backed tasks use static `tool()`; returns are asserted as `Tool & ToolSet[string]`. Raise the SDK's minimum `ai` devDependency to `^6.0.116` so emitted types resolve the same `ToolSet` as apps on AI SDK 6.0.x (avoids cross-version `ToolSet` mismatches in monorepos).

6 changes: 6 additions & 0 deletions .changeset/chat-run-pat-renewal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@trigger.dev/core": patch
"@trigger.dev/sdk": patch
---

Add run-scoped PAT renewal for chat transport (`renewRunAccessToken`), fail fast on 401/403 for SSE without retry backoff, and export `isTriggerRealtimeAuthError` for auth-error detection.
5 changes: 5 additions & 0 deletions .changeset/dry-sloths-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---

Add `chat.withUIMessage<TUIMessage>()` for typed AI SDK `UIMessage` in chat task hooks, optional factory `streamOptions` merged with `uiMessageStreamOptions`, and `InferChatUIMessage` helper. Generic `ChatUIMessageStreamOptions`, compaction, and pending-message event types. `usePendingMessages` accepts a UI message type parameter; re-export `InferChatUIMessage` from `@trigger.dev/sdk/chat/react`.
22 changes: 22 additions & 0 deletions .claude/rules/package-installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
paths:
- "**/package.json"
---

# Installing Packages

When adding a new dependency to any package.json in the monorepo:

1. **Look up the latest version** on npm before adding:
```bash
pnpm view <package-name> version
```
If unsure which version to use (e.g. major version compatibility), confirm with the user.

2. **Edit the package.json directly** — do NOT use `pnpm add` as it can cause issues in the monorepo. Add the dependency with the correct version range (typically `^x.y.z`).

3. **Run `pnpm i` from the repo root** after editing to install and update the lockfile:
```bash
pnpm i
```
Always run from the repo root, not from the package directory.
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This file provides guidance to Claude Code when working with this repository. Su

This is a pnpm 10.23.0 monorepo using Turborepo. Run commands from root with `pnpm run`.

**Adding dependencies:** Edit `package.json` directly instead of using `pnpm add`, then run `pnpm i` from the repo root. See `.claude/rules/package-installation.md` for the full process.

```bash
pnpm run docker # Start Docker services (PostgreSQL, Redis, Electric)
pnpm run db:migrate # Run database migrations
Expand Down
13 changes: 13 additions & 0 deletions apps/webapp/app/components/BulkActionFilterSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,19 @@ export function BulkActionFilterSummary({
/>
);
}
case "sources": {
const values = Array.isArray(value) ? value : [`${value}`];
return (
<AppliedFilter
variant="minimal/medium"
key={key}
label={filterTitle(key)}
icon={filterIcon(key)}
value={appliedSummary(values)}
removable={false}
/>
);
}
default: {
assertNever(typedKey);
}
Expand Down
19 changes: 19 additions & 0 deletions apps/webapp/app/components/navigation/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ClockIcon,
Cog8ToothIcon,
CogIcon,
CpuChipIcon,
CubeIcon,
ExclamationTriangleIcon,
FolderIcon,
Expand Down Expand Up @@ -69,7 +70,9 @@ import {
organizationTeamPath,
queryPath,
regionsPath,
v3AgentsPath,
v3ApiKeysPath,
v3PlaygroundPath,
v3BatchesPath,
v3BillingPath,
v3BuiltInDashboardPath,
Expand Down Expand Up @@ -470,6 +473,22 @@ export function SideMenu({
)}
onCollapseToggle={handleSectionToggle("ai")}
>
<SideMenuItem
name="Agents"
icon={CpuChipIcon}
activeIconColor="text-indigo-500"
inactiveIconColor="text-indigo-500"
to={v3AgentsPath(organization, project, environment)}
isCollapsed={isCollapsed}
/>
<SideMenuItem
name="Playground"
icon={BeakerIcon}
activeIconColor="text-indigo-400"
inactiveIconColor="text-indigo-400"
to={v3PlaygroundPath(organization, project, environment)}
isCollapsed={isCollapsed}
/>
<SideMenuItem
name="Prompts"
icon={AIPromptsIcon}
Expand Down
117 changes: 116 additions & 1 deletion apps/webapp/app/components/runs/v3/RunFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Ariakit from "@ariakit/react";
import {
CalendarIcon,
ClockIcon,
CpuChipIcon,
FingerPrintIcon,
RectangleStackIcon,
Squares2X2Icon,
Expand Down Expand Up @@ -182,6 +183,9 @@ export const TaskRunListSearchFilters = z.object({
`Machine presets to filter by (${machines.join(", ")})`
),
errorId: z.string().optional().describe("Error ID to filter runs by (e.g. error_abc123)"),
sources: StringOrStringArray.describe(
"Task trigger sources to filter by (STANDARD, SCHEDULED, AGENT)"
),
});

export type TaskRunListSearchFilters = z.infer<typeof TaskRunListSearchFilters>;
Expand Down Expand Up @@ -223,6 +227,8 @@ export function filterTitle(filterKey: string) {
return "Version";
case "errorId":
return "Error ID";
case "sources":
return "Source";
default:
return filterKey;
}
Expand Down Expand Up @@ -263,6 +269,8 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
return <IconRotateClockwise2 className="size-4" />;
case "errorId":
return <IconBugFilled className="size-4" />;
case "sources":
return <CpuChipIcon className="size-4" />;
default:
return undefined;
}
Expand Down Expand Up @@ -310,6 +318,10 @@ export function getRunFiltersFromSearchParams(
? searchParams.getAll("versions")
: undefined,
errorId: searchParams.get("errorId") ?? undefined,
sources:
searchParams.getAll("sources").filter((v) => v.length > 0).length > 0
? searchParams.getAll("sources")
: undefined,
};

const parsed = TaskRunListSearchFilters.safeParse(params);
Expand Down Expand Up @@ -351,7 +363,8 @@ export function RunsFilters(props: RunFiltersProps) {
searchParams.has("queues") ||
searchParams.has("machines") ||
searchParams.has("versions") ||
searchParams.has("errorId");
searchParams.has("errorId") ||
searchParams.has("sources");

return (
<div className="flex flex-row flex-wrap items-center gap-1">
Expand Down Expand Up @@ -388,6 +401,7 @@ const filterTypes = [
{ name: "schedule", title: "Schedule ID", icon: <ClockIcon className="size-4" /> },
{ name: "bulk", title: "Bulk action", icon: <ListCheckedIcon className="size-4" /> },
{ name: "error", title: "Error ID", icon: <IconBugFilled className="size-4" /> },
{ name: "source", title: "Source", icon: <CpuChipIcon className="size-4" /> },
] as const;

type FilterType = (typeof filterTypes)[number]["name"];
Expand Down Expand Up @@ -443,6 +457,7 @@ function AppliedFilters({ possibleTasks, bulkActions }: RunFiltersProps) {
<AppliedScheduleIdFilter />
<AppliedBulkActionsFilter bulkActions={bulkActions} />
<AppliedErrorIdFilter />
<AppliedSourceFilter />
</>
);
}
Expand Down Expand Up @@ -481,6 +496,8 @@ function Menu(props: MenuProps) {
return <VersionsDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "error":
return <ErrorIdDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
case "source":
return <SourceDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
}
}

Expand Down Expand Up @@ -1869,3 +1886,101 @@ function AppliedErrorIdFilter() {
</FilterMenuProvider>
);
}

const sourceOptions: { value: TaskTriggerSource; title: string }[] = [
{ value: "STANDARD", title: "Standard" },
{ value: "SCHEDULED", title: "Scheduled" },
{ value: "AGENT", title: "Agent" },
];

function SourceDropdown({
trigger,
clearSearchValue,
searchValue,
onClose,
}: {
trigger: ReactNode;
clearSearchValue: () => void;
searchValue: string;
onClose?: () => void;
}) {
const { values, replace } = useSearchParams();

const handleChange = (values: string[]) => {
clearSearchValue();
replace({ sources: values, cursor: undefined, direction: undefined });
};

const filtered = useMemo(() => {
return sourceOptions.filter((item) =>
item.title.toLowerCase().includes(searchValue.toLowerCase())
);
}, [searchValue]);

return (
<SelectProvider value={values("sources")} setValue={handleChange} virtualFocus={true}>
{trigger}
<SelectPopover
className="min-w-0 max-w-[min(240px,var(--popover-available-width))]"
hideOnEscape={() => {
if (onClose) {
onClose();
return false;
}
return true;
}}
>
<ComboBox placeholder={"Filter by source..."} value={searchValue} />
<SelectList>
{filtered.map((item, index) => (
<SelectItem
key={item.value}
value={item.value}
icon={
<TaskTriggerSourceIcon source={item.value} className="size-4 flex-none" />
}
shortcut={shortcutFromIndex(index, { shortcutsEnabled: true })}
>
{item.title}
</SelectItem>
))}
</SelectList>
</SelectPopover>
</SelectProvider>
);
}

function AppliedSourceFilter() {
const { values, del } = useSearchParams();
const sources = values("sources");

if (sources.length === 0 || sources.every((v) => v === "")) {
return null;
}

return (
<FilterMenuProvider>
{(search, setSearch) => (
<SourceDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Source"
icon={<CpuChipIcon className="size-4" />}
value={appliedSummary(
sources.map(
(v) => sourceOptions.find((o) => o.value === v)?.title ?? v
)
)}
onRemove={() => del(["sources", "cursor", "direction"])}
variant="secondary/small"
/>
</Ariakit.Select>
}
searchValue={search}
clearSearchValue={() => setSearch("")}
/>
)}
</FilterMenuProvider>
);
}
6 changes: 6 additions & 0 deletions apps/webapp/app/components/runs/v3/TaskRunsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ import {
filterableTaskRunStatuses,
TaskRunStatusCombo,
} from "./TaskRunStatus";
import { TaskTriggerSourceIcon } from "./TaskTriggerSource";
import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
import { useSearchParams } from "~/hooks/useSearchParam";
import type { TaskTriggerSource } from "@trigger.dev/database";

type RunsTableProps = {
total: number;
Expand Down Expand Up @@ -343,6 +345,10 @@ export function TaskRunsTable({
</TableCell>
<TableCell to={path}>
<span className="flex items-center gap-x-1">
<TaskTriggerSourceIcon
source={run.taskKind as TaskTriggerSource}
className="size-3.5 flex-none"
/>
{run.taskIdentifier}
{run.rootTaskRunId === null ? <Badge variant="extra-small">Root</Badge> : null}
</span>
Expand Down
Loading
Loading