Skip to content

Commit 3050c89

Browse files
committed
feat(sdk): type chat.createStartSessionAction against your chat agent
Parameterise the action with `<typeof yourChatAgent>` to type the `clientData` field against your agent's `clientDataSchema`. `clientData` is folded into the first run's `payload.metadata` so `onPreload` / `onChatStart` see the same shape per-turn `metadata` carries via the transport. The opaque session-level `metadata` field is unchanged. Closes the type gap where the transport's `startSession` callback hands you a typed `clientData` but the server-side action couldn't accept it without untyped routing through the `metadata` field.
1 parent d343727 commit 3050c89

2 files changed

Lines changed: 59 additions & 17 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Type `chat.createStartSessionAction` against your chat agent so `clientData` is typed end-to-end on the first turn:
6+
7+
```ts
8+
import { chat } from "@trigger.dev/sdk/ai";
9+
import type { myChat } from "@/trigger/chat";
10+
11+
export const startChatSession = chat.createStartSessionAction<typeof myChat>("my-chat");
12+
13+
// In the browser, threaded from the transport's typed startSession callback:
14+
const transport = useTriggerChatTransport<typeof myChat>({
15+
task: "my-chat",
16+
startSession: ({ chatId, clientData }) =>
17+
startChatSession({ chatId, clientData }),
18+
// ...
19+
});
20+
```
21+
22+
`ChatStartSessionParams` gains a typed `clientData` field — folded into the first run's `payload.metadata` so `onPreload` / `onChatStart` see the same shape per-turn `metadata` carries via the transport. The opaque session-level `metadata` field is unchanged.

packages/trigger-sdk/src/v3/ai.ts

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9040,17 +9040,29 @@ export type CreateChatStartSessionActionOptions = {
90409040
/**
90419041
* Params for the function returned by {@link createChatStartSessionAction}.
90429042
*/
9043-
export type ChatStartSessionParams = {
9043+
export type ChatStartSessionParams<TChat extends AnyTask = AnyTask> = {
90449044
/** Conversation id (mapped to the Session's `externalId`). */
90459045
chatId: string;
9046+
/**
9047+
* Typed client data — folded into the first run's `payload.metadata` so
9048+
* `onPreload`, `onChatStart`, etc. see the same `clientData` shape on the
9049+
* first turn as subsequent turns get via the transport's `clientData`
9050+
* option. Typed via the agent's `clientDataSchema` when the action is
9051+
* parameterised with `createStartSessionAction<typeof myChat>(...)`.
9052+
*/
9053+
clientData?: InferChatClientData<TChat>;
90469054
/**
90479055
* Per-call trigger config. Shallow-merged over the action's default
90489056
* `triggerConfig`. `basePayload` is the customer's wire payload (for
90499057
* `chat.agent`: anything beyond `chatId`/`messages`/`trigger`/`metadata`,
90509058
* which the runtime injects automatically).
90519059
*/
90529060
triggerConfig?: Partial<SessionTriggerConfig>;
9053-
/** Pass-through metadata folded into the session row. */
9061+
/**
9062+
* Opaque session-level metadata stored on the Session row. Separate from
9063+
* the per-turn `clientData` above. Use this when you want to attach
9064+
* server-side metadata that doesn't go through the agent's `clientDataSchema`.
9065+
*/
90549066
metadata?: Record<string, unknown>;
90559067
};
90569068

@@ -9078,33 +9090,37 @@ export type ChatStartSessionResult = {
90789090
* Wrap in a Next.js server action (or any server-side handler) so the
90799091
* customer's secret key never crosses to the browser.
90809092
*
9093+
* Parameterise the action with `<typeof yourChatAgent>` to type the
9094+
* `clientData` field against your agent's `clientDataSchema`.
9095+
*
90819096
* @example
90829097
* ```ts
90839098
* // actions.ts
90849099
* "use server";
90859100
* import { chat } from "@trigger.dev/sdk/ai";
9101+
* import type { myChat } from "@/trigger/chat";
90869102
*
9087-
* export const startChatSession = chat.createStartSessionAction("my-chat", {
9088-
* triggerConfig: { machine: "small-1x" },
9089-
* });
9103+
* export const startChatSession = chat.createStartSessionAction<typeof myChat>(
9104+
* "my-chat",
9105+
* { triggerConfig: { machine: "small-1x" } }
9106+
* );
90909107
* ```
90919108
*
9092-
* Then in the browser:
9109+
* Then in the browser, threading the typed `clientData` from the transport:
90939110
* ```tsx
9094-
* const transport = useTriggerChatTransport({
9111+
* const transport = useTriggerChatTransport<typeof myChat>({
90959112
* task: "my-chat",
9096-
* accessToken: async ({ chatId }) => {
9097-
* const { publicAccessToken } = await startChatSession({ chatId });
9098-
* return publicAccessToken;
9099-
* },
9113+
* accessToken: ({ chatId }) => mintChatAccessToken(chatId),
9114+
* startSession: ({ chatId, clientData }) =>
9115+
* startChatSession({ chatId, clientData }),
91009116
* });
91019117
* ```
91029118
*/
9103-
function createChatStartSessionAction(
9119+
function createChatStartSessionAction<TChat extends AnyTask = AnyTask>(
91049120
taskId: string,
91059121
options?: CreateChatStartSessionActionOptions
9106-
): (params: ChatStartSessionParams) => Promise<ChatStartSessionResult> {
9107-
return async (params: ChatStartSessionParams): Promise<ChatStartSessionResult> => {
9122+
): (params: ChatStartSessionParams<TChat>) => Promise<ChatStartSessionResult> {
9123+
return async (params: ChatStartSessionParams<TChat>): Promise<ChatStartSessionResult> => {
91089124
if (!params.chatId) {
91099125
throw new Error(
91109126
"chat.createStartSessionAction: params.chatId is required — used as the session externalId."
@@ -9117,21 +9133,25 @@ function createChatStartSessionAction(
91179133
// `onPreload` fires, the runtime opens its `.in` subscription, the
91189134
// first user message arrives moments later via `.in/append`.
91199135
//
9120-
// `metadata` is the customer's transport-level `clientData`,
9121-
// threaded through so the agent's `clientDataSchema` validates on
9122-
// the very first turn (the typical schema requires `userId` etc.).
9136+
// `clientData` is folded into `basePayload.metadata` so the agent's
9137+
// `clientDataSchema` validates on the very first turn against the same
9138+
// shape per-turn `metadata` carries via the transport.
91239139
// Auto-tag every chat.agent run with `chat:{chatId}` so the dashboard /
91249140
// run-list filter by chat works without the customer having to wire it
91259141
// up. Mirrors the browser-mediated `TriggerChatTransport.doStart` path.
91269142
const userTags = params.triggerConfig?.tags ?? options?.triggerConfig?.tags ?? [];
91279143
const tags = [`chat:${params.chatId}`, ...userTags].slice(0, 5);
91289144

9145+
const clientDataMetadata =
9146+
params.clientData !== undefined ? { metadata: params.clientData } : {};
9147+
91299148
const triggerConfig: SessionTriggerConfig = {
91309149
basePayload: {
91319150
messages: [],
91329151
trigger: "preload",
91339152
...(options?.triggerConfig?.basePayload ?? {}),
91349153
...(params.triggerConfig?.basePayload ?? {}),
9154+
...clientDataMetadata,
91359155
chatId: params.chatId,
91369156
},
91379157
...(options?.triggerConfig?.machine || params.triggerConfig?.machine

0 commit comments

Comments
 (0)