@ai_mind/dsh-a2a 1.1.0 → 1.1.2
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 +36 -5
- package/client.js +14 -14
- package/lib/panel.js +5 -2
- package/lib/server.js +111 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,6 +228,16 @@ JSON-RPC 方法名:`SendMessage`、`SendStreamingMessage`、`GetTask`、`ListT
|
|
|
228
228
|
|
|
229
229
|
凭据来源(可组合):`auth.clients[{id,token}]`、`auth.tokens[]`、`auth.token`、`auth.tokenEnv`(环境变量,支持逗号/换行分隔多个)。启用鉴权后 Agent Card 会自动声明 `securitySchemes` 与 `securityRequirements`。
|
|
230
230
|
|
|
231
|
+
内联令牌可以直接写在插件配置里(设置页的「A2A 访问令牌(内联)」字段等价):
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
auth:
|
|
235
|
+
mode: bearer
|
|
236
|
+
token: 'your-long-random-token'
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
> 在设置页输入令牌时,如果 `auth.mode` 还是 `none`,保存会自动切换为 `bearer`。令牌不会回显;它会以明文保存在 profile 的 `cordis.patch.yml`(权限 600),生产环境建议改用 `auth.tokenEnv` 或 `auth.clients`。
|
|
240
|
+
|
|
231
241
|
**每个凭据是一个独立的租户边界**:任务与 Push 配置按凭据 id 隔离,客户端 A 看不到客户端 B 的任务。未鉴权时所有 loopback 调用方共享 `anonymous` 作用域。
|
|
232
242
|
|
|
233
243
|
### 默认安全姿态
|
|
@@ -237,7 +247,7 @@ JSON-RPC 方法名:`SendMessage`、`SendStreamingMessage`、`GetTask`、`ListT
|
|
|
237
247
|
- 请求体默认上限 8 MiB;单个 `raw` 附件默认上限 16 MiB。
|
|
238
248
|
- 公开卡与受保护端点分离:卡片可公开发现,其余端点一律鉴权。
|
|
239
249
|
|
|
240
|
-
> **重要**:暴露到公网前请务必配置 `auth
|
|
250
|
+
> **重要**:暴露到公网前请务必配置 `auth`、正确的 `Host`/`X-Forwarded-*`(或 `server.publicUrl`),并将 `agent.sandboxMode`/`agent.approvalPolicy` 调整到符合你信任模型的取值。A2A 调用方能够让 Agent 在其工作区内执行任务,这与给陌生人一个 Harness 会话等价。
|
|
241
251
|
>
|
|
242
252
|
> 强烈建议把 `agent.cwd` 指向一个**专用工作区**(而不是默认的家目录):`workspace-write` 的边界就是该目录。公网部署还应在反向代理层配置限流、请求体上限与 TLS。
|
|
243
253
|
|
|
@@ -299,11 +309,32 @@ server:
|
|
|
299
309
|
|
|
300
310
|
- A2A 在 `http://127.0.0.1:41241/a2a` 单独监听(REST 为 `/a2a/rest`),Agent Card 和 `health` 也在该端口;
|
|
301
311
|
- DSH Web 端口只保留卡片镜像 `/.well-known/agent-card.json`(供设置页发现)和 `<mountPath>/panel` 配置面板(仍使用 GUI 登录凭据,不暴露在 A2A 端口上);
|
|
302
|
-
- Agent Card
|
|
303
|
-
- 独立监听本身是 HTTP;公网部署时由反向代理终止 TLS
|
|
312
|
+
- Agent Card 和 `health` 的 URL 默认根据**请求的 `Host`** 推导;设置 `server.trustProxy: true` 后同时使用 `X-Forwarded-Host` / `X-Forwarded-Proto`。`server.publicUrl` 始终优先;
|
|
313
|
+
- 独立监听本身是 HTTP;公网部署时由反向代理终止 TLS,并转发 `Host` 与 `X-Forwarded-*`。
|
|
304
314
|
|
|
305
315
|
本仓库的 `cordis.patch.yml` 默认使用 `127.0.0.1:41241`;修改 `server.port`(或在设置页的“监听端口”保存)后插件会重载并切换监听。若端口被占用,设置页会显示监听错误。
|
|
306
316
|
|
|
317
|
+
### 反向代理
|
|
318
|
+
|
|
319
|
+
A2A 前面的 Nginx/Caddy 等反代需要保留原始 `Host` 和协议头:
|
|
320
|
+
|
|
321
|
+
```nginx
|
|
322
|
+
proxy_set_header Host $host;
|
|
323
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
324
|
+
proxy_set_header X-Forwarded-Host $host;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
并设置:
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
server:
|
|
331
|
+
trustProxy: true
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
这样 Agent Card 与 `health` 会返回外部 origin(例如 `https://a2a.example.com/a2a`),不需要写死 `publicUrl`。如果代理无法传递这些头,再显式设置 `server.publicUrl`。
|
|
335
|
+
|
|
336
|
+
> 设置页使用的**卡片镜像**仍位于 DSH Web 的 origin:它使用 `server.publicUrl`,否则使用 A2A 监听地址(独立端口下是 `http://host:port`)。如果 GUI 与 A2A 通过不同外部域名暴露,请设置 `publicUrl`。
|
|
337
|
+
|
|
307
338
|
---
|
|
308
339
|
|
|
309
340
|
## 配置参考
|
|
@@ -316,10 +347,10 @@ enabled: true
|
|
|
316
347
|
server:
|
|
317
348
|
mountPath: /a2a
|
|
318
349
|
cardPath: /.well-known/agent-card.json
|
|
319
|
-
publicUrl: null #
|
|
350
|
+
publicUrl: null # 显式对外 origin;留空则按请求 Host / X-Forwarded-* 推导
|
|
320
351
|
host: 127.0.0.1 # 独立监听的绑定地址(仅 server.port 非 null 时生效)
|
|
321
352
|
port: null # null = 复用 DSH Web 端口;1-65535 = 独立监听
|
|
322
|
-
trustProxy: false # 反向代理后设为 true,以使用 X-Forwarded-For
|
|
353
|
+
trustProxy: false # 反向代理后设为 true,以使用 X-Forwarded-For 并推导对外地址
|
|
323
354
|
streaming: true
|
|
324
355
|
pushNotifications: true
|
|
325
356
|
extendedCard: false # 启用鉴权后才生效
|
package/client.js
CHANGED
|
@@ -72,11 +72,11 @@ window.__ModuleLoader__.load({
|
|
|
72
72
|
saveError: '保存失败',
|
|
73
73
|
reload: '重新读取',
|
|
74
74
|
dirty: '有未保存的修改',
|
|
75
|
-
token: '
|
|
76
|
-
tokenHint: '
|
|
77
|
-
tokenPlaceholder: '
|
|
75
|
+
token: 'A2A 访问令牌(内联)',
|
|
76
|
+
tokenHint: '保存后写入插件配置的 auth.token;如果鉴权模式为 none,会自动切换为 bearer。值不会回显。',
|
|
77
|
+
tokenPlaceholder: '输入新的访问令牌',
|
|
78
78
|
clearToken: '清除已保存的内联令牌',
|
|
79
|
-
tokenConfigured: '
|
|
79
|
+
tokenConfigured: '已配置内联令牌(值不会回显)',
|
|
80
80
|
tokenNotConfigured: '未配置内联令牌',
|
|
81
81
|
tokenEnvConfigured: '环境变量已配置:{name}',
|
|
82
82
|
openCard: '打开 Agent Card',
|
|
@@ -183,11 +183,11 @@ window.__ModuleLoader__.load({
|
|
|
183
183
|
saveError: 'Save failed',
|
|
184
184
|
reload: 'Reload',
|
|
185
185
|
dirty: 'Unsaved changes',
|
|
186
|
-
token: '
|
|
187
|
-
tokenHint: '
|
|
188
|
-
tokenPlaceholder: '
|
|
186
|
+
token: 'A2A access token (inline)',
|
|
187
|
+
tokenHint: 'Saved as auth.token in the plugin config. A mode of "none" switches to bearer automatically. The value is never echoed.',
|
|
188
|
+
tokenPlaceholder: 'Enter a new access token',
|
|
189
189
|
clearToken: 'Clear the saved inline token',
|
|
190
|
-
tokenConfigured: 'Inline token configured',
|
|
190
|
+
tokenConfigured: 'Inline token configured (value is never shown)',
|
|
191
191
|
tokenNotConfigured: 'No inline token',
|
|
192
192
|
tokenEnvConfigured: 'Environment variable: {name}',
|
|
193
193
|
openCard: 'Open Agent Card',
|
|
@@ -653,7 +653,7 @@ window.__ModuleLoader__.load({
|
|
|
653
653
|
const secretBlock = panel
|
|
654
654
|
? h(
|
|
655
655
|
'div',
|
|
656
|
-
{ className: 'a2aui-group' },
|
|
656
|
+
{ className: 'a2aui-group', key: 'inline-token' },
|
|
657
657
|
h('h5', { className: 'a2aui-group-title' }, t('token')),
|
|
658
658
|
h(
|
|
659
659
|
'div',
|
|
@@ -704,8 +704,8 @@ window.__ModuleLoader__.load({
|
|
|
704
704
|
? h(
|
|
705
705
|
'div',
|
|
706
706
|
{ className: 'a2aui-form' },
|
|
707
|
-
...groups.
|
|
708
|
-
h(
|
|
707
|
+
...groups.flatMap(([group, fields]) => {
|
|
708
|
+
const node = h(
|
|
709
709
|
'div',
|
|
710
710
|
{ className: 'a2aui-group', key: group },
|
|
711
711
|
h('h5', { className: 'a2aui-group-title' }, groupLabel(t, group)),
|
|
@@ -717,9 +717,9 @@ window.__ModuleLoader__.load({
|
|
|
717
717
|
h('div', null, renderControl(field)),
|
|
718
718
|
),
|
|
719
719
|
),
|
|
720
|
-
)
|
|
721
|
-
|
|
722
|
-
|
|
720
|
+
)
|
|
721
|
+
return group === 'auth' ? [node, secretBlock] : [node]
|
|
722
|
+
}),
|
|
723
723
|
h(
|
|
724
724
|
'div',
|
|
725
725
|
{ className: 'a2aui-form-foot' },
|
package/lib/panel.js
CHANGED
|
@@ -286,7 +286,7 @@ export function createPanelRouter({ ctx, config, mountPath, logger, status, heal
|
|
|
286
286
|
res.setHeader('Cache-Control', 'no-store')
|
|
287
287
|
res.json({
|
|
288
288
|
ok: true,
|
|
289
|
-
plugin: { name: PLUGIN_NAME, version: '1.1.
|
|
289
|
+
plugin: { name: PLUGIN_NAME, version: '1.1.2' },
|
|
290
290
|
mountPath,
|
|
291
291
|
...payload,
|
|
292
292
|
...(typeof status === 'function' ? { server: status() } : {}),
|
|
@@ -342,6 +342,9 @@ export function createPanelRouter({ ctx, config, mountPath, logger, status, heal
|
|
|
342
342
|
if (typeof body.token !== 'string') throw new Error('token must be a string')
|
|
343
343
|
if (body.token.trim() === '') throw new Error('token must not be empty')
|
|
344
344
|
setAt(next, 'auth.token', body.token)
|
|
345
|
+
// A token with auth.mode "none" would be stored but never used. Treat
|
|
346
|
+
// an explicitly supplied token as the intent to enable bearer auth.
|
|
347
|
+
if ((next.auth?.mode ?? 'none') === 'none') setAt(next, 'auth.mode', 'bearer')
|
|
345
348
|
}
|
|
346
349
|
|
|
347
350
|
const normalized = normalizeConfig(next)
|
|
@@ -380,7 +383,7 @@ export function createPanelRouter({ ctx, config, mountPath, logger, status, heal
|
|
|
380
383
|
logger.info('[a2a] configuration updated from the settings panel')
|
|
381
384
|
res.json({
|
|
382
385
|
ok: true,
|
|
383
|
-
plugin: { name: PLUGIN_NAME, version: '1.1.
|
|
386
|
+
plugin: { name: PLUGIN_NAME, version: '1.1.2' },
|
|
384
387
|
mountPath,
|
|
385
388
|
...describe(live, liveEntry, []),
|
|
386
389
|
...(typeof status === 'function' ? { server: status() } : {}),
|
package/lib/server.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import http from 'node:http'
|
|
19
|
+
import { createHash } from 'node:crypto'
|
|
19
20
|
import { isIP } from 'node:net'
|
|
20
21
|
import express from 'express'
|
|
21
22
|
import { A2A_PROTOCOL_VERSION } from '@a2a-js/sdk'
|
|
@@ -23,8 +24,9 @@ import {
|
|
|
23
24
|
DefaultPushNotificationSender,
|
|
24
25
|
DefaultRequestHandler,
|
|
25
26
|
InMemoryPushNotificationStore,
|
|
27
|
+
STATE_HEADERS_KEY,
|
|
26
28
|
} from '@a2a-js/sdk/server'
|
|
27
|
-
import {
|
|
29
|
+
import { jsonRpcHandler, restHandler } from '@a2a-js/sdk/server/express'
|
|
28
30
|
import { buildAgentCard, buildExtendedCard, createCardSigner, isAuthEnabled } from './card.js'
|
|
29
31
|
import { createAuthenticator } from './auth.js'
|
|
30
32
|
import { createPanelRouter } from './panel.js'
|
|
@@ -62,6 +64,42 @@ function formatUrlHost(host) {
|
|
|
62
64
|
return isIP(host) === 6 ? `[${host}]` : host
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
/** First value of a possibly repeated HTTP header. */
|
|
68
|
+
function firstHeader(value) {
|
|
69
|
+
if (Array.isArray(value)) return value[0]
|
|
70
|
+
return typeof value === 'string' ? value : undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Derive the externally visible origin from the request headers.
|
|
75
|
+
*
|
|
76
|
+
* `server.publicUrl` always wins. When `server.trustProxy` is enabled, the
|
|
77
|
+
* first `X-Forwarded-Host` / `X-Forwarded-Proto` values are honored so a
|
|
78
|
+
* reverse proxy can preserve its external origin. Otherwise the raw `Host`
|
|
79
|
+
* header is used. Returns null when no usable host is present.
|
|
80
|
+
*
|
|
81
|
+
* @param {Record<string, string|string[]|undefined>} headers
|
|
82
|
+
* @param {Readonly<object>} config
|
|
83
|
+
* @param {string} fallbackProtocol protocol to use without a forwarded proto
|
|
84
|
+
* @returns {string|null}
|
|
85
|
+
*/
|
|
86
|
+
function baseFromHeaders(headers, config, fallbackProtocol = 'http') {
|
|
87
|
+
if (config.server.publicUrl) return trimTrailingSlash(config.server.publicUrl)
|
|
88
|
+
const trusted = config.server.trustProxy === true
|
|
89
|
+
const forwardedHost = firstHeader(headers?.['x-forwarded-host'])
|
|
90
|
+
const host = trusted && forwardedHost ? forwardedHost.split(',')[0].trim() : firstHeader(headers?.host)
|
|
91
|
+
if (!host) return null
|
|
92
|
+
const forwardedProto = firstHeader(headers?.['x-forwarded-proto'])
|
|
93
|
+
const protocol = trusted && forwardedProto ? forwardedProto.split(',')[0].trim() : fallbackProtocol
|
|
94
|
+
try {
|
|
95
|
+
const url = new URL(`${protocol}://${host}`)
|
|
96
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return null
|
|
97
|
+
return trimTrailingSlash(url.origin)
|
|
98
|
+
} catch {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
65
103
|
/**
|
|
66
104
|
* Derive the externally visible origin.
|
|
67
105
|
*
|
|
@@ -75,8 +113,8 @@ function resolvePublicBase(config, webServer, logger) {
|
|
|
75
113
|
const host = config.server.host
|
|
76
114
|
if (host === '0.0.0.0' || host === '::') {
|
|
77
115
|
logger.warn(
|
|
78
|
-
`[a2a] server.host ${host} is a wildcard;
|
|
79
|
-
'
|
|
116
|
+
`[a2a] server.host ${host} is a wildcard; the static fallback advertises a loopback URL. ` +
|
|
117
|
+
'Agent Card and health use the request Host; set server.publicUrl only if the proxy cannot forward Host/forwarded headers.',
|
|
80
118
|
)
|
|
81
119
|
}
|
|
82
120
|
const advertised = host === '0.0.0.0' ? '127.0.0.1' : host === '::' ? '::1' : host
|
|
@@ -274,6 +312,17 @@ export class A2AService {
|
|
|
274
312
|
? undefined
|
|
275
313
|
: new DefaultPushNotificationSender(pushStore, { timeout: config.push.timeoutMs })
|
|
276
314
|
|
|
315
|
+
const extendedAgentCardProvider = extendedCard
|
|
316
|
+
? async (context) => {
|
|
317
|
+
const headers =
|
|
318
|
+
context?.state?.get?.(STATE_HEADERS_KEY) ?? context?.state?.get?.('headers') ?? {}
|
|
319
|
+
const base = baseFromHeaders(headers, config, 'http')
|
|
320
|
+
return base === null
|
|
321
|
+
? extendedAgentCard
|
|
322
|
+
: buildExtendedCard(effective, { publicBaseUrl: base, credentials })
|
|
323
|
+
}
|
|
324
|
+
: undefined
|
|
325
|
+
|
|
277
326
|
const requestHandler = new DefaultRequestHandler(
|
|
278
327
|
baseCard,
|
|
279
328
|
taskStore,
|
|
@@ -281,11 +330,54 @@ export class A2AService {
|
|
|
281
330
|
undefined,
|
|
282
331
|
pushStore,
|
|
283
332
|
pushSender,
|
|
284
|
-
|
|
333
|
+
extendedAgentCardProvider,
|
|
285
334
|
signer ?? undefined,
|
|
286
335
|
{ validateInputModes: true },
|
|
287
336
|
)
|
|
288
337
|
|
|
338
|
+
const serveCard = async (req, res, base) => {
|
|
339
|
+
try {
|
|
340
|
+
const card = buildAgentCard(effective, { publicBaseUrl: base, credentials })
|
|
341
|
+
const served = signer === null ? card : await signer(card)
|
|
342
|
+
const body = JSON.stringify(served)
|
|
343
|
+
res.setHeader('ETag', `W/"${createHash('sha256').update(body).digest('hex').slice(0, 16)}"`)
|
|
344
|
+
res.setHeader('Vary', 'Host, X-Forwarded-Host, X-Forwarded-Proto')
|
|
345
|
+
res.setHeader(
|
|
346
|
+
'Cache-Control',
|
|
347
|
+
config.server.publicUrl && config.server.cardCacheSeconds > 0
|
|
348
|
+
? `public, max-age=${config.server.cardCacheSeconds}`
|
|
349
|
+
: 'no-cache',
|
|
350
|
+
)
|
|
351
|
+
if (req.fresh) {
|
|
352
|
+
res.statusCode = 304
|
|
353
|
+
res.end()
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
res.setHeader('Content-Type', JSON_CONTENT_TYPE)
|
|
357
|
+
res.statusCode = 200
|
|
358
|
+
res.end(body)
|
|
359
|
+
} catch (error) {
|
|
360
|
+
logger.warn(`[a2a] agent card generation failed: ${errorMessage(error)}`)
|
|
361
|
+
if (!res.headersSent) {
|
|
362
|
+
res.statusCode = 500
|
|
363
|
+
res.setHeader('Content-Type', JSON_CONTENT_TYPE)
|
|
364
|
+
res.end(JSON.stringify({ error: 'INTERNAL', message: 'cannot generate the Agent Card' }))
|
|
365
|
+
} else if (!res.writableEnded) {
|
|
366
|
+
res.end()
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const dynamicCardHandler = (req, res) =>
|
|
371
|
+
serveCard(req, res, baseFromHeaders(req.headers, config, req.protocol) ?? publicBaseUrl)
|
|
372
|
+
// The DSH origin only mirrors the card for panel discovery; its interfaces
|
|
373
|
+
// must point at the A2A listener (or publicUrl), not at the GUI origin.
|
|
374
|
+
const mirrorCardHandler = (req, res) =>
|
|
375
|
+
serveCard(
|
|
376
|
+
req,
|
|
377
|
+
res,
|
|
378
|
+
config.server.publicUrl ? trimTrailingSlash(config.server.publicUrl) : publicBaseUrl,
|
|
379
|
+
)
|
|
380
|
+
|
|
289
381
|
const authenticator = createAuthenticator({
|
|
290
382
|
config,
|
|
291
383
|
credentials,
|
|
@@ -293,14 +385,10 @@ export class A2AService {
|
|
|
293
385
|
isRestBinding: (req) => isRestBinding(req, mountPath),
|
|
294
386
|
})
|
|
295
387
|
|
|
296
|
-
const cardRouter = agentCardHandler({
|
|
297
|
-
agentCardProvider: requestHandler,
|
|
298
|
-
cache: { maxAge: config.server.cardCacheSeconds },
|
|
299
|
-
})
|
|
300
388
|
const jsonRpcRouter = jsonRpcHandler({ requestHandler, userBuilder: authenticator.userBuilder })
|
|
301
389
|
const restRouter = restHandler({ requestHandler, userBuilder: authenticator.userBuilder })
|
|
302
|
-
const healthSnapshot = () =>
|
|
303
|
-
this.#health({ runtime, executor, taskStore, authenticator, publicBaseUrl, extendedCard })
|
|
390
|
+
const healthSnapshot = (base = publicBaseUrl) =>
|
|
391
|
+
this.#health({ runtime, executor, taskStore, authenticator, publicBaseUrl: base, extendedCard })
|
|
304
392
|
const panelRouter = createPanelRouter({
|
|
305
393
|
ctx: this.#ctx,
|
|
306
394
|
config,
|
|
@@ -389,8 +477,9 @@ export class A2AService {
|
|
|
389
477
|
|
|
390
478
|
const a2aApp = express()
|
|
391
479
|
applyCommon(a2aApp)
|
|
392
|
-
// The Agent Card is discovery metadata and stays public.
|
|
393
|
-
|
|
480
|
+
// The Agent Card is discovery metadata and stays public. Its interface URLs
|
|
481
|
+
// are derived per request so a reverse proxy's external Host is preserved.
|
|
482
|
+
a2aApp.get(config.server.cardPath, dynamicCardHandler)
|
|
394
483
|
if (!standalone) {
|
|
395
484
|
// In shared mode the panel is part of the same app and must precede the
|
|
396
485
|
// A2A auth middleware.
|
|
@@ -411,8 +500,9 @@ export class A2AService {
|
|
|
411
500
|
})
|
|
412
501
|
if (config.server.health) {
|
|
413
502
|
a2aApp.get(`${mountPath}/health`, (req, res) => {
|
|
503
|
+
const base = baseFromHeaders(req.headers, config, req.protocol) ?? publicBaseUrl
|
|
414
504
|
res.setHeader('Cache-Control', 'no-store')
|
|
415
|
-
res.json(healthSnapshot())
|
|
505
|
+
res.json(healthSnapshot(base))
|
|
416
506
|
})
|
|
417
507
|
}
|
|
418
508
|
a2aApp.use((req, res) => {
|
|
@@ -493,7 +583,11 @@ export class A2AService {
|
|
|
493
583
|
// Discovery stays reachable on the Harness Web origin in both modes: in
|
|
494
584
|
// shared mode it is the endpoint, and in standalone mode it bootstraps
|
|
495
585
|
// the settings panel (the card itself points at the dedicated port).
|
|
496
|
-
registerRoute(
|
|
586
|
+
registerRoute(
|
|
587
|
+
{ kind: 'exact', path: config.server.cardPath },
|
|
588
|
+
standalone ? mirrorCardHandler : callA2A,
|
|
589
|
+
standalone ? 'agent card mirror' : 'agent card',
|
|
590
|
+
)
|
|
497
591
|
if (standalone) {
|
|
498
592
|
registerRoute(
|
|
499
593
|
{ kind: 'prefix', path: `${mountPath}/panel` },
|
|
@@ -618,6 +712,8 @@ export class A2AService {
|
|
|
618
712
|
#health({ runtime, executor, taskStore, authenticator, publicBaseUrl, extendedCard }) {
|
|
619
713
|
const config = this.#config
|
|
620
714
|
const mounted = this.#mounted
|
|
715
|
+
const server = this.serverStatus()
|
|
716
|
+
server.publicUrl = publicBaseUrl
|
|
621
717
|
return {
|
|
622
718
|
status: 'ok',
|
|
623
719
|
protocolVersion: A2A_PROTOCOL_VERSION,
|
|
@@ -625,7 +721,7 @@ export class A2AService {
|
|
|
625
721
|
version: config.card.version,
|
|
626
722
|
publicUrl: publicBaseUrl,
|
|
627
723
|
uptimeSeconds: mounted === undefined ? 0 : Math.round((Date.now() - mounted.startedAt) / 1000),
|
|
628
|
-
server
|
|
724
|
+
server,
|
|
629
725
|
interfaces: [
|
|
630
726
|
{ protocolBinding: 'JSONRPC', url: `${publicBaseUrl}${config.server.mountPath}` },
|
|
631
727
|
{ protocolBinding: 'HTTP+JSON', url: `${publicBaseUrl}${config.server.mountPath}/rest` },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai_mind/dsh-a2a",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Production A2A (Agent2Agent) v1.0 server for DeepSeek Harness agents: JSON-RPC + HTTP+JSON bindings, streaming, tasks, push notifications and signed Agent Cards.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|