- Add onPreload hook and preloaded field to all lifecycle events
- Add transport.preload(chatId) for eagerly starting runs before first message
- Add preloadWarmTimeoutInSeconds and preloadTimeout task options
- Add preload:true run tag and chat.preloaded span attributes
- Add UserTool model for per-user dynamic tools loaded from DB
- Load dynamic tools in onPreload/onChatStart via chat.local
- Build dynamicTool() instances in run and spread into streamText tools
- Reference project: preload on new chat, dynamic company-info and user-preferences tools
- Store chat turn context (chatId, turn, continuation, clientData) in locals for auto-detection
- toolFromTask now auto-detects chat context and passes it to subtask metadata
- Skip serializing messages array (can be large, rarely needed by subtasks)
- Tag subtask runs with toolCallId for dashboard visibility
- Add ai.toolCallId() convenience helper
- Add ai.chatContext<typeof myChat>() with typed clientData inference
- Add ai.chatContextOrThrow<typeof myChat>() that throws if not in a chat context
- Update deepResearch example to use ai.chatContextOrThrow
- Document all helpers in ai-chat guide
- Export chat.stream (typed RealtimeDefinedStream<UIMessageChunk>) for writing custom data to the chat stream
- Add deepResearch subtask using data-* chunks to stream progress back to parent chat via target: root
- Use AI SDK data-research-progress chunk protocol with id-based updates for live progress
- Add ResearchProgress component and generic data-* fallback renderer in frontend
- Persist model per chat in DB (schema + onChatStart), model selector only on new chats
- Add collapsible debug panel showing run ID (with dashboard link), chat ID, model, status, session info
- Document chat.stream API, data-* chunks, and subtask streaming pattern in docs
- Fix onFinish race condition: await onFinishPromise so capturedResponseMessage is set before accumulation
- Add chat.isStopped() helper accessible from anywhere during a turn
- Add chat.cleanupAbortedParts() to remove incomplete tool/reasoning/text parts on stop
- Auto-cleanup aborted parts before passing to onTurnComplete
- Clean incoming messages from frontend to prevent tool_use without tool_result API errors
- Add stopped and rawResponseMessage fields to TurnCompleteEvent
- Add continuation and previousRunId fields to all lifecycle hooks and run payload
- Add span attributes (chat.id, chat.turn, chat.stopped, chat.continuation, chat.previous_run_id, etc.)
- Add webFetch tool and reasoning model support to ai-chat reference project
- Render reasoning parts in frontend chat component
- Document all new fields in ai-chat guide
@ai-sdk/openai v3 and @ai-sdk/react v3 are needed for ai v6 compatibility.
convertToModelMessages is async in newer AI SDK versions.
Co-authored-by: Eric Allam <eric@trigger.dev>
1. Add null/object guard before enqueuing UIMessageChunk from SSE stream
to handle heartbeat or malformed events safely
2. Use incrementing counter instead of Date.now() in test message
factories to avoid duplicate IDs
3. Add test covering publicAccessToken from trigger response being used
for stream subscription auth
Co-authored-by: Eric Allam <eric@trigger.dev>
Move and adapt tests from packages/ai to packages/trigger-sdk.
- Import from ./chat.js instead of ./transport.js
- Use 'task' option instead of 'taskId'
- All 17 tests passing
Co-authored-by: Eric Allam <eric@trigger.dev>
Two new subpath exports:
@trigger.dev/sdk/chat (frontend, browser-safe):
- TriggerChatTransport — ChatTransport implementation for useChat
- createChatTransport() — factory function
- TriggerChatTransportOptions type
@trigger.dev/sdk/ai (backend, adds to existing ai.tool/ai.currentToolOptions):
- chatTask() — pre-typed task wrapper with auto-pipe
- pipeChat() — pipe StreamTextResult to realtime stream
- CHAT_STREAM_KEY constant
- ChatTaskPayload type
- ChatTaskOptions type
- PipeChatOptions type
Co-authored-by: Eric Allam <eric@trigger.dev>
Use the already-resolved token when creating ApiClient instead of
calling resolveAccessToken() again through getApiClient().
Co-authored-by: Eric Allam <eric@trigger.dev>
The accessToken option now accepts either a string or a function
returning a string. This enables dynamic token refresh patterns:
new TriggerChatTransport({
taskId: 'my-task',
accessToken: () => getLatestToken(),
})
The function is called on each sendMessages() call, allowing fresh
tokens to be used for each task trigger.
Co-authored-by: Eric Allam <eric@trigger.dev>
ChatSessionState is an implementation detail of the transport's
session tracking. Users don't need to access it since the sessions
map is private.
Co-authored-by: Eric Allam <eric@trigger.dev>
Adds 3 additional test cases:
- Abort signal gracefully closes the stream
- Multiple independent chat sessions tracked correctly
- ChatRequestOptions.body is merged into task payload
Co-authored-by: Eric Allam <eric@trigger.dev>
Tests cover:
- Constructor with required and optional options
- sendMessages triggering task and returning UIMessageChunk stream
- Correct payload structure sent to trigger API
- Custom streamKey in stream URL
- Extra headers propagation
- reconnectToStream with existing and non-existing sessions
- createChatTransport factory function
- Error handling for API failures
- regenerate-message trigger type
Co-authored-by: Eric Allam <eric@trigger.dev>
New package that provides a custom AI SDK ChatTransport implementation
bridging Vercel AI SDK's useChat hook with Trigger.dev's durable task
execution and realtime streams.
Key exports:
- TriggerChatTransport class implementing ChatTransport<UIMessage>
- createChatTransport() factory function
- ChatTaskPayload type for task-side typing
- TriggerChatTransportOptions type
The transport triggers a Trigger.dev task with chat messages as payload,
then subscribes to the task's realtime stream to receive UIMessageChunk
data, which useChat processes natively.
Co-authored-by: Eric Allam <eric@trigger.dev>
Adds a `MicroVM` badge next to the region name on the regions page. Uses
the existing `small` badge variant for visual consistency with the
`Default` badge already on this page.
## Summary
Large error stacks and messages can OOM the worker process when
serialized into OTel spans or `TaskRunError` objects. This was reported
when throwing an error with a massive `.stack` property from a chat
agent hook.
This adds frame-based stack truncation (similar to Sentry's approach)
plus message length limits, applied consistently across all error
serialization paths.
### What changed
**`packages/core/src/v3/errors.ts`**
- `truncateStack()` — parses `error.stack` into message lines + frame
lines, caps at 50 frames (keep top 5 closest to throw + bottom 45 entry
points, with "... N frames omitted ..." in between). Individual lines
capped at 1024 chars.
- `truncateMessage()` — caps error messages at 1000 chars
- Applied in `parseError()` and `sanitizeError()`
**`packages/core/src/v3/otel/utils.ts`**
- `sanitizeSpanError()` now uses `truncateStack` and `truncateMessage`
from `errors.ts` instead of duplicating truncation logic
- Non-Error values (strings, JSON) capped at 5000 chars
**`packages/core/src/v3/tracer.ts`**
- `startActiveSpan` catch block now delegates to `recordSpanException()`
instead of calling `span.recordException()` directly
### Limits
| What | Limit | Rationale |
|------|-------|-----------|
| Stack frames | 50 | Matches Sentry's `STACKTRACE_FRAME_LIMIT` |
| Top frames kept | 5 | Closest to throw site |
| Bottom frames kept | 45 | Entry points / framework frames |
| Per-line length | 1024 | Matches Sentry, prevents regex DoS |
| Message length | 1000 | Bounded but generous |
| Generic string (non-Error) | 5000 | Fallback for JSON/string errors in
spans |
## Test plan
- [x] 17 unit tests in `packages/core/test/errors.test.ts`
- [x] E2E: threw a 300-frame / 5000-char-message error in the ai-chat
reference app, verified truncated stack and message in span via
`get_span_details`
- [x] Verified the run survived the error (no OOM, continued waiting for
next message)