fix(core,sdk,webapp): allow 10 session trigger tags, matching the run tag limit (#4832)
## Summary `SessionTriggerConfig.tags` was capped at 5, while runs (and the [tags docs](https://trigger.dev/docs/tags)) allow 10. Session trigger tags are forwarded verbatim as the run tags on every run a session schedules, so the lower cap was an inconsistency rather than a separate limit. For `chat.agent` it was worse in practice: the SDK prepends `chat:{chatId}` automatically and truncates, so users could only get 4 of their own tags through. The schema, the SDK truncation points, and the dashboard playground now all use 10. `chat.agent` users get 9 of their own tags plus the automatic `chat:{chatId}` tag. Docs updated to say so.
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@trigger.dev/core": patch
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
Session `triggerConfig.tags` now accepts up to 10 tags, matching the run tag limit. Previously it was capped at 5, which for `chat.agent` left room for only 4 of your own tags after the automatic `chat:{chatId}` tag.
|
||||
+1
-1
@@ -123,7 +123,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean)
|
||||
: []),
|
||||
].slice(0, 5);
|
||||
].slice(0, 10);
|
||||
|
||||
const triggerConfig = {
|
||||
basePayload: {
|
||||
|
||||
@@ -490,7 +490,7 @@ Options for [`chat.headStart()`](/ai-chat/fast-starts#head-start), the warm-serv
|
||||
| `agentId` | `string` | required | The `chat.agent` / `chat.customAgent` id to hand off to |
|
||||
| `run` | `(args: HeadStartRunArgs) => Promise<StreamTextResult>` | required | First-turn callback. Call `streamText` and spread `chat.toStreamTextOptions({ tools })` |
|
||||
| `idleTimeoutInSeconds` | `number` | `60` | How long the agent waits for the handover signal |
|
||||
| `triggerConfig` | `Partial<SessionTriggerConfig>` | `undefined` | Run options (tags, queue, machine, maxAttempts, maxDuration, region, lockToVersion) for the auto-triggered handover-prepare run. The `chat:{chatId}` tag is prepended automatically |
|
||||
| `triggerConfig` | `Partial<SessionTriggerConfig>` | `undefined` | Run options (tags, queue, machine, maxAttempts, maxDuration, region, lockToVersion) for the auto-triggered handover-prepare run. The `chat:{chatId}` tag is prepended automatically and counts toward the 10-tag limit |
|
||||
|
||||
`chat.headStart(options)` returns the handler `(req: Request) => Promise<Response>`. The `run` callback receives `HeadStartRunArgs`: `{ messages: UIMessage[], signal: AbortSignal, chat: HeadStartChatHelper }`, where the helper exposes `chat.toStreamTextOptions({ tools })` and a `chat.session` escape hatch. See [Head Start](/ai-chat/fast-starts#head-start) for the full guide.
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ const { id, runId, publicAccessToken, isCached } = await sessions.start({
|
||||
| `type` | `string` | Free-form discriminator. `chat.agent` uses `"chat.agent"`. |
|
||||
| `externalId` | `string?` | Your stable identity. Cannot start with `session_` (reserved). |
|
||||
| `taskIdentifier` | `string` | Task this session triggers runs against. |
|
||||
| `triggerConfig` | `SessionTriggerConfig` | Trigger options applied to every run: `tags`, `queue`, `machine`, `maxAttempts`, `idleTimeoutInSeconds`, `basePayload`. |
|
||||
| `triggerConfig` | `SessionTriggerConfig` | Trigger options applied to every run: `tags` (up to 10, same as [run tags](/tags); the chat helpers such as `chat.createStartSessionAction` and `AgentChat` add a `chat:{chatId}` tag themselves, which uses one slot. Direct `sessions.start` callers get all 10 and must add any chat tag themselves), `queue`, `machine`, `maxAttempts`, `idleTimeoutInSeconds`, `basePayload`. |
|
||||
| `tags` | `string[]?` | Up to 10 tags on the Session row (separate from `triggerConfig.tags`). |
|
||||
| `metadata` | `Record<string, unknown>?` | Arbitrary JSON. |
|
||||
| `expiresAt` | `Date?` | Hard retention deadline. |
|
||||
|
||||
@@ -1852,7 +1852,7 @@ export const SessionTriggerConfig = z.object({
|
||||
basePayload: z.record(z.unknown()),
|
||||
machine: MachinePresetName.optional(),
|
||||
queue: z.string().max(128).optional(),
|
||||
tags: z.array(z.string().max(128)).max(5).optional(),
|
||||
tags: z.array(z.string().max(128)).max(10).optional(),
|
||||
maxAttempts: z.number().int().positive().max(10).optional(),
|
||||
/** Per-run wall-clock cap (seconds). Forwarded to `TaskRunOptions.maxDuration`. */
|
||||
maxDuration: z.number().int().positive().optional(),
|
||||
|
||||
@@ -222,3 +222,19 @@ export function slimSubmitMessageForWire<TMsg extends UIMessage | undefined>(mes
|
||||
parts: slimParts,
|
||||
} as unknown as TMsg;
|
||||
}
|
||||
|
||||
/** Run tags accept at most 10 entries of at most 128 characters each. */
|
||||
const MAX_RUN_TAGS = 10;
|
||||
const MAX_RUN_TAG_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* Build the run tags for a chat session: the automatic `chat:{chatId}` tag
|
||||
* followed by the caller's tags, capped at the run tag limit. The chat tag
|
||||
* is skipped when the chat ID is too long to fit within a single tag, so a
|
||||
* long ID degrades to "not filterable by chat" rather than failing to start.
|
||||
*/
|
||||
export function chatRunTags(chatId: string, userTags: string[] = []): string[] {
|
||||
const chatTag = `chat:${chatId}`;
|
||||
const tags = chatTag.length <= MAX_RUN_TAG_LENGTH ? [chatTag, ...userTags] : [...userTags];
|
||||
return tags.slice(0, MAX_RUN_TAGS);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ import {
|
||||
type InferChatUIMessageFromTools,
|
||||
PENDING_MESSAGE_INJECTED_TYPE,
|
||||
upsertIncomingMessage,
|
||||
chatRunTags,
|
||||
} from "./ai-shared.js";
|
||||
import { auth } from "./auth.js";
|
||||
import { locals } from "./locals.js";
|
||||
@@ -11590,9 +11591,11 @@ function createChatStartSessionAction<TChat extends AnyTask = AnyTask>(
|
||||
// Auto-tag every chat.agent run with `chat:{chatId}` so the dashboard /
|
||||
// run-list filter by chat works without the customer having to wire it
|
||||
// up. Mirrors the browser-mediated `TriggerChatTransport.doStart` path.
|
||||
const userTags = params.triggerConfig?.tags ?? options?.triggerConfig?.tags ?? [];
|
||||
// SessionTriggerConfig.tags allows at most 5; the auto chat tag takes one slot.
|
||||
const tags = [`chat:${params.chatId}`, ...userTags].slice(0, 5);
|
||||
// IDs too long to fit within the tag length limit get no automatic tag.
|
||||
const tags = chatRunTags(
|
||||
params.chatId,
|
||||
params.triggerConfig?.tags ?? options?.triggerConfig?.tags
|
||||
);
|
||||
|
||||
const clientDataMetadata =
|
||||
params.clientData !== undefined ? { metadata: params.clientData } : {};
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
TRIGGER_CONTROL_SUBTYPE,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import type { ChatInputChunk, ChatTaskWirePayload } from "./ai-shared.js";
|
||||
import { slimSubmitMessageForWire } from "./ai-shared.js";
|
||||
import { chatRunTags, slimSubmitMessageForWire } from "./ai-shared.js";
|
||||
import { sessions } from "./sessions.js";
|
||||
|
||||
// ─── Type inference ────────────────────────────────────────────────
|
||||
@@ -671,7 +671,7 @@ export class AgentChat<TAgent = unknown> {
|
||||
},
|
||||
...(this.triggerConfigDefault?.machine ? { machine: this.triggerConfigDefault.machine } : {}),
|
||||
...(this.triggerConfigDefault?.queue ? { queue: this.triggerConfigDefault.queue } : {}),
|
||||
...(this.triggerConfigDefault?.tags ? { tags: this.triggerConfigDefault.tags } : {}),
|
||||
tags: chatRunTags(this.chatId, this.triggerConfigDefault?.tags),
|
||||
...(this.triggerConfigDefault?.maxAttempts !== undefined
|
||||
? { maxAttempts: this.triggerConfigDefault.maxAttempts }
|
||||
: {}),
|
||||
|
||||
@@ -71,6 +71,7 @@ import {
|
||||
} from "../imports/ai-runtime.js";
|
||||
import type { FinishReason, ModelMessage, Tool, UIMessage, UIMessageChunk } from "ai";
|
||||
import type { ChatInputChunk, ChatTaskWirePayload } from "./ai-shared.js";
|
||||
import { chatRunTags } from "./ai-shared.js";
|
||||
|
||||
// `StreamTextResult` is defined locally rather than imported from `ai`: its
|
||||
// generic arity diverged (v6 `StreamTextResult<TOOLS, OUTPUT>`, v7
|
||||
@@ -198,7 +199,8 @@ export type HeadStartHandlerOptions<TTools extends Record<string, Tool>> = {
|
||||
/**
|
||||
* Run options for the auto-triggered `handover-prepare` session run —
|
||||
* tags, queue, machine, etc. Mirrors `chat.createStartSessionAction`.
|
||||
* The `chat:{chatId}` tag is prepended automatically.
|
||||
* The `chat:{chatId}` tag is prepended automatically when it fits within
|
||||
* the tag length limit (see `chatRunTags`).
|
||||
*/
|
||||
triggerConfig?: Partial<SessionTriggerConfig>;
|
||||
/**
|
||||
@@ -528,9 +530,8 @@ async function openHandoverSession(opts: {
|
||||
|
||||
// Merge the customer's trigger options. `handover-prepare` and `chatId` in
|
||||
// `basePayload` are ours and can't be overridden; the `chat:{chatId}` tag is
|
||||
// prepended (SessionTriggerConfig.tags caps at 5).
|
||||
const userTags = opts.triggerConfig?.tags ?? [];
|
||||
const tags = [`chat:${chatId}`, ...userTags].slice(0, 5);
|
||||
// prepended when it fits within the tag length limit (see `chatRunTags`).
|
||||
const tags = chatRunTags(chatId, opts.triggerConfig?.tags);
|
||||
|
||||
const triggerConfig: SessionTriggerConfig = {
|
||||
basePayload: {
|
||||
|
||||
@@ -96,12 +96,12 @@ describe("chat.createStartSessionAction — runtime", () => {
|
||||
expect(lastStartBody?.triggerConfig.basePayload).not.toHaveProperty("metadata");
|
||||
});
|
||||
|
||||
it("prepends chat:{chatId} to triggerConfig.tags and caps at 5", async () => {
|
||||
it("prepends chat:{chatId} to triggerConfig.tags and caps at 10", async () => {
|
||||
installStartFixture();
|
||||
|
||||
const start = chat.createStartSessionAction("fake-chat", {
|
||||
triggerConfig: {
|
||||
tags: ["org:acme", "a", "b", "c", "d", "e"],
|
||||
tags: ["org:acme", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
|
||||
},
|
||||
});
|
||||
await start({ chatId: "chat-tags" });
|
||||
@@ -112,9 +112,27 @@ describe("chat.createStartSessionAction — runtime", () => {
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
]);
|
||||
});
|
||||
|
||||
it("omits the chat tag when the chat ID would exceed the tag length limit", async () => {
|
||||
installStartFixture();
|
||||
|
||||
const start = chat.createStartSessionAction("fake-chat", {
|
||||
triggerConfig: { tags: ["org:acme"] },
|
||||
});
|
||||
const longChatId = "c".repeat(200);
|
||||
await start({ chatId: longChatId });
|
||||
|
||||
expect(lastStartBody?.triggerConfig.tags).toEqual(["org:acme"]);
|
||||
expect(lastStartBody?.externalId).toBe(longChatId);
|
||||
});
|
||||
|
||||
it("forwards maxDuration, region, and lockToVersion from triggerConfig", async () => {
|
||||
installStartFixture();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user