Skip to content

Commit 1a97f3a

Browse files
committed
Add the chat client and strip agent crumbs
1 parent ad41d46 commit 1a97f3a

16 files changed

Lines changed: 1082 additions & 101 deletions

File tree

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.playground.$agentParam/route.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,12 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
157157
};
158158

159159
export default function PlaygroundAgentPage() {
160-
const { activeConversation } = useTypedLoaderData<typeof loader>();
161-
// Key on conversation chatId so React remounts all stateful children when
162-
// navigating between conversations (Link changes search params, loader re-runs,
163-
// but without a key change the component instance is reused and useState
164-
// initializers / useRef initializations don't re-run).
165-
const conversationKey = activeConversation?.chatId ?? "new";
160+
const { agent, activeConversation } = useTypedLoaderData<typeof loader>();
161+
// Key on agent slug + conversation chatId so React remounts all stateful
162+
// children when switching agents or navigating between conversations.
163+
// Without the agent slug, switching agents keeps key="new" and React
164+
// reuses the component — useState initializers don't re-run.
165+
const conversationKey = `${agent.slug}:${activeConversation?.chatId ?? "new"}`;
166166
return <PlaygroundChat key={conversationKey} />;
167167
}
168168

@@ -974,6 +974,7 @@ function PlaygroundSidebar({
974974
getCurrentPayload={getCurrentClientData}
975975
generateButtonLabel="Generate client data"
976976
placeholder="e.g. generate client data for a free-tier user"
977+
isAgent={true}
977978
examplePromptsOverride={[
978979
"Generate valid client data",
979980
"Generate client data with all fields",

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.tasks.$taskParam/AIPayloadTabContent.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function AIPayloadTabContent({
2525
generateButtonLabel = "Generate payload",
2626
placeholder,
2727
examplePromptsOverride,
28+
isAgent = false,
2829
}: {
2930
onPayloadGenerated: (payload: string) => void;
3031
payloadSchema?: unknown;
@@ -33,6 +34,7 @@ export function AIPayloadTabContent({
3334
generateButtonLabel?: string;
3435
placeholder?: string;
3536
examplePromptsOverride?: string[];
37+
isAgent?: boolean;
3638
}) {
3739
const [prompt, setPrompt] = useState("");
3840
const [isLoading, setIsLoading] = useState(false);
@@ -70,6 +72,7 @@ export function AIPayloadTabContent({
7072
const formData = new FormData();
7173
formData.append("prompt", queryPrompt);
7274
formData.append("taskIdentifier", taskIdentifier);
75+
formData.append("isAgent", isAgent ? "true" : "false");
7376
if (payloadSchema) {
7477
formData.append("payloadSchema", JSON.stringify(payloadSchema));
7578
}
@@ -141,7 +144,7 @@ export function AIPayloadTabContent({
141144
setIsLoading(false);
142145
}
143146
},
144-
[resourcePath, taskIdentifier, payloadSchema, getCurrentPayload]
147+
[resourcePath, taskIdentifier, payloadSchema, getCurrentPayload, isAgent]
145148
);
146149

147150
const processStreamEvent = useCallback(

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.test.ai-generate-payload.tsx

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const RequestSchema = z.object({
2121
taskIdentifier: z.string().max(256),
2222
payloadSchema: z.string().max(50_000).optional(),
2323
currentPayload: z.string().max(50_000).optional(),
24+
isAgent: z.enum(["true", "false"]).optional(),
2425
});
2526

2627
export async function action({ request, params }: ActionFunctionArgs) {
@@ -64,16 +65,20 @@ export async function action({ request, params }: ActionFunctionArgs) {
6465
);
6566
}
6667

67-
const { prompt, taskIdentifier, payloadSchema, currentPayload } = submission.data;
68+
const { prompt, taskIdentifier, payloadSchema, currentPayload, isAgent } = submission.data;
69+
const agentMode = isAgent === "true";
6870

6971
logger.info("[AI payload] Generating payload", {
7072
taskIdentifier,
7173
hasPayloadSchema: !!payloadSchema,
7274
hasCurrentPayload: !!currentPayload,
7375
promptLength: prompt.length,
76+
agentMode,
7477
});
7578

76-
const systemPrompt = buildSystemPrompt(taskIdentifier, payloadSchema, currentPayload);
79+
const systemPrompt = agentMode
80+
? buildAgentClientDataPrompt(taskIdentifier, payloadSchema, currentPayload)
81+
: buildSystemPrompt(taskIdentifier, payloadSchema, currentPayload);
7782

7883
const stream = new ReadableStream({
7984
async start(controller) {
@@ -234,6 +239,60 @@ async function getTaskFromDeployment(environmentId: string, taskIdentifier: stri
234239
return { fileId: task.fileId };
235240
}
236241

242+
function buildAgentClientDataPrompt(
243+
taskIdentifier: string,
244+
payloadSchema?: string,
245+
currentPayload?: string
246+
): string {
247+
let prompt = `You are a JSON generator for client data (metadata) of a Trigger.dev chat agent with id "${taskIdentifier}".
248+
249+
IMPORTANT: You are generating ONLY the client data object — this is the metadata sent alongside each chat message. It is NOT the full task payload. Do NOT generate fields like "chatId", "messages", "trigger", or "idleTimeoutInSeconds" — those are internal transport fields managed by the framework.
250+
251+
The client data typically contains user context like user IDs, preferences, configuration, or session info. Return ONLY valid JSON wrapped in a \`\`\`json code block.
252+
253+
Requirements:
254+
- Generate realistic, meaningful example data
255+
- All string values should be plausible (real-looking IDs, names, etc.)
256+
- The JSON must be valid and parseable
257+
- Keep it simple — client data is usually a flat or shallow object`;
258+
259+
if (payloadSchema) {
260+
prompt += `
261+
262+
The agent has the following JSON Schema for its client data:
263+
\`\`\`json
264+
${payloadSchema}
265+
\`\`\`
266+
267+
Generate client data that strictly conforms to this schema.`;
268+
} else {
269+
prompt += `
270+
271+
No JSON Schema is available for this agent's client data. Use the getTaskSourceCode tool to look up the agent's source code file.
272+
273+
IMPORTANT instructions for reading the source code:
274+
- The file may contain multiple task/agent definitions. Find the one with id "${taskIdentifier}".
275+
- Look for \`withClientData({ schema: ... })\` or \`clientDataSchema\` to find the expected client data shape.
276+
- If using \`chat.agent()\` or \`chat.customAgent()\`, the client data is accessed via \`clientData\` in hooks and \`payload.metadata\` in raw tasks.
277+
- Look for how \`clientData\` or \`payload.metadata\` is accessed/destructured to infer the shape.
278+
- Do NOT generate the full ChatTaskWirePayload (messages, chatId, trigger, etc.) — ONLY the metadata/clientData portion.
279+
- If no client data schema or usage is found, generate a simple \`{ "userId": "user_..." }\` object.`;
280+
}
281+
282+
if (currentPayload) {
283+
prompt += `
284+
285+
The current client data in the editor is:
286+
\`\`\`json
287+
${currentPayload}
288+
\`\`\`
289+
290+
Use this as context but generate new client data based on the user's prompt.`;
291+
}
292+
293+
return prompt;
294+
}
295+
237296
function buildSystemPrompt(
238297
taskIdentifier: string,
239298
payloadSchema?: string,

apps/webapp/app/v3/services/createBackgroundWorker.server.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,19 +288,13 @@ async function createWorkerTask(
288288
);
289289
}
290290

291-
// @crumbs
292-
console.log(`[crumbs:webapp] createWorkerTask task=${task.id} triggerSource=${task.triggerSource} agentConfig=${JSON.stringify(task.agentConfig)} taskKeys=${Object.keys(task).join(",")}`); // @crumbs
293-
294291
const resolvedTriggerSource =
295292
task.triggerSource === "schedule"
296293
? ("SCHEDULED" as const)
297294
: task.triggerSource === "agent"
298295
? ("AGENT" as const)
299296
: ("STANDARD" as const);
300297

301-
// @crumbs
302-
console.log(`[crumbs:webapp] createWorkerTask resolved triggerSource=${resolvedTriggerSource} for task=${task.id}`); // @crumbs
303-
304298
await prisma.backgroundWorkerTask.create({
305299
data: {
306300
friendlyId: generateFriendlyId("task"),

packages/cli-v3/src/dev/devSupervisor.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { trail } from "agentcrumbs"; // @crumbs
2-
const _cliCrumb = trail("cli"); // @crumbs
31
import { spawn, type ChildProcess } from "node:child_process";
42
import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync } from "node:fs";
53
import { join } from "node:path";
@@ -347,23 +345,6 @@ class DevSupervisor implements WorkerRuntime {
347345

348346
const sourceFiles = resolveSourceFiles(manifest.sources, backgroundWorker.manifest.tasks);
349347

350-
// #region @crumbs
351-
const _agentTasksSupervisor = (backgroundWorker.manifest.tasks as any[]).filter(
352-
(t: any) => t.triggerSource || t.agentConfig
353-
);
354-
_cliCrumb("devSupervisor sending worker metadata to API", {
355-
totalTasks: backgroundWorker.manifest.tasks.length,
356-
agentTasks: _agentTasksSupervisor.map((t: any) => ({
357-
id: t.id,
358-
triggerSource: t.triggerSource,
359-
agentConfig: t.agentConfig,
360-
})),
361-
manifestTaskKeys: backgroundWorker.manifest.tasks[0]
362-
? Object.keys(backgroundWorker.manifest.tasks[0])
363-
: [],
364-
});
365-
// #endregion @crumbs
366-
367348
const backgroundWorkerBody: CreateBackgroundWorkerRequestBody = {
368349
localOnly: true,
369350
metadata: {

packages/cli-v3/src/entryPoints/dev-index-worker.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { trail } from "agentcrumbs"; // @crumbs
2-
const _cliCrumb = trail("cli"); // @crumbs
31
import {
42
BuildManifest,
53
type HandleErrorFunction,
@@ -106,18 +104,6 @@ const { buildManifest, importErrors, config, timings } = await bootstrap();
106104

107105
let tasks = await convertSchemasToJsonSchemas(resourceCatalog.listTaskManifests());
108106

109-
// #region @crumbs
110-
const _agentTasks = tasks.filter((t: any) => t.triggerSource || t.agentConfig);
111-
_cliCrumb("dev-index-worker tasks after listTaskManifests", {
112-
totalTasks: tasks.length,
113-
agentTasks: _agentTasks.map((t: any) => ({
114-
id: t.id,
115-
triggerSource: t.triggerSource,
116-
agentConfig: t.agentConfig,
117-
})),
118-
});
119-
// #endregion @crumbs
120-
121107
// If the config has retry defaults, we need to apply them to all tasks that don't have any retry settings
122108
if (config.retries?.default) {
123109
tasks = tasks.map((task) => {

packages/core/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"./v3/utils/omit": "./src/v3/utils/omit.ts",
4242
"./v3/utils/retries": "./src/v3/utils/retries.ts",
4343
"./v3/utils/structuredLogger": "./src/v3/utils/structuredLogger.ts",
44+
"./v3/chat-client": "./src/v3/chat-client.ts",
4445
"./v3/zodfetch": "./src/v3/zodfetch.ts",
4546
"./v3/zodMessageHandler": "./src/v3/zodMessageHandler.ts",
4647
"./v3/zodNamespace": "./src/v3/zodNamespace.ts",
@@ -446,6 +447,17 @@
446447
"default": "./dist/commonjs/v3/utils/structuredLogger.js"
447448
}
448449
},
450+
"./v3/chat-client": {
451+
"import": {
452+
"@triggerdotdev/source": "./src/v3/chat-client.ts",
453+
"types": "./dist/esm/v3/chat-client.d.ts",
454+
"default": "./dist/esm/v3/chat-client.js"
455+
},
456+
"require": {
457+
"types": "./dist/commonjs/v3/chat-client.d.ts",
458+
"default": "./dist/commonjs/v3/chat-client.js"
459+
}
460+
},
449461
"./v3/zodfetch": {
450462
"import": {
451463
"@triggerdotdev/source": "./src/v3/zodfetch.ts",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Chat constants shared between backend (ai.ts) and frontend (chat.ts).
3+
* The ChatClient class lives in @trigger.dev/sdk/chat.
4+
*/
5+
6+
/** The output stream key where UIMessageChunks are written. */
7+
export const CHAT_STREAM_KEY = "chat";
8+
9+
/** Input stream ID for sending chat messages to the running task. */
10+
export const CHAT_MESSAGES_STREAM_ID = "chat-messages";
11+
12+
/** Input stream ID for sending stop signals to abort the current generation. */
13+
export const CHAT_STOP_STREAM_ID = "chat-stop";

packages/core/src/v3/resource-catalog/standardResourceCatalog.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { trail } from "agentcrumbs"; // @crumbs
2-
const _coreCrumb = trail("core"); // @crumbs
31
import {
42
PromptManifest,
53
PromptMetadata,
@@ -75,15 +73,6 @@ export class StandardResourceCatalog implements ResourceCatalog {
7573
return;
7674
}
7775

78-
// #region @crumbs
79-
_coreCrumb("registerTaskMetadata", {
80-
taskId: task.id,
81-
triggerSource: metadata.triggerSource,
82-
agentConfig: metadata.agentConfig,
83-
metadataKeys: Object.keys(metadata),
84-
});
85-
// #endregion @crumbs
86-
8776
this._taskFileMetadata.set(task.id, {
8877
...this._currentFileContext,
8978
});
@@ -140,18 +129,6 @@ export class StandardResourceCatalog implements ResourceCatalog {
140129
...fileMetadata,
141130
};
142131

143-
// #region @crumbs
144-
if (metadata.triggerSource || metadata.agentConfig) {
145-
_coreCrumb("listTaskManifests building manifest", {
146-
taskId: id,
147-
triggerSource: metadata.triggerSource,
148-
agentConfig: metadata.agentConfig,
149-
manifestTriggerSource: taskManifest.triggerSource,
150-
manifestAgentConfig: (taskManifest as any).agentConfig,
151-
});
152-
}
153-
// #endregion @crumbs
154-
155132
result.push(taskManifest);
156133
}
157134

0 commit comments

Comments
 (0)