From 4f7ef9525e2bfa6bd1f2f293cd59069afd96d74b Mon Sep 17 00:00:00 2001 From: dal Date: Thu, 21 Aug 2025 13:27:31 -0600 Subject: [PATCH] Add basic task examples and update SDK version in Trigger.dev documentation --- apps/trigger/.cursor/rules/trigger.basic.mdc | 190 +++++++++++++++++ apps/trigger/AGENTS.md | 188 +++++++++++++++++ apps/trigger/CLAUDE.md | 191 +++++++++++++++++- apps/trigger/package.json | 6 +- .../modify-reports-delta.ts | 12 +- 5 files changed, 578 insertions(+), 9 deletions(-) create mode 100644 apps/trigger/.cursor/rules/trigger.basic.mdc create mode 100644 apps/trigger/AGENTS.md diff --git a/apps/trigger/.cursor/rules/trigger.basic.mdc b/apps/trigger/.cursor/rules/trigger.basic.mdc new file mode 100644 index 000000000..3d96e6657 --- /dev/null +++ b/apps/trigger/.cursor/rules/trigger.basic.mdc @@ -0,0 +1,190 @@ +--- +description: Only the most important rules for writing basic Trigger.dev tasks +globs: **/trigger/**/*.ts +alwaysApply: false +--- +# Trigger.dev Basic Tasks (v4) + +**MUST use `@trigger.dev/sdk` (v4), NEVER `client.defineJob`** + +## Basic Task + +```ts +import { task } from "@trigger.dev/sdk"; + +export const processData = task({ + id: "process-data", + retry: { + maxAttempts: 10, + factor: 1.8, + minTimeoutInMs: 500, + maxTimeoutInMs: 30_000, + randomize: false, + }, + run: async (payload: { userId: string; data: any[] }) => { + // Task logic - runs for long time, no timeouts + console.log(`Processing ${payload.data.length} items for user ${payload.userId}`); + return { processed: payload.data.length }; + }, +}); +``` + +## Schema Task (with validation) + +```ts +import { schemaTask } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const validatedTask = schemaTask({ + id: "validated-task", + schema: z.object({ + name: z.string(), + age: z.number(), + email: z.string().email(), + }), + run: async (payload) => { + // Payload is automatically validated and typed + return { message: `Hello ${payload.name}, age ${payload.age}` }; + }, +}); +``` + +## Scheduled Task + +```ts +import { schedules } from "@trigger.dev/sdk"; + +const dailyReport = schedules.task({ + id: "daily-report", + cron: "0 9 * * *", // Daily at 9:00 AM UTC + // or with timezone: cron: { pattern: "0 9 * * *", timezone: "America/New_York" }, + run: async (payload) => { + console.log("Scheduled run at:", payload.timestamp); + console.log("Last run was:", payload.lastTimestamp); + console.log("Next 5 runs:", payload.upcoming); + + // Generate daily report logic + return { reportGenerated: true, date: payload.timestamp }; + }, +}); +``` + +## Triggering Tasks + +### From Backend Code + +```ts +import { tasks } from "@trigger.dev/sdk"; +import type { processData } from "./trigger/tasks"; + +// Single trigger +const handle = await tasks.trigger("process-data", { + userId: "123", + data: [{ id: 1 }, { id: 2 }], +}); + +// Batch trigger +const batchHandle = await tasks.batchTrigger("process-data", [ + { payload: { userId: "123", data: [{ id: 1 }] } }, + { payload: { userId: "456", data: [{ id: 2 }] } }, +]); +``` + +### From Inside Tasks (with Result handling) + +```ts +export const parentTask = task({ + id: "parent-task", + run: async (payload) => { + // Trigger and continue + const handle = await childTask.trigger({ data: "value" }); + + // Trigger and wait - returns Result object, NOT task output + const result = await childTask.triggerAndWait({ data: "value" }); + if (result.ok) { + console.log("Task output:", result.output); // Actual task return value + } else { + console.error("Task failed:", result.error); + } + + // Quick unwrap (throws on error) + const output = await childTask.triggerAndWait({ data: "value" }).unwrap(); + + // Batch trigger and wait + const results = await childTask.batchTriggerAndWait([ + { payload: { data: "item1" } }, + { payload: { data: "item2" } }, + ]); + + for (const run of results) { + if (run.ok) { + console.log("Success:", run.output); + } else { + console.log("Failed:", run.error); + } + } + }, +}); + +export const childTask = task({ + id: "child-task", + run: async (payload: { data: string }) => { + return { processed: payload.data }; + }, +}); +``` + +> Never wrap triggerAndWait or batchTriggerAndWait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks. + +## Waits + +```ts +import { task, wait } from "@trigger.dev/sdk"; + +export const taskWithWaits = task({ + id: "task-with-waits", + run: async (payload) => { + console.log("Starting task"); + + // Wait for specific duration + await wait.for({ seconds: 30 }); + await wait.for({ minutes: 5 }); + await wait.for({ hours: 1 }); + await wait.for({ days: 1 }); + + // Wait until specific date + await wait.until({ date: new Date("2024-12-25") }); + + // Wait for token (from external system) + await wait.forToken({ + token: "user-approval-token", + timeoutInSeconds: 3600, // 1 hour timeout + }); + + console.log("All waits completed"); + return { status: "completed" }; + }, +}); +``` + +> Never wrap wait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks. + +## Key Points + +- **Result vs Output**: `triggerAndWait()` returns a `Result` object with `ok`, `output`, `error` properties - NOT the direct task output +- **Type safety**: Use `import type` for task references when triggering from backend +- **Waits > 5 seconds**: Automatically checkpointed, don't count toward compute usage + +## NEVER Use (v2 deprecated) + +```ts +// BREAKS APPLICATION +client.defineJob({ + id: "job-id", + run: async (payload, io) => { + /* ... */ + }, +}); +``` + +Use v4 SDK (`@trigger.dev/sdk`), check `result.ok` before accessing `result.output` diff --git a/apps/trigger/AGENTS.md b/apps/trigger/AGENTS.md new file mode 100644 index 000000000..23f856e16 --- /dev/null +++ b/apps/trigger/AGENTS.md @@ -0,0 +1,188 @@ + +# Trigger.dev Basic Tasks (v4) + +**MUST use `@trigger.dev/sdk` (v4), NEVER `client.defineJob`** + +## Basic Task + +```ts +import { task } from "@trigger.dev/sdk"; + +export const processData = task({ + id: "process-data", + retry: { + maxAttempts: 10, + factor: 1.8, + minTimeoutInMs: 500, + maxTimeoutInMs: 30_000, + randomize: false, + }, + run: async (payload: { userId: string; data: any[] }) => { + // Task logic - runs for long time, no timeouts + console.log(`Processing ${payload.data.length} items for user ${payload.userId}`); + return { processed: payload.data.length }; + }, +}); +``` + +## Schema Task (with validation) + +```ts +import { schemaTask } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const validatedTask = schemaTask({ + id: "validated-task", + schema: z.object({ + name: z.string(), + age: z.number(), + email: z.string().email(), + }), + run: async (payload) => { + // Payload is automatically validated and typed + return { message: `Hello ${payload.name}, age ${payload.age}` }; + }, +}); +``` + +## Scheduled Task + +```ts +import { schedules } from "@trigger.dev/sdk"; + +const dailyReport = schedules.task({ + id: "daily-report", + cron: "0 9 * * *", // Daily at 9:00 AM UTC + // or with timezone: cron: { pattern: "0 9 * * *", timezone: "America/New_York" }, + run: async (payload) => { + console.log("Scheduled run at:", payload.timestamp); + console.log("Last run was:", payload.lastTimestamp); + console.log("Next 5 runs:", payload.upcoming); + + // Generate daily report logic + return { reportGenerated: true, date: payload.timestamp }; + }, +}); +``` + +## Triggering Tasks + +### From Backend Code + +```ts +import { tasks } from "@trigger.dev/sdk"; +import type { processData } from "./trigger/tasks"; + +// Single trigger +const handle = await tasks.trigger("process-data", { + userId: "123", + data: [{ id: 1 }, { id: 2 }], +}); + +// Batch trigger +const batchHandle = await tasks.batchTrigger("process-data", [ + { payload: { userId: "123", data: [{ id: 1 }] } }, + { payload: { userId: "456", data: [{ id: 2 }] } }, +]); +``` + +### From Inside Tasks (with Result handling) + +```ts +export const parentTask = task({ + id: "parent-task", + run: async (payload) => { + // Trigger and continue + const handle = await childTask.trigger({ data: "value" }); + + // Trigger and wait - returns Result object, NOT task output + const result = await childTask.triggerAndWait({ data: "value" }); + if (result.ok) { + console.log("Task output:", result.output); // Actual task return value + } else { + console.error("Task failed:", result.error); + } + + // Quick unwrap (throws on error) + const output = await childTask.triggerAndWait({ data: "value" }).unwrap(); + + // Batch trigger and wait + const results = await childTask.batchTriggerAndWait([ + { payload: { data: "item1" } }, + { payload: { data: "item2" } }, + ]); + + for (const run of results) { + if (run.ok) { + console.log("Success:", run.output); + } else { + console.log("Failed:", run.error); + } + } + }, +}); + +export const childTask = task({ + id: "child-task", + run: async (payload: { data: string }) => { + return { processed: payload.data }; + }, +}); +``` + +> Never wrap triggerAndWait or batchTriggerAndWait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks. + +## Waits + +```ts +import { task, wait } from "@trigger.dev/sdk"; + +export const taskWithWaits = task({ + id: "task-with-waits", + run: async (payload) => { + console.log("Starting task"); + + // Wait for specific duration + await wait.for({ seconds: 30 }); + await wait.for({ minutes: 5 }); + await wait.for({ hours: 1 }); + await wait.for({ days: 1 }); + + // Wait until specific date + await wait.until({ date: new Date("2024-12-25") }); + + // Wait for token (from external system) + await wait.forToken({ + token: "user-approval-token", + timeoutInSeconds: 3600, // 1 hour timeout + }); + + console.log("All waits completed"); + return { status: "completed" }; + }, +}); +``` + +> Never wrap wait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks. + +## Key Points + +- **Result vs Output**: `triggerAndWait()` returns a `Result` object with `ok`, `output`, `error` properties - NOT the direct task output +- **Type safety**: Use `import type` for task references when triggering from backend +- **Waits > 5 seconds**: Automatically checkpointed, don't count toward compute usage + +## NEVER Use (v2 deprecated) + +```ts +// BREAKS APPLICATION +client.defineJob({ + id: "job-id", + run: async (payload, io) => { + /* ... */ + }, +}); +``` + +Use v4 SDK (`@trigger.dev/sdk`), check `result.ok` before accessing `result.output` + + \ No newline at end of file diff --git a/apps/trigger/CLAUDE.md b/apps/trigger/CLAUDE.md index 193261aeb..c357d5c2e 100644 --- a/apps/trigger/CLAUDE.md +++ b/apps/trigger/CLAUDE.md @@ -449,4 +449,193 @@ export const idempotentTask = task({ - Tasks run in isolated environments with resource limits - Connection cleanup is critical for database tasks - Retry logic is configured globally but can be overridden per task -- Real-time progress tracking is supported through Trigger.dev dashboard \ No newline at end of file +- Real-time progress tracking is supported through Trigger.dev dashboard + + +# Trigger.dev Basic Tasks (v4) + +**MUST use `@trigger.dev/sdk` (v4), NEVER `client.defineJob`** + +## Basic Task + +```ts +import { task } from "@trigger.dev/sdk"; + +export const processData = task({ + id: "process-data", + retry: { + maxAttempts: 10, + factor: 1.8, + minTimeoutInMs: 500, + maxTimeoutInMs: 30_000, + randomize: false, + }, + run: async (payload: { userId: string; data: any[] }) => { + // Task logic - runs for long time, no timeouts + console.log(`Processing ${payload.data.length} items for user ${payload.userId}`); + return { processed: payload.data.length }; + }, +}); +``` + +## Schema Task (with validation) + +```ts +import { schemaTask } from "@trigger.dev/sdk"; +import { z } from "zod"; + +export const validatedTask = schemaTask({ + id: "validated-task", + schema: z.object({ + name: z.string(), + age: z.number(), + email: z.string().email(), + }), + run: async (payload) => { + // Payload is automatically validated and typed + return { message: `Hello ${payload.name}, age ${payload.age}` }; + }, +}); +``` + +## Scheduled Task + +```ts +import { schedules } from "@trigger.dev/sdk"; + +const dailyReport = schedules.task({ + id: "daily-report", + cron: "0 9 * * *", // Daily at 9:00 AM UTC + // or with timezone: cron: { pattern: "0 9 * * *", timezone: "America/New_York" }, + run: async (payload) => { + console.log("Scheduled run at:", payload.timestamp); + console.log("Last run was:", payload.lastTimestamp); + console.log("Next 5 runs:", payload.upcoming); + + // Generate daily report logic + return { reportGenerated: true, date: payload.timestamp }; + }, +}); +``` + +## Triggering Tasks + +### From Backend Code + +```ts +import { tasks } from "@trigger.dev/sdk"; +import type { processData } from "./trigger/tasks"; + +// Single trigger +const handle = await tasks.trigger("process-data", { + userId: "123", + data: [{ id: 1 }, { id: 2 }], +}); + +// Batch trigger +const batchHandle = await tasks.batchTrigger("process-data", [ + { payload: { userId: "123", data: [{ id: 1 }] } }, + { payload: { userId: "456", data: [{ id: 2 }] } }, +]); +``` + +### From Inside Tasks (with Result handling) + +```ts +export const parentTask = task({ + id: "parent-task", + run: async (payload) => { + // Trigger and continue + const handle = await childTask.trigger({ data: "value" }); + + // Trigger and wait - returns Result object, NOT task output + const result = await childTask.triggerAndWait({ data: "value" }); + if (result.ok) { + console.log("Task output:", result.output); // Actual task return value + } else { + console.error("Task failed:", result.error); + } + + // Quick unwrap (throws on error) + const output = await childTask.triggerAndWait({ data: "value" }).unwrap(); + + // Batch trigger and wait + const results = await childTask.batchTriggerAndWait([ + { payload: { data: "item1" } }, + { payload: { data: "item2" } }, + ]); + + for (const run of results) { + if (run.ok) { + console.log("Success:", run.output); + } else { + console.log("Failed:", run.error); + } + } + }, +}); + +export const childTask = task({ + id: "child-task", + run: async (payload: { data: string }) => { + return { processed: payload.data }; + }, +}); +``` + +> Never wrap triggerAndWait or batchTriggerAndWait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks. + +## Waits + +```ts +import { task, wait } from "@trigger.dev/sdk"; + +export const taskWithWaits = task({ + id: "task-with-waits", + run: async (payload) => { + console.log("Starting task"); + + // Wait for specific duration + await wait.for({ seconds: 30 }); + await wait.for({ minutes: 5 }); + await wait.for({ hours: 1 }); + await wait.for({ days: 1 }); + + // Wait until specific date + await wait.until({ date: new Date("2024-12-25") }); + + // Wait for token (from external system) + await wait.forToken({ + token: "user-approval-token", + timeoutInSeconds: 3600, // 1 hour timeout + }); + + console.log("All waits completed"); + return { status: "completed" }; + }, +}); +``` + +> Never wrap wait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks. + +## Key Points + +- **Result vs Output**: `triggerAndWait()` returns a `Result` object with `ok`, `output`, `error` properties - NOT the direct task output +- **Type safety**: Use `import type` for task references when triggering from backend +- **Waits > 5 seconds**: Automatically checkpointed, don't count toward compute usage + +## NEVER Use (v2 deprecated) + +```ts +// BREAKS APPLICATION +client.defineJob({ + id: "job-id", + run: async (payload, io) => { + /* ... */ + }, +}); +``` + +Use v4 SDK (`@trigger.dev/sdk`), check `result.ok` before accessing `result.output` + + \ No newline at end of file diff --git a/apps/trigger/package.json b/apps/trigger/package.json index 93edd95a4..b8db3449e 100644 --- a/apps/trigger/package.json +++ b/apps/trigger/package.json @@ -33,7 +33,7 @@ "@buster/typescript-config": "workspace:*", "@buster/vitest-config": "workspace:*", "@buster/web-tools": "workspace:*", - "@trigger.dev/sdk": "catalog:", + "@trigger.dev/sdk": "4.0.1", "ai": "catalog:", "braintrust": "catalog:", "drizzle-orm": "catalog:", @@ -41,6 +41,6 @@ "zod": "catalog:" }, "devDependencies": { - "@trigger.dev/build": "catalog:" + "@trigger.dev/build": "4.0.1" } -} +} \ No newline at end of file diff --git a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts index 45a46443e..579b4d340 100644 --- a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts +++ b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts @@ -10,6 +10,7 @@ import { OptimisticJsonParser, getOptimisticValue, } from '../../../../utils/streaming/optimistic-json-parser'; +import { shouldIncrementVersion } from '../helpers/report-version-helper'; import { getCachedSnapshot, updateCachedSnapshot } from '../report-snapshot-cache'; import { createModifyReportsRawLlmMessageEntry, @@ -270,17 +271,18 @@ export function createModifyReportsDelta(context: ModifyReportsContext, state: M newContent = state.snapshotContent.replace(codeToReplace, code); } - // Check if we should increment version (not if already modified in this message) - const alreadyModifiedInMessage = - state.reportsModifiedInMessage?.has(state.reportId) ?? false; - const incrementVersion = !alreadyModifiedInMessage; + // Check if we should increment version (not if report was created in current turn) + const incrementVersion = await shouldIncrementVersion( + state.reportId, + context.messageId + ); // Calculate new version const currentVersion = state.snapshotVersion || 1; const newVersion = incrementVersion ? currentVersion + 1 : currentVersion; state.version_number = newVersion; - // Track this modification + // Track this modification for this tool invocation if (!state.reportsModifiedInMessage) { state.reportsModifiedInMessage = new Set(); }