@a2anet/a2a-utils 0.5.0 → 0.6.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.
- package/README.md +350 -318
- package/dist/client/a2a-tools.d.ts +249 -147
- package/dist/client/a2a-tools.d.ts.map +1 -1
- package/dist/client/a2a-tools.js +292 -283
- package/dist/client/a2a-tools.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/client/a2a-tools.js
CHANGED
|
@@ -1,14 +1,89 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2025-present A2A Net <hello@a2anet.com>
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { z } from "zod";
|
|
4
5
|
import { DataArtifacts, TextArtifacts, minimizeArtifacts } from "../artifacts/index.js";
|
|
5
6
|
import { ArtifactSettings } from "../types.js";
|
|
6
7
|
export const TEXT_MINIMIZED_TIP = "Text was minimized. Call view_text_artifact() to view specific line ranges.";
|
|
7
8
|
export const DATA_MINIMIZED_TIP = "Data was minimized. Call view_data_artifact() to navigate to specific data.";
|
|
9
|
+
// -- Zod schemas (one per tool) --
|
|
10
|
+
const getAgentsSchema = z.object({});
|
|
11
|
+
const getAgentSchema = z.object({
|
|
12
|
+
agentId: z.string().describe("The agent's unique identifier (from get_agents)."),
|
|
13
|
+
});
|
|
14
|
+
const sendMessageSchema = z.object({
|
|
15
|
+
agentId: z.string().describe("ID of the agent to message (from get_agents)."),
|
|
16
|
+
message: z.string().describe("The message content to send."),
|
|
17
|
+
contextId: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("Continue an existing conversation by providing its context ID.")
|
|
20
|
+
.optional(),
|
|
21
|
+
taskId: z
|
|
22
|
+
.string()
|
|
23
|
+
.describe("Attach to an existing task (for input_required flows).")
|
|
24
|
+
.optional(),
|
|
25
|
+
timeout: z.number().describe("Override the default timeout in seconds.").optional(),
|
|
26
|
+
data: z
|
|
27
|
+
.array(z.unknown())
|
|
28
|
+
.describe("Structured data to include with the message. " +
|
|
29
|
+
"Each item is sent as a separate JSON object alongside the text.")
|
|
30
|
+
.optional(),
|
|
31
|
+
files: z
|
|
32
|
+
.array(z.string())
|
|
33
|
+
.describe("Files to include with the message. " +
|
|
34
|
+
"Accepts local file paths (read and sent as binary, max 1MB) " +
|
|
35
|
+
"or URLs (sent as references for the remote agent to fetch).")
|
|
36
|
+
.optional(),
|
|
37
|
+
});
|
|
38
|
+
const getTaskSchema = z.object({
|
|
39
|
+
agentId: z.string().describe("ID of the agent that owns the task."),
|
|
40
|
+
taskId: z.string().describe("Task ID from a previous send_message response."),
|
|
41
|
+
timeout: z.number().describe("Override the monitoring timeout in seconds.").optional(),
|
|
42
|
+
pollInterval: z
|
|
43
|
+
.number()
|
|
44
|
+
.describe("Override the interval between status checks in seconds.")
|
|
45
|
+
.optional(),
|
|
46
|
+
});
|
|
47
|
+
const viewTextArtifactSchema = z.object({
|
|
48
|
+
agentId: z.string().describe("ID of the agent that produced the artifact."),
|
|
49
|
+
taskId: z.string().describe("Task ID containing the artifact."),
|
|
50
|
+
artifactId: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe("The artifact's unique identifier (from the task's artifacts list)."),
|
|
53
|
+
lineStart: z.number().describe("Starting line number (1-based, inclusive).").optional(),
|
|
54
|
+
lineEnd: z.number().describe("Ending line number (1-based, inclusive).").optional(),
|
|
55
|
+
characterStart: z
|
|
56
|
+
.number()
|
|
57
|
+
.describe("Starting character index (0-based, inclusive).")
|
|
58
|
+
.optional(),
|
|
59
|
+
characterEnd: z.number().describe("Ending character index (0-based, exclusive).").optional(),
|
|
60
|
+
});
|
|
61
|
+
const viewDataArtifactSchema = z.object({
|
|
62
|
+
agentId: z.string().describe("ID of the agent that produced the artifact."),
|
|
63
|
+
taskId: z.string().describe("Task ID containing the artifact."),
|
|
64
|
+
artifactId: z
|
|
65
|
+
.string()
|
|
66
|
+
.describe("The artifact's unique identifier (from the task's artifacts list)."),
|
|
67
|
+
jsonPath: z
|
|
68
|
+
.string()
|
|
69
|
+
.describe('Dot-separated path to navigate into the data (e.g. "results.items").')
|
|
70
|
+
.optional(),
|
|
71
|
+
rows: z
|
|
72
|
+
.string()
|
|
73
|
+
.describe('Row selection for list data. Examples: "0" (single), "0-10" (range), "0,2,5" (specific), "all".')
|
|
74
|
+
.optional(),
|
|
75
|
+
columns: z
|
|
76
|
+
.string()
|
|
77
|
+
.describe('Column selection for tabular data. Examples: "name" (single), "name,age" (multiple), "all".')
|
|
78
|
+
.optional(),
|
|
79
|
+
});
|
|
8
80
|
/**
|
|
9
81
|
* LLM-friendly tools that can be used out-of-the-box with agent frameworks.
|
|
10
82
|
*
|
|
11
|
-
* Each
|
|
83
|
+
* Each tool has LLM-friendly docstrings, returns JSON-serialisable objects, and returns actionable error messages.
|
|
84
|
+
*
|
|
85
|
+
* Each tool is an instance property with `name`, `description`, `schema` (Zod),
|
|
86
|
+
* and `execute`. Use the `toolDefinitions` getter for the full array.
|
|
12
87
|
*/
|
|
13
88
|
export class A2ATools {
|
|
14
89
|
session;
|
|
@@ -17,264 +92,243 @@ export class A2ATools {
|
|
|
17
92
|
this.session = session;
|
|
18
93
|
this.artifactSettings = opts?.artifactSettings ?? new ArtifactSettings();
|
|
19
94
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
// -- Tool definitions --
|
|
96
|
+
getAgents = {
|
|
97
|
+
name: "get_agents",
|
|
98
|
+
description: "List all available agents with their names and descriptions. " +
|
|
99
|
+
"Use this first to discover what agents are available before sending messages. " +
|
|
100
|
+
"Each agent has a unique ID (the key) that you'll need for other tools like " +
|
|
101
|
+
"send_message and get_agent. " +
|
|
102
|
+
"Returns an object mapping agent IDs to their name and description. " +
|
|
103
|
+
'If any agents failed to load, an "errors" field is included with details.',
|
|
104
|
+
schema: getAgentsSchema,
|
|
105
|
+
execute: async () => {
|
|
106
|
+
try {
|
|
107
|
+
const result = await this.session.agents.getAgentsForLlm("basic");
|
|
108
|
+
const initErrors = this.session.agents.initializationErrors;
|
|
109
|
+
if (Object.keys(result).length === 0 && Object.keys(initErrors).length > 0) {
|
|
110
|
+
const errors = {};
|
|
111
|
+
for (const [agentId, error] of Object.entries(initErrors)) {
|
|
112
|
+
errors[agentId] = `Failed to load agent: ${error}`;
|
|
113
|
+
}
|
|
114
|
+
return { agents: result, errors };
|
|
38
115
|
}
|
|
39
|
-
return
|
|
116
|
+
return result;
|
|
40
117
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
catch (e) {
|
|
44
|
-
return { error: true, error_message: `Failed to list agents: ${e}` };
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Get detailed information about a specific agent, including its skills.
|
|
49
|
-
*
|
|
50
|
-
* Use this after get_agents to learn more about what a specific agent can do.
|
|
51
|
-
* The response includes the agent's name, description, and a list of skills
|
|
52
|
-
* with their descriptions.
|
|
53
|
-
*
|
|
54
|
-
* @param agentId - The agent's unique identifier (from get_agents).
|
|
55
|
-
*/
|
|
56
|
-
async getAgent(agentId) {
|
|
57
|
-
try {
|
|
58
|
-
const result = await this.session.agents.getAgentForLlm(agentId, "full");
|
|
59
|
-
if (result === null) {
|
|
60
|
-
const available = Object.keys(await this.session.agents.getAgents()).sort();
|
|
61
|
-
return {
|
|
62
|
-
error: true,
|
|
63
|
-
error_message: `Agent '${agentId}' not found. Use get_agents to see available agents. Available: ${available.length > 0 ? available.join(", ") : "(none)"}`,
|
|
64
|
-
};
|
|
118
|
+
catch (e) {
|
|
119
|
+
return { error: true, error_message: `Failed to list agents: ${e}` };
|
|
65
120
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* @param message - The message content to send.
|
|
87
|
-
* @param opts.contextId - Continue an existing conversation by providing its context ID.
|
|
88
|
-
* Omit to start a new conversation.
|
|
89
|
-
* @param opts.taskId - Attach to an existing task (for input_required flows).
|
|
90
|
-
* @param opts.timeout - Override the default timeout in seconds.
|
|
91
|
-
* @param opts.data - Structured data to include with the message. Each item
|
|
92
|
-
* is sent as a separate JSON object alongside the text.
|
|
93
|
-
* @param opts.files - Files to include with the message. Accepts local file
|
|
94
|
-
* paths (read and sent as binary, max 1MB) or URLs (sent as references
|
|
95
|
-
* for the remote agent to fetch).
|
|
96
|
-
*/
|
|
97
|
-
async sendMessage(agentId, message, opts) {
|
|
98
|
-
try {
|
|
99
|
-
const result = await this.session.sendMessage(agentId, message, opts);
|
|
100
|
-
let llmResult;
|
|
101
|
-
if (result.kind === "task") {
|
|
102
|
-
llmResult = await this.buildTaskForLlm(result);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
getAgent = {
|
|
124
|
+
name: "get_agent",
|
|
125
|
+
description: "Get detailed information about a specific agent, including its skills. " +
|
|
126
|
+
"Use this after get_agents to learn more about what a specific agent can do. " +
|
|
127
|
+
"The response includes the agent's name, description, and a list of skills " +
|
|
128
|
+
"with their descriptions.",
|
|
129
|
+
schema: getAgentSchema,
|
|
130
|
+
execute: async ({ agentId }) => {
|
|
131
|
+
try {
|
|
132
|
+
const result = await this.session.agents.getAgentForLlm(agentId, "full");
|
|
133
|
+
if (result === null) {
|
|
134
|
+
const available = Object.keys(await this.session.agents.getAgents()).sort();
|
|
135
|
+
return {
|
|
136
|
+
error: true,
|
|
137
|
+
error_message: `Agent '${agentId}' not found. Use get_agents to see available agents. Available: ${available.length > 0 ? available.join(", ") : "(none)"}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
103
141
|
}
|
|
104
|
-
|
|
105
|
-
|
|
142
|
+
catch (e) {
|
|
143
|
+
return { error: true, error_message: `Failed to get agent info: ${e}` };
|
|
106
144
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
sendMessage = {
|
|
148
|
+
name: "send_message",
|
|
149
|
+
description: "Send a message to an agent and receive a structured response. " +
|
|
150
|
+
"This is the primary way to communicate with agents. The response includes " +
|
|
151
|
+
"the agent's reply and any generated artifacts. " +
|
|
152
|
+
'Artifact data in responses may be minimized for display. Fields prefixed with "_" ' +
|
|
153
|
+
"indicate metadata about minimized content. Use view_text_artifact " +
|
|
154
|
+
"or view_data_artifact to access full artifact data. " +
|
|
155
|
+
"If the task is still in progress after the timeout, the response includes " +
|
|
156
|
+
"a task_id. Use get_task with that task_id to continue monitoring.",
|
|
157
|
+
schema: sendMessageSchema,
|
|
158
|
+
execute: async ({ agentId, message, contextId, taskId, timeout, data, files, }) => {
|
|
159
|
+
try {
|
|
160
|
+
const result = await this.session.sendMessage(agentId, message, {
|
|
161
|
+
contextId: contextId ?? null,
|
|
162
|
+
taskId: taskId ?? null,
|
|
163
|
+
timeout: timeout ?? null,
|
|
164
|
+
data: data,
|
|
165
|
+
files,
|
|
166
|
+
});
|
|
167
|
+
let llmResult;
|
|
168
|
+
if (result.kind === "task") {
|
|
169
|
+
llmResult = await this.buildTaskForLlm(result);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
llmResult = await this.buildMessageForLlm(result);
|
|
173
|
+
}
|
|
174
|
+
return llmResult;
|
|
116
175
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
176
|
+
catch (e) {
|
|
177
|
+
const errorMsg = String(e);
|
|
178
|
+
if (errorMsg.toLowerCase().includes("not found")) {
|
|
179
|
+
return {
|
|
180
|
+
error: true,
|
|
181
|
+
error_message: `${errorMsg} Use get_agents to see available agents.`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (e instanceof DOMException && e.name === "TimeoutError") {
|
|
185
|
+
return {
|
|
186
|
+
error: true,
|
|
187
|
+
error_message: "Request timed out. You can retry with a longer timeout, " +
|
|
188
|
+
"or if a task_id was returned earlier, use get_task to check progress.",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { error: true, error_message: `Failed to send message: ${e}` };
|
|
123
192
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const result = await this.session.getTask(agentId, taskId, {
|
|
144
|
-
timeout,
|
|
145
|
-
pollInterval,
|
|
146
|
-
});
|
|
147
|
-
const llmResult = await this.buildTaskForLlm(result);
|
|
148
|
-
return llmResult;
|
|
149
|
-
}
|
|
150
|
-
catch (e) {
|
|
151
|
-
const errorMsg = String(e);
|
|
152
|
-
if (errorMsg.toLowerCase().includes("not found")) {
|
|
153
|
-
return {
|
|
154
|
-
error: true,
|
|
155
|
-
error_message: `${errorMsg} Use get_agents to see available agents.`,
|
|
156
|
-
};
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
getTask = {
|
|
196
|
+
name: "get_task",
|
|
197
|
+
description: "Check the progress of a task that is still in progress. " +
|
|
198
|
+
'Use this after send_message returns a task in a non-terminal state (e.g. "working") ' +
|
|
199
|
+
"to monitor its progress. " +
|
|
200
|
+
"If the task is still running after the timeout, the current state is returned. " +
|
|
201
|
+
"Call get_task again to continue monitoring.",
|
|
202
|
+
schema: getTaskSchema,
|
|
203
|
+
execute: async ({ agentId, taskId, timeout, pollInterval, }) => {
|
|
204
|
+
try {
|
|
205
|
+
const result = await this.session.getTask(agentId, taskId, {
|
|
206
|
+
timeout,
|
|
207
|
+
pollInterval,
|
|
208
|
+
});
|
|
209
|
+
const llmResult = await this.buildTaskForLlm(result);
|
|
210
|
+
return llmResult;
|
|
157
211
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
catch (e) {
|
|
213
|
+
const errorMsg = String(e);
|
|
214
|
+
if (errorMsg.toLowerCase().includes("not found")) {
|
|
215
|
+
return {
|
|
216
|
+
error: true,
|
|
217
|
+
error_message: `${errorMsg} Use get_agents to see available agents.`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (e instanceof DOMException && e.name === "TimeoutError") {
|
|
221
|
+
return {
|
|
222
|
+
error: true,
|
|
223
|
+
error_message: "Request timed out. You can retry with a longer timeout, " +
|
|
224
|
+
"or use get_task again to continue monitoring.",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return { error: true, error_message: `Failed to get task: ${e}` };
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
viewTextArtifact = {
|
|
232
|
+
name: "view_text_artifact",
|
|
233
|
+
description: "View text content from an artifact, optionally selecting a range. " +
|
|
234
|
+
"Use this for artifacts containing text (documents, logs, code, etc.). " +
|
|
235
|
+
"You can select by line range OR character range, but not both.",
|
|
236
|
+
schema: viewTextArtifactSchema,
|
|
237
|
+
execute: async ({ agentId, taskId, artifactId, lineStart, lineEnd, characterStart, characterEnd, }) => {
|
|
238
|
+
try {
|
|
239
|
+
const artifact = await this.getArtifact(agentId, taskId, artifactId);
|
|
240
|
+
const text = A2ATools.extractText(artifact);
|
|
241
|
+
const filtered = TextArtifacts.view(text, {
|
|
242
|
+
lineStart,
|
|
243
|
+
lineEnd,
|
|
244
|
+
characterStart,
|
|
245
|
+
characterEnd,
|
|
246
|
+
characterLimit: this.artifactSettings.viewArtifactCharacterLimit,
|
|
247
|
+
});
|
|
248
|
+
const result = {
|
|
249
|
+
artifactId: artifact.artifactId,
|
|
250
|
+
description: artifact.description ?? null,
|
|
251
|
+
name: artifact.name ?? null,
|
|
252
|
+
parts: [{ kind: "text", text: filtered }],
|
|
163
253
|
};
|
|
254
|
+
return result;
|
|
164
255
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
* @param agentId - ID of the agent that produced the artifact.
|
|
175
|
-
* @param taskId - Task ID containing the artifact.
|
|
176
|
-
* @param artifactId - The artifact's unique identifier (from the task's artifacts list).
|
|
177
|
-
* @param lineStart - Starting line number (1-based, inclusive).
|
|
178
|
-
* @param lineEnd - Ending line number (1-based, inclusive).
|
|
179
|
-
* @param characterStart - Starting character index (0-based, inclusive).
|
|
180
|
-
* @param characterEnd - Ending character index (0-based, exclusive).
|
|
181
|
-
*/
|
|
182
|
-
async viewTextArtifact(agentId, taskId, artifactId, lineStart, lineEnd, characterStart, characterEnd) {
|
|
183
|
-
try {
|
|
184
|
-
const artifact = await this.getArtifact(agentId, taskId, artifactId);
|
|
185
|
-
const text = A2ATools.extractText(artifact);
|
|
186
|
-
const filtered = TextArtifacts.view(text, {
|
|
187
|
-
lineStart,
|
|
188
|
-
lineEnd,
|
|
189
|
-
characterStart,
|
|
190
|
-
characterEnd,
|
|
191
|
-
characterLimit: this.artifactSettings.viewArtifactCharacterLimit,
|
|
192
|
-
});
|
|
193
|
-
const result = {
|
|
194
|
-
artifactId: artifact.artifactId,
|
|
195
|
-
description: artifact.description ?? null,
|
|
196
|
-
name: artifact.name ?? null,
|
|
197
|
-
parts: [{ kind: "text", text: filtered }],
|
|
198
|
-
};
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
catch (e) {
|
|
202
|
-
const errorMsg = String(e);
|
|
203
|
-
if (errorMsg.toLowerCase().includes("not found")) {
|
|
204
|
-
if (errorMsg.toLowerCase().includes("artifact")) {
|
|
256
|
+
catch (e) {
|
|
257
|
+
const errorMsg = String(e);
|
|
258
|
+
if (errorMsg.toLowerCase().includes("not found")) {
|
|
259
|
+
if (errorMsg.toLowerCase().includes("artifact")) {
|
|
260
|
+
return {
|
|
261
|
+
error: true,
|
|
262
|
+
error_message: `Artifact '${artifactId}' not found in task '${taskId}'. Check the task's artifacts list for valid artifact IDs.`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
205
265
|
return {
|
|
206
266
|
error: true,
|
|
207
|
-
error_message:
|
|
267
|
+
error_message: `${errorMsg} Use get_agents to see available agents.`,
|
|
208
268
|
};
|
|
209
269
|
}
|
|
210
|
-
return {
|
|
211
|
-
|
|
212
|
-
|
|
270
|
+
return { error: true, error_message: `Failed to view text artifact: ${e}` };
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
viewDataArtifact = {
|
|
275
|
+
name: "view_data_artifact",
|
|
276
|
+
description: "View structured data from an artifact with optional filtering. " +
|
|
277
|
+
"Use this for artifacts containing JSON data (objects, arrays, tables). " +
|
|
278
|
+
"You can navigate to specific data with json_path, then filter with " +
|
|
279
|
+
"rows and columns for tabular data.",
|
|
280
|
+
schema: viewDataArtifactSchema,
|
|
281
|
+
execute: async ({ agentId, taskId, artifactId, jsonPath, rows, columns, }) => {
|
|
282
|
+
try {
|
|
283
|
+
const parsedRows = A2ATools.parseRows(rows ?? null);
|
|
284
|
+
const parsedColumns = A2ATools.parseColumns(columns ?? null);
|
|
285
|
+
const artifact = await this.getArtifact(agentId, taskId, artifactId);
|
|
286
|
+
const data = A2ATools.extractData(artifact);
|
|
287
|
+
const filtered = DataArtifacts.view(data, {
|
|
288
|
+
jsonPath,
|
|
289
|
+
rows: parsedRows,
|
|
290
|
+
columns: parsedColumns,
|
|
291
|
+
characterLimit: this.artifactSettings.viewArtifactCharacterLimit,
|
|
292
|
+
});
|
|
293
|
+
const result = {
|
|
294
|
+
artifactId: artifact.artifactId,
|
|
295
|
+
description: artifact.description ?? null,
|
|
296
|
+
name: artifact.name ?? null,
|
|
297
|
+
parts: [{ kind: "data", data: filtered }],
|
|
213
298
|
};
|
|
299
|
+
return result;
|
|
214
300
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
*
|
|
225
|
-
* @param agentId - ID of the agent that produced the artifact.
|
|
226
|
-
* @param taskId - Task ID containing the artifact.
|
|
227
|
-
* @param artifactId - The artifact's unique identifier (from the task's artifacts list).
|
|
228
|
-
* @param jsonPath - Dot-separated path to navigate into the data (e.g. "results.items").
|
|
229
|
-
* @param rows - Row selection for list data. Examples: "0" (single row), "0-10" (range),
|
|
230
|
-
* "0,2,5" (specific rows), "all" (every row).
|
|
231
|
-
* @param columns - Column selection for tabular data (list of objects). Examples:
|
|
232
|
-
* "name" (single column), "name,age" (multiple columns), "all" (every column).
|
|
233
|
-
*/
|
|
234
|
-
async viewDataArtifact(agentId, taskId, artifactId, jsonPath, rows, columns) {
|
|
235
|
-
try {
|
|
236
|
-
const parsedRows = A2ATools.parseRows(rows ?? null);
|
|
237
|
-
const parsedColumns = A2ATools.parseColumns(columns ?? null);
|
|
238
|
-
const artifact = await this.getArtifact(agentId, taskId, artifactId);
|
|
239
|
-
const data = A2ATools.extractData(artifact);
|
|
240
|
-
const filtered = DataArtifacts.view(data, {
|
|
241
|
-
jsonPath,
|
|
242
|
-
rows: parsedRows,
|
|
243
|
-
columns: parsedColumns,
|
|
244
|
-
characterLimit: this.artifactSettings.viewArtifactCharacterLimit,
|
|
245
|
-
});
|
|
246
|
-
const result = {
|
|
247
|
-
artifactId: artifact.artifactId,
|
|
248
|
-
description: artifact.description ?? null,
|
|
249
|
-
name: artifact.name ?? null,
|
|
250
|
-
parts: [{ kind: "data", data: filtered }],
|
|
251
|
-
};
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
catch (e) {
|
|
255
|
-
const errorMsg = String(e);
|
|
256
|
-
if (errorMsg.toLowerCase().includes("not found")) {
|
|
257
|
-
if (errorMsg.toLowerCase().includes("artifact")) {
|
|
301
|
+
catch (e) {
|
|
302
|
+
const errorMsg = String(e);
|
|
303
|
+
if (errorMsg.toLowerCase().includes("not found")) {
|
|
304
|
+
if (errorMsg.toLowerCase().includes("artifact")) {
|
|
305
|
+
return {
|
|
306
|
+
error: true,
|
|
307
|
+
error_message: `Artifact '${artifactId}' not found in task '${taskId}'. Check the task's artifacts list for valid artifact IDs.`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
258
310
|
return {
|
|
259
311
|
error: true,
|
|
260
|
-
error_message:
|
|
312
|
+
error_message: `${errorMsg} Use get_agents to see available agents.`,
|
|
261
313
|
};
|
|
262
314
|
}
|
|
263
|
-
return {
|
|
264
|
-
error: true,
|
|
265
|
-
error_message: `${errorMsg} Use get_agents to see available agents.`,
|
|
266
|
-
};
|
|
315
|
+
return { error: true, error_message: `Failed to view data artifact: ${e}` };
|
|
267
316
|
}
|
|
268
|
-
|
|
269
|
-
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
/** All tool definitions as an array. */
|
|
320
|
+
get toolDefinitions() {
|
|
321
|
+
return [
|
|
322
|
+
this.getAgents,
|
|
323
|
+
this.getAgent,
|
|
324
|
+
this.sendMessage,
|
|
325
|
+
this.getTask,
|
|
326
|
+
this.viewTextArtifact,
|
|
327
|
+
this.viewDataArtifact,
|
|
328
|
+
];
|
|
270
329
|
}
|
|
271
|
-
// --
|
|
272
|
-
/**
|
|
273
|
-
* Convert an A2A Message to MessageForLLM.
|
|
274
|
-
*
|
|
275
|
-
* Combines all TextParts into a single TextPartForLLM.
|
|
276
|
-
* Includes FileParts with saved path metadata.
|
|
277
|
-
*/
|
|
330
|
+
// -- Private helpers --
|
|
331
|
+
/** Convert an A2A Message to MessageForLLM. */
|
|
278
332
|
async buildMessageForLlm(message) {
|
|
279
333
|
const parts = [];
|
|
280
334
|
// Combine all text parts
|
|
@@ -312,7 +366,6 @@ export class A2ATools {
|
|
|
312
366
|
}
|
|
313
367
|
/** Convert a Task to TaskForLLM with artifact minimization and file path queries. */
|
|
314
368
|
async buildTaskForLlm(task) {
|
|
315
|
-
// Query fileStore for saved file paths
|
|
316
369
|
let savedFilePaths = null;
|
|
317
370
|
if (this.session.fileStore !== null && task.artifacts) {
|
|
318
371
|
savedFilePaths = {};
|
|
@@ -332,7 +385,6 @@ export class A2ATools {
|
|
|
332
385
|
dataTip: DATA_MINIMIZED_TIP,
|
|
333
386
|
})
|
|
334
387
|
: [];
|
|
335
|
-
// Build status message
|
|
336
388
|
let statusMessage = null;
|
|
337
389
|
if (task.status.message) {
|
|
338
390
|
statusMessage = await this.buildMessageForLlm(task.status.message);
|
|
@@ -348,18 +400,8 @@ export class A2ATools {
|
|
|
348
400
|
artifacts: minimized,
|
|
349
401
|
};
|
|
350
402
|
}
|
|
351
|
-
/**
|
|
352
|
-
* Look up an artifact through the resolution chain.
|
|
353
|
-
*
|
|
354
|
-
* 1. Check the task store (local cache)
|
|
355
|
-
* 2. Fetch fresh via session.getTask (remote retrieval)
|
|
356
|
-
*
|
|
357
|
-
* @returns The Artifact.
|
|
358
|
-
*
|
|
359
|
-
* @throws Error if artifact cannot be found.
|
|
360
|
-
*/
|
|
403
|
+
/** Look up an artifact through the resolution chain. */
|
|
361
404
|
async getArtifact(agentId, taskId, artifactId) {
|
|
362
|
-
// 1. Check task store (local cache)
|
|
363
405
|
const cachedTask = await this.session.taskStore.load(taskId);
|
|
364
406
|
if (cachedTask?.artifacts) {
|
|
365
407
|
for (const artifact of cachedTask.artifacts) {
|
|
@@ -368,7 +410,6 @@ export class A2ATools {
|
|
|
368
410
|
}
|
|
369
411
|
}
|
|
370
412
|
}
|
|
371
|
-
// 2. Fetch fresh via session.getTask
|
|
372
413
|
const task = await this.session.getTask(agentId, taskId);
|
|
373
414
|
if (task.artifacts) {
|
|
374
415
|
for (const artifact of task.artifacts) {
|
|
@@ -379,11 +420,6 @@ export class A2ATools {
|
|
|
379
420
|
}
|
|
380
421
|
throw new Error(`Artifact '${artifactId}' not found in task '${taskId}'. The artifact may have expired or the task_id may be incorrect.`);
|
|
381
422
|
}
|
|
382
|
-
/**
|
|
383
|
-
* Extract text content from artifact parts.
|
|
384
|
-
*
|
|
385
|
-
* @throws Error if artifact does not contain text content.
|
|
386
|
-
*/
|
|
387
423
|
static extractText(artifact) {
|
|
388
424
|
const textParts = [];
|
|
389
425
|
for (const part of artifact.parts) {
|
|
@@ -398,11 +434,6 @@ export class A2ATools {
|
|
|
398
434
|
}
|
|
399
435
|
return textParts.join("\n");
|
|
400
436
|
}
|
|
401
|
-
/**
|
|
402
|
-
* Extract data content from artifact parts.
|
|
403
|
-
*
|
|
404
|
-
* @throws Error if artifact does not contain data content.
|
|
405
|
-
*/
|
|
406
437
|
static extractData(artifact) {
|
|
407
438
|
const dataParts = [];
|
|
408
439
|
for (const part of artifact.parts) {
|
|
@@ -417,12 +448,6 @@ export class A2ATools {
|
|
|
417
448
|
}
|
|
418
449
|
return dataParts.length === 1 ? dataParts[0] : dataParts;
|
|
419
450
|
}
|
|
420
|
-
/**
|
|
421
|
-
* Parse a rows string into the type expected by DataArtifacts.view.
|
|
422
|
-
*
|
|
423
|
-
* Accepts: "0" (single int), "0-10" (range string), "0,2,5" (comma-separated
|
|
424
|
-
* list of ints), "all" (passthrough string), or null.
|
|
425
|
-
*/
|
|
426
451
|
static parseRows(rows) {
|
|
427
452
|
if (rows === null) {
|
|
428
453
|
return null;
|
|
@@ -431,7 +456,6 @@ export class A2ATools {
|
|
|
431
456
|
if (trimmedRows === "all") {
|
|
432
457
|
return "all";
|
|
433
458
|
}
|
|
434
|
-
// Comma-separated list: "0,2,5"
|
|
435
459
|
if (trimmedRows.includes(",")) {
|
|
436
460
|
try {
|
|
437
461
|
return trimmedRows.split(",").map((x) => {
|
|
@@ -446,23 +470,15 @@ export class A2ATools {
|
|
|
446
470
|
throw new Error(`Invalid rows format: '${trimmedRows}'. Comma-separated values must be integers. Examples: '0', '0-10', '0,2,5', 'all'.`);
|
|
447
471
|
}
|
|
448
472
|
}
|
|
449
|
-
// Range string: "0-10"
|
|
450
473
|
if (trimmedRows.includes("-")) {
|
|
451
474
|
return trimmedRows;
|
|
452
475
|
}
|
|
453
|
-
// Single integer: "0"
|
|
454
476
|
const n = Number.parseInt(trimmedRows, 10);
|
|
455
477
|
if (Number.isNaN(n)) {
|
|
456
478
|
throw new Error(`Invalid rows format: '${trimmedRows}'. Examples: '0' (single row), '0-10' (range), '0,2,5' (specific rows), 'all'.`);
|
|
457
479
|
}
|
|
458
480
|
return n;
|
|
459
481
|
}
|
|
460
|
-
/**
|
|
461
|
-
* Parse a columns string into the type expected by DataArtifacts.view.
|
|
462
|
-
*
|
|
463
|
-
* Accepts: "name" (single column), "name,age" (comma-separated list),
|
|
464
|
-
* "all" (passthrough string), or null.
|
|
465
|
-
*/
|
|
466
482
|
static parseColumns(columns) {
|
|
467
483
|
if (columns === null) {
|
|
468
484
|
return null;
|
|
@@ -471,18 +487,11 @@ export class A2ATools {
|
|
|
471
487
|
if (trimmedColumns === "all") {
|
|
472
488
|
return "all";
|
|
473
489
|
}
|
|
474
|
-
// Comma-separated list: "name,age"
|
|
475
490
|
if (trimmedColumns.includes(",")) {
|
|
476
491
|
return trimmedColumns.split(",").map((c) => c.trim());
|
|
477
492
|
}
|
|
478
|
-
// Single column name
|
|
479
493
|
return trimmedColumns;
|
|
480
494
|
}
|
|
481
|
-
/**
|
|
482
|
-
* Build a FilePartForLLM from a file Part.
|
|
483
|
-
*
|
|
484
|
-
* Used by both message and artifact file handling.
|
|
485
|
-
*/
|
|
486
495
|
static buildFilePartForLlm(part, savedPaths) {
|
|
487
496
|
if (part.kind !== "file") {
|
|
488
497
|
throw new Error("Expected file part");
|