@ryubyte/dsh-a2a 0.3.1 → 0.3.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.en.md +2 -0
- package/README.md +2 -0
- package/lib/index.js +23 -8
- package/lib/types/index.d.ts +10 -4
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +11 -9
- package/src/index.ts +29 -10
package/README.en.md
CHANGED
|
@@ -9,6 +9,8 @@ It turns a DSH instance into a full A2A peer in both directions:
|
|
|
9
9
|
- **Client mode (outbound)** — discover a remote agent via its **AgentCard**, map each **skill** to a DSH model tool (`a2a__<name>__<skill>`), and execute remote tasks through A2A **JSON-RPC**.
|
|
10
10
|
- **Server mode (inbound)** — expose this DSH to other agents: `/.well-known/agent-card.json` plus a JSON-RPC endpoint (`SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SendStreamingMessage` over SSE) on the DSH webserver.
|
|
11
11
|
|
|
12
|
+
Supports DSH `0.1.7-rc.2`, `0.2.0-rc.1–2`, `0.2.1-alpha.1–5`, and `0.2.1-rc.1–5` (13 versions), checked individually in CI and before release. [Compatibility details](docs/dsh-compat.md).
|
|
13
|
+
|
|
12
14
|
## Installation
|
|
13
15
|
|
|
14
16
|
Install into a profile with one command and restart:
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
- **Client 模式(出站)**——通过远程 Agent 的 **AgentCard** 发现它,把每个 **skill** 映射为 DSH 模型工具(`a2a__<name>__<skill>`),通过 A2A **JSON-RPC** 执行远程任务。
|
|
10
10
|
- **Server 模式(入站)**——把本 DSH 暴露给其他 Agent:在 DSH webserver 上提供 `/.well-known/agent-card.json` 与 JSON-RPC 端点(`SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、基于 SSE 的 `SendStreamingMessage`)。
|
|
11
11
|
|
|
12
|
+
兼容 DSH `0.1.7-rc.2`、`0.2.0-rc.1~2`、`0.2.1-alpha.1~5` 与 `0.2.1-rc.1~5`,共 13 个版本;CI 和发布流程逐版本验证。[兼容性说明](docs/dsh-compat.md)。
|
|
13
|
+
|
|
12
14
|
## 安装
|
|
13
15
|
|
|
14
16
|
一行命令装进 profile,重启即生效:
|
package/lib/index.js
CHANGED
|
@@ -2174,7 +2174,7 @@ function apply(ctx, config = {}) {
|
|
|
2174
2174
|
ctx.inject(["webServer"], (ctx) => {
|
|
2175
2175
|
ctx.effect(() => {
|
|
2176
2176
|
const webServer = ctx.webServer;
|
|
2177
|
-
const baseUrl = mergedConfig.baseUrl ??
|
|
2177
|
+
const baseUrl = mergedConfig.baseUrl ?? webServerBaseUrl(webServer);
|
|
2178
2178
|
const endpointPath = mergedConfig.endpointPath ?? DEFAULT_ENDPOINT;
|
|
2179
2179
|
if (mergedConfig.baseUrl) try {
|
|
2180
2180
|
const pinnedPort = new URL(mergedConfig.baseUrl).port;
|
|
@@ -2382,7 +2382,7 @@ function apply(ctx, config = {}) {
|
|
|
2382
2382
|
baseUrl: void 0
|
|
2383
2383
|
} : patch;
|
|
2384
2384
|
const { endpointChanged } = server.updateCard(cardPatch);
|
|
2385
|
-
if (clearBaseUrl) server.setBaseUrl(
|
|
2385
|
+
if (clearBaseUrl) server.setBaseUrl(webServerBaseUrl(webServer));
|
|
2386
2386
|
if (endpointChanged && enabled) {
|
|
2387
2387
|
unregisterRoutes();
|
|
2388
2388
|
registerRoutes();
|
|
@@ -2561,12 +2561,20 @@ function inboundSessionCwd(configPath) {
|
|
|
2561
2561
|
* back to loopback when no such address exists. IPv6 link-local is skipped
|
|
2562
2562
|
* (not reachable without a scope id).
|
|
2563
2563
|
*
|
|
2564
|
-
* @param host - the web server's configured bind
|
|
2564
|
+
* @param host - the web server's configured bind address.
|
|
2565
2565
|
* @param port - the OS-assigned (or configured) listen port.
|
|
2566
|
+
* @param protocol - the host listener's protocol (older hosts default to HTTP).
|
|
2566
2567
|
* @returns a scheme+host+port URL for Agents to call back into this DSH.
|
|
2567
2568
|
*/
|
|
2568
|
-
function inferBaseUrl(host, port) {
|
|
2569
|
-
|
|
2569
|
+
function inferBaseUrl(host, port, protocol = "http:") {
|
|
2570
|
+
const hostname = host === "0.0.0.0" ? primaryLanAddress() ?? "127.0.0.1" : host || "127.0.0.1";
|
|
2571
|
+
return `${protocol}//${hostname.includes(":") && !hostname.startsWith("[") ? `[${hostname}]` : hostname}:${port}`;
|
|
2572
|
+
}
|
|
2573
|
+
/** Prefer the host's authoritative origin; retain inference for older DSH. */
|
|
2574
|
+
function webServerBaseUrl(webServer) {
|
|
2575
|
+
if (typeof webServer.getOrigin === "function") return webServer.getOrigin();
|
|
2576
|
+
if (webServer.port === void 0) throw new Error("a2a: webServer has not bound a port");
|
|
2577
|
+
return inferBaseUrl(webServer.host, webServer.port, webServer.protocol);
|
|
2570
2578
|
}
|
|
2571
2579
|
/**
|
|
2572
2580
|
* Best single LAN IPv4 for this machine: the address of the non-loopback
|
|
@@ -2648,8 +2656,15 @@ function corsHeadersFor(reqOrigin, allowed) {
|
|
|
2648
2656
|
function trustedLoopback(req) {
|
|
2649
2657
|
const host = req.headers.host;
|
|
2650
2658
|
if (!host) return false;
|
|
2651
|
-
|
|
2652
|
-
|
|
2659
|
+
let hostname;
|
|
2660
|
+
try {
|
|
2661
|
+
const authority = new URL(`http://${host}`);
|
|
2662
|
+
if (authority.username || authority.password || authority.pathname !== "/" || authority.search || authority.hash) return false;
|
|
2663
|
+
hostname = authority.hostname;
|
|
2664
|
+
} catch {
|
|
2665
|
+
return false;
|
|
2666
|
+
}
|
|
2667
|
+
if (hostname !== "127.0.0.1" && hostname !== "localhost" && hostname !== "[::1]") return false;
|
|
2653
2668
|
if (req.headers["sec-fetch-site"] === "cross-site") return false;
|
|
2654
2669
|
const origin = req.headers.origin;
|
|
2655
2670
|
if (!origin) return true;
|
|
@@ -2675,4 +2690,4 @@ function toServerReq(req) {
|
|
|
2675
2690
|
};
|
|
2676
2691
|
}
|
|
2677
2692
|
//#endregion
|
|
2678
|
-
export { A2AClient, A2AError, A2AServer, A2A_ERROR_CODES, A2A_METHODS, DashboardRegistry, Role, TaskState, TaskStore, apply, createDshAgentExecutor, defaultExecutor, defaultSkills, fetchAgentCard, getSharedRegistry, inferBaseUrl, inject, isTerminal, name, notConfiguredExecutor, registerAgentTools, shellExecutor };
|
|
2693
|
+
export { A2AClient, A2AError, A2AServer, A2A_ERROR_CODES, A2A_METHODS, DashboardRegistry, Role, TaskState, TaskStore, apply, createDshAgentExecutor, defaultExecutor, defaultSkills, fetchAgentCard, getSharedRegistry, inferBaseUrl, inject, isTerminal, name, notConfiguredExecutor, registerAgentTools, shellExecutor, webServerBaseUrl };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -77,9 +77,12 @@ export type { DashboardSnapshot, InboundPeer, OutboundAgent, ControlResult, } fr
|
|
|
77
77
|
*/
|
|
78
78
|
export interface WebServerService {
|
|
79
79
|
/** The OS-assigned (or configured) listen port. */
|
|
80
|
-
readonly port: number;
|
|
81
|
-
/** The configured bind
|
|
80
|
+
readonly port: number | undefined;
|
|
81
|
+
/** The configured bind address. Older hosts also allow '0.0.0.0'. */
|
|
82
82
|
readonly host: string;
|
|
83
|
+
/** Added by DSH 0.2.1: preserve TLS and canonical IPv6/mapped addresses. */
|
|
84
|
+
getOrigin?(): string;
|
|
85
|
+
readonly protocol?: 'http:' | 'https:';
|
|
83
86
|
register(route: {
|
|
84
87
|
kind: 'exact' | 'prefix';
|
|
85
88
|
path: string;
|
|
@@ -154,9 +157,12 @@ export declare function apply(ctx: Context, config?: A2APluginConfig): void;
|
|
|
154
157
|
* back to loopback when no such address exists. IPv6 link-local is skipped
|
|
155
158
|
* (not reachable without a scope id).
|
|
156
159
|
*
|
|
157
|
-
* @param host - the web server's configured bind
|
|
160
|
+
* @param host - the web server's configured bind address.
|
|
158
161
|
* @param port - the OS-assigned (or configured) listen port.
|
|
162
|
+
* @param protocol - the host listener's protocol (older hosts default to HTTP).
|
|
159
163
|
* @returns a scheme+host+port URL for Agents to call back into this DSH.
|
|
160
164
|
*/
|
|
161
|
-
export declare function inferBaseUrl(host: string, port: number): string;
|
|
165
|
+
export declare function inferBaseUrl(host: string, port: number, protocol?: 'http:' | 'https:'): string;
|
|
166
|
+
/** Prefer the host's authoritative origin; retain inference for older DSH. */
|
|
167
|
+
export declare function webServerBaseUrl(webServer: WebServerService): string;
|
|
162
168
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMjE,OAAO,EAAa,KAAK,aAAa,EAA0B,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAY5G,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IAEpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACnC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,qBAAqB,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACjJ,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,aAAa,GACd,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryubyte/dsh-a2a",
|
|
3
3
|
"description": "Agent2Agent (A2A) protocol v1.0 plugin for DeepSeek Harness (DSH): outbound client (remote AgentCard skills as ctx.tools), inbound server (A2A JSON-RPC + AgentCard endpoint), and a settings dashboard of live A2A connections",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
7
7
|
"agent2agent",
|
|
@@ -50,24 +50,26 @@
|
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
53
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 ||
|
|
54
|
-
"@deepseek-ai/dsh-tools": "^0.1.7-rc.2 ||
|
|
53
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || >=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.2.2-0",
|
|
54
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.2 || >=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.2.2-0",
|
|
55
55
|
"react": "^18.2.0",
|
|
56
56
|
"react-dom": "^18.2.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
63
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
64
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
59
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.5",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.5",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.5",
|
|
63
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.5",
|
|
64
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.5",
|
|
65
65
|
"@types/node": "^22.0.0",
|
|
66
66
|
"@types/react": "~18.3.1",
|
|
67
67
|
"@types/react-dom": "^18.3.5",
|
|
68
|
+
"@types/semver": "^7.7.1",
|
|
68
69
|
"esbuild": "^0.28.2",
|
|
69
70
|
"react": "^18.3.1",
|
|
70
71
|
"react-dom": "^18.3.1",
|
|
72
|
+
"semver": "^7.8.5",
|
|
71
73
|
"tsdown": "^0.22.14",
|
|
72
74
|
"tsx": "^4.19.0",
|
|
73
75
|
"typescript": "^5.5.0"
|
package/src/index.ts
CHANGED
|
@@ -104,9 +104,12 @@ export type {
|
|
|
104
104
|
*/
|
|
105
105
|
export interface WebServerService {
|
|
106
106
|
/** The OS-assigned (or configured) listen port. */
|
|
107
|
-
readonly port: number;
|
|
108
|
-
/** The configured bind
|
|
107
|
+
readonly port: number | undefined;
|
|
108
|
+
/** The configured bind address. Older hosts also allow '0.0.0.0'. */
|
|
109
109
|
readonly host: string;
|
|
110
|
+
/** Added by DSH 0.2.1: preserve TLS and canonical IPv6/mapped addresses. */
|
|
111
|
+
getOrigin?(): string;
|
|
112
|
+
readonly protocol?: 'http:' | 'https:';
|
|
110
113
|
register(route: {
|
|
111
114
|
kind: 'exact' | 'prefix';
|
|
112
115
|
path: string;
|
|
@@ -570,7 +573,7 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
570
573
|
// Derive the advertised base URL from the real listen address when the
|
|
571
574
|
// config doesn't pin one; avoids advertising a stale port. When bound
|
|
572
575
|
// to 0.0.0.0, advertise a reachable LAN address, not loopback.
|
|
573
|
-
const baseUrl = mergedConfig.baseUrl ??
|
|
576
|
+
const baseUrl = mergedConfig.baseUrl ?? webServerBaseUrl(webServer);
|
|
574
577
|
const endpointPath = mergedConfig.endpointPath ?? DEFAULT_ENDPOINT;
|
|
575
578
|
// Surface a stale pinned baseUrl: if the config pins a port that does
|
|
576
579
|
// not match the real listen port, the advertised AgentCard URL is
|
|
@@ -845,7 +848,7 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
845
848
|
const { endpointChanged } = server.updateCard(cardPatch);
|
|
846
849
|
if (clearBaseUrl) {
|
|
847
850
|
// Re-derive from the actual listen address and re-advertise.
|
|
848
|
-
server.setBaseUrl(
|
|
851
|
+
server.setBaseUrl(webServerBaseUrl(webServer));
|
|
849
852
|
}
|
|
850
853
|
// The JSON-RPC path moved: re-register on the new path, but only
|
|
851
854
|
// while serving (disabled → routes get the new path on next enable).
|
|
@@ -1033,16 +1036,25 @@ function inboundSessionCwd(configPath: string): string {
|
|
|
1033
1036
|
* back to loopback when no such address exists. IPv6 link-local is skipped
|
|
1034
1037
|
* (not reachable without a scope id).
|
|
1035
1038
|
*
|
|
1036
|
-
* @param host - the web server's configured bind
|
|
1039
|
+
* @param host - the web server's configured bind address.
|
|
1037
1040
|
* @param port - the OS-assigned (or configured) listen port.
|
|
1041
|
+
* @param protocol - the host listener's protocol (older hosts default to HTTP).
|
|
1038
1042
|
* @returns a scheme+host+port URL for Agents to call back into this DSH.
|
|
1039
1043
|
*/
|
|
1040
|
-
export function inferBaseUrl(host: string, port: number): string {
|
|
1044
|
+
export function inferBaseUrl(host: string, port: number, protocol: 'http:' | 'https:' = 'http:'): string {
|
|
1041
1045
|
const hostname =
|
|
1042
1046
|
host === '0.0.0.0'
|
|
1043
1047
|
? primaryLanAddress() ?? '127.0.0.1'
|
|
1044
1048
|
: host || '127.0.0.1';
|
|
1045
|
-
|
|
1049
|
+
const urlHost = hostname.includes(':') && !hostname.startsWith('[') ? `[${hostname}]` : hostname;
|
|
1050
|
+
return `${protocol}//${urlHost}:${port}`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/** Prefer the host's authoritative origin; retain inference for older DSH. */
|
|
1054
|
+
export function webServerBaseUrl(webServer: WebServerService): string {
|
|
1055
|
+
if (typeof webServer.getOrigin === 'function') return webServer.getOrigin();
|
|
1056
|
+
if (webServer.port === undefined) throw new Error('a2a: webServer has not bound a port');
|
|
1057
|
+
return inferBaseUrl(webServer.host, webServer.port, webServer.protocol);
|
|
1046
1058
|
}
|
|
1047
1059
|
|
|
1048
1060
|
/**
|
|
@@ -1135,8 +1147,15 @@ function corsHeadersFor(reqOrigin: string | undefined, allowed: readonly string[
|
|
|
1135
1147
|
function trustedLoopback(req: IncomingMessage): boolean {
|
|
1136
1148
|
const host = req.headers.host;
|
|
1137
1149
|
if (!host) return false;
|
|
1138
|
-
|
|
1139
|
-
|
|
1150
|
+
let hostname: string;
|
|
1151
|
+
try {
|
|
1152
|
+
const authority = new URL(`http://${host}`);
|
|
1153
|
+
if (authority.username || authority.password || authority.pathname !== '/' || authority.search || authority.hash) return false;
|
|
1154
|
+
hostname = authority.hostname;
|
|
1155
|
+
} catch {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
if (hostname !== '127.0.0.1' && hostname !== 'localhost' && hostname !== '[::1]') return false;
|
|
1140
1159
|
if (req.headers['sec-fetch-site'] === 'cross-site') return false;
|
|
1141
1160
|
const origin = req.headers.origin;
|
|
1142
1161
|
if (!origin) return true;
|
|
@@ -1165,4 +1184,4 @@ function toServerReq(req: IncomingMessage): {
|
|
|
1165
1184
|
headers,
|
|
1166
1185
|
socket: req.socket ? { remoteAddress: req.socket.remoteAddress, remotePort: req.socket.remotePort } : undefined,
|
|
1167
1186
|
};
|
|
1168
|
-
}
|
|
1187
|
+
}
|