remotion-claude-agent-demo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +160 -0
  2. package/apps/web/README.md +36 -0
  3. package/apps/web/env.example +20 -0
  4. package/apps/web/eslint.config.mjs +18 -0
  5. package/apps/web/next.config.ts +7 -0
  6. package/apps/web/package-lock.json +10348 -0
  7. package/apps/web/package.json +35 -0
  8. package/apps/web/postcss.config.mjs +7 -0
  9. package/apps/web/public/file.svg +1 -0
  10. package/apps/web/public/globe.svg +1 -0
  11. package/apps/web/public/next.svg +1 -0
  12. package/apps/web/public/vercel.svg +1 -0
  13. package/apps/web/public/window.svg +1 -0
  14. package/apps/web/src/app/.well-known/agent-card.json/route.ts +50 -0
  15. package/apps/web/src/app/background-tasks/[jobId]/cancel/route.ts +29 -0
  16. package/apps/web/src/app/events/stream/route.ts +58 -0
  17. package/apps/web/src/app/favicon.ico +0 -0
  18. package/apps/web/src/app/globals.css +174 -0
  19. package/apps/web/src/app/layout.tsx +34 -0
  20. package/apps/web/src/app/messages/answer/route.ts +57 -0
  21. package/apps/web/src/app/messages/stream/route.ts +381 -0
  22. package/apps/web/src/app/page.tsx +358 -0
  23. package/apps/web/src/app/tasks/[taskId]/cancel/route.ts +24 -0
  24. package/apps/web/src/app/tasks/[taskId]/route.ts +24 -0
  25. package/apps/web/src/app/tasks/route.ts +13 -0
  26. package/apps/web/src/components/chat/agent-blocks.tsx +111 -0
  27. package/apps/web/src/components/chat/ask-user-question-panel.tsx +172 -0
  28. package/apps/web/src/components/chat/session-sidebar.tsx +222 -0
  29. package/apps/web/src/components/chat/subagent-activity-sidebar.tsx +248 -0
  30. package/apps/web/src/components/chat/tool-blocks.tsx +550 -0
  31. package/apps/web/src/lib/a2a/activity-store.ts +150 -0
  32. package/apps/web/src/lib/a2a/client.ts +357 -0
  33. package/apps/web/src/lib/a2a/sse.ts +19 -0
  34. package/apps/web/src/lib/a2a/task-store.ts +111 -0
  35. package/apps/web/src/lib/a2a/types.ts +216 -0
  36. package/apps/web/src/lib/agent/answer-store.ts +109 -0
  37. package/apps/web/src/lib/agent/background-delivery.ts +343 -0
  38. package/apps/web/src/lib/agent/background-tool.ts +78 -0
  39. package/apps/web/src/lib/agent/background.ts +452 -0
  40. package/apps/web/src/lib/agent/chat.ts +543 -0
  41. package/apps/web/src/lib/agent/session-store.ts +26 -0
  42. package/apps/web/src/lib/chat/types.ts +44 -0
  43. package/apps/web/src/lib/env.ts +31 -0
  44. package/apps/web/src/lib/hooks/useA2AChat.ts +863 -0
  45. package/apps/web/src/lib/state/chat-atoms.ts +52 -0
  46. package/apps/web/src/lib/workspace.ts +9 -0
  47. package/apps/web/tsconfig.json +35 -0
  48. package/bin/remotion-agent.js +451 -0
  49. package/package.json +34 -0
  50. package/templates/.claude/CLAUDE.md +95 -0
  51. package/templates/.claude/README.md +129 -0
  52. package/templates/.claude/agents/composer-agent.md +188 -0
  53. package/templates/.claude/agents/crafter.md +181 -0
  54. package/templates/.claude/agents/creator.md +134 -0
  55. package/templates/.claude/agents/perceiver.md +92 -0
  56. package/templates/.claude/settings.json +36 -0
  57. package/templates/.claude/settings.local.json +39 -0
  58. package/templates/.claude/skills/agent-browser/SKILL.md +349 -0
  59. package/templates/.claude/skills/agent-browser/references/authentication.md +188 -0
  60. package/templates/.claude/skills/agent-browser/references/proxy-support.md +175 -0
  61. package/templates/.claude/skills/agent-browser/references/session-management.md +181 -0
  62. package/templates/.claude/skills/agent-browser/references/snapshot-refs.md +186 -0
  63. package/templates/.claude/skills/agent-browser/references/video-recording.md +162 -0
  64. package/templates/.claude/skills/agent-browser/templates/authenticated-session.sh +91 -0
  65. package/templates/.claude/skills/agent-browser/templates/capture-workflow.sh +68 -0
  66. package/templates/.claude/skills/agent-browser/templates/form-automation.sh +64 -0
  67. package/templates/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
  68. package/templates/.claude/skills/algorithmic-art/SKILL.md +405 -0
  69. package/templates/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
  70. package/templates/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
  71. package/templates/.claude/skills/asset-validator/SKILL.md +376 -0
  72. package/templates/.claude/skills/audio-video-sync/SKILL.md +219 -0
  73. package/templates/.claude/skills/bgm-manager/SKILL.md +334 -0
  74. package/templates/.claude/skills/remotion-best-practices/SKILL.md +45 -0
  75. package/templates/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
  76. package/templates/.claude/skills/remotion-best-practices/rules/animations.md +29 -0
  77. package/templates/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  78. package/templates/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  79. package/templates/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  80. package/templates/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
  81. package/templates/.claude/skills/remotion-best-practices/rules/audio.md +172 -0
  82. package/templates/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  83. package/templates/.claude/skills/remotion-best-practices/rules/can-decode.md +75 -0
  84. package/templates/.claude/skills/remotion-best-practices/rules/charts.md +58 -0
  85. package/templates/.claude/skills/remotion-best-practices/rules/compositions.md +141 -0
  86. package/templates/.claude/skills/remotion-best-practices/rules/display-captions.md +126 -0
  87. package/templates/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  88. package/templates/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
  89. package/templates/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  90. package/templates/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  91. package/templates/.claude/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  92. package/templates/.claude/skills/remotion-best-practices/rules/gifs.md +138 -0
  93. package/templates/.claude/skills/remotion-best-practices/rules/images.md +130 -0
  94. package/templates/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
  95. package/templates/.claude/skills/remotion-best-practices/rules/lottie.md +68 -0
  96. package/templates/.claude/skills/remotion-best-practices/rules/maps.md +403 -0
  97. package/templates/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
  98. package/templates/.claude/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  99. package/templates/.claude/skills/remotion-best-practices/rules/parameters.md +98 -0
  100. package/templates/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
  101. package/templates/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
  102. package/templates/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
  103. package/templates/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
  104. package/templates/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
  105. package/templates/.claude/skills/remotion-best-practices/rules/transitions.md +122 -0
  106. package/templates/.claude/skills/remotion-best-practices/rules/trimming.md +53 -0
  107. package/templates/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
  108. package/templates/.claude/skills/remotion-components/SKILL.md +453 -0
  109. package/templates/.claude/skills/render-config/SKILL.md +290 -0
  110. package/templates/.claude/skills/script-writer/SKILL.md +59 -0
  111. package/templates/.claude/skills/style-director/script-writer/SKILL.md +82 -0
  112. package/templates/.claude/skills/style-director/style-director/SKILL.md +287 -0
  113. package/templates/.claude/skills/style-director/style-director/references/audience-and-scenarios.md +43 -0
  114. package/templates/.claude/skills/style-director/style-director/references/interaction-innovation.md +26 -0
  115. package/templates/.claude/skills/style-director/style-director/references/motion-grammar.md +66 -0
  116. package/templates/.claude/skills/style-director/style-director/references/quality-checklist.md +29 -0
  117. package/templates/.claude/skills/style-director/style-director/references/scene-recipes.md +38 -0
  118. package/templates/.claude/skills/style-director/style-director/references/visual-style-system.md +148 -0
  119. package/templates/.claude/skills/subtitle-composer/SKILL.md +304 -0
  120. package/templates/.claude/skills/subtitle-processor/SKILL.md +308 -0
  121. package/templates/.claude/skills/timeline-generator/SKILL.md +253 -0
  122. package/templates/.claude/skills/video-preflight-check/SKILL.md +353 -0
  123. package/templates/.claude/skills/voice-synthesizer/SKILL.md +296 -0
  124. package/templates/.claude/skills/voice-synthesizer/scripts/synthesize_voice.py +315 -0
  125. package/templates/.claude/skills/voice-synthesizer/scripts/tts_cli.py +142 -0
  126. package/templates/.claude/skills/web-design-guidelines/SKILL.md +36 -0
  127. package/templates/.claude/skills/youtube-downloader/SKILL.md +99 -0
  128. package/templates/.claude/skills/youtube-downloader/scripts/download_video.py +145 -0
@@ -0,0 +1,381 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import type { A2aError, A2aMessage } from "@/lib/a2a/types";
4
+ import { sseData, sseHeaders } from "@/lib/a2a/sse";
5
+ import {
6
+ attachAbortController,
7
+ createTask,
8
+ isCancelRequested,
9
+ updateStatus,
10
+ appendHistory,
11
+ } from "@/lib/a2a/task-store";
12
+ import { streamChatReply } from "@/lib/agent/chat";
13
+ import {
14
+ getSessionIdForContext,
15
+ setSessionIdForContext,
16
+ } from "@/lib/agent/session-store";
17
+ import { markContextActive, markContextIdle } from "@/lib/agent/background-delivery";
18
+ import { getEnv } from "@/lib/env";
19
+
20
+ export const runtime = "nodejs";
21
+
22
+ function badRequest(message: string): NextResponse<A2aError> {
23
+ return NextResponse.json(
24
+ { error: { code: "ValidationError", message } },
25
+ { status: 400, headers: { "Content-Type": "application/a2a+json" } },
26
+ );
27
+ }
28
+
29
+ function ensureTextMessage(msg: unknown): msg is A2aMessage {
30
+ if (!msg || typeof msg !== "object") return false;
31
+ const rec = msg as Record<string, unknown>;
32
+ if (rec.kind !== "message") return false;
33
+ if (rec.role !== "user" && rec.role !== "agent") return false;
34
+ if (!Array.isArray(rec.parts)) return false;
35
+ return true;
36
+ }
37
+
38
+ function extractUserText(message: A2aMessage): string {
39
+ const parts = message.parts;
40
+ for (const p of parts) {
41
+ if (p && typeof p === "object") {
42
+ const rec = p as Record<string, unknown>;
43
+ if (rec.kind === "text" && typeof rec.text === "string") return rec.text;
44
+ }
45
+ }
46
+ return "";
47
+ }
48
+
49
+ function extractClientSessionId(body: Record<string, unknown>): string | undefined {
50
+ const config = body.configuration;
51
+ if (!config || typeof config !== "object") return undefined;
52
+ const rec = config as Record<string, unknown>;
53
+ const meta = rec.metadata;
54
+ if (!meta || typeof meta !== "object") return undefined;
55
+ const metaRec = meta as Record<string, unknown>;
56
+ const sessionId = metaRec.sessionId;
57
+ return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : undefined;
58
+ }
59
+
60
+ export async function POST(request: Request) {
61
+ const env = getEnv();
62
+ if (
63
+ (request.headers.get("A2A-Version") ?? env.a2aVersion) !== env.a2aVersion
64
+ ) {
65
+ return NextResponse.json(
66
+ {
67
+ error: {
68
+ code: "VersionNotSupportedError",
69
+ message: "Unsupported A2A-Version",
70
+ },
71
+ },
72
+ { status: 400, headers: { "Content-Type": "application/a2a+json" } },
73
+ );
74
+ }
75
+
76
+ let body: unknown;
77
+ try {
78
+ body = await request.json();
79
+ } catch {
80
+ return badRequest("Invalid JSON body");
81
+ }
82
+
83
+ if (!body || typeof body !== "object")
84
+ return badRequest("Invalid request body");
85
+ const rec = body as Record<string, unknown>;
86
+ const msg = rec.message;
87
+ if (!ensureTextMessage(msg)) return badRequest("Missing or invalid message");
88
+
89
+ const userText = extractUserText(msg);
90
+ if (!userText) return badRequest("Message must include a text part");
91
+
92
+ const clientSessionId = extractClientSessionId(rec);
93
+ const taskId = globalThis.crypto.randomUUID();
94
+ const contextId =
95
+ typeof msg.contextId === "string" && msg.contextId.length > 0
96
+ ? msg.contextId
97
+ : globalThis.crypto.randomUUID();
98
+ const task = createTask({ taskId, contextId, initialMessage: msg });
99
+
100
+ const abortController = new AbortController();
101
+ attachAbortController(taskId, abortController);
102
+
103
+ const resumeSessionId = getSessionIdForContext(contextId) ?? clientSessionId;
104
+ if (resumeSessionId && !getSessionIdForContext(contextId)) {
105
+ setSessionIdForContext({ contextId, sessionId: resumeSessionId });
106
+ }
107
+
108
+ const stream = new ReadableStream<Uint8Array>({
109
+ async start(controller) {
110
+ controller.enqueue(
111
+ sseData({
112
+ kind: "task",
113
+ task,
114
+ }),
115
+ );
116
+
117
+ const assistantMessageId = globalThis.crypto.randomUUID();
118
+ let assistantText = "";
119
+ let statusIndex = 0;
120
+
121
+ markContextActive(contextId);
122
+ statusIndex = updateStatus(taskId, {
123
+ state: "working",
124
+ timestamp: new Date().toISOString(),
125
+ message: "responding",
126
+ });
127
+ controller.enqueue(
128
+ sseData({
129
+ kind: "statusUpdate",
130
+ statusUpdate: {
131
+ kind: "statusUpdate",
132
+ taskId,
133
+ status: {
134
+ state: "working",
135
+ timestamp: new Date().toISOString(),
136
+ message: "responding",
137
+ },
138
+ index: statusIndex,
139
+ },
140
+ }),
141
+ );
142
+
143
+ const enqueueToolCall = (ev: {
144
+ toolName: string;
145
+ toolId: string;
146
+ input: unknown;
147
+ status: "started" | "completed";
148
+ output?: unknown;
149
+ toolSeq?: number;
150
+ }) =>
151
+ controller.enqueue(
152
+ sseData({
153
+ kind: "toolCall",
154
+ toolCall: {
155
+ kind: "toolCall",
156
+ toolName: ev.toolName,
157
+ toolId: ev.toolId,
158
+ input: ev.input,
159
+ status: ev.status,
160
+ output: ev.output,
161
+ toolSeq: ev.toolSeq,
162
+ },
163
+ }),
164
+ );
165
+
166
+ const enqueueAskUserQuestion = (ev: {
167
+ requestId: string;
168
+ taskId: string;
169
+ questions: Array<{
170
+ question: string;
171
+ header: string;
172
+ options: Array<{ label: string; description: string }>;
173
+ multiSelect?: boolean;
174
+ }>;
175
+ }) =>
176
+ controller.enqueue(
177
+ sseData({
178
+ kind: "askUserQuestion",
179
+ askUserQuestion: {
180
+ kind: "askUserQuestion",
181
+ requestId: ev.requestId,
182
+ taskId: ev.taskId,
183
+ questions: ev.questions,
184
+ },
185
+ }),
186
+ );
187
+
188
+ const enqueueQuestionDismissed = (ev: {
189
+ requestId: string;
190
+ taskId: string;
191
+ reason: "timeout" | "cancelled";
192
+ }) =>
193
+ controller.enqueue(
194
+ sseData({
195
+ kind: "questionDismissed",
196
+ questionDismissed: {
197
+ kind: "questionDismissed",
198
+ requestId: ev.requestId,
199
+ taskId: ev.taskId,
200
+ reason: ev.reason,
201
+ },
202
+ }),
203
+ );
204
+
205
+ const enqueueSubagentEvent = (ev: {
206
+ agentId: string;
207
+ agentType: string;
208
+ status: "started" | "stopped";
209
+ }) =>
210
+ controller.enqueue(
211
+ sseData({
212
+ kind: "subagent",
213
+ subagent: {
214
+ kind: "subagent",
215
+ agentId: ev.agentId,
216
+ agentType: ev.agentType,
217
+ status: ev.status,
218
+ },
219
+ }),
220
+ );
221
+
222
+ try {
223
+ for await (const ev of streamChatReply({
224
+ userText,
225
+ taskId,
226
+ contextId,
227
+ resumeSessionId,
228
+ abortController,
229
+ onSessionId: (sid) => {
230
+ setSessionIdForContext({ contextId, sessionId: sid });
231
+ controller.enqueue(
232
+ sseData({
233
+ kind: "session",
234
+ sessionId: sid,
235
+ contextId,
236
+ }),
237
+ );
238
+ },
239
+ onToolEvent: enqueueToolCall,
240
+ onAskUserQuestion: enqueueAskUserQuestion,
241
+ onQuestionDismissed: enqueueQuestionDismissed,
242
+ onSubagentEvent: enqueueSubagentEvent,
243
+ })) {
244
+ if (isCancelRequested(taskId)) {
245
+ controller.enqueue(
246
+ sseData({
247
+ kind: "statusUpdate",
248
+ statusUpdate: {
249
+ kind: "statusUpdate",
250
+ taskId,
251
+ status: {
252
+ state: "cancelled",
253
+ timestamp: new Date().toISOString(),
254
+ message: "cancelled",
255
+ },
256
+ index: updateStatus(taskId, {
257
+ state: "cancelled",
258
+ timestamp: new Date().toISOString(),
259
+ message: "cancelled",
260
+ }),
261
+ final: true,
262
+ },
263
+ }),
264
+ );
265
+ controller.close();
266
+ return;
267
+ }
268
+
269
+ if (ev.type === "token") {
270
+ assistantText += ev.text;
271
+ controller.enqueue(
272
+ sseData({
273
+ kind: "message",
274
+ message: {
275
+ kind: "message",
276
+ messageId: assistantMessageId,
277
+ role: "agent",
278
+ parts: [{ kind: "text", text: ev.text }],
279
+ contextId,
280
+ },
281
+ }),
282
+ );
283
+ }
284
+
285
+ if (ev.type === "toolCall") {
286
+ // 兜底路径:仅当没有 onToolEvent callback 时才会触发
287
+ controller.enqueue(
288
+ sseData({
289
+ kind: "toolCall",
290
+ toolCall: {
291
+ kind: "toolCall",
292
+ toolName: ev.toolName,
293
+ toolId: ev.toolId,
294
+ input: ev.input,
295
+ status: ev.status,
296
+ output: ev.output,
297
+ toolSeq: ev.toolSeq,
298
+ },
299
+ }),
300
+ );
301
+ }
302
+
303
+ if (ev.type === "subagent") {
304
+ controller.enqueue(
305
+ sseData({
306
+ kind: "subagent",
307
+ subagent: {
308
+ kind: "subagent",
309
+ agentId: ev.agentId,
310
+ agentType: ev.agentType,
311
+ status: ev.status,
312
+ },
313
+ }),
314
+ );
315
+ }
316
+ }
317
+
318
+ const finalMsg: A2aMessage = {
319
+ kind: "message",
320
+ messageId: assistantMessageId,
321
+ role: "agent",
322
+ parts: [{ kind: "text", text: assistantText }],
323
+ contextId,
324
+ };
325
+ appendHistory(taskId, finalMsg);
326
+
327
+ controller.enqueue(
328
+ sseData({
329
+ kind: "statusUpdate",
330
+ statusUpdate: {
331
+ kind: "statusUpdate",
332
+ taskId,
333
+ status: {
334
+ state: "completed",
335
+ timestamp: new Date().toISOString(),
336
+ message: "completed",
337
+ },
338
+ index: updateStatus(taskId, {
339
+ state: "completed",
340
+ timestamp: new Date().toISOString(),
341
+ message: "completed",
342
+ }),
343
+ final: true,
344
+ },
345
+ }),
346
+ );
347
+ } catch (e) {
348
+ const message = e instanceof Error ? e.message : "Unknown error";
349
+ controller.enqueue(
350
+ sseData({
351
+ kind: "statusUpdate",
352
+ statusUpdate: {
353
+ kind: "statusUpdate",
354
+ taskId,
355
+ status: {
356
+ state: "failed",
357
+ timestamp: new Date().toISOString(),
358
+ message,
359
+ },
360
+ index: updateStatus(taskId, {
361
+ state: "failed",
362
+ timestamp: new Date().toISOString(),
363
+ message,
364
+ }),
365
+ final: true,
366
+ },
367
+ }),
368
+ );
369
+ } finally {
370
+ markContextIdle(contextId);
371
+ controller.close();
372
+ }
373
+ },
374
+ });
375
+
376
+ return new Response(stream, {
377
+ headers: sseHeaders({
378
+ "A2A-Version": env.a2aVersion,
379
+ }),
380
+ });
381
+ }