@a2a-js/sdk 0.2.1

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 (49) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +422 -0
  3. package/build/src/a2a_response.d.ts +5 -0
  4. package/build/src/a2a_response.js +2 -0
  5. package/build/src/client/client.d.ts +118 -0
  6. package/build/src/client/client.js +409 -0
  7. package/build/src/index.d.ts +21 -0
  8. package/build/src/index.js +18 -0
  9. package/build/src/samples/agents/movie-agent/genkit.d.ts +2 -0
  10. package/build/src/samples/agents/movie-agent/genkit.js +11 -0
  11. package/build/src/samples/agents/movie-agent/index.d.ts +1 -0
  12. package/build/src/samples/agents/movie-agent/index.js +252 -0
  13. package/build/src/samples/agents/movie-agent/tmdb.d.ts +7 -0
  14. package/build/src/samples/agents/movie-agent/tmdb.js +32 -0
  15. package/build/src/samples/agents/movie-agent/tools.d.ts +15 -0
  16. package/build/src/samples/agents/movie-agent/tools.js +74 -0
  17. package/build/src/samples/cli.d.ts +2 -0
  18. package/build/src/samples/cli.js +289 -0
  19. package/build/src/server/a2a_express_app.d.ts +14 -0
  20. package/build/src/server/a2a_express_app.js +98 -0
  21. package/build/src/server/agent_execution/agent_executor.d.ts +18 -0
  22. package/build/src/server/agent_execution/agent_executor.js +2 -0
  23. package/build/src/server/agent_execution/request_context.d.ts +9 -0
  24. package/build/src/server/agent_execution/request_context.js +15 -0
  25. package/build/src/server/error.d.ts +23 -0
  26. package/build/src/server/error.js +57 -0
  27. package/build/src/server/events/execution_event_bus.d.ts +16 -0
  28. package/build/src/server/events/execution_event_bus.js +13 -0
  29. package/build/src/server/events/execution_event_bus_manager.d.ts +27 -0
  30. package/build/src/server/events/execution_event_bus_manager.js +36 -0
  31. package/build/src/server/events/execution_event_queue.d.ts +24 -0
  32. package/build/src/server/events/execution_event_queue.js +63 -0
  33. package/build/src/server/request_handler/a2a_request_handler.d.ts +11 -0
  34. package/build/src/server/request_handler/a2a_request_handler.js +2 -0
  35. package/build/src/server/request_handler/default_request_handler.d.ts +22 -0
  36. package/build/src/server/request_handler/default_request_handler.js +296 -0
  37. package/build/src/server/result_manager.d.ts +29 -0
  38. package/build/src/server/result_manager.js +149 -0
  39. package/build/src/server/store.d.ts +25 -0
  40. package/build/src/server/store.js +17 -0
  41. package/build/src/server/transports/jsonrpc_transport_handler.d.ts +15 -0
  42. package/build/src/server/transports/jsonrpc_transport_handler.js +114 -0
  43. package/build/src/server/utils.d.ts +22 -0
  44. package/build/src/server/utils.js +34 -0
  45. package/build/src/types-old.d.ts +832 -0
  46. package/build/src/types-old.js +20 -0
  47. package/build/src/types.d.ts +2046 -0
  48. package/build/src/types.js +8 -0
  49. package/package.json +52 -0
@@ -0,0 +1,252 @@
1
+ import express from "express";
2
+ import { v4 as uuidv4 } from 'uuid'; // For generating unique IDs
3
+ import { InMemoryTaskStore, A2AExpressApp, DefaultRequestHandler } from "../../../index.js";
4
+ import { ai } from "./genkit.js";
5
+ import { searchMovies, searchPeople } from "./tools.js";
6
+ if (!process.env.GEMINI_API_KEY || !process.env.TMDB_API_KEY) {
7
+ console.error("GEMINI_API_KEY and TMDB_API_KEY environment variables are required");
8
+ process.exit(1);
9
+ }
10
+ // Simple store for contexts
11
+ const contexts = new Map();
12
+ // Load the Genkit prompt
13
+ const movieAgentPrompt = ai.prompt('movie_agent');
14
+ /**
15
+ * MovieAgentExecutor implements the agent's core logic.
16
+ */
17
+ class MovieAgentExecutor {
18
+ cancelledTasks = new Set();
19
+ cancelTask = async (taskId, eventBus) => {
20
+ this.cancelledTasks.add(taskId);
21
+ // The execute loop is responsible for publishing the final state
22
+ };
23
+ async execute(requestContext, eventBus) {
24
+ const userMessage = requestContext.userMessage;
25
+ const existingTask = requestContext.task;
26
+ // Determine IDs for the task and context
27
+ const taskId = requestContext.taskId;
28
+ const contextId = requestContext.contextId;
29
+ console.log(`[MovieAgentExecutor] Processing message ${userMessage.messageId} for task ${taskId} (context: ${contextId})`);
30
+ // 1. Publish initial Task event if it's a new task
31
+ if (!existingTask) {
32
+ const initialTask = {
33
+ kind: 'task',
34
+ id: taskId,
35
+ contextId: contextId,
36
+ status: {
37
+ state: 'submitted',
38
+ timestamp: new Date().toISOString(),
39
+ },
40
+ history: [userMessage], // Start history with the current user message
41
+ metadata: userMessage.metadata, // Carry over metadata from message if any
42
+ };
43
+ eventBus.publish(initialTask);
44
+ }
45
+ // 2. Publish "working" status update
46
+ const workingStatusUpdate = {
47
+ kind: 'status-update',
48
+ taskId: taskId,
49
+ contextId: contextId,
50
+ status: {
51
+ state: 'working',
52
+ message: {
53
+ kind: 'message',
54
+ role: 'agent',
55
+ messageId: uuidv4(),
56
+ parts: [{ kind: 'text', text: 'Processing your question, hang tight!' }],
57
+ taskId: taskId,
58
+ contextId: contextId,
59
+ },
60
+ timestamp: new Date().toISOString(),
61
+ },
62
+ final: false,
63
+ };
64
+ eventBus.publish(workingStatusUpdate);
65
+ // 3. Prepare messages for Genkit prompt
66
+ const historyForGenkit = contexts.get(contextId) || [];
67
+ if (!historyForGenkit.find(m => m.messageId === userMessage.messageId)) {
68
+ historyForGenkit.push(userMessage);
69
+ }
70
+ contexts.set(contextId, historyForGenkit);
71
+ const messages = historyForGenkit
72
+ .map((m) => ({
73
+ role: (m.role === 'agent' ? 'model' : 'user'),
74
+ content: m.parts
75
+ .filter((p) => p.kind === 'text' && !!p.text)
76
+ .map((p) => ({
77
+ text: p.text,
78
+ })),
79
+ }))
80
+ .filter((m) => m.content.length > 0);
81
+ if (messages.length === 0) {
82
+ console.warn(`[MovieAgentExecutor] No valid text messages found in history for task ${taskId}.`);
83
+ const failureUpdate = {
84
+ kind: 'status-update',
85
+ taskId: taskId,
86
+ contextId: contextId,
87
+ status: {
88
+ state: 'failed',
89
+ message: {
90
+ kind: 'message',
91
+ role: 'agent',
92
+ messageId: uuidv4(),
93
+ parts: [{ kind: 'text', text: 'No message found to process.' }],
94
+ taskId: taskId,
95
+ contextId: contextId,
96
+ },
97
+ timestamp: new Date().toISOString(),
98
+ },
99
+ final: true,
100
+ };
101
+ eventBus.publish(failureUpdate);
102
+ return;
103
+ }
104
+ const goal = existingTask?.metadata?.goal || userMessage.metadata?.goal;
105
+ try {
106
+ // 4. Run the Genkit prompt
107
+ const response = await movieAgentPrompt({ goal: goal, now: new Date().toISOString() }, {
108
+ messages,
109
+ tools: [searchMovies, searchPeople],
110
+ });
111
+ // Check if the request has been cancelled
112
+ if (this.cancelledTasks.has(taskId)) {
113
+ console.log(`[MovieAgentExecutor] Request cancelled for task: ${taskId}`);
114
+ const cancelledUpdate = {
115
+ kind: 'status-update',
116
+ taskId: taskId,
117
+ contextId: contextId,
118
+ status: {
119
+ state: 'canceled',
120
+ timestamp: new Date().toISOString(),
121
+ },
122
+ final: true, // Cancellation is a final state
123
+ };
124
+ eventBus.publish(cancelledUpdate);
125
+ return;
126
+ }
127
+ const responseText = response.text; // Access the text property using .text()
128
+ console.info(`[MovieAgentExecutor] Prompt response: ${responseText}`);
129
+ const lines = responseText.trim().split('\n');
130
+ const finalStateLine = lines.at(-1)?.trim().toUpperCase();
131
+ const agentReplyText = lines.slice(0, lines.length - 1).join('\n').trim();
132
+ let finalA2AState = "unknown";
133
+ if (finalStateLine === 'COMPLETED') {
134
+ finalA2AState = 'completed';
135
+ }
136
+ else if (finalStateLine === 'AWAITING_USER_INPUT') {
137
+ finalA2AState = 'input-required';
138
+ }
139
+ else {
140
+ console.warn(`[MovieAgentExecutor] Unexpected final state line from prompt: ${finalStateLine}. Defaulting to 'completed'.`);
141
+ finalA2AState = 'completed'; // Default if LLM deviates
142
+ }
143
+ // 5. Publish final task status update
144
+ const agentMessage = {
145
+ kind: 'message',
146
+ role: 'agent',
147
+ messageId: uuidv4(),
148
+ parts: [{ kind: 'text', text: agentReplyText || "Completed." }], // Ensure some text
149
+ taskId: taskId,
150
+ contextId: contextId,
151
+ };
152
+ historyForGenkit.push(agentMessage);
153
+ contexts.set(contextId, historyForGenkit);
154
+ const finalUpdate = {
155
+ kind: 'status-update',
156
+ taskId: taskId,
157
+ contextId: contextId,
158
+ status: {
159
+ state: finalA2AState,
160
+ message: agentMessage,
161
+ timestamp: new Date().toISOString(),
162
+ },
163
+ final: true,
164
+ };
165
+ eventBus.publish(finalUpdate);
166
+ console.log(`[MovieAgentExecutor] Task ${taskId} finished with state: ${finalA2AState}`);
167
+ }
168
+ catch (error) {
169
+ console.error(`[MovieAgentExecutor] Error processing task ${taskId}:`, error);
170
+ const errorUpdate = {
171
+ kind: 'status-update',
172
+ taskId: taskId,
173
+ contextId: contextId,
174
+ status: {
175
+ state: 'failed',
176
+ message: {
177
+ kind: 'message',
178
+ role: 'agent',
179
+ messageId: uuidv4(),
180
+ parts: [{ kind: 'text', text: `Agent error: ${error.message}` }],
181
+ taskId: taskId,
182
+ contextId: contextId,
183
+ },
184
+ timestamp: new Date().toISOString(),
185
+ },
186
+ final: true,
187
+ };
188
+ eventBus.publish(errorUpdate);
189
+ }
190
+ }
191
+ }
192
+ // --- Server Setup ---
193
+ const movieAgentCard = {
194
+ name: 'Movie Agent',
195
+ description: 'An agent that can answer questions about movies and actors using TMDB.',
196
+ // Adjust the base URL and port as needed. /a2a is the default base in A2AExpressApp
197
+ url: 'http://localhost:41241/', // Example: if baseUrl in A2AExpressApp
198
+ provider: {
199
+ organization: 'A2A Samples',
200
+ url: 'https://example.com/a2a-samples' // Added provider URL
201
+ },
202
+ version: '0.0.2', // Incremented version
203
+ capabilities: {
204
+ streaming: true, // The new framework supports streaming
205
+ pushNotifications: false, // Assuming not implemented for this agent yet
206
+ stateTransitionHistory: true, // Agent uses history
207
+ },
208
+ // authentication: null, // Property 'authentication' does not exist on type 'AgentCard'.
209
+ securitySchemes: undefined, // Or define actual security schemes if any
210
+ security: undefined,
211
+ defaultInputModes: ['text'],
212
+ defaultOutputModes: ['text', 'task-status'], // task-status is a common output mode
213
+ skills: [
214
+ {
215
+ id: 'general_movie_chat',
216
+ name: 'General Movie Chat',
217
+ description: 'Answer general questions or chat about movies, actors, directors.',
218
+ tags: ['movies', 'actors', 'directors'],
219
+ examples: [
220
+ 'Tell me about the plot of Inception.',
221
+ 'Recommend a good sci-fi movie.',
222
+ 'Who directed The Matrix?',
223
+ 'What other movies has Scarlett Johansson been in?',
224
+ 'Find action movies starring Keanu Reeves',
225
+ 'Which came out first, Jurassic Park or Terminator 2?',
226
+ ],
227
+ inputModes: ['text'], // Explicitly defining for skill
228
+ outputModes: ['text', 'task-status'] // Explicitly defining for skill
229
+ },
230
+ ],
231
+ supportsAuthenticatedExtendedCard: false,
232
+ };
233
+ async function main() {
234
+ // 1. Create TaskStore
235
+ const taskStore = new InMemoryTaskStore();
236
+ // 2. Create AgentExecutor
237
+ const agentExecutor = new MovieAgentExecutor();
238
+ // 3. Create DefaultRequestHandler
239
+ const requestHandler = new DefaultRequestHandler(movieAgentCard, taskStore, agentExecutor);
240
+ // 4. Create and setup A2AExpressApp
241
+ const appBuilder = new A2AExpressApp(requestHandler);
242
+ const expressApp = appBuilder.setupRoutes(express());
243
+ // 5. Start the server
244
+ const PORT = process.env.PORT || 41241;
245
+ expressApp.listen(PORT, () => {
246
+ console.log(`[MovieAgent] Server using new framework started on http://localhost:${PORT}`);
247
+ console.log(`[MovieAgent] Agent Card: http://localhost:${PORT}/.well-known/agent.json`);
248
+ console.log('[MovieAgent] Press Ctrl+C to stop the server');
249
+ });
250
+ }
251
+ main().catch(console.error);
252
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Utility function to call the TMDB API
3
+ * @param endpoint The TMDB API endpoint (e.g., 'movie', 'person')
4
+ * @param query The search query
5
+ * @returns Promise that resolves to the API response data
6
+ */
7
+ export declare function callTmdbApi(endpoint: string, query: string): Promise<any>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utility function to call the TMDB API
3
+ * @param endpoint The TMDB API endpoint (e.g., 'movie', 'person')
4
+ * @param query The search query
5
+ * @returns Promise that resolves to the API response data
6
+ */
7
+ export async function callTmdbApi(endpoint, query) {
8
+ // Validate API key
9
+ const apiKey = process.env.TMDB_API_KEY;
10
+ if (!apiKey) {
11
+ throw new Error("TMDB_API_KEY environment variable is not set");
12
+ }
13
+ try {
14
+ // Make request to TMDB API
15
+ const url = new URL(`https://api.themoviedb.org/3/search/${endpoint}`);
16
+ url.searchParams.append("api_key", apiKey);
17
+ url.searchParams.append("query", query);
18
+ url.searchParams.append("include_adult", "false");
19
+ url.searchParams.append("language", "en-US");
20
+ url.searchParams.append("page", "1");
21
+ const response = await fetch(url.toString());
22
+ if (!response.ok) {
23
+ throw new Error(`TMDB API error: ${response.status} ${response.statusText}`);
24
+ }
25
+ return await response.json();
26
+ }
27
+ catch (error) {
28
+ console.error(`Error calling TMDB API (${endpoint}):`, error);
29
+ throw error;
30
+ }
31
+ }
32
+ //# sourceMappingURL=tmdb.js.map
@@ -0,0 +1,15 @@
1
+ import { z } from "./genkit.js";
2
+ export declare const searchMovies: import("genkit").ToolAction<z.ZodObject<{
3
+ query: z.ZodString;
4
+ }, "strip", z.ZodTypeAny, {
5
+ query?: string;
6
+ }, {
7
+ query?: string;
8
+ }>, z.ZodTypeAny>;
9
+ export declare const searchPeople: import("genkit").ToolAction<z.ZodObject<{
10
+ query: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ query?: string;
13
+ }, {
14
+ query?: string;
15
+ }>, z.ZodTypeAny>;
@@ -0,0 +1,74 @@
1
+ import { ai, z } from "./genkit.js";
2
+ import { callTmdbApi } from "./tmdb.js";
3
+ export const searchMovies = ai.defineTool({
4
+ name: "searchMovies",
5
+ description: "search TMDB for movies by title",
6
+ inputSchema: z.object({
7
+ query: z.string(),
8
+ }),
9
+ }, async ({ query }) => {
10
+ console.log("[tmdb:searchMovies]", JSON.stringify(query));
11
+ try {
12
+ const data = await callTmdbApi("movie", query);
13
+ // Only modify image paths to be full URLs
14
+ const results = data.results.map((movie) => {
15
+ if (movie.poster_path) {
16
+ movie.poster_path = `https://image.tmdb.org/t/p/w500${movie.poster_path}`;
17
+ }
18
+ if (movie.backdrop_path) {
19
+ movie.backdrop_path = `https://image.tmdb.org/t/p/w500${movie.backdrop_path}`;
20
+ }
21
+ return movie;
22
+ });
23
+ return {
24
+ ...data,
25
+ results,
26
+ };
27
+ }
28
+ catch (error) {
29
+ console.error("Error searching movies:", error);
30
+ // Re-throwing allows Genkit/the caller to handle it appropriately
31
+ throw error;
32
+ }
33
+ });
34
+ export const searchPeople = ai.defineTool({
35
+ name: "searchPeople",
36
+ description: "search TMDB for people by name",
37
+ inputSchema: z.object({
38
+ query: z.string(),
39
+ }),
40
+ }, async ({ query }) => {
41
+ console.log("[tmdb:searchPeople]", JSON.stringify(query));
42
+ try {
43
+ const data = await callTmdbApi("person", query);
44
+ // Only modify image paths to be full URLs
45
+ const results = data.results.map((person) => {
46
+ if (person.profile_path) {
47
+ person.profile_path = `https://image.tmdb.org/t/p/w500${person.profile_path}`;
48
+ }
49
+ // Also modify poster paths in known_for works
50
+ if (person.known_for && Array.isArray(person.known_for)) {
51
+ person.known_for = person.known_for.map((work) => {
52
+ if (work.poster_path) {
53
+ work.poster_path = `https://image.tmdb.org/t/p/w500${work.poster_path}`;
54
+ }
55
+ if (work.backdrop_path) {
56
+ work.backdrop_path = `https://image.tmdb.org/t/p/w500${work.backdrop_path}`;
57
+ }
58
+ return work;
59
+ });
60
+ }
61
+ return person;
62
+ });
63
+ return {
64
+ ...data,
65
+ results,
66
+ };
67
+ }
68
+ catch (error) {
69
+ console.error("Error searching people:", error);
70
+ // Re-throwing allows Genkit/the caller to handle it appropriately
71
+ throw error;
72
+ }
73
+ });
74
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ import readline from "node:readline";
3
+ import crypto from "node:crypto";
4
+ import { // Added for explicit Part typing
5
+ A2AClient, } from "../index.js";
6
+ // --- ANSI Colors ---
7
+ const colors = {
8
+ reset: "\x1b[0m",
9
+ bright: "\x1b[1m",
10
+ dim: "\x1b[2m",
11
+ red: "\x1b[31m",
12
+ green: "\x1b[32m",
13
+ yellow: "\x1b[33m",
14
+ blue: "\x1b[34m",
15
+ magenta: "\x1b[35m",
16
+ cyan: "\x1b[36m",
17
+ gray: "\x1b[90m",
18
+ };
19
+ // --- Helper Functions ---
20
+ function colorize(color, text) {
21
+ return `${colors[color]}${text}${colors.reset}`;
22
+ }
23
+ function generateId() {
24
+ return crypto.randomUUID();
25
+ }
26
+ // --- State ---
27
+ let currentTaskId = undefined; // Initialize as undefined
28
+ let currentContextId = undefined; // Initialize as undefined
29
+ const serverUrl = process.argv[2] || "http://localhost:41241"; // Agent's base URL
30
+ const client = new A2AClient(serverUrl);
31
+ let agentName = "Agent"; // Default, try to get from agent card later
32
+ // --- Readline Setup ---
33
+ const rl = readline.createInterface({
34
+ input: process.stdin,
35
+ output: process.stdout,
36
+ prompt: colorize("cyan", "You: "),
37
+ });
38
+ // --- Response Handling ---
39
+ // Function now accepts the unwrapped event payload directly
40
+ function printAgentEvent(event) {
41
+ const timestamp = new Date().toLocaleTimeString();
42
+ const prefix = colorize("magenta", `\n${agentName} [${timestamp}]:`);
43
+ // Check if it's a TaskStatusUpdateEvent
44
+ if (event.kind === "status-update") {
45
+ const update = event; // Cast for type safety
46
+ const state = update.status.state;
47
+ let stateEmoji = "❓";
48
+ let stateColor = "yellow";
49
+ switch (state) {
50
+ case "working":
51
+ stateEmoji = "⏳";
52
+ stateColor = "blue";
53
+ break;
54
+ case "input-required":
55
+ stateEmoji = "🤔";
56
+ stateColor = "yellow";
57
+ break;
58
+ case "completed":
59
+ stateEmoji = "✅";
60
+ stateColor = "green";
61
+ break;
62
+ case "canceled":
63
+ stateEmoji = "⏹️";
64
+ stateColor = "gray";
65
+ break;
66
+ case "failed":
67
+ stateEmoji = "❌";
68
+ stateColor = "red";
69
+ break;
70
+ default:
71
+ stateEmoji = "ℹ️"; // For other states like submitted, rejected etc.
72
+ stateColor = "dim";
73
+ break;
74
+ }
75
+ console.log(`${prefix} ${stateEmoji} Status: ${colorize(stateColor, state)} (Task: ${update.taskId}, Context: ${update.contextId}) ${update.final ? colorize("bright", "[FINAL]") : ""}`);
76
+ if (update.status.message) {
77
+ printMessageContent(update.status.message);
78
+ }
79
+ }
80
+ // Check if it's a TaskArtifactUpdateEvent
81
+ else if (event.kind === "artifact-update") {
82
+ const update = event; // Cast for type safety
83
+ console.log(`${prefix} 📄 Artifact Received: ${update.artifact.name || "(unnamed)"} (ID: ${update.artifact.artifactId}, Task: ${update.taskId}, Context: ${update.contextId})`);
84
+ // Create a temporary message-like structure to reuse printMessageContent
85
+ printMessageContent({
86
+ messageId: generateId(), // Dummy messageId
87
+ kind: "message", // Dummy kind
88
+ role: "agent", // Assuming artifact parts are from agent
89
+ parts: update.artifact.parts,
90
+ taskId: update.taskId,
91
+ contextId: update.contextId,
92
+ });
93
+ }
94
+ else {
95
+ // This case should ideally not be reached if called correctly
96
+ console.log(prefix, colorize("yellow", "Received unknown event type in printAgentEvent:"), event);
97
+ }
98
+ }
99
+ function printMessageContent(message) {
100
+ message.parts.forEach((part, index) => {
101
+ const partPrefix = colorize("red", ` Part ${index + 1}:`);
102
+ if (part.kind === "text") { // Check kind property
103
+ console.log(`${partPrefix} ${colorize("green", "📝 Text:")}`, part.text);
104
+ }
105
+ else if (part.kind === "file") { // Check kind property
106
+ const filePart = part;
107
+ console.log(`${partPrefix} ${colorize("blue", "📄 File:")} Name: ${filePart.file.name || "N/A"}, Type: ${filePart.file.mimeType || "N/A"}, Source: ${("bytes" in filePart.file) ? "Inline (bytes)" : filePart.file.uri}`);
108
+ }
109
+ else if (part.kind === "data") { // Check kind property
110
+ const dataPart = part;
111
+ console.log(`${partPrefix} ${colorize("yellow", "📊 Data:")}`, JSON.stringify(dataPart.data, null, 2));
112
+ }
113
+ else {
114
+ console.log(`${partPrefix} ${colorize("yellow", "Unsupported part kind:")}`, part);
115
+ }
116
+ });
117
+ }
118
+ // --- Agent Card Fetching ---
119
+ async function fetchAndDisplayAgentCard() {
120
+ // Use the client's getAgentCard method.
121
+ // The client was initialized with serverUrl, which is the agent's base URL.
122
+ console.log(colorize("dim", `Attempting to fetch agent card from agent at: ${serverUrl}`));
123
+ try {
124
+ // client.getAgentCard() uses the agentBaseUrl provided during client construction
125
+ const card = await client.getAgentCard();
126
+ agentName = card.name || "Agent"; // Update global agent name
127
+ console.log(colorize("green", `✓ Agent Card Found:`));
128
+ console.log(` Name: ${colorize("bright", agentName)}`);
129
+ if (card.description) {
130
+ console.log(` Description: ${card.description}`);
131
+ }
132
+ console.log(` Version: ${card.version || "N/A"}`);
133
+ if (card.capabilities?.streaming) {
134
+ console.log(` Streaming: ${colorize("green", "Supported")}`);
135
+ }
136
+ else {
137
+ console.log(` Streaming: ${colorize("yellow", "Not Supported (or not specified)")}`);
138
+ }
139
+ // Update prompt prefix to use the fetched name
140
+ // The prompt is set dynamically before each rl.prompt() call in the main loop
141
+ // to reflect the current agentName if it changes (though unlikely after initial fetch).
142
+ }
143
+ catch (error) {
144
+ console.log(colorize("yellow", `⚠️ Error fetching or parsing agent card`));
145
+ throw error;
146
+ }
147
+ }
148
+ // --- Main Loop ---
149
+ async function main() {
150
+ console.log(colorize("bright", `A2A Terminal Client`));
151
+ console.log(colorize("dim", `Agent Base URL: ${serverUrl}`));
152
+ await fetchAndDisplayAgentCard(); // Fetch the card before starting the loop
153
+ console.log(colorize("dim", `No active task or context initially. Use '/new' to start a fresh session or send a message.`));
154
+ console.log(colorize("green", `Enter messages, or use '/new' to start a new session. '/exit' to quit.`));
155
+ rl.setPrompt(colorize("cyan", `${agentName} > You: `)); // Set initial prompt
156
+ rl.prompt();
157
+ rl.on("line", async (line) => {
158
+ const input = line.trim();
159
+ rl.setPrompt(colorize("cyan", `${agentName} > You: `)); // Ensure prompt reflects current agentName
160
+ if (!input) {
161
+ rl.prompt();
162
+ return;
163
+ }
164
+ if (input.toLowerCase() === "/new") {
165
+ currentTaskId = undefined;
166
+ currentContextId = undefined; // Reset contextId on /new
167
+ console.log(colorize("bright", `✨ Starting new session. Task and Context IDs are cleared.`));
168
+ rl.prompt();
169
+ return;
170
+ }
171
+ if (input.toLowerCase() === "/exit") {
172
+ rl.close();
173
+ return;
174
+ }
175
+ // Construct params for sendMessageStream
176
+ const messageId = generateId(); // Generate a unique message ID
177
+ const messagePayload = {
178
+ messageId: messageId,
179
+ kind: "message", // Required by Message interface
180
+ role: "user",
181
+ parts: [
182
+ {
183
+ kind: "text", // Required by TextPart interface
184
+ text: input,
185
+ },
186
+ ],
187
+ };
188
+ // Conditionally add taskId to the message payload
189
+ if (currentTaskId) {
190
+ messagePayload.taskId = currentTaskId;
191
+ }
192
+ // Conditionally add contextId to the message payload
193
+ if (currentContextId) {
194
+ messagePayload.contextId = currentContextId;
195
+ }
196
+ const params = {
197
+ message: messagePayload,
198
+ // Optional: configuration for streaming, blocking, etc.
199
+ // configuration: {
200
+ // acceptedOutputModes: ['text/plain', 'application/json'], // Example
201
+ // blocking: false // Default for streaming is usually non-blocking
202
+ // }
203
+ };
204
+ try {
205
+ console.log(colorize("red", "Sending message..."));
206
+ // Use sendMessageStream
207
+ const stream = client.sendMessageStream(params);
208
+ // Iterate over the events from the stream
209
+ for await (const event of stream) {
210
+ const timestamp = new Date().toLocaleTimeString(); // Get fresh timestamp for each event
211
+ const prefix = colorize("magenta", `\n${agentName} [${timestamp}]:`);
212
+ if (event.kind === "status-update" || event.kind === "artifact-update") {
213
+ const typedEvent = event;
214
+ printAgentEvent(typedEvent);
215
+ // If the event is a TaskStatusUpdateEvent and it's final, reset currentTaskId
216
+ if (typedEvent.kind === "status-update" && typedEvent.final && typedEvent.status.state !== "input-required") {
217
+ console.log(colorize("yellow", ` Task ${typedEvent.taskId} is final. Clearing current task ID.`));
218
+ currentTaskId = undefined;
219
+ // Optionally, you might want to clear currentContextId as well if a task ending implies context ending.
220
+ // currentContextId = undefined;
221
+ // console.log(colorize("dim", ` Context ID also cleared as task is final.`));
222
+ }
223
+ }
224
+ else if (event.kind === "message") {
225
+ const msg = event;
226
+ console.log(`${prefix} ${colorize("green", "✉️ Message Stream Event:")}`);
227
+ printMessageContent(msg);
228
+ if (msg.taskId && msg.taskId !== currentTaskId) {
229
+ console.log(colorize("dim", ` Task ID context updated to ${msg.taskId} based on message event.`));
230
+ currentTaskId = msg.taskId;
231
+ }
232
+ if (msg.contextId && msg.contextId !== currentContextId) {
233
+ console.log(colorize("dim", ` Context ID updated to ${msg.contextId} based on message event.`));
234
+ currentContextId = msg.contextId;
235
+ }
236
+ }
237
+ else if (event.kind === "task") {
238
+ const task = event;
239
+ console.log(`${prefix} ${colorize("blue", "ℹ️ Task Stream Event:")} ID: ${task.id}, Context: ${task.contextId}, Status: ${task.status.state}`);
240
+ if (task.id !== currentTaskId) {
241
+ console.log(colorize("dim", ` Task ID updated from ${currentTaskId || 'N/A'} to ${task.id}`));
242
+ currentTaskId = task.id;
243
+ }
244
+ if (task.contextId && task.contextId !== currentContextId) {
245
+ console.log(colorize("dim", ` Context ID updated from ${currentContextId || 'N/A'} to ${task.contextId}`));
246
+ currentContextId = task.contextId;
247
+ }
248
+ if (task.status.message) {
249
+ console.log(colorize("gray", " Task includes message:"));
250
+ printMessageContent(task.status.message);
251
+ }
252
+ if (task.artifacts && task.artifacts.length > 0) {
253
+ console.log(colorize("gray", ` Task includes ${task.artifacts.length} artifact(s).`));
254
+ }
255
+ }
256
+ else {
257
+ console.log(prefix, colorize("yellow", "Received unknown event structure from stream:"), event);
258
+ }
259
+ }
260
+ console.log(colorize("dim", `--- End of response stream for this input ---`));
261
+ }
262
+ catch (error) {
263
+ const timestamp = new Date().toLocaleTimeString();
264
+ const prefix = colorize("red", `\n${agentName} [${timestamp}] ERROR:`);
265
+ console.error(prefix, `Error communicating with agent:`, error.message || error);
266
+ if (error.code) {
267
+ console.error(colorize("gray", ` Code: ${error.code}`));
268
+ }
269
+ if (error.data) {
270
+ console.error(colorize("gray", ` Data: ${JSON.stringify(error.data)}`));
271
+ }
272
+ if (!(error.code || error.data) && error.stack) {
273
+ console.error(colorize("gray", error.stack.split('\n').slice(1, 3).join('\n')));
274
+ }
275
+ }
276
+ finally {
277
+ rl.prompt();
278
+ }
279
+ }).on("close", () => {
280
+ console.log(colorize("yellow", "\nExiting A2A Terminal Client. Goodbye!"));
281
+ process.exit(0);
282
+ });
283
+ }
284
+ // --- Start ---
285
+ main().catch(err => {
286
+ console.error(colorize("red", "Unhandled error in main:"), err);
287
+ process.exit(1);
288
+ });
289
+ //# sourceMappingURL=cli.js.map