proofscan 0.10.61 → 0.11.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 (223) hide show
  1. package/README.ja.md +1 -0
  2. package/README.md +2 -0
  3. package/dist/a2a/agent-card.d.ts +40 -0
  4. package/dist/a2a/agent-card.d.ts.map +1 -0
  5. package/dist/a2a/agent-card.js +227 -0
  6. package/dist/a2a/agent-card.js.map +1 -0
  7. package/dist/a2a/client.d.ts +169 -0
  8. package/dist/a2a/client.d.ts.map +1 -0
  9. package/dist/a2a/client.js +854 -0
  10. package/dist/a2a/client.js.map +1 -0
  11. package/dist/a2a/config.d.ts +35 -0
  12. package/dist/a2a/config.d.ts.map +1 -0
  13. package/dist/a2a/config.js +474 -0
  14. package/dist/a2a/config.js.map +1 -0
  15. package/dist/a2a/index.d.ts +11 -0
  16. package/dist/a2a/index.d.ts.map +1 -0
  17. package/dist/a2a/index.js +11 -0
  18. package/dist/a2a/index.js.map +1 -0
  19. package/dist/a2a/normalizer.d.ts +66 -0
  20. package/dist/a2a/normalizer.d.ts.map +1 -0
  21. package/dist/a2a/normalizer.js +146 -0
  22. package/dist/a2a/normalizer.js.map +1 -0
  23. package/dist/a2a/session-manager.d.ts +81 -0
  24. package/dist/a2a/session-manager.d.ts.map +1 -0
  25. package/dist/a2a/session-manager.js +176 -0
  26. package/dist/a2a/session-manager.js.map +1 -0
  27. package/dist/a2a/types.d.ts +249 -0
  28. package/dist/a2a/types.d.ts.map +1 -0
  29. package/dist/a2a/types.js +8 -0
  30. package/dist/a2a/types.js.map +1 -0
  31. package/dist/cli.d.ts +2 -1
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +8 -3
  34. package/dist/cli.js.map +1 -1
  35. package/dist/commands/agent.d.ts +12 -0
  36. package/dist/commands/agent.d.ts.map +1 -0
  37. package/dist/commands/agent.js +339 -0
  38. package/dist/commands/agent.js.map +1 -0
  39. package/dist/commands/analyze.d.ts.map +1 -1
  40. package/dist/commands/analyze.js +12 -10
  41. package/dist/commands/analyze.js.map +1 -1
  42. package/dist/commands/connectors.js +2 -2
  43. package/dist/commands/connectors.js.map +1 -1
  44. package/dist/commands/index.d.ts +2 -0
  45. package/dist/commands/index.d.ts.map +1 -1
  46. package/dist/commands/index.js +2 -0
  47. package/dist/commands/index.js.map +1 -1
  48. package/dist/commands/plans.js +1 -1
  49. package/dist/commands/plans.js.map +1 -1
  50. package/dist/commands/record.js +5 -4
  51. package/dist/commands/record.js.map +1 -1
  52. package/dist/commands/rpc.d.ts.map +1 -1
  53. package/dist/commands/rpc.js +220 -3
  54. package/dist/commands/rpc.js.map +1 -1
  55. package/dist/commands/scan.d.ts.map +1 -1
  56. package/dist/commands/scan.js +8 -10
  57. package/dist/commands/scan.js.map +1 -1
  58. package/dist/commands/secrets.d.ts.map +1 -1
  59. package/dist/commands/secrets.js +11 -10
  60. package/dist/commands/secrets.js.map +1 -1
  61. package/dist/commands/sessions.js +2 -2
  62. package/dist/commands/sessions.js.map +1 -1
  63. package/dist/commands/summary.d.ts.map +1 -1
  64. package/dist/commands/summary.js +4 -2
  65. package/dist/commands/summary.js.map +1 -1
  66. package/dist/commands/task.d.ts +14 -0
  67. package/dist/commands/task.d.ts.map +1 -0
  68. package/dist/commands/task.js +520 -0
  69. package/dist/commands/task.js.map +1 -0
  70. package/dist/db/agent-cache-store.d.ts +57 -0
  71. package/dist/db/agent-cache-store.d.ts.map +1 -0
  72. package/dist/db/agent-cache-store.js +99 -0
  73. package/dist/db/agent-cache-store.js.map +1 -0
  74. package/dist/db/connection.d.ts.map +1 -1
  75. package/dist/db/connection.js +86 -1
  76. package/dist/db/connection.js.map +1 -1
  77. package/dist/db/events-store.d.ts +321 -7
  78. package/dist/db/events-store.d.ts.map +1 -1
  79. package/dist/db/events-store.js +659 -31
  80. package/dist/db/events-store.js.map +1 -1
  81. package/dist/db/index.d.ts +2 -0
  82. package/dist/db/index.d.ts.map +1 -1
  83. package/dist/db/index.js +2 -0
  84. package/dist/db/index.js.map +1 -1
  85. package/dist/db/proofs-store.d.ts +8 -1
  86. package/dist/db/proofs-store.d.ts.map +1 -1
  87. package/dist/db/proofs-store.js +18 -8
  88. package/dist/db/proofs-store.js.map +1 -1
  89. package/dist/db/schema.d.ts +27 -3
  90. package/dist/db/schema.d.ts.map +1 -1
  91. package/dist/db/schema.js +201 -5
  92. package/dist/db/schema.js.map +1 -1
  93. package/dist/db/targets-store.d.ts +79 -0
  94. package/dist/db/targets-store.d.ts.map +1 -0
  95. package/dist/db/targets-store.js +150 -0
  96. package/dist/db/targets-store.js.map +1 -0
  97. package/dist/db/tool-analysis.d.ts +15 -3
  98. package/dist/db/tool-analysis.d.ts.map +1 -1
  99. package/dist/db/tool-analysis.js +35 -17
  100. package/dist/db/tool-analysis.js.map +1 -1
  101. package/dist/db/types.d.ts +86 -2
  102. package/dist/db/types.d.ts.map +1 -1
  103. package/dist/db/types.js +1 -1
  104. package/dist/filter/fields.d.ts.map +1 -1
  105. package/dist/filter/fields.js +22 -0
  106. package/dist/filter/fields.js.map +1 -1
  107. package/dist/filter/parser.js +2 -2
  108. package/dist/filter/parser.js.map +1 -1
  109. package/dist/filter/types.d.ts +1 -1
  110. package/dist/filter/types.d.ts.map +1 -1
  111. package/dist/html/analytics.test.ts +682 -0
  112. package/dist/html/analytics.ts +499 -0
  113. package/dist/html/browser.ts +39 -0
  114. package/dist/html/index.ts +97 -0
  115. package/dist/html/rpc-inspector.test.ts +529 -0
  116. package/dist/html/rpc-inspector.ts +1700 -0
  117. package/dist/html/templates.js +4 -4
  118. package/dist/html/templates.js.map +1 -1
  119. package/dist/html/templates.test.ts +861 -0
  120. package/dist/html/templates.ts +3163 -0
  121. package/dist/html/trace-viewer.html +624 -0
  122. package/dist/html/types.d.ts +3 -3
  123. package/dist/html/types.d.ts.map +1 -1
  124. package/dist/html/types.ts +491 -0
  125. package/dist/html/utils.ts +107 -0
  126. package/dist/monitor/data/connectors.d.ts.map +1 -1
  127. package/dist/monitor/data/connectors.js +113 -8
  128. package/dist/monitor/data/connectors.js.map +1 -1
  129. package/dist/monitor/data/popl.js +2 -2
  130. package/dist/monitor/data/popl.js.map +1 -1
  131. package/dist/monitor/routes/api.js +2 -2
  132. package/dist/monitor/routes/api.js.map +1 -1
  133. package/dist/monitor/routes/connectors.js +15 -15
  134. package/dist/monitor/routes/connectors.js.map +1 -1
  135. package/dist/monitor/routes/popl.js +5 -5
  136. package/dist/monitor/routes/popl.js.map +1 -1
  137. package/dist/monitor/templates/components.js +2 -2
  138. package/dist/monitor/templates/components.js.map +1 -1
  139. package/dist/monitor/templates/popl.js +4 -4
  140. package/dist/monitor/templates/popl.js.map +1 -1
  141. package/dist/monitor/types.d.ts +2 -2
  142. package/dist/monitor/types.d.ts.map +1 -1
  143. package/dist/proxy/bridge-utils.d.ts +41 -0
  144. package/dist/proxy/bridge-utils.d.ts.map +1 -0
  145. package/dist/proxy/bridge-utils.js +60 -0
  146. package/dist/proxy/bridge-utils.js.map +1 -0
  147. package/dist/proxy/ipc-client.d.ts.map +1 -1
  148. package/dist/proxy/ipc-client.js +1 -2
  149. package/dist/proxy/ipc-client.js.map +1 -1
  150. package/dist/proxy/ipc-server.d.ts.map +1 -1
  151. package/dist/proxy/ipc-server.js +4 -2
  152. package/dist/proxy/ipc-server.js.map +1 -1
  153. package/dist/proxy/mcp-server.d.ts +31 -0
  154. package/dist/proxy/mcp-server.d.ts.map +1 -1
  155. package/dist/proxy/mcp-server.js +393 -4
  156. package/dist/proxy/mcp-server.js.map +1 -1
  157. package/dist/proxy/types.d.ts +95 -0
  158. package/dist/proxy/types.d.ts.map +1 -1
  159. package/dist/secrets/management.d.ts +2 -2
  160. package/dist/secrets/management.d.ts.map +1 -1
  161. package/dist/secrets/management.js +7 -7
  162. package/dist/secrets/management.js.map +1 -1
  163. package/dist/shell/completer.d.ts.map +1 -1
  164. package/dist/shell/completer.js +16 -0
  165. package/dist/shell/completer.js.map +1 -1
  166. package/dist/shell/context-applicator.d.ts.map +1 -1
  167. package/dist/shell/context-applicator.js +32 -0
  168. package/dist/shell/context-applicator.js.map +1 -1
  169. package/dist/shell/filter-mappers.d.ts +5 -1
  170. package/dist/shell/filter-mappers.d.ts.map +1 -1
  171. package/dist/shell/filter-mappers.js +12 -0
  172. package/dist/shell/filter-mappers.js.map +1 -1
  173. package/dist/shell/find-command.js +13 -13
  174. package/dist/shell/find-command.js.map +1 -1
  175. package/dist/shell/inscribe-commands.js +5 -5
  176. package/dist/shell/inscribe-commands.js.map +1 -1
  177. package/dist/shell/pager/less-pager.d.ts +1 -1
  178. package/dist/shell/pager/less-pager.d.ts.map +1 -1
  179. package/dist/shell/pager/less-pager.js +5 -2
  180. package/dist/shell/pager/less-pager.js.map +1 -1
  181. package/dist/shell/pager/more-pager.d.ts +1 -1
  182. package/dist/shell/pager/more-pager.d.ts.map +1 -1
  183. package/dist/shell/pager/more-pager.js +3 -2
  184. package/dist/shell/pager/more-pager.js.map +1 -1
  185. package/dist/shell/pager/renderer.d.ts.map +1 -1
  186. package/dist/shell/pager/renderer.js +66 -15
  187. package/dist/shell/pager/renderer.js.map +1 -1
  188. package/dist/shell/pager/types.d.ts +5 -2
  189. package/dist/shell/pager/types.d.ts.map +1 -1
  190. package/dist/shell/pager/utils.d.ts +5 -2
  191. package/dist/shell/pager/utils.d.ts.map +1 -1
  192. package/dist/shell/pager/utils.js +14 -17
  193. package/dist/shell/pager/utils.js.map +1 -1
  194. package/dist/shell/pipeline-types.d.ts +12 -4
  195. package/dist/shell/pipeline-types.d.ts.map +1 -1
  196. package/dist/shell/ref-commands.js +7 -7
  197. package/dist/shell/ref-commands.js.map +1 -1
  198. package/dist/shell/ref-resolver.d.ts +15 -15
  199. package/dist/shell/ref-resolver.d.ts.map +1 -1
  200. package/dist/shell/ref-resolver.js +34 -20
  201. package/dist/shell/ref-resolver.js.map +1 -1
  202. package/dist/shell/repl.d.ts +25 -0
  203. package/dist/shell/repl.d.ts.map +1 -1
  204. package/dist/shell/repl.js +285 -51
  205. package/dist/shell/repl.js.map +1 -1
  206. package/dist/shell/router-commands.d.ts +30 -0
  207. package/dist/shell/router-commands.d.ts.map +1 -1
  208. package/dist/shell/router-commands.js +1011 -62
  209. package/dist/shell/router-commands.js.map +1 -1
  210. package/dist/shell/selector.d.ts +1 -1
  211. package/dist/shell/selector.d.ts.map +1 -1
  212. package/dist/shell/selector.js +1 -1
  213. package/dist/shell/selector.js.map +1 -1
  214. package/dist/shell/types.d.ts.map +1 -1
  215. package/dist/shell/types.js +3 -1
  216. package/dist/shell/types.js.map +1 -1
  217. package/dist/shell/where-command.d.ts.map +1 -1
  218. package/dist/shell/where-command.js +19 -3
  219. package/dist/shell/where-command.js.map +1 -1
  220. package/dist/utils/output.d.ts.map +1 -1
  221. package/dist/utils/output.js +7 -1
  222. package/dist/utils/output.js.map +1 -1
  223. package/package.json +2 -2
@@ -0,0 +1,854 @@
1
+ /**
2
+ * A2A Client
3
+ *
4
+ * Client for sending messages to A2A agents via JSON-RPC 2.0.
5
+ * Implements message/send, tasks/get, tasks/list, and tasks/cancel operations.
6
+ *
7
+ * Phase 4 - Client Implementation
8
+ * Phase 2 - Task Management (getTask, listTasks, cancelTask)
9
+ * Phase 2.4 - Task DB event recording
10
+ */
11
+ import { randomUUID } from 'crypto';
12
+ import { isPrivateUrl } from './agent-card.js';
13
+ // Maximum response size (1MB) to prevent DoS
14
+ const MAX_RESPONSE_SIZE = 1024 * 1024;
15
+ // ===== A2A Client =====
16
+ /**
17
+ * A2A Client for sending messages to agents
18
+ */
19
+ export class A2AClient {
20
+ baseUrl;
21
+ defaultHeaders;
22
+ agentCard;
23
+ allowLocal;
24
+ eventsStore; // Phase 2.4: Optional events store for task event recording
25
+ constructor(agentCard, options) {
26
+ this.agentCard = agentCard;
27
+ this.baseUrl = agentCard.url.replace(/\/$/, '');
28
+ this.allowLocal = options?.allowLocal ?? false;
29
+ this.eventsStore = options?.eventsStore; // Phase 2.4
30
+ // SSRF protection: Block private URLs in constructor
31
+ if (isPrivateUrl(this.baseUrl) && !this.allowLocal) {
32
+ throw new Error('Private or local URLs are not allowed');
33
+ }
34
+ this.defaultHeaders = {
35
+ 'Content-Type': 'application/json',
36
+ 'Accept': 'application/json',
37
+ ...options?.headers,
38
+ };
39
+ }
40
+ /**
41
+ * Get EventsStore instance (Phase 2.4)
42
+ * @private
43
+ */
44
+ getEventsStore() {
45
+ return this.eventsStore;
46
+ }
47
+ /**
48
+ * Send a message to the agent
49
+ * POST /message/send (JSON-RPC 2.0)
50
+ */
51
+ async sendMessage(message, options = {}) {
52
+ const { timeout = 30000, headers = {}, blocking = false } = options;
53
+ // Convert string message to A2AMessage with unique messageId
54
+ const messageId = randomUUID();
55
+ const a2aMessage = typeof message === 'string'
56
+ ? { role: 'user', parts: [{ text: message }], messageId }
57
+ : { ...message, messageId: message.messageId ?? messageId };
58
+ // Build JSON-RPC request with unique ID
59
+ const requestId = randomUUID();
60
+ const request = {
61
+ jsonrpc: '2.0',
62
+ id: requestId,
63
+ method: 'message/send',
64
+ params: {
65
+ message: a2aMessage,
66
+ configuration: blocking ? { blocking: true } : undefined,
67
+ },
68
+ };
69
+ // Set up abort controller for timeout
70
+ const controller = new AbortController();
71
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
72
+ try {
73
+ // SSRF protection (defense-in-depth): Double-check URL even though constructor validates
74
+ if (isPrivateUrl(this.baseUrl) && !this.allowLocal) {
75
+ return {
76
+ ok: false,
77
+ error: 'Private or local URLs are not allowed',
78
+ };
79
+ }
80
+ const response = await fetch(this.baseUrl, {
81
+ method: 'POST',
82
+ headers: { ...this.defaultHeaders, ...headers },
83
+ body: JSON.stringify(request),
84
+ signal: controller.signal,
85
+ });
86
+ // Validate Content-Type
87
+ const contentType = response.headers.get('content-type');
88
+ if (!contentType?.includes('application/json')) {
89
+ return {
90
+ ok: false,
91
+ statusCode: response.status,
92
+ error: `Expected JSON response, got ${contentType || 'unknown'}`,
93
+ };
94
+ }
95
+ // Check Content-Length for size limit
96
+ const contentLength = response.headers.get('content-length');
97
+ if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_SIZE) {
98
+ return {
99
+ ok: false,
100
+ statusCode: response.status,
101
+ error: `Response too large: ${contentLength} bytes (max ${MAX_RESPONSE_SIZE})`,
102
+ };
103
+ }
104
+ const responseText = await response.text();
105
+ // Validate actual response size
106
+ if (responseText.length > MAX_RESPONSE_SIZE) {
107
+ return {
108
+ ok: false,
109
+ statusCode: response.status,
110
+ error: `Response too large: ${responseText.length} bytes (max ${MAX_RESPONSE_SIZE})`,
111
+ };
112
+ }
113
+ let responseData;
114
+ try {
115
+ responseData = JSON.parse(responseText);
116
+ }
117
+ catch {
118
+ return {
119
+ ok: false,
120
+ statusCode: response.status,
121
+ error: `Invalid JSON response: ${responseText.slice(0, 200)}`,
122
+ };
123
+ }
124
+ // Handle JSON-RPC error
125
+ if (responseData.error) {
126
+ return {
127
+ ok: false,
128
+ statusCode: response.status,
129
+ error: `${responseData.error.code}: ${responseData.error.message}`,
130
+ };
131
+ }
132
+ // Parse result - can be Task or Message
133
+ if (!responseData.result) {
134
+ return {
135
+ ok: false,
136
+ statusCode: response.status,
137
+ error: 'No result in response',
138
+ };
139
+ }
140
+ const result = responseData.result;
141
+ // Check if result is a Task (has 'status' field)
142
+ if ('status' in result) {
143
+ const task = this.parseTask(result);
144
+ return { ok: true, task };
145
+ }
146
+ // Check if result is a Message (has 'role' field)
147
+ if ('role' in result) {
148
+ const msg = this.parseMessage(result);
149
+ return { ok: true, message: msg };
150
+ }
151
+ return {
152
+ ok: false,
153
+ statusCode: response.status,
154
+ error: `Unknown response type: ${JSON.stringify(result).slice(0, 200)}`,
155
+ };
156
+ }
157
+ catch (error) {
158
+ if (error instanceof Error) {
159
+ if (error.name === 'AbortError') {
160
+ return {
161
+ ok: false,
162
+ error: `Request timeout after ${timeout}ms`,
163
+ };
164
+ }
165
+ return {
166
+ ok: false,
167
+ error: error.message,
168
+ };
169
+ }
170
+ return {
171
+ ok: false,
172
+ error: String(error),
173
+ };
174
+ }
175
+ finally {
176
+ clearTimeout(timeoutId);
177
+ }
178
+ }
179
+ /**
180
+ * Get task by ID (Phase 2 - A2A Protocol Compliant)
181
+ * POST /a2a (JSON-RPC 2.0)
182
+ *
183
+ * @param taskId - The task ID to retrieve
184
+ * @param options - Optional: historyLength, headers, timeout
185
+ * @returns Promise<GetTaskResult> with task or error
186
+ */
187
+ async getTask(taskId, options = {}) {
188
+ const { historyLength, headers = {}, timeout = 30000 } = options;
189
+ const requestId = randomUUID();
190
+ const request = {
191
+ jsonrpc: '2.0',
192
+ id: requestId,
193
+ method: 'tasks/get',
194
+ params: {
195
+ id: taskId,
196
+ ...(historyLength !== undefined && { historyLength }),
197
+ },
198
+ };
199
+ const controller = new AbortController();
200
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
201
+ try {
202
+ const response = await fetch(this.baseUrl, {
203
+ method: 'POST',
204
+ headers: { ...this.defaultHeaders, ...headers },
205
+ body: JSON.stringify(request),
206
+ signal: controller.signal,
207
+ });
208
+ const contentType = response.headers.get('content-type');
209
+ if (!contentType?.includes('application/json')) {
210
+ return {
211
+ ok: false,
212
+ statusCode: response.status,
213
+ error: `Expected JSON response, got ${contentType || 'unknown'}`,
214
+ };
215
+ }
216
+ const responseText = await response.text();
217
+ if (responseText.length > MAX_RESPONSE_SIZE) {
218
+ return {
219
+ ok: false,
220
+ statusCode: response.status,
221
+ error: `Response too large: ${responseText.length} bytes (max ${MAX_RESPONSE_SIZE})`,
222
+ };
223
+ }
224
+ let responseData;
225
+ try {
226
+ responseData = JSON.parse(responseText);
227
+ }
228
+ catch {
229
+ return {
230
+ ok: false,
231
+ statusCode: response.status,
232
+ error: `Invalid JSON response: ${responseText.slice(0, 200)}`,
233
+ };
234
+ }
235
+ if (responseData.error) {
236
+ return {
237
+ ok: false,
238
+ statusCode: response.status,
239
+ error: `${responseData.error.code}: ${responseData.error.message}`,
240
+ };
241
+ }
242
+ if (!responseData.result) {
243
+ return {
244
+ ok: false,
245
+ statusCode: response.status,
246
+ error: 'No result in response',
247
+ };
248
+ }
249
+ const task = this.parseTask(responseData.result);
250
+ return { ok: true, task };
251
+ }
252
+ catch (error) {
253
+ if (error instanceof Error) {
254
+ if (error.name === 'AbortError') {
255
+ return {
256
+ ok: false,
257
+ error: `Request timeout after ${timeout}ms`,
258
+ };
259
+ }
260
+ return {
261
+ ok: false,
262
+ error: error.message,
263
+ };
264
+ }
265
+ return {
266
+ ok: false,
267
+ error: String(error),
268
+ };
269
+ }
270
+ finally {
271
+ clearTimeout(timeoutId);
272
+ }
273
+ }
274
+ /**
275
+ * List tasks with optional filters (Phase 2)
276
+ * POST /a2a (JSON-RPC 2.0)
277
+ *
278
+ * @param params - Optional: contextId, status, pageSize, pageToken, includeArtifacts
279
+ * @returns Promise<ListTasksResult> with tasks list or error
280
+ */
281
+ async listTasks(params) {
282
+ const requestId = randomUUID();
283
+ const request = {
284
+ jsonrpc: '2.0',
285
+ id: requestId,
286
+ method: 'tasks/list',
287
+ params: (params || {}),
288
+ };
289
+ try {
290
+ const response = await fetch(this.baseUrl, {
291
+ method: 'POST',
292
+ headers: this.defaultHeaders,
293
+ body: JSON.stringify(request),
294
+ });
295
+ const contentType = response.headers.get('content-type');
296
+ if (!contentType?.includes('application/json')) {
297
+ return {
298
+ ok: false,
299
+ statusCode: response.status,
300
+ error: `Expected JSON response, got ${contentType || 'unknown'}`,
301
+ };
302
+ }
303
+ const responseText = await response.text();
304
+ if (responseText.length > MAX_RESPONSE_SIZE) {
305
+ return {
306
+ ok: false,
307
+ statusCode: response.status,
308
+ error: `Response too large: ${responseText.length} bytes (max ${MAX_RESPONSE_SIZE})`,
309
+ };
310
+ }
311
+ let responseData;
312
+ try {
313
+ responseData = JSON.parse(responseText);
314
+ }
315
+ catch {
316
+ return {
317
+ ok: false,
318
+ statusCode: response.status,
319
+ error: `Invalid JSON response: ${responseText.slice(0, 200)}`,
320
+ };
321
+ }
322
+ if (responseData.error) {
323
+ return {
324
+ ok: false,
325
+ statusCode: response.status,
326
+ error: `${responseData.error.code}: ${responseData.error.message}`,
327
+ };
328
+ }
329
+ if (!responseData.result) {
330
+ return {
331
+ ok: false,
332
+ statusCode: response.status,
333
+ error: 'No result in response',
334
+ };
335
+ }
336
+ const result = responseData.result;
337
+ const tasks = [];
338
+ if (Array.isArray(result.tasks)) {
339
+ for (const taskData of result.tasks) {
340
+ tasks.push(this.parseTask(taskData));
341
+ }
342
+ }
343
+ const listResponse = {
344
+ tasks,
345
+ nextPageToken: result.nextPageToken ? String(result.nextPageToken) : '',
346
+ pageSize: typeof result.pageSize === 'number' ? result.pageSize : 50,
347
+ totalSize: typeof result.totalSize === 'number' ? result.totalSize : undefined,
348
+ };
349
+ return { ok: true, response: listResponse };
350
+ }
351
+ catch (error) {
352
+ if (error instanceof Error) {
353
+ return {
354
+ ok: false,
355
+ error: error.message,
356
+ };
357
+ }
358
+ return {
359
+ ok: false,
360
+ error: String(error),
361
+ };
362
+ }
363
+ }
364
+ /**
365
+ * Cancel a task (Phase 2 - A2A Protocol Compliant)
366
+ * POST /a2a (JSON-RPC 2.0)
367
+ *
368
+ * @param taskId - The task ID to cancel
369
+ * @returns Promise<CancelTaskResult> with canceled task or error
370
+ */
371
+ async cancelTask(taskId) {
372
+ const requestId = randomUUID();
373
+ const request = {
374
+ jsonrpc: '2.0',
375
+ id: requestId,
376
+ method: 'tasks/cancel',
377
+ params: {
378
+ id: taskId,
379
+ },
380
+ };
381
+ try {
382
+ const response = await fetch(this.baseUrl, {
383
+ method: 'POST',
384
+ headers: this.defaultHeaders,
385
+ body: JSON.stringify(request),
386
+ });
387
+ const contentType = response.headers.get('content-type');
388
+ if (!contentType?.includes('application/json')) {
389
+ return {
390
+ ok: false,
391
+ statusCode: response.status,
392
+ error: `Expected JSON response, got ${contentType || 'unknown'}`,
393
+ };
394
+ }
395
+ const responseText = await response.text();
396
+ if (responseText.length > MAX_RESPONSE_SIZE) {
397
+ return {
398
+ ok: false,
399
+ statusCode: response.status,
400
+ error: `Response too large: ${responseText.length} bytes (max ${MAX_RESPONSE_SIZE})`,
401
+ };
402
+ }
403
+ let responseData;
404
+ try {
405
+ responseData = JSON.parse(responseText);
406
+ }
407
+ catch {
408
+ return {
409
+ ok: false,
410
+ statusCode: response.status,
411
+ error: `Invalid JSON response: ${responseText.slice(0, 200)}`,
412
+ };
413
+ }
414
+ if (responseData.error) {
415
+ return {
416
+ ok: false,
417
+ statusCode: response.status,
418
+ error: `${responseData.error.code}: ${responseData.error.message}`,
419
+ };
420
+ }
421
+ // Parse the canceled task from response
422
+ if (responseData.result) {
423
+ const task = this.parseTask(responseData.result);
424
+ return { ok: true, task };
425
+ }
426
+ return { ok: true };
427
+ }
428
+ catch (error) {
429
+ if (error instanceof Error) {
430
+ return {
431
+ ok: false,
432
+ error: error.message,
433
+ };
434
+ }
435
+ return {
436
+ ok: false,
437
+ error: String(error),
438
+ };
439
+ }
440
+ }
441
+ /**
442
+ * Stream message to agent
443
+ * POST /message/stream (JSON-RPC 2.0 + SSE response)
444
+ */
445
+ async streamMessage(message, options = {}) {
446
+ const { timeout = 60000, headers = {}, onStatus, onArtifact, onMessage, onTask, onError, signal, } = options;
447
+ // Build JSON-RPC request
448
+ const a2aMessage = typeof message === 'string'
449
+ ? { role: 'user', parts: [{ text: message }] }
450
+ : message;
451
+ const requestId = randomUUID();
452
+ const request = {
453
+ jsonrpc: '2.0',
454
+ id: requestId,
455
+ method: 'message/stream',
456
+ params: { message: a2aMessage },
457
+ };
458
+ // Abort controller for timeout
459
+ const controller = new AbortController();
460
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
461
+ // Combine with external signal if provided
462
+ if (signal) {
463
+ signal.addEventListener('abort', () => controller.abort());
464
+ }
465
+ try {
466
+ // SSRF protection (defense-in-depth)
467
+ if (isPrivateUrl(this.baseUrl) && !this.allowLocal) {
468
+ return {
469
+ ok: false,
470
+ error: 'Private or local URLs are not allowed',
471
+ };
472
+ }
473
+ const response = await fetch(`${this.baseUrl}/message/stream`, {
474
+ method: 'POST',
475
+ headers: { ...this.defaultHeaders, ...headers },
476
+ body: JSON.stringify(request),
477
+ signal: controller.signal,
478
+ });
479
+ if (!response.ok) {
480
+ return {
481
+ ok: false,
482
+ error: `HTTP ${response.status}: ${response.statusText}`,
483
+ };
484
+ }
485
+ const contentType = response.headers.get('content-type');
486
+ if (!contentType?.includes('text/event-stream')) {
487
+ return {
488
+ ok: false,
489
+ error: `Expected SSE, got ${contentType}`,
490
+ };
491
+ }
492
+ // Process SSE stream
493
+ const reader = response.body?.getReader();
494
+ if (!reader) {
495
+ return { ok: false, error: 'No response body' };
496
+ }
497
+ const decoder = new TextDecoder();
498
+ let buffer = '';
499
+ let taskId;
500
+ while (true) {
501
+ const { done, value } = await reader.read();
502
+ if (done)
503
+ break;
504
+ buffer += decoder.decode(value, { stream: true });
505
+ const lines = buffer.split('\n');
506
+ buffer = lines.pop() || ''; // Keep incomplete line
507
+ for (const line of lines) {
508
+ if (line.startsWith('data: ')) {
509
+ const data = line.slice(6);
510
+ if (data === '[DONE]')
511
+ continue;
512
+ try {
513
+ const json = JSON.parse(data);
514
+ const event = this.parseStreamEvent(json);
515
+ if (event) {
516
+ switch (event.type) {
517
+ case 'status':
518
+ taskId = event.event.taskId;
519
+ onStatus?.(event.event);
520
+ if (event.event.final) {
521
+ return { ok: true, taskId };
522
+ }
523
+ break;
524
+ case 'artifact':
525
+ onArtifact?.(event.event);
526
+ break;
527
+ case 'message':
528
+ onMessage?.(event.message);
529
+ break;
530
+ case 'task':
531
+ taskId = event.task.id;
532
+ onTask?.(event.task);
533
+ break;
534
+ }
535
+ }
536
+ }
537
+ catch (e) {
538
+ onError?.(`Parse error: ${e}`);
539
+ }
540
+ }
541
+ }
542
+ }
543
+ return { ok: true, taskId };
544
+ }
545
+ catch (error) {
546
+ if (error instanceof Error && error.name === 'AbortError') {
547
+ return { ok: false, error: `Timeout after ${timeout}ms` };
548
+ }
549
+ return { ok: false, error: String(error) };
550
+ }
551
+ finally {
552
+ clearTimeout(timeoutId);
553
+ }
554
+ }
555
+ /**
556
+ * Parse Task from JSON-RPC response
557
+ */
558
+ parseTask(data) {
559
+ const task = {
560
+ id: String(data.id || ''),
561
+ status: this.parseTaskStatus(data.status),
562
+ messages: [],
563
+ artifacts: undefined,
564
+ createdAt: data.createdAt ? String(data.createdAt) : undefined,
565
+ updatedAt: data.updatedAt ? String(data.updatedAt) : undefined,
566
+ contextId: data.contextId ? String(data.contextId) : undefined,
567
+ };
568
+ // Parse messages (A2A uses 'history' field, some agents use 'messages')
569
+ const messageList = Array.isArray(data.history) ? data.history :
570
+ Array.isArray(data.messages) ? data.messages : [];
571
+ if (messageList.length > 0) {
572
+ task.messages = messageList.map(msg => this.parseMessage(msg));
573
+ }
574
+ // Parse artifacts
575
+ if (Array.isArray(data.artifacts)) {
576
+ task.artifacts = data.artifacts.map(art => ({
577
+ name: art.name ? String(art.name) : undefined,
578
+ description: art.description ? String(art.description) : undefined,
579
+ parts: Array.isArray(art.parts)
580
+ ? art.parts.map((part) => this.parsePart(part))
581
+ : [],
582
+ }));
583
+ }
584
+ return task;
585
+ }
586
+ /**
587
+ * Parse Message from JSON-RPC response
588
+ */
589
+ parseMessage(data) {
590
+ if (!data || typeof data !== 'object') {
591
+ return { role: 'user', parts: [] };
592
+ }
593
+ const obj = data;
594
+ const message = {
595
+ // A2A protocol uses 'agent' for assistant responses
596
+ role: (obj.role === 'assistant' || obj.role === 'agent') ? 'assistant' : 'user',
597
+ parts: [],
598
+ metadata: obj.metadata,
599
+ contextId: obj.contextId ? String(obj.contextId) : undefined,
600
+ referenceTaskIds: Array.isArray(obj.referenceTaskIds)
601
+ ? obj.referenceTaskIds.map(String)
602
+ : undefined,
603
+ };
604
+ if (Array.isArray(obj.parts)) {
605
+ message.parts = obj.parts.map(part => this.parsePart(part));
606
+ }
607
+ return message;
608
+ }
609
+ /**
610
+ * Parse Message Part from JSON-RPC response
611
+ */
612
+ parsePart(data) {
613
+ if (!data || typeof data !== 'object') {
614
+ return { text: '' };
615
+ }
616
+ const obj = data;
617
+ // TextPart
618
+ if ('text' in obj && typeof obj.text === 'string') {
619
+ return { text: obj.text };
620
+ }
621
+ // DataPart (file data)
622
+ if ('data' in obj && 'mimeType' in obj) {
623
+ return {
624
+ data: String(obj.data),
625
+ mimeType: String(obj.mimeType),
626
+ };
627
+ }
628
+ // Fallback: if 'text' exists but not a string, convert
629
+ if ('text' in obj) {
630
+ return { text: String(obj.text) };
631
+ }
632
+ return { text: '' };
633
+ }
634
+ /**
635
+ * Parse task status string
636
+ */
637
+ parseTaskStatus(status) {
638
+ if (typeof status !== 'string') {
639
+ return 'pending';
640
+ }
641
+ const validStatuses = [
642
+ 'pending',
643
+ 'working',
644
+ 'input_required',
645
+ 'completed',
646
+ 'failed',
647
+ 'canceled',
648
+ 'rejected',
649
+ ];
650
+ if (validStatuses.includes(status)) {
651
+ return status;
652
+ }
653
+ return 'pending';
654
+ }
655
+ /**
656
+ * Parse SSE event data
657
+ */
658
+ parseStreamEvent(data) {
659
+ if (!data || typeof data !== 'object')
660
+ return null;
661
+ const obj = data;
662
+ // JSON-RPC response wrapper
663
+ const result = obj.result;
664
+ if (!result)
665
+ return null;
666
+ // Check event type
667
+ if ('status' in result && 'taskId' in result) {
668
+ return {
669
+ type: 'status',
670
+ event: this.parseStatusEvent(result),
671
+ };
672
+ }
673
+ if ('artifact' in result && 'taskId' in result) {
674
+ return {
675
+ type: 'artifact',
676
+ event: this.parseArtifactEvent(result),
677
+ };
678
+ }
679
+ if ('id' in result && 'status' in result && 'messages' in result) {
680
+ return {
681
+ type: 'task',
682
+ task: this.parseTask(result),
683
+ };
684
+ }
685
+ if ('role' in result && 'parts' in result) {
686
+ return {
687
+ type: 'message',
688
+ message: this.parseMessage(result),
689
+ };
690
+ }
691
+ return null;
692
+ }
693
+ /**
694
+ * Parse status event
695
+ */
696
+ parseStatusEvent(data) {
697
+ return {
698
+ taskId: String(data.taskId || ''),
699
+ contextId: data.contextId ? String(data.contextId) : undefined,
700
+ status: this.parseTaskStatus(data.status),
701
+ message: data.message ? this.parseMessage(data.message) : undefined,
702
+ final: Boolean(data.final),
703
+ };
704
+ }
705
+ /**
706
+ * Parse artifact event
707
+ */
708
+ parseArtifactEvent(data) {
709
+ const artifact = data.artifact || {};
710
+ return {
711
+ taskId: String(data.taskId || ''),
712
+ contextId: data.contextId ? String(data.contextId) : undefined,
713
+ artifact: {
714
+ name: artifact.name ? String(artifact.name) : undefined,
715
+ description: artifact.description ? String(artifact.description) : undefined,
716
+ parts: Array.isArray(artifact.parts)
717
+ ? artifact.parts.map((p) => this.parsePart(p))
718
+ : [],
719
+ index: typeof artifact.index === 'number' ? artifact.index : undefined,
720
+ append: Boolean(artifact.append),
721
+ lastChunk: Boolean(artifact.lastChunk),
722
+ },
723
+ };
724
+ }
725
+ }
726
+ // ===== Helper Functions =====
727
+ import { TargetsStore } from '../db/targets-store.js';
728
+ import { AgentCacheStore } from '../db/agent-cache-store.js';
729
+ import { fetchAgentCard } from './agent-card.js';
730
+ /**
731
+ * Create A2A client from agent ID
732
+ * Fetches Agent Card from cache or remote endpoint
733
+ */
734
+ export async function createA2AClient(configDir, agentId) {
735
+ const targetsStore = new TargetsStore(configDir);
736
+ const cacheStore = new AgentCacheStore(configDir);
737
+ // Find agent by ID or prefix
738
+ const agents = targetsStore.list({ type: 'agent' });
739
+ const agent = agents.find(a => a.id === agentId || a.id.startsWith(agentId));
740
+ if (!agent) {
741
+ return { ok: false, error: `Agent '${agentId}' not found` };
742
+ }
743
+ if (!agent.enabled) {
744
+ return { ok: false, error: `Agent '${agentId}' is disabled` };
745
+ }
746
+ // Get agent config
747
+ const config = agent.config;
748
+ if (!config.url) {
749
+ return { ok: false, error: `Agent '${agentId}' has no URL configured` };
750
+ }
751
+ // Check cache
752
+ const cached = cacheStore.get(agent.id);
753
+ const now = new Date();
754
+ let agentCard = null;
755
+ if (cached?.agentCard && cached.expiresAt && new Date(cached.expiresAt) > now) {
756
+ // Use cached card
757
+ agentCard = cached.agentCard;
758
+ }
759
+ else if (cached?.agentCard && !cached.expiresAt) {
760
+ // Use cached card (no expiration)
761
+ agentCard = cached.agentCard;
762
+ }
763
+ else {
764
+ // Fetch fresh agent card
765
+ const fetchResult = await fetchAgentCard(config.url, { allowLocal: config.allow_local ?? false });
766
+ if (!fetchResult.ok || !fetchResult.agentCard) {
767
+ return {
768
+ ok: false,
769
+ error: `Failed to fetch agent card: ${fetchResult.error}`,
770
+ };
771
+ }
772
+ agentCard = fetchResult.agentCard;
773
+ // Update cache
774
+ const ttl = config.ttl_seconds || 3600;
775
+ const expiresAt = new Date(now.getTime() + ttl * 1000).toISOString();
776
+ cacheStore.set({
777
+ targetId: agent.id,
778
+ agentCard,
779
+ agentCardHash: fetchResult.hash,
780
+ fetchedAt: now.toISOString(),
781
+ expiresAt,
782
+ });
783
+ }
784
+ // Create client with allowLocal from config
785
+ const client = new A2AClient(agentCard, { allowLocal: config.allow_local ?? false });
786
+ return { ok: true, client, agentCard };
787
+ }
788
+ /**
789
+ * Probe agent capabilities (Phase 2.5)
790
+ *
791
+ * Detects agent capabilities by:
792
+ * 1. Checking Agent Card capabilities field (if present)
793
+ * 2. Probing tasks/list endpoint to detect task support
794
+ *
795
+ * Note: Streaming capability is only detected from Agent Card, not probed,
796
+ * because SSE probing is more complex and unreliable.
797
+ *
798
+ * @param agentCard - The agent's Agent Card
799
+ * @param _allowLocal - Reserved for future use (URL validation)
800
+ * @returns Promise<AgentCapabilities> with detected capabilities
801
+ */
802
+ export async function probeCapabilities(agentCard, _allowLocal = false) {
803
+ // Default capabilities (all false)
804
+ const capabilities = {
805
+ tasks: false,
806
+ streaming: false,
807
+ };
808
+ // First, check Agent Card capabilities field (if present)
809
+ // This is the authoritative source if provided by the agent
810
+ if (agentCard.capabilities) {
811
+ if (agentCard.capabilities.streaming !== undefined) {
812
+ capabilities.streaming = agentCard.capabilities.streaming;
813
+ }
814
+ }
815
+ // Probe tasks/list endpoint
816
+ try {
817
+ const response = await fetch(agentCard.url, {
818
+ method: 'POST',
819
+ headers: {
820
+ 'Content-Type': 'application/json',
821
+ },
822
+ body: JSON.stringify({
823
+ jsonrpc: '2.0',
824
+ id: 'probe-tasks',
825
+ method: 'tasks/list',
826
+ params: {},
827
+ }),
828
+ signal: AbortSignal.timeout(5000), // 5 second timeout for probe
829
+ });
830
+ // Check for successful JSON-RPC response
831
+ // A 200 with { "error": { "code": -32601, ... } } means method not found
832
+ if (response.ok) {
833
+ try {
834
+ const data = await response.json();
835
+ // Only set tasks=true if we get a result (not a JSON-RPC error)
836
+ if (data.result !== undefined && !data.error) {
837
+ capabilities.tasks = true;
838
+ }
839
+ }
840
+ catch {
841
+ // If we can't parse JSON, assume tasks not supported
842
+ }
843
+ }
844
+ }
845
+ catch {
846
+ // On network error or timeout, assume tasks not supported (keep false)
847
+ }
848
+ // Note: We don't probe message/stream here because:
849
+ // 1. It's more complex (SSE)
850
+ // 2. The Agent Card capabilities field is the preferred source
851
+ // 3. Most A2A agents that support tasks also support streaming
852
+ return capabilities;
853
+ }
854
+ //# sourceMappingURL=client.js.map