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,357 @@
1
+ import type {
2
+ A2aMessage,
3
+ A2aStreamResponse,
4
+ A2aToolCallEvent,
5
+ A2aAskUserQuestionEvent,
6
+ A2aQuestionDismissedEvent,
7
+ A2aSubagentEvent,
8
+ A2aActivityEvent,
9
+ A2aActivityStreamResponse,
10
+ } from "@/lib/a2a/types";
11
+ import { getEnv } from "@/lib/env";
12
+
13
+ type StreamCallbacks = {
14
+ onOpen?: (initial: { taskId: string; contextId?: string }) => void;
15
+ onDelta?: (deltaText: string) => void;
16
+ onSessionId?: (sessionId: string) => void;
17
+ onToolCall?: (toolCall: A2aToolCallEvent) => void;
18
+ onAskUserQuestion?: (event: A2aAskUserQuestionEvent) => void;
19
+ onQuestionDismissed?: (event: A2aQuestionDismissedEvent) => void;
20
+ onSubagent?: (event: A2aSubagentEvent) => void;
21
+ onFinal?: (final: { taskId: string }) => void;
22
+ onError?: (error: Error) => void;
23
+ };
24
+
25
+ type ActivityStreamCallbacks = {
26
+ onActivity?: (event: A2aActivityEvent) => void;
27
+ onError?: (error: Error) => void;
28
+ };
29
+
30
+ const decoder = new TextDecoder();
31
+
32
+ function isObject(v: unknown): v is Record<string, unknown> {
33
+ return !!v && typeof v === "object";
34
+ }
35
+
36
+ function extractDeltaText(msg: A2aMessage): string {
37
+ for (const p of msg.parts) {
38
+ if (p.kind === "text") return p.text;
39
+ }
40
+ return "";
41
+ }
42
+
43
+ export async function streamA2aChat(input: {
44
+ baseUrl?: string;
45
+ text: string;
46
+ contextId?: string;
47
+ sessionId?: string;
48
+ signal?: AbortSignal;
49
+ callbacks: StreamCallbacks;
50
+ }): Promise<void> {
51
+ const env = getEnv();
52
+ const baseUrl = input.baseUrl ?? env.nextPublicA2aBaseUrl;
53
+
54
+ const body = {
55
+ message: {
56
+ kind: "message",
57
+ role: "user",
58
+ parts: [{ kind: "text", text: input.text }],
59
+ contextId: input.contextId,
60
+ },
61
+ ...(input.sessionId
62
+ ? { configuration: { metadata: { sessionId: input.sessionId } } }
63
+ : {}),
64
+ };
65
+
66
+ const res = await fetch(`${baseUrl}/messages/stream`, {
67
+ method: "POST",
68
+ headers: {
69
+ "Content-Type": "application/a2a+json",
70
+ Accept: "text/event-stream",
71
+ "A2A-Version": env.a2aVersion,
72
+ },
73
+ body: JSON.stringify(body),
74
+ signal: input.signal,
75
+ });
76
+
77
+ if (!res.ok) {
78
+ const text = await res.text().catch(() => "");
79
+ throw new Error(
80
+ `A2A request failed (${res.status}): ${text || res.statusText}`,
81
+ );
82
+ }
83
+ if (!res.body) throw new Error("Missing response body");
84
+
85
+ const reader = res.body.getReader();
86
+ let buf = "";
87
+ let taskId: string | undefined;
88
+ let contextId: string | undefined;
89
+
90
+ try {
91
+ while (true) {
92
+ const { value, done } = await reader.read();
93
+ if (done) break;
94
+ buf += decoder.decode(value, { stream: true });
95
+
96
+ while (true) {
97
+ const idx = buf.indexOf("\n\n");
98
+ if (idx === -1) break;
99
+ const rawEvent = buf.slice(0, idx);
100
+ buf = buf.slice(idx + 2);
101
+
102
+ const lines = rawEvent.split("\n");
103
+ for (const line of lines) {
104
+ const trimmed = line.trim();
105
+ if (!trimmed.startsWith("data:")) continue;
106
+ const jsonStr = trimmed.slice("data:".length).trim();
107
+ if (!jsonStr) continue;
108
+
109
+ let payload: unknown;
110
+ try {
111
+ payload = JSON.parse(jsonStr);
112
+ } catch {
113
+ continue;
114
+ }
115
+ if (!isObject(payload) || typeof payload.kind !== "string") continue;
116
+
117
+ const sr = payload as A2aStreamResponse;
118
+
119
+ if (sr.kind === "task") {
120
+ taskId = sr.task.taskId;
121
+ contextId = sr.task.contextId;
122
+ input.callbacks.onOpen?.({ taskId, contextId });
123
+ }
124
+
125
+ if (sr.kind === "session") {
126
+ input.callbacks.onSessionId?.(sr.sessionId);
127
+ }
128
+
129
+ if (sr.kind === "message" && sr.message.role === "agent") {
130
+ const delta = extractDeltaText(sr.message);
131
+ if (delta) input.callbacks.onDelta?.(delta);
132
+ }
133
+
134
+ if (sr.kind === "statusUpdate") {
135
+ if (
136
+ sr.statusUpdate.final &&
137
+ sr.statusUpdate.status.state === "completed" &&
138
+ taskId
139
+ ) {
140
+ input.callbacks.onFinal?.({ taskId });
141
+ }
142
+ }
143
+
144
+ if (sr.kind === "toolCall") {
145
+ input.callbacks.onToolCall?.(sr.toolCall);
146
+ }
147
+
148
+ if (sr.kind === "askUserQuestion") {
149
+ input.callbacks.onAskUserQuestion?.(sr.askUserQuestion);
150
+ }
151
+
152
+ if (sr.kind === "questionDismissed") {
153
+ input.callbacks.onQuestionDismissed?.(sr.questionDismissed);
154
+ }
155
+
156
+ if (sr.kind === "subagent") {
157
+ input.callbacks.onSubagent?.(sr.subagent);
158
+ }
159
+ }
160
+ }
161
+ }
162
+ } catch (e) {
163
+ const err = e instanceof Error ? e : new Error("Unknown streaming error");
164
+ if (input.signal?.aborted || err.name === "AbortError") return;
165
+ input.callbacks.onError?.(err);
166
+ throw err;
167
+ } finally {
168
+ reader.releaseLock();
169
+ }
170
+ }
171
+
172
+ export async function streamA2aEvents(input: {
173
+ baseUrl?: string;
174
+ contextId: string;
175
+ since?: number;
176
+ signal?: AbortSignal;
177
+ callbacks: ActivityStreamCallbacks;
178
+ }): Promise<void> {
179
+ const env = getEnv();
180
+ const baseUrl = input.baseUrl ?? env.nextPublicA2aBaseUrl;
181
+ const params = new URLSearchParams({ contextId: input.contextId });
182
+ if (input.since && input.since > 0) {
183
+ params.set("since", String(input.since));
184
+ }
185
+
186
+ const res = await fetch(`${baseUrl}/events/stream?${params.toString()}`, {
187
+ method: "GET",
188
+ headers: {
189
+ Accept: "text/event-stream",
190
+ "A2A-Version": env.a2aVersion,
191
+ ...(input.since && input.since > 0
192
+ ? { "Last-Event-ID": String(input.since) }
193
+ : {}),
194
+ },
195
+ signal: input.signal,
196
+ });
197
+
198
+ if (!res.ok) {
199
+ const text = await res.text().catch(() => "");
200
+ throw new Error(
201
+ `A2A events request failed (${res.status}): ${text || res.statusText}`,
202
+ );
203
+ }
204
+ if (!res.body) throw new Error("Missing response body");
205
+
206
+ const reader = res.body.getReader();
207
+ let buf = "";
208
+ let pendingEventId: number | undefined;
209
+
210
+ try {
211
+ while (true) {
212
+ const { value, done } = await reader.read();
213
+ if (done) break;
214
+ buf += decoder.decode(value, { stream: true });
215
+
216
+ while (true) {
217
+ const idx = buf.indexOf("\n\n");
218
+ if (idx === -1) break;
219
+ const rawEvent = buf.slice(0, idx);
220
+ buf = buf.slice(idx + 2);
221
+
222
+ const lines = rawEvent.split("\n");
223
+ pendingEventId = undefined;
224
+ for (const line of lines) {
225
+ const trimmed = line.trim();
226
+ if (trimmed.startsWith("id:")) {
227
+ const value = trimmed.slice("id:".length).trim();
228
+ const parsed = Number(value);
229
+ if (Number.isFinite(parsed)) pendingEventId = parsed;
230
+ continue;
231
+ }
232
+ if (!trimmed.startsWith("data:")) continue;
233
+ const jsonStr = trimmed.slice("data:".length).trim();
234
+ if (!jsonStr) continue;
235
+
236
+ let payload: unknown;
237
+ try {
238
+ payload = JSON.parse(jsonStr);
239
+ } catch {
240
+ continue;
241
+ }
242
+ if (!isObject(payload) || typeof payload.kind !== "string") continue;
243
+
244
+ const sr = payload as A2aActivityStreamResponse;
245
+ if (sr.kind === "activity") {
246
+ const activity = pendingEventId
247
+ ? { ...sr.activity, id: pendingEventId }
248
+ : sr.activity;
249
+ input.callbacks.onActivity?.(activity);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ } catch (e) {
255
+ const err = e instanceof Error ? e : new Error("Unknown streaming error");
256
+ input.callbacks.onError?.(err);
257
+ throw err;
258
+ } finally {
259
+ reader.releaseLock();
260
+ }
261
+ }
262
+
263
+ /**
264
+ * 请求后端取消指定 task(会触发 AbortController + 标记为 cancelled)
265
+ * 设计为 fire-and-forget,前端通常不需要关心返回值。
266
+ */
267
+ export async function cancelA2aTask(input: {
268
+ baseUrl?: string;
269
+ taskId: string;
270
+ }): Promise<void> {
271
+ const env = getEnv();
272
+ const baseUrl = input.baseUrl ?? env.nextPublicA2aBaseUrl;
273
+
274
+ try {
275
+ const res = await fetch(`${baseUrl}/tasks/${input.taskId}/cancel`, {
276
+ method: "POST",
277
+ headers: {
278
+ "Content-Type": "application/a2a+json",
279
+ "A2A-Version": env.a2aVersion,
280
+ },
281
+ });
282
+
283
+ // 404 = task 已不在内存里了,可以忽略;其他非 2xx 仅做调试用
284
+ if (!res.ok && res.status !== 404) {
285
+ console.warn(
286
+ "cancelA2aTask: backend returned non-2xx",
287
+ res.status,
288
+ await res.text().catch(() => ""),
289
+ );
290
+ }
291
+ } catch (e) {
292
+ // 网络错误 / dev 服务器重启之类,终止本身就已经打断前端流,这里静默即可
293
+ console.warn("cancelA2aTask: request failed", e);
294
+ }
295
+ }
296
+
297
+ export async function cancelA2aBackgroundTask(input: {
298
+ baseUrl?: string;
299
+ jobId: string;
300
+ }): Promise<void> {
301
+ const env = getEnv();
302
+ const baseUrl = input.baseUrl ?? env.nextPublicA2aBaseUrl;
303
+
304
+ try {
305
+ const res = await fetch(`${baseUrl}/background-tasks/${input.jobId}/cancel`, {
306
+ method: "POST",
307
+ headers: {
308
+ "Content-Type": "application/a2a+json",
309
+ "A2A-Version": env.a2aVersion,
310
+ },
311
+ });
312
+
313
+ if (!res.ok && res.status !== 404) {
314
+ console.warn(
315
+ "cancelA2aBackgroundTask: backend returned non-2xx",
316
+ res.status,
317
+ await res.text().catch(() => ""),
318
+ );
319
+ }
320
+ } catch (e) {
321
+ console.warn("cancelA2aBackgroundTask: request failed", e);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * 提交用户对 AskUserQuestion 的回答
327
+ */
328
+ export async function submitA2aAnswer(input: {
329
+ baseUrl?: string;
330
+ requestId: string;
331
+ taskId: string;
332
+ answers: Record<string, string>;
333
+ }): Promise<{ success: boolean }> {
334
+ const env = getEnv();
335
+ const baseUrl = input.baseUrl ?? env.nextPublicA2aBaseUrl;
336
+
337
+ const res = await fetch(`${baseUrl}/messages/answer`, {
338
+ method: "POST",
339
+ headers: {
340
+ "Content-Type": "application/json",
341
+ },
342
+ body: JSON.stringify({
343
+ requestId: input.requestId,
344
+ taskId: input.taskId,
345
+ answers: input.answers,
346
+ }),
347
+ });
348
+
349
+ if (!res.ok) {
350
+ const text = await res.text().catch(() => "");
351
+ throw new Error(
352
+ `Failed to submit answer (${res.status}): ${text || res.statusText}`,
353
+ );
354
+ }
355
+
356
+ return res.json();
357
+ }
@@ -0,0 +1,19 @@
1
+ import type { A2aStreamResponse } from "@/lib/a2a/types";
2
+
3
+ const encoder = new TextEncoder();
4
+
5
+ export function sseHeaders(extra?: Record<string, string>): Headers {
6
+ const h = new Headers();
7
+ h.set("Content-Type", "text/event-stream; charset=utf-8");
8
+ h.set("Cache-Control", "no-cache, no-transform");
9
+ h.set("Connection", "keep-alive");
10
+ h.set("X-Accel-Buffering", "no");
11
+ if (extra) {
12
+ for (const [k, v] of Object.entries(extra)) h.set(k, v);
13
+ }
14
+ return h;
15
+ }
16
+
17
+ export function sseData(payload: A2aStreamResponse): Uint8Array {
18
+ return encoder.encode(`data: ${JSON.stringify(payload)}\n\n`);
19
+ }
@@ -0,0 +1,111 @@
1
+ import type { A2aArtifact, A2aMessage, A2aTask, A2aTaskStatus } from "@/lib/a2a/types";
2
+
3
+ type TaskRecord = {
4
+ task: A2aTask;
5
+ createdAtMs: number;
6
+ updatedAtMs: number;
7
+ cancelRequested: boolean;
8
+ abortController?: AbortController;
9
+ statusIndex: number;
10
+ artifactIndex: number;
11
+ };
12
+
13
+ declare global {
14
+ var __A2A_TASK_STORE__: Map<string, TaskRecord> | undefined;
15
+ }
16
+
17
+ function getStore(): Map<string, TaskRecord> {
18
+ if (!globalThis.__A2A_TASK_STORE__) {
19
+ globalThis.__A2A_TASK_STORE__ = new Map();
20
+ }
21
+ return globalThis.__A2A_TASK_STORE__;
22
+ }
23
+
24
+ export function createTask(input: {
25
+ taskId: string;
26
+ contextId?: string;
27
+ initialMessage?: A2aMessage;
28
+ }): A2aTask {
29
+ const now = Date.now();
30
+ const task: A2aTask = {
31
+ taskId: input.taskId,
32
+ contextId: input.contextId,
33
+ status: {
34
+ state: "working",
35
+ timestamp: new Date(now).toISOString(),
36
+ message: "working",
37
+ },
38
+ artifacts: [],
39
+ history: input.initialMessage ? [input.initialMessage] : [],
40
+ };
41
+
42
+ getStore().set(input.taskId, {
43
+ task,
44
+ createdAtMs: now,
45
+ updatedAtMs: now,
46
+ cancelRequested: false,
47
+ statusIndex: 0,
48
+ artifactIndex: 0,
49
+ });
50
+
51
+ return task;
52
+ }
53
+
54
+ export function getTask(taskId: string): A2aTask | undefined {
55
+ return getStore().get(taskId)?.task;
56
+ }
57
+
58
+ export function listTasks(): A2aTask[] {
59
+ return Array.from(getStore().values())
60
+ .sort((a, b) => b.updatedAtMs - a.updatedAtMs)
61
+ .map((r) => r.task);
62
+ }
63
+
64
+ export function requestCancel(taskId: string): A2aTask | undefined {
65
+ const rec = getStore().get(taskId);
66
+ if (!rec) return undefined;
67
+ rec.cancelRequested = true;
68
+ rec.abortController?.abort();
69
+ rec.task.status = {
70
+ state: "cancelled",
71
+ timestamp: new Date().toISOString(),
72
+ message: "cancelled",
73
+ };
74
+ rec.updatedAtMs = Date.now();
75
+ return rec.task;
76
+ }
77
+
78
+ export function attachAbortController(taskId: string, abortController: AbortController): void {
79
+ const rec = getStore().get(taskId);
80
+ if (!rec) return;
81
+ rec.abortController = abortController;
82
+ }
83
+
84
+ export function isCancelRequested(taskId: string): boolean {
85
+ return getStore().get(taskId)?.cancelRequested ?? false;
86
+ }
87
+
88
+ export function updateStatus(taskId: string, status: A2aTaskStatus): number {
89
+ const rec = getStore().get(taskId);
90
+ if (!rec) return 0;
91
+ rec.task.status = status;
92
+ rec.updatedAtMs = Date.now();
93
+ rec.statusIndex += 1;
94
+ return rec.statusIndex;
95
+ }
96
+
97
+ export function appendHistory(taskId: string, msg: A2aMessage): void {
98
+ const rec = getStore().get(taskId);
99
+ if (!rec) return;
100
+ rec.task.history = [...(rec.task.history ?? []), msg];
101
+ rec.updatedAtMs = Date.now();
102
+ }
103
+
104
+ export function addArtifact(taskId: string, artifact: A2aArtifact): number {
105
+ const rec = getStore().get(taskId);
106
+ if (!rec) return 0;
107
+ rec.task.artifacts = [...(rec.task.artifacts ?? []), artifact];
108
+ rec.updatedAtMs = Date.now();
109
+ rec.artifactIndex += 1;
110
+ return rec.artifactIndex;
111
+ }
@@ -0,0 +1,216 @@
1
+ export type A2aTaskState =
2
+ | "working"
3
+ | "input_required"
4
+ | "completed"
5
+ | "failed"
6
+ | "cancelled"
7
+ | "rejected";
8
+
9
+ export type A2aRole = "user" | "agent";
10
+
11
+ export type A2aTextPart = { kind: "text"; text: string };
12
+ export type A2aFilePart = {
13
+ kind: "file";
14
+ url: string;
15
+ mimeType?: string;
16
+ name?: string;
17
+ };
18
+ export type A2aDataPart = { kind: "data"; data: unknown; mimeType?: string };
19
+ export type A2aPart = A2aTextPart | A2aFilePart | A2aDataPart;
20
+
21
+ export type A2aMessage = {
22
+ kind: "message";
23
+ messageId?: string;
24
+ role: A2aRole;
25
+ parts: A2aPart[];
26
+ contextId?: string;
27
+ referenceTaskIds?: string[];
28
+ };
29
+
30
+ export type A2aTaskStatus = {
31
+ state: A2aTaskState;
32
+ timestamp: string;
33
+ message?: string;
34
+ };
35
+
36
+ export type A2aArtifact = {
37
+ artifactId: string;
38
+ name?: string;
39
+ description?: string;
40
+ parts: A2aPart[];
41
+ index?: number;
42
+ };
43
+
44
+ export type A2aTask = {
45
+ taskId: string;
46
+ contextId?: string;
47
+ status: A2aTaskStatus;
48
+ artifacts?: A2aArtifact[];
49
+ history?: A2aMessage[];
50
+ };
51
+
52
+ export type A2aTaskStatusUpdateEvent = {
53
+ kind: "statusUpdate";
54
+ taskId: string;
55
+ status: A2aTaskStatus;
56
+ index: number;
57
+ message?: A2aMessage;
58
+ final?: boolean;
59
+ };
60
+
61
+ export type A2aTaskArtifactUpdateEvent = {
62
+ kind: "artifactUpdate";
63
+ taskId: string;
64
+ artifact: A2aArtifact;
65
+ index: number;
66
+ };
67
+
68
+ export type A2aSessionEvent = {
69
+ kind: "session";
70
+ sessionId: string;
71
+ contextId?: string;
72
+ };
73
+
74
+ export type A2aToolCallEvent = {
75
+ kind: "toolCall";
76
+ toolName: string;
77
+ toolId: string;
78
+ input: unknown;
79
+ status: "started" | "completed";
80
+ output?: unknown;
81
+ /** 递增序号,用于前端匹配 started/completed 事件(解决 toolId 不一致问题) */
82
+ toolSeq?: number;
83
+ };
84
+
85
+ export type A2aTodoItem = {
86
+ content: string;
87
+ status: "pending" | "in_progress" | "completed";
88
+ activeForm?: string;
89
+ };
90
+
91
+ // AskUserQuestion 问题选项
92
+ export type AskUserQuestionOption = {
93
+ label: string;
94
+ description: string;
95
+ };
96
+
97
+ // AskUserQuestion 单个问题
98
+ export type AskUserQuestion = {
99
+ question: string;
100
+ header: string;
101
+ options: AskUserQuestionOption[];
102
+ multiSelect?: boolean;
103
+ };
104
+
105
+ // AskUserQuestion 事件 - 当 Claude 需要用户回答时发送
106
+ export type A2aAskUserQuestionEvent = {
107
+ kind: "askUserQuestion";
108
+ requestId: string;
109
+ taskId: string;
110
+ questions: AskUserQuestion[];
111
+ };
112
+
113
+ // AskUserQuestion 回答格式
114
+ export type AskUserQuestionAnswer = {
115
+ requestId: string;
116
+ taskId: string;
117
+ answers: Record<string, string>; // question -> selected label(s)
118
+ };
119
+
120
+ // AskUserQuestion 超时/取消事件 - 当问题超时或被取消时发送
121
+ export type A2aQuestionDismissedEvent = {
122
+ kind: "questionDismissed";
123
+ requestId: string;
124
+ taskId: string;
125
+ reason: "timeout" | "cancelled";
126
+ };
127
+
128
+ // 子代理事件
129
+ export type A2aSubagentEvent = {
130
+ kind: "subagent";
131
+ agentId: string;
132
+ agentType: string;
133
+ status: "started" | "stopped";
134
+ };
135
+
136
+ // 后台任务活动事件
137
+ export type A2aBackgroundTaskActivity = {
138
+ jobId: string;
139
+ status: "queued" | "running" | "completed" | "failed" | "cancelled";
140
+ description?: string;
141
+ prompt?: string;
142
+ subagentType?: string;
143
+ parentTaskId?: string;
144
+ createdAt: number;
145
+ startedAt?: number;
146
+ finishedAt?: number;
147
+ result?: string;
148
+ error?: string;
149
+ sessionId?: string;
150
+ };
151
+
152
+ export type A2aTodoUpdateActivity = {
153
+ jobId: string;
154
+ todos: A2aTodoItem[];
155
+ updatedAt: number;
156
+ };
157
+
158
+ export type A2aDeliveryMessageActivity = {
159
+ jobId?: string;
160
+ text: string;
161
+ createdAt: number;
162
+ isDelta?: boolean;
163
+ };
164
+
165
+ export type A2aActivityEvent =
166
+ | {
167
+ id: number;
168
+ contextId: string;
169
+ kind: "backgroundTask";
170
+ data: A2aBackgroundTaskActivity;
171
+ }
172
+ | {
173
+ id: number;
174
+ contextId: string;
175
+ kind: "todoUpdate";
176
+ data: A2aTodoUpdateActivity;
177
+ }
178
+ | {
179
+ id: number;
180
+ contextId: string;
181
+ kind: "deliveryMessage";
182
+ data: A2aDeliveryMessageActivity;
183
+ };
184
+
185
+ export type A2aActivityStreamResponse = {
186
+ kind: "activity";
187
+ activity: A2aActivityEvent;
188
+ };
189
+
190
+ export type A2aStreamResponse =
191
+ | { kind: "task"; task: A2aTask }
192
+ | { kind: "message"; message: A2aMessage }
193
+ | A2aSessionEvent
194
+ | { kind: "statusUpdate"; statusUpdate: A2aTaskStatusUpdateEvent }
195
+ | { kind: "artifactUpdate"; artifactUpdate: A2aTaskArtifactUpdateEvent }
196
+ | { kind: "toolCall"; toolCall: A2aToolCallEvent }
197
+ | { kind: "askUserQuestion"; askUserQuestion: A2aAskUserQuestionEvent }
198
+ | { kind: "questionDismissed"; questionDismissed: A2aQuestionDismissedEvent }
199
+ | { kind: "subagent"; subagent: A2aSubagentEvent };
200
+
201
+ export type A2aSendMessageRequest = {
202
+ message: A2aMessage;
203
+ configuration?: {
204
+ blocking?: boolean;
205
+ historyLength?: number;
206
+ metadata?: Record<string, unknown>;
207
+ };
208
+ };
209
+
210
+ export type A2aError = {
211
+ error: {
212
+ code: string;
213
+ message: string;
214
+ details?: unknown;
215
+ };
216
+ };