@hanphone/dsh-a2a 0.1.0 → 0.3.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 +133 -87
- package/README.zh.md +64 -87
- package/cordis.patch.yml +4 -7
- package/lib/client.js +228 -36
- package/lib/index.js +1271 -625
- package/lib/types/api.d.ts +43 -13
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +60 -20
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts +50 -14
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +59 -46
- package/lib/types/commands.js.map +1 -1
- package/lib/types/events.d.ts +32 -9
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -32
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +178 -213
- package/lib/types/index.js.map +1 -1
- package/lib/types/outbound/calls.d.ts.map +1 -1
- package/lib/types/outbound/calls.js +8 -6
- package/lib/types/outbound/calls.js.map +1 -1
- package/lib/types/protocol.d.ts +174 -109
- package/lib/types/protocol.d.ts.map +1 -1
- package/lib/types/protocol.js +58 -34
- package/lib/types/protocol.js.map +1 -1
- package/lib/types/server/a2a-server.d.ts +7 -1
- package/lib/types/server/a2a-server.d.ts.map +1 -1
- package/lib/types/server/a2a-server.js +11 -3
- package/lib/types/server/a2a-server.js.map +1 -1
- package/lib/types/server/card.d.ts +6 -30
- package/lib/types/server/card.d.ts.map +1 -1
- package/lib/types/server/card.js +13 -49
- package/lib/types/server/card.js.map +1 -1
- package/lib/types/server/exec/agent-runtime.d.ts +3 -0
- package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
- package/lib/types/server/exec/agent-runtime.js +2 -1
- package/lib/types/server/exec/agent-runtime.js.map +1 -1
- package/lib/types/server/identity.d.ts +38 -0
- package/lib/types/server/identity.d.ts.map +1 -0
- package/lib/types/server/identity.js +89 -0
- package/lib/types/server/identity.js.map +1 -0
- package/lib/types/server/inbound-registry.d.ts +60 -0
- package/lib/types/server/inbound-registry.d.ts.map +1 -0
- package/lib/types/server/inbound-registry.js +86 -0
- package/lib/types/server/inbound-registry.js.map +1 -0
- package/lib/types/server/routes.d.ts +5 -1
- package/lib/types/server/routes.d.ts.map +1 -1
- package/lib/types/server/routes.js +9 -5
- package/lib/types/server/routes.js.map +1 -1
- package/lib/types/server/store.d.ts +37 -1
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js +14 -0
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +131 -0
- package/lib/types/servers/inbound-manager.d.ts.map +1 -0
- package/lib/types/servers/inbound-manager.js +312 -0
- package/lib/types/servers/inbound-manager.js.map +1 -0
- package/lib/types/servers/outbound-manager.d.ts +129 -0
- package/lib/types/servers/outbound-manager.d.ts.map +1 -0
- package/lib/types/servers/outbound-manager.js +238 -0
- package/lib/types/servers/outbound-manager.js.map +1 -0
- package/lib/types/service.d.ts +108 -23
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +39 -18
- package/lib/types/service.js.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +70 -31
- package/src/client/index.ts +339 -81
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +204 -249
- package/src/outbound/calls.ts +8 -6
- package/src/protocol.ts +204 -95
- package/src/server/a2a-server.ts +16 -4
- package/src/server/card.ts +13 -62
- package/src/server/exec/agent-runtime.ts +5 -2
- package/src/server/identity.ts +109 -0
- package/src/server/inbound-registry.ts +129 -0
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +50 -4
- package/src/servers/inbound-manager.ts +385 -0
- package/src/servers/outbound-manager.ts +289 -0
- package/src/service.ts +136 -28
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
package/lib/index.js
CHANGED
|
@@ -5,107 +5,28 @@ import { mkdirSync } from "node:fs";
|
|
|
5
5
|
import { z as z$1 } from "zod";
|
|
6
6
|
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
7
7
|
import { Service } from "@deepseek-ai/cordis";
|
|
8
|
-
//#region lib/types/server/card.js
|
|
9
|
-
/**
|
|
10
|
-
* AgentCard assembly for the inbound half: skills are DERIVED from the live
|
|
11
|
-
* tool registry by an explicit id list (never invented), plus a built-in
|
|
12
|
-
* `chat` skill so a fresh install is immediately exercisable end-to-end.
|
|
13
|
-
* @module dsh-a2a/server/card
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Derive the inbound skill list from the live tool registry.
|
|
17
|
-
*
|
|
18
|
-
* Every configured id must resolve to a registered tool — a missing referent
|
|
19
|
-
* fails the derivation loudly (the plugin's misconfiguration contract) with
|
|
20
|
-
* all missing ids listed. The built-in `chat` skill is always present so the
|
|
21
|
-
* server answers before any tool is exposed.
|
|
22
|
-
*
|
|
23
|
-
* @param tools - the tool registry getter.
|
|
24
|
-
* @param policy - explicit-id derivation policy.
|
|
25
|
-
* @returns the derived skills, `chat` first, configured tools after, excludes applied.
|
|
26
|
-
*/
|
|
27
|
-
function deriveSkills(tools, policy) {
|
|
28
|
-
const excluded = new Set(policy.exclude);
|
|
29
|
-
const skills = [{
|
|
30
|
-
id: "chat",
|
|
31
|
-
name: "chat",
|
|
32
|
-
description: "Conversational assistance over a DSH agent session.",
|
|
33
|
-
tags: ["chat"]
|
|
34
|
-
}];
|
|
35
|
-
const missing = [];
|
|
36
|
-
for (const id of policy.ids) {
|
|
37
|
-
if (excluded.has(id)) continue;
|
|
38
|
-
const tool = tools.get(id);
|
|
39
|
-
if (tool === void 0) {
|
|
40
|
-
missing.push(id);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
skills.push({
|
|
44
|
-
id,
|
|
45
|
-
name: id,
|
|
46
|
-
description: tool.description ?? `Expose the DSH tool \`${id}\` over A2A.`,
|
|
47
|
-
tags: ["tool"]
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
if (missing.length > 0) throw new Error(`dsh-a2a: server.skills.ids reference unregistered tools: ${missing.join(", ")}`);
|
|
51
|
-
return skills;
|
|
52
|
-
}
|
|
53
|
-
/** Assemble the AgentCard advertised by this DSH. */
|
|
54
|
-
function buildCard(options) {
|
|
55
|
-
const iface = {
|
|
56
|
-
url: `${options.baseUrl.replace(/\/$/, "")}${options.endpointPath}`,
|
|
57
|
-
protocolBinding: "JSONRPC",
|
|
58
|
-
protocolVersion: "1.0"
|
|
59
|
-
};
|
|
60
|
-
return {
|
|
61
|
-
name: options.name,
|
|
62
|
-
description: options.description,
|
|
63
|
-
version: options.version,
|
|
64
|
-
provider: {
|
|
65
|
-
url: "https://deepseek.com",
|
|
66
|
-
organization: "DeepSeek"
|
|
67
|
-
},
|
|
68
|
-
capabilities: {
|
|
69
|
-
streaming: true,
|
|
70
|
-
pushNotifications: false,
|
|
71
|
-
extensions: []
|
|
72
|
-
},
|
|
73
|
-
defaultInputModes: ["text/plain"],
|
|
74
|
-
defaultOutputModes: ["text/plain"],
|
|
75
|
-
skills: options.skills,
|
|
76
|
-
supportedInterfaces: [iface],
|
|
77
|
-
...options.authToken !== void 0 ? {
|
|
78
|
-
securitySchemes: { bearerAuth: {
|
|
79
|
-
type: "http",
|
|
80
|
-
scheme: "bearer",
|
|
81
|
-
description: "Shared bearer token"
|
|
82
|
-
} },
|
|
83
|
-
securityRequirements: [{ bearerAuth: [] }]
|
|
84
|
-
} : {}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
//#endregion
|
|
88
8
|
//#region lib/types/protocol.js
|
|
89
9
|
/**
|
|
90
|
-
* Agent2Agent (A2A) Protocol v1.0 — types,
|
|
91
|
-
* error codes
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* truth
|
|
10
|
+
* Agent2Agent (A2A) Protocol v1.0.1 — types, JSON-RPC methods, task states,
|
|
11
|
+
* error codes and JSON serialization, transcribed from the official
|
|
12
|
+
* `a2aproject/A2A` specification (Apache-2.0), cross-checked against
|
|
13
|
+
* `specification/a2a.proto` and `docs/specification.md` (JSON-RPC binding,
|
|
14
|
+
* ADR-001 ProtoJSON). This file is the plugin's single source of protocol
|
|
15
|
+
* truth for the JSON-RPC over HTTP binding. Other bindings (gRPC, REST) are
|
|
16
|
+
* not provided.
|
|
96
17
|
* @module dsh-a2a/protocol
|
|
97
18
|
*/
|
|
98
|
-
/** Task lifecycle states (spec `TaskState
|
|
19
|
+
/** Task lifecycle states (spec `TaskState`; JSON = SCREAMING_SNAKE_CASE per ADR-001). */
|
|
99
20
|
var TaskState;
|
|
100
21
|
(function(TaskState) {
|
|
101
|
-
TaskState["SUBMITTED"] = "
|
|
102
|
-
TaskState["WORKING"] = "
|
|
103
|
-
TaskState["INPUT_REQUIRED"] = "
|
|
104
|
-
TaskState["AUTH_REQUIRED"] = "
|
|
105
|
-
TaskState["COMPLETED"] = "
|
|
106
|
-
TaskState["FAILED"] = "
|
|
107
|
-
TaskState["CANCELED"] = "
|
|
108
|
-
TaskState["REJECTED"] = "
|
|
22
|
+
TaskState["SUBMITTED"] = "TASK_STATE_SUBMITTED";
|
|
23
|
+
TaskState["WORKING"] = "TASK_STATE_WORKING";
|
|
24
|
+
TaskState["INPUT_REQUIRED"] = "TASK_STATE_INPUT_REQUIRED";
|
|
25
|
+
TaskState["AUTH_REQUIRED"] = "TASK_STATE_AUTH_REQUIRED";
|
|
26
|
+
TaskState["COMPLETED"] = "TASK_STATE_COMPLETED";
|
|
27
|
+
TaskState["FAILED"] = "TASK_STATE_FAILED";
|
|
28
|
+
TaskState["CANCELED"] = "TASK_STATE_CANCELED";
|
|
29
|
+
TaskState["REJECTED"] = "TASK_STATE_REJECTED";
|
|
109
30
|
})(TaskState || (TaskState = {}));
|
|
110
31
|
/** States that settle a task; a settled task no longer transitions. */
|
|
111
32
|
const TERMINAL_STATES = new Set([
|
|
@@ -114,17 +35,34 @@ const TERMINAL_STATES = new Set([
|
|
|
114
35
|
TaskState.CANCELED,
|
|
115
36
|
TaskState.REJECTED
|
|
116
37
|
]);
|
|
38
|
+
new Set([TaskState.INPUT_REQUIRED, TaskState.AUTH_REQUIRED]);
|
|
117
39
|
/** @returns whether the state settles the task. */
|
|
118
40
|
function isTerminal(state) {
|
|
119
41
|
return TERMINAL_STATES.has(state);
|
|
120
42
|
}
|
|
121
|
-
/** Message sender role (spec `Role`). */
|
|
43
|
+
/** Message sender role (spec `Role`; JSON = `ROLE_*` per ADR-001). */
|
|
122
44
|
var Role;
|
|
123
45
|
(function(Role) {
|
|
124
|
-
Role["USER"] = "
|
|
125
|
-
Role["AGENT"] = "
|
|
46
|
+
Role["USER"] = "ROLE_USER";
|
|
47
|
+
Role["AGENT"] = "ROLE_AGENT";
|
|
126
48
|
})(Role || (Role = {}));
|
|
127
|
-
/**
|
|
49
|
+
/** JSON-RPC + A2A error codes. */
|
|
50
|
+
const A2A_ERROR_CODES = {
|
|
51
|
+
PARSE_ERROR: -32700,
|
|
52
|
+
INVALID_REQUEST: -32600,
|
|
53
|
+
METHOD_NOT_FOUND: -32601,
|
|
54
|
+
INVALID_PARAMS: -32602,
|
|
55
|
+
INTERNAL_ERROR: -32603,
|
|
56
|
+
TASK_NOT_FOUND: -32001,
|
|
57
|
+
TASK_CANCEL_NOT_ALLOWED: -32002,
|
|
58
|
+
PUSH_NOTIFICATION_NOT_SUPPORTED: -32003,
|
|
59
|
+
UNSUPPORTED_OPERATION: -32004,
|
|
60
|
+
CONTENT_TYPE_NOT_SUPPORTED: -32005,
|
|
61
|
+
STREAMING_NOT_SUPPORTED: -32006,
|
|
62
|
+
VERSION_NOT_SUPPORTED: -32007,
|
|
63
|
+
INVALID_AGENT_RESPONSE: -32008
|
|
64
|
+
};
|
|
65
|
+
/** A2A v1.0 JSON-RPC method names (spec §5.3 Method Mapping Reference). */
|
|
128
66
|
const A2A_METHODS = {
|
|
129
67
|
sendMessage: "SendMessage",
|
|
130
68
|
sendStreamingMessage: "SendStreamingMessage",
|
|
@@ -132,27 +70,25 @@ const A2A_METHODS = {
|
|
|
132
70
|
listTasks: "ListTasks",
|
|
133
71
|
cancelTask: "CancelTask",
|
|
134
72
|
subscribeToTask: "SubscribeToTask",
|
|
73
|
+
createTaskPushNotificationConfig: "CreateTaskPushNotificationConfig",
|
|
74
|
+
getTaskPushNotificationConfig: "GetTaskPushNotificationConfig",
|
|
75
|
+
listTaskPushNotificationConfigs: "ListTaskPushNotificationConfigs",
|
|
76
|
+
deleteTaskPushNotificationConfig: "DeleteTaskPushNotificationConfig",
|
|
135
77
|
getExtendedAgentCard: "GetExtendedAgentCard"
|
|
136
78
|
};
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
INVALID_PARAMS: -32602,
|
|
142
|
-
INTERNAL_ERROR: -32603,
|
|
143
|
-
UNAUTHORIZED: -32e3,
|
|
144
|
-
TASK_NOT_FOUND: -32001,
|
|
145
|
-
TASK_CANCEL_NOT_ALLOWED: -32002,
|
|
146
|
-
AGENT_CARD_NOT_FOUND: -32004,
|
|
147
|
-
AGENT_CARD_SIGNATURE_INVALID: -32005
|
|
148
|
-
};
|
|
149
|
-
/** Read a message's text parts as a single string (model-facing convenience). */
|
|
79
|
+
/**
|
|
80
|
+
* Read a message's text parts as a single string (model-facing convenience).
|
|
81
|
+
* File parts render as their URL/name; data parts as JSON.
|
|
82
|
+
*/
|
|
150
83
|
function partsToText(parts) {
|
|
151
84
|
if (!parts) return "";
|
|
152
85
|
return parts.map((p) => {
|
|
153
86
|
if ("text" in p && p.text) return p.text;
|
|
154
87
|
if ("data" in p && p.data !== void 0) return JSON.stringify(p.data);
|
|
155
|
-
if ("
|
|
88
|
+
if ("raw" in p || "url" in p || "filename" in p) {
|
|
89
|
+
const f = p;
|
|
90
|
+
return f.url ?? `[file ${f.filename ?? f.mediaType ?? "binary"}]`;
|
|
91
|
+
}
|
|
156
92
|
return "";
|
|
157
93
|
}).filter(Boolean).join("\n");
|
|
158
94
|
}
|
|
@@ -175,7 +111,10 @@ const a2aDomainSpec = defineDomain({
|
|
|
175
111
|
tables: {
|
|
176
112
|
tasks: domainTable(json$1),
|
|
177
113
|
contexts: domainTable(json$1),
|
|
178
|
-
agents: domainTable(json$1)
|
|
114
|
+
agents: domainTable(json$1),
|
|
115
|
+
identity: domainTable(json$1),
|
|
116
|
+
inbound_servers: domainTable(json$1),
|
|
117
|
+
outbound_servers: domainTable(json$1)
|
|
179
118
|
}
|
|
180
119
|
});
|
|
181
120
|
/**
|
|
@@ -214,6 +153,15 @@ var A2aDomain = class {
|
|
|
214
153
|
get agents() {
|
|
215
154
|
return this.handle.table("agents");
|
|
216
155
|
}
|
|
156
|
+
get identity() {
|
|
157
|
+
return this.handle.table("identity");
|
|
158
|
+
}
|
|
159
|
+
get inbound_servers() {
|
|
160
|
+
return this.handle.table("inbound_servers");
|
|
161
|
+
}
|
|
162
|
+
get outbound_servers() {
|
|
163
|
+
return this.handle.table("outbound_servers");
|
|
164
|
+
}
|
|
217
165
|
async close() {
|
|
218
166
|
await this.handle.close();
|
|
219
167
|
}
|
|
@@ -269,7 +217,8 @@ var DomainTaskStore = class {
|
|
|
269
217
|
parts: input.parts,
|
|
270
218
|
artifacts: [],
|
|
271
219
|
executor: "session",
|
|
272
|
-
summary: null
|
|
220
|
+
summary: null,
|
|
221
|
+
...input.serverId !== void 0 ? { serverId: input.serverId } : {}
|
|
273
222
|
};
|
|
274
223
|
this.records.set(record.taskId, record);
|
|
275
224
|
this.domain.tasks.put(recordKey(record.taskId), encodeRecord(record)).catch((err) => {
|
|
@@ -329,41 +278,6 @@ function withState(current, state, message, timestamp) {
|
|
|
329
278
|
};
|
|
330
279
|
}
|
|
331
280
|
//#endregion
|
|
332
|
-
//#region lib/types/server/executor.js
|
|
333
|
-
/**
|
|
334
|
-
* Executor abstraction for inbound tasks: an executor turns one task into
|
|
335
|
-
* output parts while reporting status and artifact updates. The registry is
|
|
336
|
-
* INTERNAL (built by the plugin from config) — public registration was cut:
|
|
337
|
-
* there is no external caller yet, so the seam stays an interface with two
|
|
338
|
-
* built-in implementations (session, subagent).
|
|
339
|
-
* @module dsh-a2a/server/executor
|
|
340
|
-
*/
|
|
341
|
-
/**
|
|
342
|
-
* Skill → executor resolution. The subagent executor is absent on
|
|
343
|
-
* compositions without the subagent seam; resolving a skill bound to a
|
|
344
|
-
* missing kind fails loudly with a readable message.
|
|
345
|
-
*/
|
|
346
|
-
var ExecutorSet = class {
|
|
347
|
-
bySkill;
|
|
348
|
-
session;
|
|
349
|
-
subagent;
|
|
350
|
-
constructor(bySkill, session, subagent) {
|
|
351
|
-
this.bySkill = bySkill;
|
|
352
|
-
this.session = session;
|
|
353
|
-
this.subagent = subagent;
|
|
354
|
-
}
|
|
355
|
-
resolve(skill) {
|
|
356
|
-
if ((this.bySkill[skill] ?? "session") === "session") return this.session;
|
|
357
|
-
const subagent = this.subagent;
|
|
358
|
-
if (subagent === void 0) throw new Error(`a2a: skill "${skill}" is bound to the subagent executor but no subagent seam is mounted`);
|
|
359
|
-
return subagent;
|
|
360
|
-
}
|
|
361
|
-
async disposeAll() {
|
|
362
|
-
await this.session.disposeAll?.();
|
|
363
|
-
await this.subagent?.disposeAll?.();
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
//#endregion
|
|
367
281
|
//#region lib/types/server/exec/agent-runtime.js
|
|
368
282
|
/**
|
|
369
283
|
* Shared agent machinery for the inbound executors: one DSH agent session per
|
|
@@ -406,7 +320,8 @@ var ContextSessionPool = class ContextSessionPool {
|
|
|
406
320
|
let presetId;
|
|
407
321
|
let setup;
|
|
408
322
|
if (presets !== void 0) {
|
|
409
|
-
|
|
323
|
+
const wanted = this.opts.presetId?.() ?? void 0;
|
|
324
|
+
presetId = (wanted !== void 0 ? await presets.resolve(wanted) : await presets.resolve()).id;
|
|
410
325
|
setup = async (agentCtx) => {
|
|
411
326
|
await presets.mount(agentCtx, presetId);
|
|
412
327
|
};
|
|
@@ -499,62 +414,328 @@ function probeService(ctx, name, method) {
|
|
|
499
414
|
} catch {}
|
|
500
415
|
}
|
|
501
416
|
//#endregion
|
|
502
|
-
//#region lib/types/
|
|
417
|
+
//#region lib/types/api.js
|
|
503
418
|
/**
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
419
|
+
* GUI control API for the A2A settings dashboard: one `GET /a2a/api`
|
|
420
|
+
* snapshot plus `POST /a2a/api` control actions (inbound/outbound server
|
|
421
|
+
* CRUD + enable/disable, task view/cancel) and `GET /a2a/api/presets` for the
|
|
422
|
+
* preset roster the pickers need. The browser half talks only to this route;
|
|
423
|
+
* it carries no protocol knowledge. Loopback-only by default so the dashboard
|
|
424
|
+
* cannot be driven from the wire.
|
|
425
|
+
*
|
|
426
|
+
* The route body is bound to the same `A2AServiceImpl` facade the `/a2a`
|
|
427
|
+
* command surface uses, so GUI actions and commands cannot disagree.
|
|
428
|
+
* @module dsh-a2a/api
|
|
508
429
|
*/
|
|
509
|
-
|
|
510
|
-
|
|
430
|
+
function readBody$1(req) {
|
|
431
|
+
return new Promise((resolve, reject) => {
|
|
432
|
+
const chunks = [];
|
|
433
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
434
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
435
|
+
req.once("error", reject);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
/** The caller is the local machine (never a remote peer). */
|
|
439
|
+
function isLoopback(req) {
|
|
440
|
+
const address = req.socket?.remoteAddress;
|
|
441
|
+
return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
|
|
442
|
+
}
|
|
443
|
+
function json(res, status, value) {
|
|
444
|
+
res.writeHead(status, {
|
|
445
|
+
"content-type": "application/json",
|
|
446
|
+
"cache-control": "no-cache"
|
|
447
|
+
});
|
|
448
|
+
res.end(JSON.stringify(value));
|
|
449
|
+
}
|
|
450
|
+
/** Handle one dashboard API request against the service facade. */
|
|
451
|
+
async function handleApiRequest(req, res, impl) {
|
|
452
|
+
if (!isLoopback(req)) {
|
|
453
|
+
res.writeHead(403, { "content-type": "text/plain" });
|
|
454
|
+
res.end("forbidden");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const path = (req.url ?? "").split("?")[0] ?? "";
|
|
458
|
+
if (req.method === "GET" && path === "/a2a/api/presets") {
|
|
459
|
+
json(res, 200, await impl.presets());
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (req.method === "GET") {
|
|
463
|
+
json(res, 200, snapshotOf(impl));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (req.method === "POST") {
|
|
467
|
+
let payload;
|
|
468
|
+
try {
|
|
469
|
+
payload = JSON.parse(await readBody$1(req));
|
|
470
|
+
} catch {
|
|
471
|
+
json(res, 400, {
|
|
472
|
+
ok: false,
|
|
473
|
+
message: "invalid JSON body"
|
|
474
|
+
});
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const result = await dispatch(payload, impl);
|
|
478
|
+
json(res, result.ok ? 200 : 409, result);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
res.writeHead(405, { "content-type": "text/plain" });
|
|
482
|
+
res.end("Method Not Allowed");
|
|
483
|
+
}
|
|
484
|
+
function snapshotOf(impl) {
|
|
485
|
+
const status = impl.status();
|
|
511
486
|
return {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return { parts: [{ text: (input.signal.aborted && !reply ? "(canceled)" : reply) || "(no reply)" }] };
|
|
517
|
-
},
|
|
518
|
-
disposeAll: () => pool.disposeAll()
|
|
487
|
+
inbounds: status.inbounds ?? [],
|
|
488
|
+
outbounds: status.outbounds ?? [],
|
|
489
|
+
tasks: impl.listTasks(),
|
|
490
|
+
peers: status.peers ?? impl.inbounds()
|
|
519
491
|
};
|
|
520
492
|
}
|
|
493
|
+
async function dispatch(payload, impl) {
|
|
494
|
+
switch (payload.action) {
|
|
495
|
+
case "inbound.create": return impl.createInboundServer({
|
|
496
|
+
name: payload.name,
|
|
497
|
+
description: payload.description,
|
|
498
|
+
version: payload.version,
|
|
499
|
+
...payload.endpointPath !== void 0 ? { endpointPath: payload.endpointPath } : {},
|
|
500
|
+
...payload.preset !== void 0 ? { preset: payload.preset } : {},
|
|
501
|
+
...payload.authTokenEnv !== void 0 ? { authTokenEnv: payload.authTokenEnv } : {},
|
|
502
|
+
...payload.skills !== void 0 ? { skills: payload.skills } : {},
|
|
503
|
+
...payload.enabled !== void 0 ? { enabled: payload.enabled } : {}
|
|
504
|
+
});
|
|
505
|
+
case "inbound.remove": return impl.removeInboundServer(payload.id);
|
|
506
|
+
case "inbound.enable": return impl.setInboundServerEnabled(payload.id, true);
|
|
507
|
+
case "inbound.disable": return impl.setInboundServerEnabled(payload.id, false);
|
|
508
|
+
case "inbound.update": return impl.updateInboundServer(payload.id, {
|
|
509
|
+
...payload.name !== void 0 ? { name: payload.name } : {},
|
|
510
|
+
...payload.description !== void 0 ? { description: payload.description } : {},
|
|
511
|
+
...payload.version !== void 0 ? { version: payload.version } : {},
|
|
512
|
+
...payload.endpointPath !== void 0 ? { endpointPath: payload.endpointPath } : {},
|
|
513
|
+
...payload.preset !== void 0 ? { preset: payload.preset } : {},
|
|
514
|
+
...payload.authTokenEnv !== void 0 ? { authTokenEnv: payload.authTokenEnv } : {},
|
|
515
|
+
...payload.skills !== void 0 ? { skills: payload.skills } : {}
|
|
516
|
+
});
|
|
517
|
+
case "outbound.create": return impl.createOutboundServer({
|
|
518
|
+
name: payload.name,
|
|
519
|
+
agentCardUrl: payload.agentCardUrl,
|
|
520
|
+
...payload.bearerTokenEnv !== void 0 ? { bearerTokenEnv: payload.bearerTokenEnv } : {},
|
|
521
|
+
...payload.preset !== void 0 ? { preset: payload.preset } : {},
|
|
522
|
+
...payload.enabled !== void 0 ? { enabled: payload.enabled } : {},
|
|
523
|
+
...payload.timeoutMs !== void 0 ? { timeoutMs: payload.timeoutMs } : {}
|
|
524
|
+
});
|
|
525
|
+
case "outbound.remove": return impl.removeOutboundServer(payload.id);
|
|
526
|
+
case "outbound.enable": return impl.setOutboundServerEnabled(payload.id, true);
|
|
527
|
+
case "outbound.disable": return impl.setOutboundServerEnabled(payload.id, false);
|
|
528
|
+
case "outbound.refresh": return impl.refreshOutboundServer(payload.id);
|
|
529
|
+
case "task.cancel": return impl.cancelTask(payload.id);
|
|
530
|
+
case "inbound.close": return impl.closeInbound(payload.id);
|
|
531
|
+
default: return {
|
|
532
|
+
ok: false,
|
|
533
|
+
message: `unknown action ${String(payload.action)}`
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
521
537
|
//#endregion
|
|
522
|
-
//#region lib/types/
|
|
538
|
+
//#region lib/types/service.js
|
|
523
539
|
/**
|
|
524
|
-
* The `
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* fails loudly at resolution time.
|
|
529
|
-
* @module dsh-a2a/server/exec/subagent
|
|
540
|
+
* The `ctx.a2a` service facade: the thin read/control surface commands, the
|
|
541
|
+
* GUI API, and other plugins use. Registered as a Cordis Service so the key
|
|
542
|
+
* disappears with the plugin fiber.
|
|
543
|
+
* @module dsh-a2a/service
|
|
530
544
|
*/
|
|
531
|
-
/**
|
|
532
|
-
|
|
533
|
-
|
|
545
|
+
/** The service other plugins read as `ctx.a2a`. */
|
|
546
|
+
var A2AService = class extends Service {
|
|
547
|
+
impl;
|
|
548
|
+
constructor(ctx, impl) {
|
|
549
|
+
super(ctx, "a2a");
|
|
550
|
+
this.impl = impl;
|
|
551
|
+
}
|
|
552
|
+
status() {
|
|
553
|
+
return this.impl.status();
|
|
554
|
+
}
|
|
555
|
+
async presets() {
|
|
556
|
+
return this.impl.presets();
|
|
557
|
+
}
|
|
558
|
+
listInboundServers() {
|
|
559
|
+
return this.impl.listInboundServers();
|
|
560
|
+
}
|
|
561
|
+
async createInboundServer(input) {
|
|
562
|
+
return this.impl.createInboundServer(input);
|
|
563
|
+
}
|
|
564
|
+
async removeInboundServer(id) {
|
|
565
|
+
return this.impl.removeInboundServer(id);
|
|
566
|
+
}
|
|
567
|
+
async setInboundServerEnabled(id, enabled) {
|
|
568
|
+
return this.impl.setInboundServerEnabled(id, enabled);
|
|
569
|
+
}
|
|
570
|
+
async updateInboundServer(id, patch) {
|
|
571
|
+
return this.impl.updateInboundServer(id, patch);
|
|
572
|
+
}
|
|
573
|
+
listOutboundServers() {
|
|
574
|
+
return this.impl.listOutboundServers();
|
|
575
|
+
}
|
|
576
|
+
async createOutboundServer(input) {
|
|
577
|
+
return this.impl.createOutboundServer(input);
|
|
578
|
+
}
|
|
579
|
+
async removeOutboundServer(id) {
|
|
580
|
+
return this.impl.removeOutboundServer(id);
|
|
581
|
+
}
|
|
582
|
+
async setOutboundServerEnabled(id, enabled) {
|
|
583
|
+
return this.impl.setOutboundServerEnabled(id, enabled);
|
|
584
|
+
}
|
|
585
|
+
async refreshOutboundServer(id) {
|
|
586
|
+
return this.impl.refreshOutboundServer(id);
|
|
587
|
+
}
|
|
588
|
+
getTask(taskId) {
|
|
589
|
+
return this.impl.getTask(taskId);
|
|
590
|
+
}
|
|
591
|
+
listTasks() {
|
|
592
|
+
return this.impl.listTasks();
|
|
593
|
+
}
|
|
594
|
+
async cancelTask(taskId) {
|
|
595
|
+
return this.impl.cancelTask(taskId);
|
|
596
|
+
}
|
|
597
|
+
inbounds() {
|
|
598
|
+
return this.impl.inbounds();
|
|
599
|
+
}
|
|
600
|
+
async closeInbound(peerId) {
|
|
601
|
+
return this.impl.closeInbound(peerId);
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region lib/types/commands.js
|
|
606
|
+
/**
|
|
607
|
+
* The `/a2a` command surface: status, inbound/outbound server CRUD +
|
|
608
|
+
* enable/disable, and task control — a text backup to the GUI (the settings
|
|
609
|
+
* panel is the primary surface). All operations delegate to the same
|
|
610
|
+
* `A2AServiceImpl` facade as the dashboard API.
|
|
611
|
+
* @module dsh-a2a/commands
|
|
612
|
+
*/
|
|
613
|
+
/** Build the `/a2a` command definition over the service facade. */
|
|
614
|
+
function buildA2aCommand(impl) {
|
|
534
615
|
return {
|
|
535
|
-
name: "
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
616
|
+
name: "a2a",
|
|
617
|
+
description: "Manage the A2A plugin: status, inbound/outbound servers, tasks.",
|
|
618
|
+
handler: async (invocation) => {
|
|
619
|
+
const [verb, ...rest] = invocation.rawInput.trim().split(/\s+/);
|
|
620
|
+
switch (verb ?? "") {
|
|
621
|
+
case "":
|
|
622
|
+
case "status": return success(JSON.stringify(impl.status(), null, 2));
|
|
623
|
+
case "presets": return success(JSON.stringify(await impl.presets(), null, 2));
|
|
624
|
+
case "inbound": {
|
|
625
|
+
const [action, id, ...args] = rest;
|
|
626
|
+
switch (action ?? "") {
|
|
627
|
+
case "list": return success(JSON.stringify(impl.listInboundServers(), null, 2));
|
|
628
|
+
case "create": {
|
|
629
|
+
const name = args[0];
|
|
630
|
+
if (!name) return error("usage: /a2a inbound create <name> [preset] [description]");
|
|
631
|
+
const preset = args[1];
|
|
632
|
+
return outcome(await impl.createInboundServer({
|
|
633
|
+
name,
|
|
634
|
+
description: args[2] ?? "An A2A inbound server",
|
|
635
|
+
version: "1.0.0",
|
|
636
|
+
...preset && preset !== "default" ? { preset } : {}
|
|
637
|
+
}));
|
|
638
|
+
}
|
|
639
|
+
case "remove": return id ? outcome(await impl.removeInboundServer(id)) : error("usage: /a2a inbound remove <id>");
|
|
640
|
+
case "enable": return id ? outcome(await impl.setInboundServerEnabled(id, true)) : error("usage: /a2a inbound enable <id>");
|
|
641
|
+
case "disable": return id ? outcome(await impl.setInboundServerEnabled(id, false)) : error("usage: /a2a inbound disable <id>");
|
|
642
|
+
default: return error("usage: /a2a inbound list|create|remove|enable|disable ...");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
case "outbound": {
|
|
646
|
+
const [action, id, ...args] = rest;
|
|
647
|
+
switch (action ?? "") {
|
|
648
|
+
case "list": return success(JSON.stringify(impl.listOutboundServers(), null, 2));
|
|
649
|
+
case "create": {
|
|
650
|
+
const url = args[0];
|
|
651
|
+
if (!url) return error("usage: /a2a outbound create <agentCardUrl> [name] [preset]");
|
|
652
|
+
const name = args[1] ?? url;
|
|
653
|
+
const preset = args[2];
|
|
654
|
+
return outcome(await impl.createOutboundServer({
|
|
655
|
+
name,
|
|
656
|
+
agentCardUrl: url,
|
|
657
|
+
...preset && preset !== "default" ? { preset } : {}
|
|
658
|
+
}));
|
|
659
|
+
}
|
|
660
|
+
case "remove": return id ? outcome(await impl.removeOutboundServer(id)) : error("usage: /a2a outbound remove <id>");
|
|
661
|
+
case "enable": return id ? outcome(await impl.setOutboundServerEnabled(id, true)) : error("usage: /a2a outbound enable <id>");
|
|
662
|
+
case "disable": return id ? outcome(await impl.setOutboundServerEnabled(id, false)) : error("usage: /a2a outbound disable <id>");
|
|
663
|
+
case "refresh": return id ? outcome(await impl.refreshOutboundServer(id)) : error("usage: /a2a outbound refresh <id>");
|
|
664
|
+
default: return error("usage: /a2a outbound list|create|remove|enable|disable|refresh ...");
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
case "tasks": return success(JSON.stringify(impl.listTasks(), null, 2));
|
|
668
|
+
case "task": {
|
|
669
|
+
const [action, id] = rest;
|
|
670
|
+
if (action === "get" && id) return success(JSON.stringify(impl.getTask(id), null, 2));
|
|
671
|
+
if (action === "cancel" && id) return outcome(await impl.cancelTask(id));
|
|
672
|
+
return error("usage: /a2a task get|cancel <taskId>");
|
|
673
|
+
}
|
|
674
|
+
case "peers": return success(JSON.stringify(impl.inbounds(), null, 2));
|
|
675
|
+
case "help": return success("a2a: status | presets | inbound list/create/remove/enable/disable | outbound list/create/remove/enable/disable/refresh | tasks | task get/cancel <id> | peers | help");
|
|
676
|
+
default: return error(`unknown a2a verb "${verb}" (try /a2a help)`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function success(text) {
|
|
682
|
+
return {
|
|
683
|
+
kind: "success",
|
|
684
|
+
text
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function error(text) {
|
|
688
|
+
return {
|
|
689
|
+
kind: "error",
|
|
690
|
+
text
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function outcome(result) {
|
|
694
|
+
return result.ok ? success(result.message) : error(result.message);
|
|
695
|
+
}
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region lib/types/server/card.js
|
|
698
|
+
/**
|
|
699
|
+
* AgentCard assembly for the inbound half: identity and skill DECLARATION at
|
|
700
|
+
* the instance level. The skill list is exactly the creator-declared
|
|
701
|
+
* `AgentSkill[]` stored on the inbound server instance — no derivation from
|
|
702
|
+
* the live tool registry (the v0.2 white-list mechanism is removed). The
|
|
703
|
+
* advertised card is a pure function of the instance record plus its base URL
|
|
704
|
+
* and endpoint path.
|
|
705
|
+
* @module dsh-a2a/server/card
|
|
706
|
+
*/
|
|
707
|
+
/** Assemble the AgentCard advertised by this DSH. */
|
|
708
|
+
function buildCard(options) {
|
|
709
|
+
const iface = {
|
|
710
|
+
url: `${options.baseUrl.replace(/\/$/, "")}${options.endpointPath}`,
|
|
711
|
+
protocolBinding: "JSONRPC",
|
|
712
|
+
protocolVersion: "1.0"
|
|
713
|
+
};
|
|
714
|
+
return {
|
|
715
|
+
name: options.name,
|
|
716
|
+
description: options.description,
|
|
717
|
+
version: options.version,
|
|
718
|
+
provider: {
|
|
719
|
+
url: "https://deepseek.com",
|
|
720
|
+
organization: "DeepSeek"
|
|
556
721
|
},
|
|
557
|
-
|
|
722
|
+
capabilities: {
|
|
723
|
+
streaming: true,
|
|
724
|
+
pushNotifications: false,
|
|
725
|
+
extendedAgentCard: true,
|
|
726
|
+
stateTransitionHistory: false
|
|
727
|
+
},
|
|
728
|
+
defaultInputModes: ["text/plain"],
|
|
729
|
+
defaultOutputModes: ["text/plain"],
|
|
730
|
+
skills: options.skills,
|
|
731
|
+
supportedInterfaces: [iface],
|
|
732
|
+
...options.authToken !== void 0 ? {
|
|
733
|
+
securitySchemes: { bearer: { httpAuthSecurityScheme: {
|
|
734
|
+
scheme: "bearer",
|
|
735
|
+
description: "Shared bearer token"
|
|
736
|
+
} } },
|
|
737
|
+
securityRequirements: [{ schemes: { bearer: { list: ["bearer"] } } }]
|
|
738
|
+
} : {}
|
|
558
739
|
};
|
|
559
740
|
}
|
|
560
741
|
//#endregion
|
|
@@ -621,6 +802,14 @@ var A2AServer = class {
|
|
|
621
802
|
this.opts = opts;
|
|
622
803
|
this.card = opts.card;
|
|
623
804
|
}
|
|
805
|
+
/** Swap the served AgentCard (runtime identity edits). Routes re-read `server.card` on every request. */
|
|
806
|
+
setCard(next) {
|
|
807
|
+
this.card = next;
|
|
808
|
+
}
|
|
809
|
+
/** The AgentCard route path for this instance. */
|
|
810
|
+
cardPath() {
|
|
811
|
+
return this.opts.cardPath ?? "/.well-known/agent-card.json";
|
|
812
|
+
}
|
|
624
813
|
/**
|
|
625
814
|
* Abort a running task by control path (facade /a2a task cancel): abort the
|
|
626
815
|
* executor's signal, settle the task CANCELED, and wake stream waiters.
|
|
@@ -648,7 +837,7 @@ var A2AServer = class {
|
|
|
648
837
|
/** Route one inbound HTTP request (GET card, POST JSON-RPC). */
|
|
649
838
|
async handle(req, body) {
|
|
650
839
|
const path = (req.url ?? "").split("?")[0] ?? "";
|
|
651
|
-
if (req.method === "GET" && path ===
|
|
840
|
+
if (req.method === "GET" && path === this.cardPath()) return {
|
|
652
841
|
status: 200,
|
|
653
842
|
contentType: "application/json",
|
|
654
843
|
body: JSON.stringify(this.card)
|
|
@@ -661,7 +850,7 @@ var A2AServer = class {
|
|
|
661
850
|
if (!this.authorized(req)) return {
|
|
662
851
|
status: 401,
|
|
663
852
|
contentType: "application/json",
|
|
664
|
-
body: JSON.stringify(rpcError(null,
|
|
853
|
+
body: JSON.stringify(rpcError(null, -32040, "Unauthorized")),
|
|
665
854
|
headers: { "WWW-Authenticate": "Bearer" }
|
|
666
855
|
};
|
|
667
856
|
const rpc = parseRpc(body);
|
|
@@ -790,7 +979,6 @@ var A2AServer = class {
|
|
|
790
979
|
const gateOutcome = await this.opts.gate(gateInputFrom(message, null));
|
|
791
980
|
if (!gateOutcome.ok) throw rpcFault(A2A_ERROR_CODES.INVALID_PARAMS, gateOutcome.reason);
|
|
792
981
|
const record = this.ensureTask(message, null);
|
|
793
|
-
this.noteInbound({ method }, method, [record.taskId], false);
|
|
794
982
|
await this.runTask(record);
|
|
795
983
|
return toTask(this.opts.store.get(record.taskId) ?? record);
|
|
796
984
|
}
|
|
@@ -1013,16 +1201,18 @@ function extractTaskIds(result) {
|
|
|
1013
1201
|
* `@deepseek-ai/dsh-host-webserver`), with runtime enable/disable.
|
|
1014
1202
|
* @module dsh-a2a/server/routes
|
|
1015
1203
|
*/
|
|
1016
|
-
const
|
|
1204
|
+
const DEFAULT_CARD_PATH = "/.well-known/agent-card.json";
|
|
1017
1205
|
/** Runtime-registered HTTP routes for one A2A server. */
|
|
1018
1206
|
var A2aRoutes = class {
|
|
1019
1207
|
webServer;
|
|
1020
1208
|
server;
|
|
1209
|
+
cardPath;
|
|
1021
1210
|
disposers = [];
|
|
1022
1211
|
registered = false;
|
|
1023
|
-
constructor(webServer, server) {
|
|
1212
|
+
constructor(webServer, server, cardPath = DEFAULT_CARD_PATH) {
|
|
1024
1213
|
this.webServer = webServer;
|
|
1025
1214
|
this.server = server;
|
|
1215
|
+
this.cardPath = cardPath;
|
|
1026
1216
|
}
|
|
1027
1217
|
get active() {
|
|
1028
1218
|
return this.registered;
|
|
@@ -1032,7 +1222,7 @@ var A2aRoutes = class {
|
|
|
1032
1222
|
if (this.registered) return;
|
|
1033
1223
|
this.disposers.push(this.webServer.register({
|
|
1034
1224
|
kind: "exact",
|
|
1035
|
-
path:
|
|
1225
|
+
path: this.cardPath,
|
|
1036
1226
|
handler: (_req, res) => {
|
|
1037
1227
|
res.writeHead(200, { "content-type": "application/json" });
|
|
1038
1228
|
res.end(JSON.stringify(this.server.card));
|
|
@@ -1057,7 +1247,7 @@ var A2aRoutes = class {
|
|
|
1057
1247
|
const path = (req.url ?? "").split("?")[0] ?? "";
|
|
1058
1248
|
const streaming = req.method === "POST" && (req.headers.accept ?? "").includes("text/event-stream");
|
|
1059
1249
|
if (req.method === "POST" && path === endpointPathOf(this.server)) {
|
|
1060
|
-
const body = await readBody
|
|
1250
|
+
const body = await readBody(req);
|
|
1061
1251
|
if (streaming) {
|
|
1062
1252
|
if (!this.server.authorized(toServerReq(req))) {
|
|
1063
1253
|
res.writeHead(401, {
|
|
@@ -1093,7 +1283,7 @@ var A2aRoutes = class {
|
|
|
1093
1283
|
res.end(out.body);
|
|
1094
1284
|
return;
|
|
1095
1285
|
}
|
|
1096
|
-
const out = await this.server.handle(toServerReq(req), path ===
|
|
1286
|
+
const out = await this.server.handle(toServerReq(req), path === this.cardPath ? "" : "");
|
|
1097
1287
|
res.writeHead(out.status, {
|
|
1098
1288
|
"content-type": out.contentType,
|
|
1099
1289
|
...out.headers ?? {}
|
|
@@ -1110,7 +1300,7 @@ function endpointPathOf(server) {
|
|
|
1110
1300
|
return "/a2a";
|
|
1111
1301
|
}
|
|
1112
1302
|
}
|
|
1113
|
-
function readBody
|
|
1303
|
+
function readBody(req) {
|
|
1114
1304
|
return new Promise((resolve, reject) => {
|
|
1115
1305
|
const chunks = [];
|
|
1116
1306
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
@@ -1136,94 +1326,494 @@ function toServerReq(req) {
|
|
|
1136
1326
|
};
|
|
1137
1327
|
}
|
|
1138
1328
|
//#endregion
|
|
1139
|
-
//#region lib/types/
|
|
1329
|
+
//#region lib/types/server/exec/session.js
|
|
1140
1330
|
/**
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
|
|
1331
|
+
* The `session` executor: one inbound task is one turn on the context's DSH
|
|
1332
|
+
* agent session; reply text becomes the task's output part. Works on any
|
|
1333
|
+
* composition with an agent loop (the `chat` skill and the default path).
|
|
1334
|
+
* @module dsh-a2a/server/exec/session
|
|
1335
|
+
*/
|
|
1336
|
+
/** Build the session executor over a context session pool. */
|
|
1337
|
+
function createSessionExecutor(pool) {
|
|
1338
|
+
return {
|
|
1339
|
+
name: "session",
|
|
1340
|
+
async execute(input) {
|
|
1341
|
+
if (!input.prompt) return { parts: [{ text: "No prompt provided." }] };
|
|
1342
|
+
const reply = await pool.runTurn(input.contextId, input.prompt, input.signal);
|
|
1343
|
+
return { parts: [{ text: (input.signal.aborted && !reply ? "(canceled)" : reply) || "(no reply)" }] };
|
|
1344
|
+
},
|
|
1345
|
+
disposeAll: () => pool.disposeAll()
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
//#endregion
|
|
1349
|
+
//#region lib/types/server/exec/subagent.js
|
|
1350
|
+
/**
|
|
1351
|
+
* The `subagent` executor: one inbound task is delegated to a named one-shot
|
|
1352
|
+
* subagent under the context's parent session. The child runs with the
|
|
1353
|
+
* composition's own tools and the delegation boundary; its output becomes the
|
|
1354
|
+
* task's artifact. The subagent seam must be mounted, else the skill binding
|
|
1355
|
+
* fails loudly at resolution time.
|
|
1356
|
+
* @module dsh-a2a/server/exec/subagent
|
|
1357
|
+
*/
|
|
1358
|
+
/** Build a subagent executor delegating inbound tasks to one-shot children. */
|
|
1359
|
+
function createSubagentExecutor(opts) {
|
|
1360
|
+
const { pool, subagents, provider } = opts;
|
|
1361
|
+
return {
|
|
1362
|
+
name: "subagent",
|
|
1363
|
+
async execute(input, execCtx) {
|
|
1364
|
+
if (!input.prompt) return { parts: [{ text: "No prompt provided." }] };
|
|
1365
|
+
const { agent } = await pool.agentFor(input.contextId);
|
|
1366
|
+
const text = (await (await subagents.start(provider, {
|
|
1367
|
+
label: `a2a:${input.skill}`,
|
|
1368
|
+
prompt: [{
|
|
1369
|
+
type: "text",
|
|
1370
|
+
text: input.prompt
|
|
1371
|
+
}],
|
|
1372
|
+
parent: agent,
|
|
1373
|
+
signal: input.signal
|
|
1374
|
+
})).result).output.filter((b) => typeof b.text === "string").map((b) => b.text).filter(Boolean).join("\n").trim();
|
|
1375
|
+
execCtx.onEvent({
|
|
1376
|
+
type: "artifact",
|
|
1377
|
+
artifactId: "result",
|
|
1378
|
+
parts: [{ text: text || "(subagent returned no output)" }],
|
|
1379
|
+
name: input.skill,
|
|
1380
|
+
lastChunk: true
|
|
1381
|
+
});
|
|
1382
|
+
return { parts: [{ text: text || "(subagent returned no output)" }] };
|
|
1383
|
+
},
|
|
1384
|
+
disposeAll: () => pool.disposeAll()
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region lib/types/server/executor.js
|
|
1389
|
+
/**
|
|
1390
|
+
* Executor abstraction for inbound tasks: an executor turns one task into
|
|
1391
|
+
* output parts while reporting status and artifact updates. The registry is
|
|
1392
|
+
* INTERNAL (built by the plugin from config) — public registration was cut:
|
|
1393
|
+
* there is no external caller yet, so the seam stays an interface with two
|
|
1394
|
+
* built-in implementations (session, subagent).
|
|
1395
|
+
* @module dsh-a2a/server/executor
|
|
1396
|
+
*/
|
|
1397
|
+
/**
|
|
1398
|
+
* Skill → executor resolution. The subagent executor is absent on
|
|
1399
|
+
* compositions without the subagent seam; resolving a skill bound to a
|
|
1400
|
+
* missing kind fails loudly with a readable message.
|
|
1401
|
+
*/
|
|
1402
|
+
var ExecutorSet = class {
|
|
1403
|
+
bySkill;
|
|
1404
|
+
session;
|
|
1405
|
+
subagent;
|
|
1406
|
+
constructor(bySkill, session, subagent) {
|
|
1407
|
+
this.bySkill = bySkill;
|
|
1408
|
+
this.session = session;
|
|
1409
|
+
this.subagent = subagent;
|
|
1410
|
+
}
|
|
1411
|
+
resolve(skill) {
|
|
1412
|
+
if ((this.bySkill[skill] ?? "session") === "session") return this.session;
|
|
1413
|
+
const subagent = this.subagent;
|
|
1414
|
+
if (subagent === void 0) throw new Error(`a2a: skill "${skill}" is bound to the subagent executor but no subagent seam is mounted`);
|
|
1415
|
+
return subagent;
|
|
1416
|
+
}
|
|
1417
|
+
async disposeAll() {
|
|
1418
|
+
await this.session.disposeAll?.();
|
|
1419
|
+
await this.subagent?.disposeAll?.();
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
//#endregion
|
|
1423
|
+
//#region lib/types/server/inbound-registry.js
|
|
1424
|
+
/**
|
|
1425
|
+
* Inbound connection registry: which remote peers are talking to this DSH's
|
|
1426
|
+
* A2A server. Fed by the A2A server's `onInbound` hook (every JSON-RPC
|
|
1427
|
+
* request / SSE open), surfaced through the dashboard API, and disconnectable
|
|
1428
|
+
* (`closePeer` cancels the peer's active tasks and drops the record).
|
|
1429
|
+
* @module dsh-a2a/server/inbound-registry
|
|
1430
|
+
*/
|
|
1431
|
+
function newPeerId() {
|
|
1432
|
+
return `peer-${crypto.randomUUID()}`;
|
|
1433
|
+
}
|
|
1434
|
+
/** In-memory inbound peer registry. */
|
|
1435
|
+
var LiveInboundRegistry = class {
|
|
1436
|
+
peers = /* @__PURE__ */ new Map();
|
|
1437
|
+
findBySource(source) {
|
|
1438
|
+
if (source === null) return void 0;
|
|
1439
|
+
for (const peer of this.peers.values()) if (peer.source === source) return peer;
|
|
1440
|
+
}
|
|
1441
|
+
/** Observe one inbound event: method, source, task ids, streaming flag. */
|
|
1442
|
+
note(input) {
|
|
1443
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1444
|
+
const source = input.source ?? null;
|
|
1445
|
+
let peer = this.findBySource(source);
|
|
1446
|
+
if (peer === void 0) {
|
|
1447
|
+
peer = {
|
|
1448
|
+
id: newPeerId(),
|
|
1449
|
+
label: source ?? "unknown",
|
|
1450
|
+
source,
|
|
1451
|
+
firstSeen: now,
|
|
1452
|
+
lastSeen: now,
|
|
1453
|
+
taskCount: 0,
|
|
1454
|
+
active: /* @__PURE__ */ new Set(),
|
|
1455
|
+
streamingCount: 0
|
|
1456
|
+
};
|
|
1457
|
+
this.peers.set(peer.id, peer);
|
|
1458
|
+
}
|
|
1459
|
+
peer.lastSeen = now;
|
|
1460
|
+
peer.taskCount += input.taskIds.length;
|
|
1461
|
+
for (const id of input.taskIds) peer.active.add(id);
|
|
1462
|
+
if (input.streaming) peer.streamingCount += 1;
|
|
1463
|
+
}
|
|
1464
|
+
/** A task settled: drop it from every peer's active set. */
|
|
1465
|
+
settle(taskId) {
|
|
1466
|
+
for (const peer of this.peers.values()) if (peer.active.delete(taskId)) peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
1467
|
+
}
|
|
1468
|
+
/** Decrement streaming count when an SSE connection closes. */
|
|
1469
|
+
endStream(source) {
|
|
1470
|
+
const peer = this.findBySource(source ?? null);
|
|
1471
|
+
if (peer !== void 0 && peer.streamingCount > 0) {
|
|
1472
|
+
peer.streamingCount -= 1;
|
|
1473
|
+
peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
list() {
|
|
1477
|
+
return [...this.peers.values()].map((p) => ({
|
|
1478
|
+
id: p.id,
|
|
1479
|
+
label: p.label,
|
|
1480
|
+
source: p.source,
|
|
1481
|
+
firstSeen: p.firstSeen,
|
|
1482
|
+
lastSeen: p.lastSeen,
|
|
1483
|
+
taskCount: p.taskCount,
|
|
1484
|
+
activeTaskIds: [...p.active],
|
|
1485
|
+
streaming: p.streamingCount > 0
|
|
1486
|
+
})).sort((a, b) => a.lastSeen < b.lastSeen ? 1 : -1);
|
|
1487
|
+
}
|
|
1488
|
+
closePeer(peerId) {
|
|
1489
|
+
const peer = this.peers.get(peerId);
|
|
1490
|
+
if (peer === void 0) return {
|
|
1491
|
+
ok: false,
|
|
1492
|
+
message: `inbound peer ${peerId} not found`
|
|
1493
|
+
};
|
|
1494
|
+
this.peers.delete(peerId);
|
|
1495
|
+
return {
|
|
1496
|
+
ok: true,
|
|
1497
|
+
message: `inbound peer ${peer.label} closed`
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
activeTasksOf(peerId) {
|
|
1501
|
+
return [...this.peers.get(peerId)?.active ?? []];
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
//#endregion
|
|
1505
|
+
//#region lib/types/servers/inbound-manager.js
|
|
1506
|
+
/**
|
|
1507
|
+
* Inbound server manager: owns every inbound A2A server instance. Each
|
|
1508
|
+
* instance binds one agent preset (its session pool is composed from that
|
|
1509
|
+
* preset), advertises creator-declared skills, and serves its own endpoint and
|
|
1510
|
+
* AgentCard route. Instances are persisted in the `inbound_servers` domain
|
|
1511
|
+
* table and assembled on boot; the manager owns route registration and the
|
|
1512
|
+
* full lifecycle (add/enable/disable/update/remove) driven from the GUI and
|
|
1513
|
+
* facade, and disposes every instance's environment on teardown.
|
|
1146
1514
|
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1515
|
+
* Skill declaration: the stored `skills` list is exactly what the creator
|
|
1516
|
+
* declared. When a creator leaves it empty at creation time, the default skill
|
|
1517
|
+
* derives from the bound preset's display name (else the built-in `chat`
|
|
1518
|
+
* skill), per the v1.0 design — the v0.2 tool white-list derivation is gone.
|
|
1519
|
+
* @module dsh-a2a/servers/inbound-manager
|
|
1150
1520
|
*/
|
|
1151
|
-
function
|
|
1152
|
-
return
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1521
|
+
function refuseExecutor(reason) {
|
|
1522
|
+
return {
|
|
1523
|
+
name: "refuse",
|
|
1524
|
+
async execute() {
|
|
1525
|
+
throw new Error(`a2a: ${reason}; refusing inbound task`);
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1158
1528
|
}
|
|
1159
|
-
/**
|
|
1160
|
-
function
|
|
1161
|
-
|
|
1162
|
-
|
|
1529
|
+
/** Default skkill when a creator declares none: the preset display name. */
|
|
1530
|
+
function defaultSkillFor(skills, presetName) {
|
|
1531
|
+
if (skills !== void 0 && skills.length > 0) return skills;
|
|
1532
|
+
if (presetName !== void 0 && presetName.length > 0) return [{
|
|
1533
|
+
id: "chat",
|
|
1534
|
+
name: presetName,
|
|
1535
|
+
description: `Compose inbound sessions from the "${presetName}" agent preset.`,
|
|
1536
|
+
tags: ["preset"]
|
|
1537
|
+
}];
|
|
1538
|
+
return [{
|
|
1539
|
+
id: "chat",
|
|
1540
|
+
name: "chat",
|
|
1541
|
+
description: "Conversational assistance over a DSH agent session.",
|
|
1542
|
+
tags: ["chat"]
|
|
1543
|
+
}];
|
|
1163
1544
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1545
|
+
/** Assemble one live instance from a persisted record. */
|
|
1546
|
+
function assemble(record, host) {
|
|
1547
|
+
const skills = record.skills;
|
|
1548
|
+
const endpointPath = record.endpointPath;
|
|
1549
|
+
const cardPath = `${endpointPath.replace(/\/$/, "")}/agent-card.json`;
|
|
1550
|
+
const card = buildCard({
|
|
1551
|
+
baseUrl: host.defaultBaseUrl,
|
|
1552
|
+
endpointPath,
|
|
1553
|
+
name: record.name,
|
|
1554
|
+
description: record.description,
|
|
1555
|
+
version: record.version,
|
|
1556
|
+
skills,
|
|
1557
|
+
...record.authTokenEnv !== void 0 && process.env[record.authTokenEnv] !== void 0 ? { authToken: process.env[record.authTokenEnv] } : {}
|
|
1168
1558
|
});
|
|
1169
|
-
|
|
1559
|
+
const sessionPool = host.agents ? new ContextSessionPool(host.agents, {
|
|
1560
|
+
cwd: host.sessionCwd,
|
|
1561
|
+
...host.agentPresets !== void 0 ? { agentPresets: host.agentPresets } : {},
|
|
1562
|
+
...record.preset !== void 0 ? { presetId: () => record.preset } : {},
|
|
1563
|
+
resolveAgentOptions: () => host.resolveDefaultModel?.()
|
|
1564
|
+
}) : void 0;
|
|
1565
|
+
const executors = new ExecutorSet({ chat: "session" }, sessionPool ? createSessionExecutor(sessionPool) : refuseExecutor("no agent loop mounted"), sessionPool !== void 0 && host.subagents !== void 0 ? createSubagentExecutor({
|
|
1566
|
+
pool: sessionPool,
|
|
1567
|
+
subagents: host.subagents,
|
|
1568
|
+
provider: host.subagentProvider
|
|
1569
|
+
}) : void 0);
|
|
1570
|
+
const inbound = new LiveInboundRegistry();
|
|
1571
|
+
const skillIds = new Set(skills.map((s) => s.id));
|
|
1572
|
+
const gate = async (input) => {
|
|
1573
|
+
if (!skillIds.has(input.skill)) return {
|
|
1574
|
+
ok: false,
|
|
1575
|
+
reason: `unknown skill "${input.skill}"`
|
|
1576
|
+
};
|
|
1577
|
+
const decision = {
|
|
1578
|
+
contextId: input.contextId,
|
|
1579
|
+
skill: input.skill,
|
|
1580
|
+
parts: input.parts,
|
|
1581
|
+
remotePeerId: input.remotePeerId
|
|
1582
|
+
};
|
|
1583
|
+
const decided = await host.ctx.waterfall("a2a/inbound-task", decision, async (d) => d);
|
|
1584
|
+
host.logger.info(`[a2a:${record.id}] inbound skill=${decided.skill} rejected=${decided.rejected?.reason ?? "no"}`);
|
|
1585
|
+
if (decided.rejected !== void 0) return {
|
|
1586
|
+
ok: false,
|
|
1587
|
+
reason: decided.rejected.reason
|
|
1588
|
+
};
|
|
1589
|
+
return { ok: true };
|
|
1590
|
+
};
|
|
1591
|
+
const server = new A2AServer({
|
|
1592
|
+
card,
|
|
1593
|
+
store: host.tasks,
|
|
1594
|
+
executors,
|
|
1595
|
+
...card.securitySchemes !== void 0 ? { authToken: "configured" } : {},
|
|
1596
|
+
cardPath,
|
|
1597
|
+
gate,
|
|
1598
|
+
onInbound: (facts) => inbound.note({
|
|
1599
|
+
method: facts.method,
|
|
1600
|
+
...facts.source !== void 0 ? { source: facts.source } : {},
|
|
1601
|
+
taskIds: facts.taskIds,
|
|
1602
|
+
streaming: facts.streaming
|
|
1603
|
+
}),
|
|
1604
|
+
onTaskSettled: (taskId) => {
|
|
1605
|
+
host.logger.info(`[a2a:${record.id}] task settled ${taskId}`);
|
|
1606
|
+
inbound.settle(taskId);
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
const routes = new A2aRoutes(host.webServer, server, cardPath);
|
|
1610
|
+
return {
|
|
1611
|
+
id: record.id,
|
|
1612
|
+
record,
|
|
1613
|
+
card,
|
|
1614
|
+
server,
|
|
1615
|
+
routes,
|
|
1616
|
+
inbound,
|
|
1617
|
+
executors,
|
|
1618
|
+
endpointPath,
|
|
1619
|
+
cardPath,
|
|
1620
|
+
dispose: () => {
|
|
1621
|
+
routes.dispose();
|
|
1622
|
+
executors.disposeAll().catch(() => {});
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1170
1625
|
}
|
|
1171
|
-
/**
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
return;
|
|
1626
|
+
/** Persisted-instance store over the inbound_servers table. */
|
|
1627
|
+
var DomainInboundStore = class DomainInboundStore {
|
|
1628
|
+
table;
|
|
1629
|
+
static KEY(id) {
|
|
1630
|
+
return `in:${id}`;
|
|
1177
1631
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1632
|
+
constructor(table) {
|
|
1633
|
+
this.table = table;
|
|
1634
|
+
}
|
|
1635
|
+
list() {
|
|
1636
|
+
const out = [];
|
|
1637
|
+
const entries = this.table.entries;
|
|
1638
|
+
if (entries === void 0) return out;
|
|
1639
|
+
for (const [key, raw] of entries.call(this.table)) {
|
|
1640
|
+
if (!key.startsWith("in:")) continue;
|
|
1641
|
+
const parsed = safeParse$1(raw);
|
|
1642
|
+
if (parsed !== void 0) out.push(parsed);
|
|
1643
|
+
}
|
|
1644
|
+
return out;
|
|
1645
|
+
}
|
|
1646
|
+
get(id) {
|
|
1647
|
+
const raw = this.table.get(DomainInboundStore.KEY(id));
|
|
1648
|
+
return raw === void 0 ? void 0 : safeParse$1(raw);
|
|
1649
|
+
}
|
|
1650
|
+
async save(record) {
|
|
1651
|
+
await this.table.put(DomainInboundStore.KEY(record.id), JSON.stringify(record));
|
|
1652
|
+
}
|
|
1653
|
+
async remove(id) {
|
|
1654
|
+
await this.table.put(DomainInboundStore.KEY(id), "");
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
function safeParse$1(raw) {
|
|
1658
|
+
try {
|
|
1659
|
+
const parsed = JSON.parse(raw);
|
|
1660
|
+
if (typeof parsed.id !== "string" || typeof parsed.name !== "string" || !Array.isArray(parsed.skills)) return void 0;
|
|
1661
|
+
return parsed;
|
|
1662
|
+
} catch {
|
|
1180
1663
|
return;
|
|
1181
1664
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1665
|
+
}
|
|
1666
|
+
/** Manages the live set of inbound server instances. */
|
|
1667
|
+
var InboundServerManager = class {
|
|
1668
|
+
host;
|
|
1669
|
+
store;
|
|
1670
|
+
live = /* @__PURE__ */ new Map();
|
|
1671
|
+
constructor(host, store) {
|
|
1672
|
+
this.host = host;
|
|
1673
|
+
this.store = store;
|
|
1674
|
+
}
|
|
1675
|
+
/** Assemble every persisted enabled instance (boot). */
|
|
1676
|
+
boot() {
|
|
1677
|
+
for (const record of this.store.list()) try {
|
|
1678
|
+
const live = assemble(record, this.host);
|
|
1679
|
+
this.live.set(record.id, live);
|
|
1680
|
+
if (record.enabled) live.routes.enable();
|
|
1681
|
+
} catch (err) {
|
|
1682
|
+
this.host.logger.error(`[a2a:${record.id}] failed to assemble: ${String(err.message)}`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
list() {
|
|
1686
|
+
return [...this.live.values()];
|
|
1687
|
+
}
|
|
1688
|
+
get(id) {
|
|
1689
|
+
return this.live.get(id);
|
|
1690
|
+
}
|
|
1691
|
+
/** Resolve the display name of a preset id (undefined when unnamed/absent). */
|
|
1692
|
+
async presetDisplayName(preset) {
|
|
1693
|
+
if (preset === void 0 || this.host.agentPresets === void 0) return void 0;
|
|
1184
1694
|
try {
|
|
1185
|
-
|
|
1695
|
+
const resolved = await this.host.agentPresets.resolve(preset);
|
|
1696
|
+
return resolved.name ?? resolved.id;
|
|
1186
1697
|
} catch {
|
|
1187
|
-
json(res, 400, {
|
|
1188
|
-
ok: false,
|
|
1189
|
-
message: "invalid JSON body"
|
|
1190
|
-
});
|
|
1191
1698
|
return;
|
|
1192
1699
|
}
|
|
1193
|
-
const result = await dispatch(payload, impl);
|
|
1194
|
-
json(res, result.ok ? 200 : 409, result);
|
|
1195
|
-
return;
|
|
1196
1700
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1701
|
+
/** Create, persist, and assemble a new inbound instance. */
|
|
1702
|
+
async add(input) {
|
|
1703
|
+
const id = this.newId(input.name);
|
|
1704
|
+
if (this.live.has(id)) return {
|
|
1705
|
+
ok: false,
|
|
1706
|
+
message: `inbound server ${id} already exists`
|
|
1707
|
+
};
|
|
1708
|
+
const presetName = await this.presetDisplayName(input.preset);
|
|
1709
|
+
const record = {
|
|
1710
|
+
id,
|
|
1711
|
+
name: input.name,
|
|
1712
|
+
description: input.description,
|
|
1713
|
+
version: input.version,
|
|
1714
|
+
endpointPath: input.endpointPath ?? `/a2a/${id}`,
|
|
1715
|
+
...input.preset !== void 0 ? { preset: input.preset } : {},
|
|
1716
|
+
...input.authTokenEnv !== void 0 ? { authTokenEnv: input.authTokenEnv } : {},
|
|
1717
|
+
skills: [...defaultSkillFor(input.skills, presetName)],
|
|
1718
|
+
enabled: input.enabled ?? true
|
|
1719
|
+
};
|
|
1720
|
+
try {
|
|
1721
|
+
const live = assemble(record, this.host);
|
|
1722
|
+
await this.store.save(record);
|
|
1723
|
+
this.live.set(id, live);
|
|
1724
|
+
if (record.enabled) live.routes.enable();
|
|
1725
|
+
return {
|
|
1726
|
+
ok: true,
|
|
1727
|
+
message: `inbound server "${record.name}" created`,
|
|
1728
|
+
id
|
|
1729
|
+
};
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
return {
|
|
1732
|
+
ok: false,
|
|
1733
|
+
message: `failed to create inbound server: ${String(err.message)}`
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
/** Persist, rebuild, and apply a patch onto a live instance. */
|
|
1738
|
+
async update(id, patch) {
|
|
1739
|
+
const live = this.live.get(id);
|
|
1740
|
+
const stored = this.store.get(id);
|
|
1741
|
+
if (live === void 0 || stored === void 0) return {
|
|
1742
|
+
ok: false,
|
|
1743
|
+
message: `inbound server ${id} not found`
|
|
1744
|
+
};
|
|
1745
|
+
const next = {
|
|
1746
|
+
...stored,
|
|
1747
|
+
name: patch.name ?? stored.name,
|
|
1748
|
+
description: patch.description ?? stored.description,
|
|
1749
|
+
version: patch.version ?? stored.version,
|
|
1750
|
+
endpointPath: patch.endpointPath ?? stored.endpointPath,
|
|
1751
|
+
...patch.preset !== void 0 ? { preset: patch.preset } : {},
|
|
1752
|
+
...patch.authTokenEnv !== void 0 ? { authTokenEnv: patch.authTokenEnv } : {},
|
|
1753
|
+
skills: patch.skills ?? stored.skills,
|
|
1754
|
+
enabled: stored.enabled
|
|
1755
|
+
};
|
|
1756
|
+
try {
|
|
1757
|
+
await this.store.save(next);
|
|
1758
|
+
const replaced = assemble(next, this.host);
|
|
1759
|
+
const wasEnabled = live.routes.active;
|
|
1760
|
+
live.dispose();
|
|
1761
|
+
this.live.set(id, replaced);
|
|
1762
|
+
if (wasEnabled || next.enabled) replaced.routes.enable();
|
|
1763
|
+
return {
|
|
1764
|
+
ok: true,
|
|
1765
|
+
message: `inbound server ${id} updated`
|
|
1766
|
+
};
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
return {
|
|
1769
|
+
ok: false,
|
|
1770
|
+
message: `failed to update inbound server: ${String(err.message)}`
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/** Enable/disable an instance's routes. */
|
|
1775
|
+
setEnabled(id, enabled) {
|
|
1776
|
+
const live = this.live.get(id);
|
|
1777
|
+
if (live === void 0) return {
|
|
1222
1778
|
ok: false,
|
|
1223
|
-
message: `
|
|
1779
|
+
message: `inbound server ${id} not found`
|
|
1780
|
+
};
|
|
1781
|
+
if (enabled) live.routes.enable();
|
|
1782
|
+
else live.routes.disable();
|
|
1783
|
+
const stored = this.store.get(id);
|
|
1784
|
+
if (stored !== void 0) this.store.save({
|
|
1785
|
+
...stored,
|
|
1786
|
+
enabled
|
|
1787
|
+
}).catch(() => {});
|
|
1788
|
+
return {
|
|
1789
|
+
ok: true,
|
|
1790
|
+
message: `inbound server ${id} ${enabled ? "enabled" : "disabled"}`
|
|
1224
1791
|
};
|
|
1225
1792
|
}
|
|
1226
|
-
|
|
1793
|
+
/** Remove, dispose, and unpersist an instance. */
|
|
1794
|
+
async remove(id) {
|
|
1795
|
+
const live = this.live.get(id);
|
|
1796
|
+
if (live === void 0) return {
|
|
1797
|
+
ok: false,
|
|
1798
|
+
message: `inbound server ${id} not found`
|
|
1799
|
+
};
|
|
1800
|
+
live.dispose();
|
|
1801
|
+
this.live.delete(id);
|
|
1802
|
+
await this.store.remove(id);
|
|
1803
|
+
return {
|
|
1804
|
+
ok: true,
|
|
1805
|
+
message: `inbound server ${id} removed`
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
/** Dispose every instance. */
|
|
1809
|
+
disposeAll() {
|
|
1810
|
+
for (const live of this.live.values()) live.dispose();
|
|
1811
|
+
this.live.clear();
|
|
1812
|
+
}
|
|
1813
|
+
newId(seed) {
|
|
1814
|
+
return `${seed.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "server"}-${this.host.newId()}`;
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1227
1817
|
//#endregion
|
|
1228
1818
|
//#region lib/types/outbound/calls.js
|
|
1229
1819
|
/**
|
|
@@ -1262,11 +1852,11 @@ var A2AClient = class A2AClient {
|
|
|
1262
1852
|
},
|
|
1263
1853
|
signal: AbortSignal.timeout(timeoutMs)
|
|
1264
1854
|
});
|
|
1265
|
-
if (!response.ok) throw new A2AError(A2A_ERROR_CODES.
|
|
1855
|
+
if (!response.ok) throw new A2AError(A2A_ERROR_CODES.INVALID_AGENT_RESPONSE, `agent card fetch failed: HTTP ${response.status}`);
|
|
1266
1856
|
const card = await response.json();
|
|
1267
|
-
if (typeof card.name !== "string" ||
|
|
1268
|
-
const endpoint = (card.supportedInterfaces?.find((i) => (i.protocolBinding ?? "JSONRPC") === "JSONRPC") ?? card.supportedInterfaces?.[0])?.url
|
|
1269
|
-
if (!endpoint) throw new A2AError(A2A_ERROR_CODES.
|
|
1857
|
+
if (typeof card.name !== "string" || !Array.isArray(card.skills)) throw new A2AError(A2A_ERROR_CODES.INVALID_AGENT_RESPONSE, `invalid agent card at ${agentCardUrl}`);
|
|
1858
|
+
const endpoint = (card.supportedInterfaces?.find((i) => (i.protocolBinding ?? "JSONRPC") === "JSONRPC") ?? card.supportedInterfaces?.[0])?.url;
|
|
1859
|
+
if (!endpoint) throw new A2AError(A2A_ERROR_CODES.INVALID_AGENT_RESPONSE, `agent card at ${agentCardUrl} advertises no JSON-RPC interface`);
|
|
1270
1860
|
return new A2AClient(card, endpoint, opts);
|
|
1271
1861
|
}
|
|
1272
1862
|
/** Send a message and wait (or poll) until the task settles. */
|
|
@@ -1319,7 +1909,7 @@ var A2AClient = class A2AClient {
|
|
|
1319
1909
|
if (err instanceof Error && err.name === "TimeoutError") throw new A2AError(-32e3, `A2A call ${method} timed out after ${timeoutMs}ms`);
|
|
1320
1910
|
throw err;
|
|
1321
1911
|
}
|
|
1322
|
-
if (response.status === 401) throw new A2AError(
|
|
1912
|
+
if (response.status === 401) throw new A2AError(-32040, "remote agent rejected credentials (401)");
|
|
1323
1913
|
if (!response.ok) throw new A2AError(A2A_ERROR_CODES.INTERNAL_ERROR, `A2A call ${method} failed: HTTP ${response.status}`);
|
|
1324
1914
|
const payload = await response.json();
|
|
1325
1915
|
if ("error" in payload) {
|
|
@@ -1413,27 +2003,6 @@ function textOf(artifacts, state, statusParts) {
|
|
|
1413
2003
|
* connection. Loaded at boot, mutations persist through the agent store.
|
|
1414
2004
|
* @module dsh-a2a/client/registry
|
|
1415
2005
|
*/
|
|
1416
|
-
/** Domain-backed agent store: one JSON array under the `agents` table key. */
|
|
1417
|
-
var DomainAgentStore = class DomainAgentStore {
|
|
1418
|
-
table;
|
|
1419
|
-
static KEY = "records";
|
|
1420
|
-
constructor(table) {
|
|
1421
|
-
this.table = table;
|
|
1422
|
-
}
|
|
1423
|
-
list() {
|
|
1424
|
-
const raw = this.table.get(DomainAgentStore.KEY);
|
|
1425
|
-
if (raw === void 0) return [];
|
|
1426
|
-
try {
|
|
1427
|
-
const parsed = JSON.parse(raw);
|
|
1428
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
1429
|
-
} catch {
|
|
1430
|
-
return [];
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
async save(records) {
|
|
1434
|
-
await this.table.put(DomainAgentStore.KEY, JSON.stringify(records));
|
|
1435
|
-
}
|
|
1436
|
-
};
|
|
1437
2006
|
/** A list of remote agents, each with live tool registrations when enabled. */
|
|
1438
2007
|
var OutboundAgentRegistry = class {
|
|
1439
2008
|
opts;
|
|
@@ -1664,150 +2233,265 @@ var OutboundAgentRegistry = class {
|
|
|
1664
2233
|
}
|
|
1665
2234
|
};
|
|
1666
2235
|
//#endregion
|
|
1667
|
-
//#region lib/types/
|
|
2236
|
+
//#region lib/types/servers/outbound-manager.js
|
|
1668
2237
|
/**
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1672
|
-
*
|
|
2238
|
+
* Outbound server manager: owns every outbound A2A connection instance. Each
|
|
2239
|
+
* instance is one connected remote — its own AgentCard URL, auth env, timeout,
|
|
2240
|
+
* creator-declared name, and an optional agent-preset binding. Each live
|
|
2241
|
+
* instance is one `OutboundAgentRegistry` with an isolated per-instance agent
|
|
2242
|
+
* store; enabled instances map their remote skills to `a2a__<name>__<skill>`
|
|
2243
|
+
* model tools. The manager owns the full lifecycle (add/enable/disable/remove/
|
|
2244
|
+
* refresh) driven from the GUI and facade, persists each instance in the
|
|
2245
|
+
* `outbound_servers` domain table, and disposes every connection on teardown.
|
|
2246
|
+
*
|
|
2247
|
+
* The `preset` field names the agent preset this DSH would compose its local
|
|
2248
|
+
* hand-off session with when driving that remote. P0 connects skills to tools
|
|
2249
|
+
* and manages the connection; the hand-off session composition behind `preset`
|
|
2250
|
+
* is a documented extension point (the outbound tools currently call the
|
|
2251
|
+
* remote directly) and the value is persisted metadata surfaced to the GUI.
|
|
2252
|
+
* @module dsh-a2a/servers/outbound-manager
|
|
1673
2253
|
*/
|
|
1674
|
-
/**
|
|
1675
|
-
var
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
this.
|
|
2254
|
+
/** An `AgentStore` bound to one outbound server instance's agents-table key. */
|
|
2255
|
+
var ServerAgentStore = class {
|
|
2256
|
+
table;
|
|
2257
|
+
key;
|
|
2258
|
+
constructor(table, key) {
|
|
2259
|
+
this.table = table;
|
|
2260
|
+
this.key = key;
|
|
1680
2261
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
2262
|
+
list() {
|
|
2263
|
+
const raw = this.table.get(this.key);
|
|
2264
|
+
if (raw === void 0 || raw === "") return [];
|
|
2265
|
+
try {
|
|
2266
|
+
const parsed = JSON.parse(raw);
|
|
2267
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
2268
|
+
} catch {
|
|
2269
|
+
return [];
|
|
2270
|
+
}
|
|
1683
2271
|
}
|
|
1684
|
-
async
|
|
1685
|
-
|
|
2272
|
+
async save(records) {
|
|
2273
|
+
await this.table.put(this.key, JSON.stringify(records));
|
|
1686
2274
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
2275
|
+
};
|
|
2276
|
+
/** Persisted-instance store over the outbound_servers table. */
|
|
2277
|
+
var DomainOutboundStore = class DomainOutboundStore {
|
|
2278
|
+
table;
|
|
2279
|
+
static KEY(id) {
|
|
2280
|
+
return `out:${id}`;
|
|
1689
2281
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
2282
|
+
constructor(table) {
|
|
2283
|
+
this.table = table;
|
|
1692
2284
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
2285
|
+
list() {
|
|
2286
|
+
const out = [];
|
|
2287
|
+
const entries = this.table.entries;
|
|
2288
|
+
if (entries === void 0) return out;
|
|
2289
|
+
for (const [key, raw] of entries.call(this.table)) {
|
|
2290
|
+
if (!key.startsWith("out:")) continue;
|
|
2291
|
+
const parsed = safeParse(raw);
|
|
2292
|
+
if (parsed !== void 0) out.push(parsed);
|
|
2293
|
+
}
|
|
2294
|
+
return out;
|
|
1695
2295
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
2296
|
+
get(id) {
|
|
2297
|
+
const raw = this.table.get(DomainOutboundStore.KEY(id));
|
|
2298
|
+
return raw === void 0 ? void 0 : safeParse(raw);
|
|
1698
2299
|
}
|
|
1699
|
-
async
|
|
1700
|
-
|
|
2300
|
+
async save(record) {
|
|
2301
|
+
await this.table.put(DomainOutboundStore.KEY(record.id), JSON.stringify(record));
|
|
1701
2302
|
}
|
|
1702
|
-
async
|
|
1703
|
-
|
|
2303
|
+
async remove(id) {
|
|
2304
|
+
await this.table.put(DomainOutboundStore.KEY(id), "");
|
|
1704
2305
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
2306
|
+
};
|
|
2307
|
+
function safeParse(raw) {
|
|
2308
|
+
try {
|
|
2309
|
+
const parsed = JSON.parse(raw);
|
|
2310
|
+
if (typeof parsed.id !== "string" || typeof parsed.name !== "string" || typeof parsed.agentCardUrl !== "string") return void 0;
|
|
2311
|
+
return parsed;
|
|
2312
|
+
} catch {
|
|
2313
|
+
return;
|
|
1707
2314
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
2315
|
+
}
|
|
2316
|
+
/** Manages the live set of outbound connection instances. */
|
|
2317
|
+
var OutboundServerManager = class {
|
|
2318
|
+
host;
|
|
2319
|
+
agentSink;
|
|
2320
|
+
records;
|
|
2321
|
+
newId;
|
|
2322
|
+
registries = /* @__PURE__ */ new Map();
|
|
2323
|
+
constructor(host, agentSink, records, newId) {
|
|
2324
|
+
this.host = host;
|
|
2325
|
+
this.agentSink = agentSink;
|
|
2326
|
+
this.records = records;
|
|
2327
|
+
this.newId = newId;
|
|
2328
|
+
}
|
|
2329
|
+
/** The host default connection timeout (what a minimal record falls back to). */
|
|
2330
|
+
get defaultTimeoutMs() {
|
|
2331
|
+
return this.host.defaultTimeoutMs;
|
|
2332
|
+
}
|
|
2333
|
+
agentKeyFor(id) {
|
|
2334
|
+
return `out:${id}`;
|
|
2335
|
+
}
|
|
2336
|
+
makeRegistry(record) {
|
|
2337
|
+
return new OutboundAgentRegistry({
|
|
2338
|
+
registrar: { register: (def) => this.host.registrar.register(def) },
|
|
2339
|
+
store: new ServerAgentStore(this.agentSink, this.agentKeyFor(record.id)),
|
|
2340
|
+
toolPrefix: "a2a",
|
|
2341
|
+
defaultTimeoutMs: this.host.defaultTimeoutMs,
|
|
2342
|
+
tokenOf: (env) => this.host.tokenOf(env),
|
|
2343
|
+
onError: this.host.onError
|
|
2344
|
+
});
|
|
1710
2345
|
}
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
return success(`A2A status\n${JSON.stringify(status, null, 2)}`);
|
|
1730
|
-
}
|
|
1731
|
-
case "status": return success(JSON.stringify(impl.status(), null, 2));
|
|
1732
|
-
case "enable": return outcome(await impl.enableServer(true));
|
|
1733
|
-
case "disable": return outcome(await impl.enableServer(false));
|
|
1734
|
-
case "card": return success(JSON.stringify(impl.listTasks(), null, 2).slice(0, 4e3));
|
|
1735
|
-
case "agents": return success(JSON.stringify(impl.agents(), null, 2));
|
|
1736
|
-
case "agent": {
|
|
1737
|
-
const [action, ...args] = rest;
|
|
1738
|
-
switch (action ?? "") {
|
|
1739
|
-
case "add": {
|
|
1740
|
-
const url = args[0];
|
|
1741
|
-
if (!url) return error("usage: /a2a agent add <agentCardUrl> [name]");
|
|
1742
|
-
const name = args[1] ?? url;
|
|
1743
|
-
return outcome(await impl.addAgent({
|
|
1744
|
-
name,
|
|
1745
|
-
agentCardUrl: url
|
|
1746
|
-
}));
|
|
1747
|
-
}
|
|
1748
|
-
case "remove": {
|
|
1749
|
-
const id = args[0];
|
|
1750
|
-
if (!id) return error("usage: /a2a agent remove <id>");
|
|
1751
|
-
return outcome(await impl.removeAgent(id));
|
|
1752
|
-
}
|
|
1753
|
-
case "enable": {
|
|
1754
|
-
const id = args[0];
|
|
1755
|
-
if (!id) return error("usage: /a2a agent enable <id>");
|
|
1756
|
-
return outcome(await impl.setAgentEnabled(id, true));
|
|
1757
|
-
}
|
|
1758
|
-
case "disable": {
|
|
1759
|
-
const id = args[0];
|
|
1760
|
-
if (!id) return error("usage: /a2a agent disable <id>");
|
|
1761
|
-
return outcome(await impl.setAgentEnabled(id, false));
|
|
1762
|
-
}
|
|
1763
|
-
case "refresh": {
|
|
1764
|
-
const id = args[0];
|
|
1765
|
-
if (!id) return error("usage: /a2a agent refresh <id>");
|
|
1766
|
-
return outcome(await impl.refreshAgentCard(id));
|
|
1767
|
-
}
|
|
1768
|
-
default: return error("usage: /a2a agent add|remove|enable|disable|refresh ...");
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
case "tasks": return success(JSON.stringify(impl.listTasks(), null, 2));
|
|
1772
|
-
case "task": {
|
|
1773
|
-
const [action, id] = rest;
|
|
1774
|
-
if (action === "get" && id) return success(JSON.stringify(impl.getTask(id), null, 2));
|
|
1775
|
-
if (action === "cancel" && id) return outcome(await impl.cancelTask(id));
|
|
1776
|
-
return error("usage: /a2a task get|cancel <taskId>");
|
|
1777
|
-
}
|
|
1778
|
-
case "help": return success("a2a commands: status | enable | disable | card | agents | agent add/remove/enable/disable/refresh | tasks | task get/cancel <id> | help");
|
|
1779
|
-
default: return error(`unknown a2a verb "${verb}" (try /a2a help)`);
|
|
1780
|
-
}
|
|
2346
|
+
/** Record → registry spec for connecting one instance. */
|
|
2347
|
+
toSpec(record) {
|
|
2348
|
+
return {
|
|
2349
|
+
name: record.name,
|
|
2350
|
+
agentCardUrl: record.agentCardUrl,
|
|
2351
|
+
...record.bearerTokenEnv !== void 0 ? { bearerTokenEnv: record.bearerTokenEnv } : {},
|
|
2352
|
+
enabled: record.enabled,
|
|
2353
|
+
timeoutMs: record.timeoutMs ?? this.host.defaultTimeoutMs
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
/** Boot every persisted enabled instance as a connected registry. */
|
|
2357
|
+
boot() {
|
|
2358
|
+
for (const record of this.records.list()) try {
|
|
2359
|
+
const registry = this.makeRegistry(record);
|
|
2360
|
+
this.registries.set(record.id, registry);
|
|
2361
|
+
if (record.enabled) registry.loadAll();
|
|
2362
|
+
} catch (err) {
|
|
2363
|
+
this.host.onError(`[a2a:out:${record.id}] failed to boot: ${String(err.message)}`);
|
|
1781
2364
|
}
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2365
|
+
}
|
|
2366
|
+
/** One view per live instance (enriched from its registry / record). */
|
|
2367
|
+
list() {
|
|
2368
|
+
return [...this.registries.keys()].map((id) => this.viewFor(id)).filter((v) => v !== void 0);
|
|
2369
|
+
}
|
|
2370
|
+
get(id) {
|
|
2371
|
+
const registry = this.registries.get(id);
|
|
2372
|
+
return registry === void 0 ? void 0 : {
|
|
2373
|
+
id,
|
|
2374
|
+
registry
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
viewFor(id) {
|
|
2378
|
+
const registry = this.registries.get(id);
|
|
2379
|
+
const record = this.records.get(id);
|
|
2380
|
+
if (registry === void 0 || record === void 0) return void 0;
|
|
2381
|
+
const agent = registry.list()[0];
|
|
2382
|
+
return {
|
|
2383
|
+
id,
|
|
2384
|
+
name: record.name,
|
|
2385
|
+
agentCardUrl: record.agentCardUrl,
|
|
2386
|
+
...record.preset !== void 0 ? { preset: record.preset } : {},
|
|
2387
|
+
enabled: agent?.enabled ?? record.enabled,
|
|
2388
|
+
timeoutMs: record.timeoutMs,
|
|
2389
|
+
state: agent?.state ?? "disconnected",
|
|
2390
|
+
skillCount: agent?.skillCount ?? 0,
|
|
2391
|
+
toolCount: agent?.toolCount ?? 0,
|
|
2392
|
+
...agent?.lastError !== void 0 ? { lastError: agent.lastError } : {}
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
/** Create, persist, and connect a new outbound instance. */
|
|
2396
|
+
async add(input) {
|
|
2397
|
+
const record = {
|
|
2398
|
+
id: input.id ?? this.newId(),
|
|
2399
|
+
name: input.name,
|
|
2400
|
+
agentCardUrl: input.agentCardUrl,
|
|
2401
|
+
...input.bearerTokenEnv !== void 0 ? { bearerTokenEnv: input.bearerTokenEnv } : {},
|
|
2402
|
+
...input.preset !== void 0 ? { preset: input.preset } : {},
|
|
2403
|
+
enabled: input.enabled ?? true,
|
|
2404
|
+
timeoutMs: input.timeoutMs ?? this.host.defaultTimeoutMs
|
|
2405
|
+
};
|
|
2406
|
+
if (this.registries.has(record.id)) return {
|
|
2407
|
+
ok: false,
|
|
2408
|
+
message: `outbound server ${record.id} already exists`
|
|
2409
|
+
};
|
|
2410
|
+
const registry = this.makeRegistry(record);
|
|
2411
|
+
await this.records.save(record);
|
|
2412
|
+
this.registries.set(record.id, registry);
|
|
2413
|
+
if (record.enabled) {
|
|
2414
|
+
const result = await registry.add(this.toSpec(record));
|
|
2415
|
+
return result.ok ? {
|
|
2416
|
+
...result,
|
|
2417
|
+
id: record.id
|
|
2418
|
+
} : result;
|
|
2419
|
+
}
|
|
2420
|
+
return {
|
|
2421
|
+
ok: true,
|
|
2422
|
+
message: `outbound server "${record.name}" added (disabled)`,
|
|
2423
|
+
id: record.id
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
async setEnabled(id, enabled) {
|
|
2427
|
+
const registry = this.registries.get(id);
|
|
2428
|
+
if (registry === void 0) return {
|
|
2429
|
+
ok: false,
|
|
2430
|
+
message: `outbound server ${id} not found`
|
|
2431
|
+
};
|
|
2432
|
+
const view = registry.list()[0];
|
|
2433
|
+
const record = this.records.get(id);
|
|
2434
|
+
if (record !== void 0) await this.records.save({
|
|
2435
|
+
...record,
|
|
2436
|
+
enabled
|
|
2437
|
+
});
|
|
2438
|
+
if (view === void 0) return {
|
|
2439
|
+
ok: true,
|
|
2440
|
+
message: `outbound server ${id} marked ${enabled ? "enabled" : "disabled"}`
|
|
2441
|
+
};
|
|
2442
|
+
return registry.setEnabled(view.id, enabled);
|
|
2443
|
+
}
|
|
2444
|
+
async remove(id) {
|
|
2445
|
+
const registry = this.registries.get(id);
|
|
2446
|
+
if (registry === void 0) return {
|
|
2447
|
+
ok: false,
|
|
2448
|
+
message: `outbound server ${id} not found`
|
|
2449
|
+
};
|
|
2450
|
+
const view = registry.list()[0];
|
|
2451
|
+
if (view !== void 0) await registry.remove(view.id);
|
|
2452
|
+
await registry.disposeAll();
|
|
2453
|
+
this.registries.delete(id);
|
|
2454
|
+
await this.records.remove(id);
|
|
2455
|
+
return {
|
|
2456
|
+
ok: true,
|
|
2457
|
+
message: `outbound server ${id} removed`
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
async refresh(id) {
|
|
2461
|
+
const registry = this.registries.get(id);
|
|
2462
|
+
if (registry === void 0) return {
|
|
2463
|
+
ok: false,
|
|
2464
|
+
message: `outbound server ${id} not found`
|
|
2465
|
+
};
|
|
2466
|
+
const view = registry.list()[0];
|
|
2467
|
+
if (view === void 0) return {
|
|
2468
|
+
ok: false,
|
|
2469
|
+
message: `outbound server ${id} has no connected agent`
|
|
2470
|
+
};
|
|
2471
|
+
return registry.refresh(view.id);
|
|
2472
|
+
}
|
|
2473
|
+
async disposeAll() {
|
|
2474
|
+
for (const registry of this.registries.values()) await registry.disposeAll();
|
|
2475
|
+
this.registries.clear();
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
1799
2478
|
//#endregion
|
|
1800
2479
|
//#region lib/types/index.js
|
|
1801
2480
|
/**
|
|
1802
2481
|
* @hanphone/dsh-a2a — Cordis plugin entry.
|
|
1803
2482
|
*
|
|
1804
|
-
* Mounts the A2A v1.0 dual-end plugin
|
|
1805
|
-
*
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
1810
|
-
*
|
|
2483
|
+
* Mounts the A2A v1.0.1 dual-end plugin as a MULTI-INSTANCE composition: an
|
|
2484
|
+
* arbitrary set of inbound A2A servers (each a preset-bound session pool, its
|
|
2485
|
+
* own endpoint + AgentCard + creator-declared skills + auth) and an arbitrary
|
|
2486
|
+
* set of outbound A2A connections (each a remote URL + preset + timeout).
|
|
2487
|
+
* Instances are persisted in the `a2a` domain (`inbound_servers` /
|
|
2488
|
+
* `outbound_servers` tables) and created/edited/started/stopped entirely from
|
|
2489
|
+
* the GUI — the plugin `Config` is minimal and instance setup is not
|
|
2490
|
+
* patch-config driven. The protocol surface is aligned with the official A2A
|
|
2491
|
+
* v1.0.1 spec (see docs/design.md).
|
|
2492
|
+
*
|
|
2493
|
+
* Optional services are probed rather than injected so a composition lacking
|
|
2494
|
+
* one half idles just that half; only the storage domain is required.
|
|
1811
2495
|
*
|
|
1812
2496
|
* Function-plugin export shape: named `name`/`inject`/`Config`/`apply`, no
|
|
1813
2497
|
* default export (mixing forms makes the Loader drop the namespace).
|
|
@@ -1818,245 +2502,197 @@ const name = "a2a";
|
|
|
1818
2502
|
const inject = ["storageDomain"];
|
|
1819
2503
|
/** Loader-validated config schema (defaults applied by the Loader). */
|
|
1820
2504
|
const Config = z.object({
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
description: z.string().default("A DeepSeek Harness agent exposed over A2A v1.0"),
|
|
1825
|
-
version: z.string().default("0.1.0"),
|
|
1826
|
-
baseUrl: z.string(),
|
|
1827
|
-
endpointPath: z.string().default("/a2a"),
|
|
1828
|
-
authTokenEnv: z.string(),
|
|
1829
|
-
skills: z.object({
|
|
1830
|
-
ids: z.array(z.string()).default([]),
|
|
1831
|
-
exclude: z.array(z.string()).default([])
|
|
1832
|
-
}),
|
|
1833
|
-
executors: z.dict(z.union(["session", "subagent"])).default({ chat: "session" }),
|
|
1834
|
-
subagentProvider: z.string().default("in-process")
|
|
1835
|
-
}),
|
|
1836
|
-
client: z.object({
|
|
1837
|
-
agents: z.array(z.object({
|
|
1838
|
-
name: z.string(),
|
|
1839
|
-
agentCardUrl: z.string(),
|
|
1840
|
-
bearerTokenEnv: z.string(),
|
|
1841
|
-
enabled: z.boolean().default(true),
|
|
1842
|
-
timeoutMs: z.number().default(6e4)
|
|
1843
|
-
})).default([]),
|
|
1844
|
-
toolPrefix: z.string().default("a2a")
|
|
1845
|
-
})
|
|
2505
|
+
baseUrl: z.string(),
|
|
2506
|
+
subagentProvider: z.string().default("in-process"),
|
|
2507
|
+
defaultTimeoutMs: z.number().default(6e4)
|
|
1846
2508
|
});
|
|
1847
2509
|
function apply(ctx, config) {
|
|
1848
2510
|
const logger = ctx.logger("a2a");
|
|
1849
|
-
const
|
|
1850
|
-
const clientConfig = { ...config.client };
|
|
1851
|
-
const authToken = serverConfig.authTokenEnv !== void 0 ? process.env[serverConfig.authTokenEnv] : void 0;
|
|
1852
|
-
const toolPrefix = clientConfig.toolPrefix;
|
|
1853
|
-
const subagentProvider = serverConfig.subagentProvider;
|
|
2511
|
+
const subagentProvider = config.subagentProvider;
|
|
1854
2512
|
const inboundCwd = inboundSessionCwd();
|
|
1855
2513
|
ctx.inject(["storageDomain"], (ctx) => {
|
|
1856
2514
|
ctx.effect(async () => {
|
|
1857
2515
|
const domain = await openDomain(ctx.storageDomain);
|
|
1858
2516
|
const store = new DomainTaskStore(domain);
|
|
1859
|
-
const
|
|
2517
|
+
const webServer = probeService(ctx, "webServer", "register");
|
|
2518
|
+
const tools = probeService(ctx, "tools", "register");
|
|
2519
|
+
const agents = probeService(ctx, "agents", "create");
|
|
2520
|
+
const agentPresets = probeService(ctx, "agentPresets", "list");
|
|
2521
|
+
const subagents = probeService(ctx, "subagents", "start");
|
|
2522
|
+
const commandsRef = probeService(ctx, "commands", "register");
|
|
2523
|
+
const baseUrl = config.baseUrl ?? `http://127.0.0.1:${webServerPort(webServer) ?? process.env["DSH_A2A_PORT"] ?? "3000"}`;
|
|
2524
|
+
const inboundManager = new InboundServerManager({
|
|
2525
|
+
ctx,
|
|
2526
|
+
webServer: webServer ?? { register: noopRegister },
|
|
2527
|
+
tasks: store,
|
|
2528
|
+
logger,
|
|
2529
|
+
...agents !== void 0 ? { agents } : {},
|
|
2530
|
+
...agentPresets !== void 0 ? { agentPresets } : {},
|
|
2531
|
+
...subagents !== void 0 ? { subagents } : {},
|
|
2532
|
+
resolveDefaultModel: () => resolveDefaultModel(ctx),
|
|
2533
|
+
sessionCwd: inboundCwd,
|
|
2534
|
+
defaultBaseUrl: baseUrl,
|
|
2535
|
+
subagentProvider,
|
|
2536
|
+
newId: () => crypto.randomUUID().slice(0, 8)
|
|
2537
|
+
}, new DomainInboundStore(domain.inbound_servers));
|
|
2538
|
+
inboundManager.boot();
|
|
2539
|
+
const outboundManager = new OutboundServerManager({
|
|
2540
|
+
registrar: { register: (def) => tools?.register(def) },
|
|
2541
|
+
tokenOf: (env) => env ? process.env[env] : void 0,
|
|
2542
|
+
onError: (message) => logger.warn(message),
|
|
2543
|
+
defaultTimeoutMs: config.defaultTimeoutMs
|
|
2544
|
+
}, domain.agents, new DomainOutboundStore(domain.outbound_servers), () => `out-${crypto.randomUUID().slice(0, 12)}`);
|
|
2545
|
+
outboundManager.boot();
|
|
2546
|
+
const impl = makeFacade({
|
|
1860
2547
|
domain,
|
|
1861
2548
|
store,
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
enabled: serverConfig.enabled
|
|
1868
|
-
};
|
|
1869
|
-
const webServer = probeService(ctx, "webServer", "register");
|
|
1870
|
-
if (webServer === void 0) logger.warn("a2a: webServer not mounted; inbound server idle");
|
|
1871
|
-
else try {
|
|
1872
|
-
const skills = deriveSkills(probeService(ctx, "tools", "get") ?? { get: () => void 0 }, {
|
|
1873
|
-
ids: serverConfig.skills.ids,
|
|
1874
|
-
exclude: serverConfig.skills.exclude
|
|
1875
|
-
});
|
|
1876
|
-
const card = buildCard({
|
|
1877
|
-
baseUrl: serverConfig.baseUrl ?? `http://127.0.0.1:${process.env["DSH_A2A_PORT"] ?? "3000"}`,
|
|
1878
|
-
endpointPath: serverConfig.endpointPath ?? "/a2a",
|
|
1879
|
-
name: serverConfig.name ?? "My DSH Agent",
|
|
1880
|
-
description: serverConfig.description ?? "A DeepSeek Harness agent exposed over A2A v1.0",
|
|
1881
|
-
version: serverConfig.version ?? "0.1.0",
|
|
1882
|
-
skills,
|
|
1883
|
-
...authToken ? { authToken } : {}
|
|
1884
|
-
});
|
|
1885
|
-
holder.card = card;
|
|
1886
|
-
const agents = probeService(ctx, "agents", "create");
|
|
1887
|
-
const presets = probeService(ctx, "agentPresets", "resolve");
|
|
1888
|
-
const sessionPool = agents ? new ContextSessionPool(agents, {
|
|
1889
|
-
cwd: inboundCwd,
|
|
1890
|
-
...presets ? { agentPresets: presets } : {},
|
|
1891
|
-
resolveAgentOptions: () => resolveDefaultModel(ctx)
|
|
1892
|
-
}) : void 0;
|
|
1893
|
-
const sessionExecutor = sessionPool ? createSessionExecutor(sessionPool) : refuseExecutor("no agent loop mounted");
|
|
1894
|
-
const subagents = probeService(ctx, "subagents", "start");
|
|
1895
|
-
const subagentExecutor = sessionPool !== void 0 && subagents !== void 0 ? createSubagentExecutor({
|
|
1896
|
-
pool: sessionPool,
|
|
1897
|
-
subagents,
|
|
1898
|
-
provider: subagentProvider
|
|
1899
|
-
}) : void 0;
|
|
1900
|
-
const executors = new ExecutorSet(serverConfig.executors, sessionExecutor, subagentExecutor);
|
|
1901
|
-
holder.executors = executors;
|
|
1902
|
-
const skillIds = new Set(skills.map((s) => s.id));
|
|
1903
|
-
const gate = async (input) => {
|
|
1904
|
-
if (!skillIds.has(input.skill)) return {
|
|
1905
|
-
ok: false,
|
|
1906
|
-
reason: `unknown skill "${input.skill}"`
|
|
1907
|
-
};
|
|
1908
|
-
const decision = {
|
|
1909
|
-
contextId: input.contextId,
|
|
1910
|
-
skill: input.skill,
|
|
1911
|
-
parts: input.parts,
|
|
1912
|
-
remotePeerId: input.remotePeerId
|
|
1913
|
-
};
|
|
1914
|
-
const decided = await ctx.waterfall("a2a/inbound-task", decision, async (d) => d);
|
|
1915
|
-
logger.info(`[a2a] inbound skill=${decided.skill} remote=${decided.remotePeerId} rejected=${decided.rejected?.reason ?? "no"}`);
|
|
1916
|
-
if (decided.rejected !== void 0) return {
|
|
1917
|
-
ok: false,
|
|
1918
|
-
reason: decided.rejected.reason
|
|
1919
|
-
};
|
|
1920
|
-
return { ok: true };
|
|
1921
|
-
};
|
|
1922
|
-
const server = new A2AServer({
|
|
1923
|
-
card,
|
|
1924
|
-
store,
|
|
1925
|
-
executors,
|
|
1926
|
-
...authToken ? { authToken } : {},
|
|
1927
|
-
gate,
|
|
1928
|
-
onTaskSettled: (taskId) => logger.info(`[a2a] task settled ${taskId}`)
|
|
1929
|
-
});
|
|
1930
|
-
holder.server = server;
|
|
1931
|
-
const routes = new A2aRoutes(webServer, server);
|
|
1932
|
-
holder.routes = routes;
|
|
1933
|
-
if (holder.enabled) routes.enable();
|
|
1934
|
-
} catch (err) {
|
|
1935
|
-
logger.error(`a2a: server half failed to build: ${err.message}`);
|
|
1936
|
-
}
|
|
1937
|
-
const toolsService = probeService(ctx, "tools", "get");
|
|
1938
|
-
if (toolsService === void 0) logger.warn("a2a: tools service not mounted; outbound client idle");
|
|
1939
|
-
else {
|
|
1940
|
-
const registry = new OutboundAgentRegistry({
|
|
1941
|
-
registrar: { register: (def) => toolsService.register(def) },
|
|
1942
|
-
store: new DomainAgentStore(domain.agents),
|
|
1943
|
-
toolPrefix,
|
|
1944
|
-
tokenOf: (env) => env ? process.env[env] : void 0,
|
|
1945
|
-
onError: (message) => logger.warn(message)
|
|
1946
|
-
});
|
|
1947
|
-
holder.registry = registry;
|
|
1948
|
-
registry.loadAll();
|
|
1949
|
-
if (clientConfig.agents.length > 0) await registry.seed(clientConfig.agents);
|
|
1950
|
-
}
|
|
1951
|
-
const impl = makeFacade(holder, logger.info.bind(logger));
|
|
2549
|
+
inboundManager,
|
|
2550
|
+
outboundManager,
|
|
2551
|
+
agentPresets,
|
|
2552
|
+
logger: logger.info.bind(logger)
|
|
2553
|
+
});
|
|
1952
2554
|
new A2AService(ctx, impl);
|
|
1953
|
-
|
|
1954
|
-
if (
|
|
1955
|
-
const dashboardWebServer = probeService(ctx, "webServer", "register");
|
|
1956
|
-
if (dashboardWebServer !== void 0) ctx.effect(() => dashboardWebServer.register({
|
|
2555
|
+
if (commandsRef !== void 0) ctx.effect(() => commandsRef.register(buildA2aCommand(impl)), "a2a: command");
|
|
2556
|
+
if (webServer !== void 0) ctx.effect(() => webServer.register({
|
|
1957
2557
|
kind: "prefix",
|
|
1958
2558
|
path: "/a2a/api",
|
|
1959
2559
|
handler: (req, res) => handleApiRequest(req, res, impl)
|
|
1960
2560
|
}), "a2a: dashboard api");
|
|
1961
2561
|
else logger.warn("a2a: webServer not mounted; GUI dashboard API idle");
|
|
1962
2562
|
return async () => {
|
|
1963
|
-
|
|
1964
|
-
await
|
|
1965
|
-
await holder.registry?.disposeAll();
|
|
2563
|
+
inboundManager.disposeAll();
|
|
2564
|
+
await outboundManager.disposeAll();
|
|
1966
2565
|
await domain.close();
|
|
1967
2566
|
};
|
|
1968
|
-
}, "a2a:
|
|
2567
|
+
}, "a2a: multi-instance composition");
|
|
1969
2568
|
});
|
|
1970
2569
|
}
|
|
1971
|
-
/** Facade closure over the
|
|
1972
|
-
function makeFacade(
|
|
1973
|
-
const
|
|
2570
|
+
/** Facade closure over the two managers (commands and `ctx.a2a` consumers). */
|
|
2571
|
+
function makeFacade(host) {
|
|
2572
|
+
const inboundView = (id) => {
|
|
2573
|
+
const live = host.inboundManager.get(id);
|
|
2574
|
+
if (live === void 0) return void 0;
|
|
2575
|
+
const r = live.record;
|
|
2576
|
+
return {
|
|
2577
|
+
id: r.id,
|
|
2578
|
+
name: r.name,
|
|
2579
|
+
description: r.description,
|
|
2580
|
+
version: r.version,
|
|
2581
|
+
endpointPath: live.endpointPath,
|
|
2582
|
+
...r.preset !== void 0 ? { preset: r.preset } : {},
|
|
2583
|
+
...r.authTokenEnv !== void 0 ? { authTokenEnv: r.authTokenEnv } : {},
|
|
2584
|
+
cardPath: live.cardPath,
|
|
2585
|
+
...live.card.supportedInterfaces?.[0]?.url !== void 0 ? { cardUrl: live.card.supportedInterfaces[0].url } : {},
|
|
2586
|
+
enabled: live.routes.active,
|
|
2587
|
+
skills: r.skills.map((s) => ({
|
|
2588
|
+
id: s.id,
|
|
2589
|
+
name: s.name,
|
|
2590
|
+
...s.description != null ? { description: s.description } : {}
|
|
2591
|
+
}))
|
|
2592
|
+
};
|
|
2593
|
+
};
|
|
1974
2594
|
return {
|
|
1975
2595
|
status() {
|
|
1976
2596
|
return {
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
skills: holder.card?.skills?.map((s) => s.id) ?? [],
|
|
1981
|
-
executors: holder.executors ? ["session", ...holder.executors["subagent"] !== void 0 ? ["subagent"] : []] : []
|
|
1982
|
-
},
|
|
1983
|
-
tasks: holder.store.list().length,
|
|
1984
|
-
agents: holder.registry?.list() ?? []
|
|
2597
|
+
inbounds: host.inboundManager.list().map((live) => inboundView(live.id)).filter((v) => v !== void 0),
|
|
2598
|
+
outbounds: host.outboundManager.list(),
|
|
2599
|
+
tasks: host.store.list().length
|
|
1985
2600
|
};
|
|
1986
2601
|
},
|
|
1987
|
-
async
|
|
1988
|
-
if (
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
}
|
|
2602
|
+
async presets() {
|
|
2603
|
+
if (host.agentPresets === void 0) return [];
|
|
2604
|
+
try {
|
|
2605
|
+
return (await host.agentPresets.list()).map((p) => ({
|
|
2606
|
+
id: p.id,
|
|
2607
|
+
...p.name !== void 0 ? { name: p.name } : {},
|
|
2608
|
+
...p.description !== void 0 ? { description: p.description } : {},
|
|
2609
|
+
...p.isDefault === true ? { isDefault: true } : {}
|
|
2610
|
+
}));
|
|
2611
|
+
} catch {
|
|
2612
|
+
return [];
|
|
2613
|
+
}
|
|
1999
2614
|
},
|
|
2000
|
-
|
|
2001
|
-
return
|
|
2615
|
+
listInboundServers() {
|
|
2616
|
+
return host.inboundManager.list().map((live) => inboundView(live.id)).filter((v) => v !== void 0);
|
|
2002
2617
|
},
|
|
2003
|
-
|
|
2004
|
-
|
|
2618
|
+
async createInboundServer(input) {
|
|
2619
|
+
const result = await host.inboundManager.add({
|
|
2620
|
+
name: input.name,
|
|
2621
|
+
description: input.description,
|
|
2622
|
+
version: input.version,
|
|
2623
|
+
...input.endpointPath !== void 0 ? { endpointPath: input.endpointPath } : {},
|
|
2624
|
+
...input.preset !== void 0 ? { preset: input.preset } : {},
|
|
2625
|
+
...input.authTokenEnv !== void 0 ? { authTokenEnv: input.authTokenEnv } : {},
|
|
2626
|
+
...input.skills !== void 0 ? { skills: input.skills } : {},
|
|
2627
|
+
...input.enabled !== void 0 ? { enabled: input.enabled } : {}
|
|
2628
|
+
});
|
|
2629
|
+
host.logger(`[a2a] inbound created: ${result.message}`);
|
|
2630
|
+
return result;
|
|
2005
2631
|
},
|
|
2006
|
-
async
|
|
2007
|
-
|
|
2008
|
-
ok: false,
|
|
2009
|
-
message: `task ${taskId} not found`
|
|
2010
|
-
};
|
|
2011
|
-
return {
|
|
2012
|
-
ok: true,
|
|
2013
|
-
message: holder.server?.abort?.(taskId) ?? false ? `task ${taskId} canceled` : `task ${taskId} already terminal`
|
|
2014
|
-
};
|
|
2632
|
+
async removeInboundServer(id) {
|
|
2633
|
+
return host.inboundManager.remove(id);
|
|
2015
2634
|
},
|
|
2016
|
-
|
|
2017
|
-
return
|
|
2635
|
+
async setInboundServerEnabled(id, enabled) {
|
|
2636
|
+
return host.inboundManager.setEnabled(id, enabled);
|
|
2018
2637
|
},
|
|
2019
|
-
async
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2638
|
+
async updateInboundServer(id, patch) {
|
|
2639
|
+
return host.inboundManager.update(id, patch);
|
|
2640
|
+
},
|
|
2641
|
+
listOutboundServers() {
|
|
2642
|
+
return [...host.outboundManager.list()];
|
|
2643
|
+
},
|
|
2644
|
+
async createOutboundServer(input) {
|
|
2645
|
+
return host.outboundManager.add({
|
|
2646
|
+
...input.id !== void 0 ? { id: input.id } : {},
|
|
2647
|
+
name: input.name,
|
|
2648
|
+
agentCardUrl: input.agentCardUrl,
|
|
2649
|
+
...input.bearerTokenEnv !== void 0 ? { bearerTokenEnv: input.bearerTokenEnv } : {},
|
|
2650
|
+
...input.preset !== void 0 ? { preset: input.preset } : {},
|
|
2651
|
+
enabled: input.enabled ?? true,
|
|
2652
|
+
timeoutMs: input.timeoutMs ?? host.outboundManager.defaultTimeoutMs
|
|
2029
2653
|
});
|
|
2030
2654
|
},
|
|
2031
|
-
async
|
|
2032
|
-
return
|
|
2033
|
-
ok: false,
|
|
2034
|
-
message: "outbound client not mounted"
|
|
2035
|
-
};
|
|
2655
|
+
async removeOutboundServer(id) {
|
|
2656
|
+
return host.outboundManager.remove(id);
|
|
2036
2657
|
},
|
|
2037
|
-
async
|
|
2038
|
-
return
|
|
2039
|
-
|
|
2040
|
-
|
|
2658
|
+
async setOutboundServerEnabled(id, enabled) {
|
|
2659
|
+
return host.outboundManager.setEnabled(id, enabled);
|
|
2660
|
+
},
|
|
2661
|
+
async refreshOutboundServer(id) {
|
|
2662
|
+
return host.outboundManager.refresh(id);
|
|
2663
|
+
},
|
|
2664
|
+
getTask(taskId) {
|
|
2665
|
+
return host.store.get(taskId);
|
|
2666
|
+
},
|
|
2667
|
+
listTasks() {
|
|
2668
|
+
return host.store.list();
|
|
2669
|
+
},
|
|
2670
|
+
async cancelTask(taskId) {
|
|
2671
|
+
const live = host.inboundManager.list().find((l) => l.server.abort(taskId));
|
|
2672
|
+
return {
|
|
2673
|
+
ok: live !== void 0,
|
|
2674
|
+
message: live !== void 0 ? `task ${taskId} canceled` : `task ${taskId} not found or already terminal`
|
|
2041
2675
|
};
|
|
2042
2676
|
},
|
|
2043
|
-
|
|
2044
|
-
return
|
|
2677
|
+
inbounds() {
|
|
2678
|
+
return host.inboundManager.list().flatMap((live) => live.inbound.list());
|
|
2679
|
+
},
|
|
2680
|
+
async closeInbound(peerId) {
|
|
2681
|
+
for (const live of host.inboundManager.list()) {
|
|
2682
|
+
const peers = live.inbound.activeTasksOf(peerId);
|
|
2683
|
+
for (const taskId of peers) live.server.abort(taskId);
|
|
2684
|
+
if (live.inbound.closePeer(peerId)) return {
|
|
2685
|
+
ok: true,
|
|
2686
|
+
message: `inbound peer ${peerId} closed`
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
return {
|
|
2045
2690
|
ok: false,
|
|
2046
|
-
message:
|
|
2691
|
+
message: `inbound peer ${peerId} not found`
|
|
2047
2692
|
};
|
|
2048
2693
|
}
|
|
2049
2694
|
};
|
|
2050
2695
|
}
|
|
2051
|
-
/** A task that refuses readably on compositions without an agent loop. */
|
|
2052
|
-
function refuseExecutor(reason) {
|
|
2053
|
-
return {
|
|
2054
|
-
name: "refuse",
|
|
2055
|
-
async execute() {
|
|
2056
|
-
throw new Error(`a2a: ${reason}; refusing inbound task`);
|
|
2057
|
-
}
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
2696
|
function resolveDefaultModel(ctx) {
|
|
2061
2697
|
return probeService(ctx, "agentDefaultModel", "currentSelection")?.currentSelection();
|
|
2062
2698
|
}
|
|
@@ -2069,5 +2705,15 @@ function inboundSessionCwd() {
|
|
|
2069
2705
|
} catch {}
|
|
2070
2706
|
return dir;
|
|
2071
2707
|
}
|
|
2708
|
+
function noopRegister() {
|
|
2709
|
+
return () => {};
|
|
2710
|
+
}
|
|
2711
|
+
/** The listening port of a mounted webServer (undefined when unavailable). */
|
|
2712
|
+
function webServerPort(webServer) {
|
|
2713
|
+
const candidate = webServer;
|
|
2714
|
+
if (candidate === void 0) return void 0;
|
|
2715
|
+
const value = typeof candidate.port === "function" ? candidate.port() : candidate.port;
|
|
2716
|
+
return typeof value === "number" && Number.isFinite(value) ? String(value) : void 0;
|
|
2717
|
+
}
|
|
2072
2718
|
//#endregion
|
|
2073
2719
|
export { A2AClient, A2AError, A2AServer, A2AService, Config, apply, inject, name };
|