Skip to content

Commit 9885cef

Browse files
committed
Add the chat client and strip agent crumbs
1 parent 65fe8ac commit 9885cef

File tree

16 files changed

+1082
-101
lines changed

16 files changed

+1082
-101
lines changed

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
@@ -20,6 +20,7 @@ const RequestSchema = z.object({
2020
taskIdentifier: z.string().max(256),
2121
payloadSchema: z.string().max(50_000).optional(),
2222
currentPayload: z.string().max(50_000).optional(),
23+
isAgent: z.enum(["true", "false"]).optional(),
2324
});
2425

2526
export async function action({ request, params }: ActionFunctionArgs) {
@@ -63,16 +64,20 @@ export async function action({ request, params }: ActionFunctionArgs) {
6364
);
6465
}
6566

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

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

75-
const systemPrompt = buildSystemPrompt(taskIdentifier, payloadSchema, currentPayload);
78+
const systemPrompt = agentMode
79+
? buildAgentClientDataPrompt(taskIdentifier, payloadSchema, currentPayload)
80+
: buildSystemPrompt(taskIdentifier, payloadSchema, currentPayload);
7681

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

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

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,19 +270,13 @@ async function createWorkerTask(
270270
);
271271
}
272272

273-
// @crumbs
274-
console.log(`[crumbs:webapp] createWorkerTask task=${task.id} triggerSource=${task.triggerSource} agentConfig=${JSON.stringify(task.agentConfig)} taskKeys=${Object.keys(task).join(",")}`); // @crumbs
275-
276273
const resolvedTriggerSource =
277274
task.triggerSource === "schedule"
278275
? ("SCHEDULED" as const)
279276
: task.triggerSource === "agent"
280277
? ("AGENT" as const)
281278
: ("STANDARD" as const);
282279

283-
// @crumbs
284-
console.log(`[crumbs:webapp] createWorkerTask resolved triggerSource=${resolvedTriggerSource} for task=${task.id}`); // @crumbs
285-
286280
await prisma.backgroundWorkerTask.create({
287281
data: {
288282
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)