apcore-a2a 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -0
- package/dist/adapters/agent-card.d.ts +32 -0
- package/dist/adapters/agent-card.d.ts.map +1 -0
- package/dist/adapters/agent-card.js +49 -0
- package/dist/adapters/agent-card.js.map +1 -0
- package/dist/adapters/errors.d.ts +10 -0
- package/dist/adapters/errors.d.ts.map +1 -0
- package/dist/adapters/errors.js +48 -0
- package/dist/adapters/errors.js.map +1 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/parts.d.ts +10 -0
- package/dist/adapters/parts.d.ts.map +1 -0
- package/dist/adapters/parts.js +53 -0
- package/dist/adapters/parts.js.map +1 -0
- package/dist/adapters/schema.d.ts +15 -0
- package/dist/adapters/schema.d.ts.map +1 -0
- package/dist/adapters/schema.js +77 -0
- package/dist/adapters/schema.js.map +1 -0
- package/dist/adapters/skill-mapper.d.ts +20 -0
- package/dist/adapters/skill-mapper.d.ts.map +1 -0
- package/dist/adapters/skill-mapper.js +46 -0
- package/dist/adapters/skill-mapper.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/jwt.d.ts +30 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +76 -0
- package/dist/auth/jwt.js.map +1 -0
- package/dist/auth/middleware.d.ts +10 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +33 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/storage.d.ts +5 -0
- package/dist/auth/storage.d.ts.map +1 -0
- package/dist/auth/storage.js +6 -0
- package/dist/auth/storage.js.map +1 -0
- package/dist/auth/types.d.ts +6 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +174 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/card-fetcher.d.ts +13 -0
- package/dist/client/card-fetcher.d.ts.map +1 -0
- package/dist/client/card-fetcher.js +34 -0
- package/dist/client/card-fetcher.js.map +1 -0
- package/dist/client/client.d.ts +30 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +155 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/exceptions.d.ts +20 -0
- package/dist/client/exceptions.d.ts.map +1 -0
- package/dist/client/exceptions.js +32 -0
- package/dist/client/exceptions.js.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/explorer/handler.d.ts +4 -0
- package/dist/explorer/handler.d.ts.map +1 -0
- package/dist/explorer/handler.js +28 -0
- package/dist/explorer/handler.js.map +1 -0
- package/dist/explorer/index.d.ts +2 -0
- package/dist/explorer/index.d.ts.map +1 -0
- package/dist/explorer/index.html +760 -0
- package/dist/explorer/index.js +2 -0
- package/dist/explorer/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/serve.d.ts +38 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +98 -0
- package/dist/serve.js.map +1 -0
- package/dist/server/executor.d.ts +26 -0
- package/dist/server/executor.d.ts.map +1 -0
- package/dist/server/executor.js +191 -0
- package/dist/server/executor.js.map +1 -0
- package/dist/server/factory.d.ts +33 -0
- package/dist/server/factory.d.ts.map +1 -0
- package/dist/server/factory.js +177 -0
- package/dist/server/factory.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>A2A Explorer</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
11
|
+
background: #f0f2f5;
|
|
12
|
+
color: #1a1a2e;
|
|
13
|
+
line-height: 1.6;
|
|
14
|
+
}
|
|
15
|
+
header {
|
|
16
|
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
17
|
+
color: white;
|
|
18
|
+
padding: 16px 24px;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 12px;
|
|
22
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
23
|
+
}
|
|
24
|
+
header h1 { font-size: 1.4rem; font-weight: 600; }
|
|
25
|
+
header .badge {
|
|
26
|
+
background: #e94560;
|
|
27
|
+
color: white;
|
|
28
|
+
font-size: 0.7rem;
|
|
29
|
+
padding: 2px 8px;
|
|
30
|
+
border-radius: 12px;
|
|
31
|
+
font-weight: 700;
|
|
32
|
+
letter-spacing: 0.5px;
|
|
33
|
+
}
|
|
34
|
+
.layout {
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-columns: 340px 1fr;
|
|
37
|
+
gap: 0;
|
|
38
|
+
min-height: calc(100vh - 60px);
|
|
39
|
+
}
|
|
40
|
+
.sidebar {
|
|
41
|
+
background: white;
|
|
42
|
+
border-right: 1px solid #e2e8f0;
|
|
43
|
+
overflow-y: auto;
|
|
44
|
+
padding: 16px;
|
|
45
|
+
}
|
|
46
|
+
.main {
|
|
47
|
+
padding: 16px;
|
|
48
|
+
overflow-y: auto;
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 16px;
|
|
52
|
+
}
|
|
53
|
+
.card {
|
|
54
|
+
background: white;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
.card-header {
|
|
60
|
+
padding: 12px 16px;
|
|
61
|
+
background: #f8fafc;
|
|
62
|
+
border-bottom: 1px solid #e2e8f0;
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: space-between;
|
|
66
|
+
}
|
|
67
|
+
.card-header h2 {
|
|
68
|
+
font-size: 0.95rem;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
color: #334155;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
letter-spacing: 0.5px;
|
|
73
|
+
}
|
|
74
|
+
.card-body { padding: 16px; }
|
|
75
|
+
.info-grid { display: grid; gap: 8px; }
|
|
76
|
+
.info-row {
|
|
77
|
+
display: flex;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
font-size: 0.875rem;
|
|
80
|
+
}
|
|
81
|
+
.info-label {
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
color: #64748b;
|
|
84
|
+
min-width: 100px;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
}
|
|
87
|
+
.info-value { color: #1e293b; word-break: break-all; }
|
|
88
|
+
.cap-badge {
|
|
89
|
+
display: inline-block;
|
|
90
|
+
background: #dbeafe;
|
|
91
|
+
color: #1d4ed8;
|
|
92
|
+
font-size: 0.72rem;
|
|
93
|
+
padding: 2px 8px;
|
|
94
|
+
border-radius: 12px;
|
|
95
|
+
font-weight: 500;
|
|
96
|
+
margin: 2px;
|
|
97
|
+
}
|
|
98
|
+
.skill-item {
|
|
99
|
+
border: 1px solid #e2e8f0;
|
|
100
|
+
border-radius: 6px;
|
|
101
|
+
margin-bottom: 8px;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
}
|
|
104
|
+
.skill-summary {
|
|
105
|
+
padding: 10px 12px;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
background: #f8fafc;
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
gap: 8px;
|
|
111
|
+
font-size: 0.875rem;
|
|
112
|
+
font-weight: 500;
|
|
113
|
+
user-select: none;
|
|
114
|
+
list-style: none;
|
|
115
|
+
}
|
|
116
|
+
.skill-summary::-webkit-details-marker { display: none; }
|
|
117
|
+
.skill-summary::before {
|
|
118
|
+
content: "▶";
|
|
119
|
+
font-size: 0.7rem;
|
|
120
|
+
color: #94a3b8;
|
|
121
|
+
transition: transform 0.15s;
|
|
122
|
+
display: inline-block;
|
|
123
|
+
}
|
|
124
|
+
details[open] .skill-summary::before { transform: rotate(90deg); }
|
|
125
|
+
.skill-id {
|
|
126
|
+
font-family: monospace;
|
|
127
|
+
background: #e0e7ff;
|
|
128
|
+
color: #3730a3;
|
|
129
|
+
padding: 1px 6px;
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
font-size: 0.8rem;
|
|
132
|
+
}
|
|
133
|
+
.skill-name { color: #475569; }
|
|
134
|
+
.skill-detail { padding: 12px; font-size: 0.82rem; color: #475569; border-top: 1px solid #e2e8f0; }
|
|
135
|
+
.skill-detail p { margin-bottom: 6px; }
|
|
136
|
+
.mode-badge {
|
|
137
|
+
display: inline-block;
|
|
138
|
+
background: #f0fdf4;
|
|
139
|
+
color: #166534;
|
|
140
|
+
border: 1px solid #bbf7d0;
|
|
141
|
+
font-size: 0.72rem;
|
|
142
|
+
padding: 1px 6px;
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
margin: 2px;
|
|
145
|
+
}
|
|
146
|
+
.mode-badge.out {
|
|
147
|
+
background: #fdf4ff;
|
|
148
|
+
color: #7e22ce;
|
|
149
|
+
border-color: #e9d5ff;
|
|
150
|
+
}
|
|
151
|
+
pre.skill-json {
|
|
152
|
+
background: #1e293b;
|
|
153
|
+
color: #e2e8f0;
|
|
154
|
+
padding: 10px;
|
|
155
|
+
border-radius: 4px;
|
|
156
|
+
font-size: 0.75rem;
|
|
157
|
+
overflow-x: auto;
|
|
158
|
+
margin-top: 8px;
|
|
159
|
+
max-height: 200px;
|
|
160
|
+
overflow-y: auto;
|
|
161
|
+
}
|
|
162
|
+
.form-group { margin-bottom: 12px; }
|
|
163
|
+
.form-group label {
|
|
164
|
+
display: block;
|
|
165
|
+
font-size: 0.8rem;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
color: #475569;
|
|
168
|
+
margin-bottom: 4px;
|
|
169
|
+
}
|
|
170
|
+
select, input[type="text"], textarea {
|
|
171
|
+
width: 100%;
|
|
172
|
+
padding: 8px 10px;
|
|
173
|
+
border: 1px solid #cbd5e1;
|
|
174
|
+
border-radius: 6px;
|
|
175
|
+
font-size: 0.875rem;
|
|
176
|
+
font-family: inherit;
|
|
177
|
+
background: white;
|
|
178
|
+
color: #1e293b;
|
|
179
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
180
|
+
}
|
|
181
|
+
select:focus, input[type="text"]:focus, textarea:focus {
|
|
182
|
+
outline: none;
|
|
183
|
+
border-color: #6366f1;
|
|
184
|
+
box-shadow: 0 0 0 3px rgba(99,102,241,0.1);
|
|
185
|
+
}
|
|
186
|
+
textarea {
|
|
187
|
+
resize: vertical;
|
|
188
|
+
min-height: 90px;
|
|
189
|
+
font-family: monospace;
|
|
190
|
+
font-size: 0.82rem;
|
|
191
|
+
}
|
|
192
|
+
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
193
|
+
.btn {
|
|
194
|
+
padding: 8px 18px;
|
|
195
|
+
border: none;
|
|
196
|
+
border-radius: 6px;
|
|
197
|
+
font-size: 0.875rem;
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
cursor: pointer;
|
|
200
|
+
transition: opacity 0.15s, transform 0.1s;
|
|
201
|
+
}
|
|
202
|
+
.btn:hover { opacity: 0.88; }
|
|
203
|
+
.btn:active { transform: scale(0.97); }
|
|
204
|
+
.btn-primary { background: #6366f1; color: white; }
|
|
205
|
+
.btn-stream { background: #0ea5e9; color: white; }
|
|
206
|
+
.btn-danger { background: #ef4444; color: white; }
|
|
207
|
+
.btn-neutral { background: #64748b; color: white; }
|
|
208
|
+
pre.result-box {
|
|
209
|
+
background: #0f172a;
|
|
210
|
+
color: #94a3b8;
|
|
211
|
+
padding: 12px;
|
|
212
|
+
border-radius: 6px;
|
|
213
|
+
font-size: 0.78rem;
|
|
214
|
+
overflow-x: auto;
|
|
215
|
+
max-height: 280px;
|
|
216
|
+
overflow-y: auto;
|
|
217
|
+
margin-top: 10px;
|
|
218
|
+
white-space: pre-wrap;
|
|
219
|
+
word-break: break-word;
|
|
220
|
+
}
|
|
221
|
+
pre.result-box.has-content { color: #e2e8f0; }
|
|
222
|
+
#sse-events {
|
|
223
|
+
min-height: 80px;
|
|
224
|
+
max-height: 320px;
|
|
225
|
+
overflow-y: auto;
|
|
226
|
+
background: #0f172a;
|
|
227
|
+
border-radius: 6px;
|
|
228
|
+
padding: 10px;
|
|
229
|
+
font-size: 0.78rem;
|
|
230
|
+
color: #64748b;
|
|
231
|
+
}
|
|
232
|
+
.sse-event {
|
|
233
|
+
padding: 6px 8px;
|
|
234
|
+
border-bottom: 1px solid #1e293b;
|
|
235
|
+
color: #e2e8f0;
|
|
236
|
+
}
|
|
237
|
+
.sse-event:last-child { border-bottom: none; }
|
|
238
|
+
.event-type {
|
|
239
|
+
display: inline-block;
|
|
240
|
+
font-size: 0.7rem;
|
|
241
|
+
font-weight: 700;
|
|
242
|
+
padding: 1px 6px;
|
|
243
|
+
border-radius: 10px;
|
|
244
|
+
margin-right: 6px;
|
|
245
|
+
text-transform: uppercase;
|
|
246
|
+
}
|
|
247
|
+
.et-status { background: #1d4ed8; color: #bfdbfe; }
|
|
248
|
+
.et-artifact { background: #166534; color: #bbf7d0; }
|
|
249
|
+
.et-error { background: #991b1b; color: #fecaca; }
|
|
250
|
+
.et-other { background: #374151; color: #d1d5db; }
|
|
251
|
+
.state-badge {
|
|
252
|
+
display: inline-block;
|
|
253
|
+
font-size: 0.75rem;
|
|
254
|
+
font-weight: 700;
|
|
255
|
+
padding: 2px 10px;
|
|
256
|
+
border-radius: 12px;
|
|
257
|
+
margin-left: 8px;
|
|
258
|
+
text-transform: uppercase;
|
|
259
|
+
letter-spacing: 0.3px;
|
|
260
|
+
}
|
|
261
|
+
.s-working { background: #fef9c3; color: #854d0e; }
|
|
262
|
+
.s-completed { background: #dcfce7; color: #14532d; }
|
|
263
|
+
.s-failed { background: #fee2e2; color: #7f1d1d; }
|
|
264
|
+
.s-canceled { background: #f1f5f9; color: #475569; }
|
|
265
|
+
.s-submitted { background: #ede9fe; color: #4c1d95; }
|
|
266
|
+
.s-other { background: #e2e8f0; color: #334155; }
|
|
267
|
+
.status-bar {
|
|
268
|
+
font-size: 0.8rem;
|
|
269
|
+
color: #64748b;
|
|
270
|
+
padding: 4px 0 0 0;
|
|
271
|
+
min-height: 20px;
|
|
272
|
+
}
|
|
273
|
+
.status-bar.err { color: #dc2626; }
|
|
274
|
+
.loading { color: #94a3b8; font-style: italic; font-size: 0.82rem; }
|
|
275
|
+
@media (max-width: 768px) {
|
|
276
|
+
.layout { grid-template-columns: 1fr; }
|
|
277
|
+
.sidebar { border-right: none; border-bottom: 1px solid #e2e8f0; }
|
|
278
|
+
}
|
|
279
|
+
</style>
|
|
280
|
+
</head>
|
|
281
|
+
<body>
|
|
282
|
+
|
|
283
|
+
<header>
|
|
284
|
+
<h1>A2A Agent Explorer</h1>
|
|
285
|
+
<span class="badge">A2A</span>
|
|
286
|
+
<span id="agent-name-header" style="margin-left:auto;opacity:0.7;font-size:0.85rem;"></span>
|
|
287
|
+
</header>
|
|
288
|
+
|
|
289
|
+
<div class="layout">
|
|
290
|
+
|
|
291
|
+
<!-- Sidebar: Agent Card + Skills -->
|
|
292
|
+
<aside class="sidebar">
|
|
293
|
+
|
|
294
|
+
<div class="card" style="margin-bottom:16px;">
|
|
295
|
+
<div class="card-header"><h2>Agent Card</h2></div>
|
|
296
|
+
<div class="card-body">
|
|
297
|
+
<div id="card-info" class="info-grid">
|
|
298
|
+
<span class="loading">Loading…</span>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<div class="card">
|
|
304
|
+
<div class="card-header">
|
|
305
|
+
<h2>Skills</h2>
|
|
306
|
+
<span id="skill-count" style="font-size:0.78rem;color:#94a3b8;"></span>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="card-body" style="padding:8px;">
|
|
309
|
+
<div id="skills-list"><span class="loading">Loading…</span></div>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
</aside>
|
|
314
|
+
|
|
315
|
+
<!-- Main: Composer + SSE + Task Viewer -->
|
|
316
|
+
<main class="main">
|
|
317
|
+
|
|
318
|
+
<!-- Message Composer -->
|
|
319
|
+
<div class="card">
|
|
320
|
+
<div class="card-header"><h2>Message Composer</h2></div>
|
|
321
|
+
<div class="card-body">
|
|
322
|
+
<div class="form-group">
|
|
323
|
+
<label for="skill-select">Skill</label>
|
|
324
|
+
<select id="skill-select"></select>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="form-group">
|
|
327
|
+
<label for="message-input">Message (text or JSON parts array)</label>
|
|
328
|
+
<textarea id="message-input" placeholder='Hello! or ["part1","part2"]'></textarea>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="form-group">
|
|
331
|
+
<label for="context-id">Context ID</label>
|
|
332
|
+
<input type="text" id="context-id" placeholder="Auto-generated if empty">
|
|
333
|
+
</div>
|
|
334
|
+
<div class="btn-row">
|
|
335
|
+
<button class="btn btn-primary" onclick="sendMessage()">Send (message/send)</button>
|
|
336
|
+
<button class="btn btn-stream" onclick="streamMessage()">Stream (message/stream)</button>
|
|
337
|
+
<button class="btn btn-neutral" onclick="clearComposer()">Clear</button>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="status-bar" id="composer-status"></div>
|
|
340
|
+
<pre class="result-box" id="send-result">— response will appear here —</pre>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<!-- SSE Stream Viewer -->
|
|
345
|
+
<div class="card">
|
|
346
|
+
<div class="card-header">
|
|
347
|
+
<h2>SSE Stream Viewer</h2>
|
|
348
|
+
<button class="btn btn-neutral" style="font-size:0.75rem;padding:4px 10px;" onclick="clearSSE()">Clear</button>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="card-body" style="padding:8px;">
|
|
351
|
+
<div id="sse-events"><span style="color:#4b5563;font-style:italic;font-size:0.82rem;">No stream events yet.</span></div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<!-- Task State Viewer -->
|
|
356
|
+
<div class="card">
|
|
357
|
+
<div class="card-header"><h2>Task Viewer</h2></div>
|
|
358
|
+
<div class="card-body">
|
|
359
|
+
<div class="form-group">
|
|
360
|
+
<label for="task-id-input">Task ID</label>
|
|
361
|
+
<input type="text" id="task-id-input" placeholder="Enter task ID">
|
|
362
|
+
</div>
|
|
363
|
+
<div class="btn-row">
|
|
364
|
+
<button class="btn btn-primary" onclick="getTask()">Fetch (tasks/get)</button>
|
|
365
|
+
<button class="btn btn-danger" onclick="cancelTask()">Cancel (tasks/cancel)</button>
|
|
366
|
+
</div>
|
|
367
|
+
<div class="status-bar" id="task-status"></div>
|
|
368
|
+
<pre class="result-box" id="task-result">— task info will appear here —</pre>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
</main>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<script>
|
|
376
|
+
"use strict";
|
|
377
|
+
|
|
378
|
+
// Derive agent base URL and explorer prefix from window.location
|
|
379
|
+
const EXPLORER_PREFIX = window.location.pathname.replace(/\/+$/, "");
|
|
380
|
+
const AGENT_BASE = window.location.origin;
|
|
381
|
+
|
|
382
|
+
let agentCard = null;
|
|
383
|
+
let rpcId = 1;
|
|
384
|
+
|
|
385
|
+
// ── Bootstrap ────────────────────────────────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
async function init() {
|
|
388
|
+
try {
|
|
389
|
+
const res = await fetch(EXPLORER_PREFIX + "/agent-card");
|
|
390
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
391
|
+
agentCard = await res.json();
|
|
392
|
+
renderCard(agentCard);
|
|
393
|
+
renderSkills(agentCard.skills || []);
|
|
394
|
+
populateSkillSelect(agentCard.skills || []);
|
|
395
|
+
document.getElementById("agent-name-header").textContent = agentCard.name || "";
|
|
396
|
+
} catch (e) {
|
|
397
|
+
document.getElementById("card-info").innerHTML =
|
|
398
|
+
`<span style="color:#dc2626">Failed to load agent card: ${esc(e.message)}</span>`;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── Render helpers ───────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
function esc(s) {
|
|
405
|
+
return String(s)
|
|
406
|
+
.replace(/&/g, "&")
|
|
407
|
+
.replace(/</g, "<")
|
|
408
|
+
.replace(/>/g, ">")
|
|
409
|
+
.replace(/"/g, """);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function renderCard(card) {
|
|
413
|
+
const caps = card.capabilities || {};
|
|
414
|
+
const capBadges = Object.entries(caps)
|
|
415
|
+
.filter(([, v]) => v)
|
|
416
|
+
.map(([k]) => `<span class="cap-badge">${esc(k)}</span>`)
|
|
417
|
+
.join("");
|
|
418
|
+
|
|
419
|
+
document.getElementById("card-info").innerHTML = `
|
|
420
|
+
<div class="info-row">
|
|
421
|
+
<span class="info-label">Name</span>
|
|
422
|
+
<span class="info-value">${esc(card.name || "")}</span>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="info-row">
|
|
425
|
+
<span class="info-label">Description</span>
|
|
426
|
+
<span class="info-value">${esc(card.description || "—")}</span>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="info-row">
|
|
429
|
+
<span class="info-label">Version</span>
|
|
430
|
+
<span class="info-value">${esc(card.version || "")}</span>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="info-row">
|
|
433
|
+
<span class="info-label">URL</span>
|
|
434
|
+
<span class="info-value"><a href="${esc(card.url || "#")}" target="_blank" rel="noopener noreferrer">${esc(card.url || "")}</a></span>
|
|
435
|
+
</div>
|
|
436
|
+
${capBadges ? `<div class="info-row"><span class="info-label">Capabilities</span><span class="info-value">${capBadges}</span></div>` : ""}
|
|
437
|
+
`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function renderSkills(skills) {
|
|
441
|
+
document.getElementById("skill-count").textContent = skills.length + " skill" + (skills.length !== 1 ? "s" : "");
|
|
442
|
+
if (!skills.length) {
|
|
443
|
+
document.getElementById("skills-list").innerHTML = "<span style='color:#94a3b8;font-size:0.82rem;'>No skills defined.</span>";
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const html = skills.map(s => {
|
|
447
|
+
const inModes = (s.inputModes || []).map(m => `<span class="mode-badge">${esc(m)}</span>`).join("");
|
|
448
|
+
const outModes = (s.outputModes || []).map(m => `<span class="mode-badge out">${esc(m)}</span>`).join("");
|
|
449
|
+
const tags = (s.tags || []).map(t => `<span class="cap-badge">${esc(t)}</span>`).join("");
|
|
450
|
+
const examples = (s.examples || []).slice(0, 3);
|
|
451
|
+
const examplesHtml = examples.length
|
|
452
|
+
? `<p><strong>Examples:</strong></p><ul style="margin-left:16px;font-size:0.8rem;">${examples.map(e => `<li>${esc(String(e))}</li>`).join("")}</ul>`
|
|
453
|
+
: "";
|
|
454
|
+
return `
|
|
455
|
+
<details class="skill-item">
|
|
456
|
+
<summary class="skill-summary">
|
|
457
|
+
<span class="skill-id">${esc(s.id)}</span>
|
|
458
|
+
<span class="skill-name">${esc(s.name || s.id)}</span>
|
|
459
|
+
</summary>
|
|
460
|
+
<div class="skill-detail">
|
|
461
|
+
${s.description ? `<p>${esc(s.description)}</p>` : ""}
|
|
462
|
+
${tags ? `<p style="margin-top:4px;">${tags}</p>` : ""}
|
|
463
|
+
${inModes ? `<p style="margin-top:6px;"><strong>Input:</strong> ${inModes}</p>` : ""}
|
|
464
|
+
${outModes ? `<p><strong>Output:</strong> ${outModes}</p>` : ""}
|
|
465
|
+
${examplesHtml}
|
|
466
|
+
<pre class="skill-json">${esc(JSON.stringify(s, null, 2))}</pre>
|
|
467
|
+
</div>
|
|
468
|
+
</details>
|
|
469
|
+
`;
|
|
470
|
+
}).join("");
|
|
471
|
+
document.getElementById("skills-list").innerHTML = html;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function populateSkillSelect(skills) {
|
|
475
|
+
const sel = document.getElementById("skill-select");
|
|
476
|
+
if (!skills.length) {
|
|
477
|
+
sel.innerHTML = `<option value="">— no skills —</option>`;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
sel.innerHTML = skills.map(s =>
|
|
481
|
+
`<option value="${esc(s.id)}">${esc(s.name || s.id)} (${esc(s.id)})</option>`
|
|
482
|
+
).join("");
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Utilities ────────────────────────────────────────────────────────────────
|
|
486
|
+
|
|
487
|
+
function generateId() {
|
|
488
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
489
|
+
return crypto.randomUUID();
|
|
490
|
+
}
|
|
491
|
+
return "id-" + Date.now() + "-" + Math.random().toString(36).slice(2);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function getAuthHeaders() {
|
|
495
|
+
const token = sessionStorage.getItem("auth_token");
|
|
496
|
+
return token ? { "Authorization": "Bearer " + token } : {};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function buildMessageParams(skillId, text, contextId) {
|
|
500
|
+
let parts;
|
|
501
|
+
try {
|
|
502
|
+
const parsed = JSON.parse(text);
|
|
503
|
+
parts = Array.isArray(parsed) ? parsed : [{ kind: "text", text }];
|
|
504
|
+
} catch {
|
|
505
|
+
parts = [{ kind: "text", text }];
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
message: {
|
|
509
|
+
role: "user",
|
|
510
|
+
parts,
|
|
511
|
+
metadata: { skillId },
|
|
512
|
+
contextId,
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function nextId() { return rpcId++; }
|
|
518
|
+
|
|
519
|
+
function setStatus(elemId, msg, isErr) {
|
|
520
|
+
const el = document.getElementById(elemId);
|
|
521
|
+
el.textContent = msg;
|
|
522
|
+
el.className = "status-bar" + (isErr ? " err" : "");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function setResult(elemId, data) {
|
|
526
|
+
const el = document.getElementById(elemId);
|
|
527
|
+
el.textContent = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
528
|
+
el.className = "result-box has-content";
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function taskStateBadge(state) {
|
|
532
|
+
const map = {
|
|
533
|
+
working: "s-working", completed: "s-completed", failed: "s-failed",
|
|
534
|
+
canceled: "s-canceled", submitted: "s-submitted",
|
|
535
|
+
};
|
|
536
|
+
const cls = map[state] || "s-other";
|
|
537
|
+
return `<span class="state-badge ${cls}">${esc(state || "unknown")}</span>`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ── Message Composer ─────────────────────────────────────────────────────────
|
|
541
|
+
|
|
542
|
+
async function sendMessage() {
|
|
543
|
+
const skillId = document.getElementById("skill-select").value;
|
|
544
|
+
const text = document.getElementById("message-input").value.trim();
|
|
545
|
+
const contextId = document.getElementById("context-id").value.trim() || generateId();
|
|
546
|
+
if (!text) { setStatus("composer-status", "Message cannot be empty.", true); return; }
|
|
547
|
+
|
|
548
|
+
setStatus("composer-status", "Sending…");
|
|
549
|
+
const body = {
|
|
550
|
+
jsonrpc: "2.0",
|
|
551
|
+
id: nextId(),
|
|
552
|
+
method: "message/send",
|
|
553
|
+
params: buildMessageParams(skillId, text, contextId),
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
const res = await fetch(AGENT_BASE + "/", {
|
|
558
|
+
method: "POST",
|
|
559
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
560
|
+
body: JSON.stringify(body),
|
|
561
|
+
});
|
|
562
|
+
const data = await res.json();
|
|
563
|
+
setResult("send-result", data);
|
|
564
|
+
// If the response contains a task ID, populate the task viewer
|
|
565
|
+
const taskId = data?.result?.id || data?.result?.task?.id;
|
|
566
|
+
if (taskId) {
|
|
567
|
+
document.getElementById("task-id-input").value = taskId;
|
|
568
|
+
setStatus("composer-status", "Sent. Task ID: " + taskId);
|
|
569
|
+
} else {
|
|
570
|
+
setStatus("composer-status", "Sent. Status: " + res.status);
|
|
571
|
+
}
|
|
572
|
+
} catch (e) {
|
|
573
|
+
setStatus("composer-status", "Error: " + e.message, true);
|
|
574
|
+
setResult("send-result", "Error: " + e.message);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function streamMessage() {
|
|
579
|
+
const skillId = document.getElementById("skill-select").value;
|
|
580
|
+
const text = document.getElementById("message-input").value.trim();
|
|
581
|
+
const contextId = document.getElementById("context-id").value.trim() || generateId();
|
|
582
|
+
if (!text) { setStatus("composer-status", "Message cannot be empty.", true); return; }
|
|
583
|
+
|
|
584
|
+
setStatus("composer-status", "Streaming…");
|
|
585
|
+
const body = {
|
|
586
|
+
jsonrpc: "2.0",
|
|
587
|
+
id: nextId(),
|
|
588
|
+
method: "message/stream",
|
|
589
|
+
params: buildMessageParams(skillId, text, contextId),
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const eventsDiv = document.getElementById("sse-events");
|
|
593
|
+
eventsDiv.innerHTML = "<span style='color:#60a5fa;font-style:italic;font-size:0.82rem;'>Stream started…</span>";
|
|
594
|
+
|
|
595
|
+
fetch(AGENT_BASE + "/", {
|
|
596
|
+
method: "POST",
|
|
597
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
598
|
+
body: JSON.stringify(body),
|
|
599
|
+
}).then(res => {
|
|
600
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
601
|
+
const reader = res.body.getReader();
|
|
602
|
+
const decoder = new TextDecoder();
|
|
603
|
+
let buffer = "";
|
|
604
|
+
|
|
605
|
+
function readChunk() {
|
|
606
|
+
reader.read().then(({ done, value }) => {
|
|
607
|
+
if (done) {
|
|
608
|
+
setStatus("composer-status", "Stream complete.");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
buffer += decoder.decode(value, { stream: true });
|
|
612
|
+
const blocks = buffer.split("\n\n");
|
|
613
|
+
buffer = blocks.pop();
|
|
614
|
+
blocks.forEach(block => {
|
|
615
|
+
const lines = block.split("\n");
|
|
616
|
+
let eventType = "message";
|
|
617
|
+
let dataStr = "";
|
|
618
|
+
for (const line of lines) {
|
|
619
|
+
if (line.startsWith("event:")) eventType = line.slice(6).trim();
|
|
620
|
+
else if (line.startsWith("data:")) dataStr += line.slice(5).trim();
|
|
621
|
+
}
|
|
622
|
+
if (!dataStr) return;
|
|
623
|
+
appendSSEEvent(eventType, dataStr);
|
|
624
|
+
});
|
|
625
|
+
readChunk();
|
|
626
|
+
}).catch(e => {
|
|
627
|
+
setStatus("composer-status", "Stream error: " + e.message, true);
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
readChunk();
|
|
631
|
+
}).catch(e => {
|
|
632
|
+
setStatus("composer-status", "Stream error: " + e.message, true);
|
|
633
|
+
eventsDiv.innerHTML = `<span style="color:#f87171;">Error: ${esc(e.message)}</span>`;
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function appendSSEEvent(type, dataStr) {
|
|
638
|
+
const eventsDiv = document.getElementById("sse-events");
|
|
639
|
+
// Remove placeholder
|
|
640
|
+
const placeholder = eventsDiv.querySelector("span");
|
|
641
|
+
if (placeholder) placeholder.remove();
|
|
642
|
+
|
|
643
|
+
let parsed = null;
|
|
644
|
+
try { parsed = JSON.parse(dataStr); } catch {}
|
|
645
|
+
|
|
646
|
+
const typeCls = type === "message" || type === "status" ? "et-status"
|
|
647
|
+
: type === "artifact" ? "et-artifact"
|
|
648
|
+
: type === "error" ? "et-error"
|
|
649
|
+
: "et-other";
|
|
650
|
+
|
|
651
|
+
const div = document.createElement("div");
|
|
652
|
+
div.className = "sse-event";
|
|
653
|
+
|
|
654
|
+
// Extract state for badge
|
|
655
|
+
const state = parsed?.result?.status?.state || parsed?.status?.state;
|
|
656
|
+
const stateBadge = state ? taskStateBadge(state) : "";
|
|
657
|
+
const taskId = parsed?.result?.id || parsed?.id;
|
|
658
|
+
|
|
659
|
+
if (taskId && !document.getElementById("task-id-input").value) {
|
|
660
|
+
document.getElementById("task-id-input").value = taskId;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
div.innerHTML = `
|
|
664
|
+
<span class="event-type ${typeCls}">${esc(type)}</span>
|
|
665
|
+
${stateBadge}
|
|
666
|
+
<span style="color:#94a3b8;font-size:0.72rem;margin-left:6px;">${new Date().toLocaleTimeString()}</span>
|
|
667
|
+
<pre style="margin-top:4px;color:#cbd5e1;font-size:0.75rem;white-space:pre-wrap;word-break:break-word;">${esc(parsed ? JSON.stringify(parsed, null, 2) : dataStr)}</pre>
|
|
668
|
+
`;
|
|
669
|
+
eventsDiv.appendChild(div);
|
|
670
|
+
eventsDiv.scrollTop = eventsDiv.scrollHeight;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function clearSSE() {
|
|
674
|
+
document.getElementById("sse-events").innerHTML =
|
|
675
|
+
"<span style='color:#4b5563;font-style:italic;font-size:0.82rem;'>Cleared.</span>";
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function clearComposer() {
|
|
679
|
+
document.getElementById("message-input").value = "";
|
|
680
|
+
document.getElementById("context-id").value = "";
|
|
681
|
+
document.getElementById("send-result").textContent = "— response will appear here —";
|
|
682
|
+
document.getElementById("send-result").className = "result-box";
|
|
683
|
+
setStatus("composer-status", "");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ── Task Viewer ──────────────────────────────────────────────────────────────
|
|
687
|
+
|
|
688
|
+
async function getTask() {
|
|
689
|
+
const taskId = document.getElementById("task-id-input").value.trim();
|
|
690
|
+
if (!taskId) { setStatus("task-status", "Enter a task ID.", true); return; }
|
|
691
|
+
|
|
692
|
+
setStatus("task-status", "Fetching…");
|
|
693
|
+
const body = {
|
|
694
|
+
jsonrpc: "2.0",
|
|
695
|
+
id: nextId(),
|
|
696
|
+
method: "tasks/get",
|
|
697
|
+
params: { id: taskId },
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
const res = await fetch(AGENT_BASE + "/", {
|
|
702
|
+
method: "POST",
|
|
703
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
704
|
+
body: JSON.stringify(body),
|
|
705
|
+
});
|
|
706
|
+
const data = await res.json();
|
|
707
|
+
setResult("task-result", data);
|
|
708
|
+
const state = data?.result?.status?.state;
|
|
709
|
+
setStatus("task-status", state ? "State: " + state : "Fetched.");
|
|
710
|
+
} catch (e) {
|
|
711
|
+
setStatus("task-status", "Error: " + e.message, true);
|
|
712
|
+
setResult("task-result", "Error: " + e.message);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function cancelTask() {
|
|
717
|
+
const taskId = document.getElementById("task-id-input").value.trim();
|
|
718
|
+
if (!taskId) { setStatus("task-status", "Enter a task ID.", true); return; }
|
|
719
|
+
|
|
720
|
+
setStatus("task-status", "Canceling…");
|
|
721
|
+
const body = {
|
|
722
|
+
jsonrpc: "2.0",
|
|
723
|
+
id: nextId(),
|
|
724
|
+
method: "tasks/cancel",
|
|
725
|
+
params: { id: taskId },
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
const res = await fetch(AGENT_BASE + "/", {
|
|
730
|
+
method: "POST",
|
|
731
|
+
headers: { "Content-Type": "application/json", ...getAuthHeaders() },
|
|
732
|
+
body: JSON.stringify(body),
|
|
733
|
+
});
|
|
734
|
+
const data = await res.json();
|
|
735
|
+
setResult("task-result", data);
|
|
736
|
+
setStatus("task-status", "Cancel request sent.");
|
|
737
|
+
} catch (e) {
|
|
738
|
+
setStatus("task-status", "Error: " + e.message, true);
|
|
739
|
+
setResult("task-result", "Error: " + e.message);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// ── Keyboard shortcuts ───────────────────────────────────────────────────────
|
|
744
|
+
|
|
745
|
+
document.addEventListener("keydown", function(e) {
|
|
746
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
|
|
747
|
+
const focused = document.activeElement;
|
|
748
|
+
if (focused && focused.id === "message-input") {
|
|
749
|
+
e.preventDefault();
|
|
750
|
+
sendMessage();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// ── Init ─────────────────────────────────────────────────────────────────────
|
|
756
|
+
|
|
757
|
+
init().catch(console.error);
|
|
758
|
+
</script>
|
|
759
|
+
</body>
|
|
760
|
+
</html>
|