@hanphone/dsh-a2a 0.3.0 → 0.3.1
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 +20 -15
- package/README.zh.md +6 -6
- package/lib/client.js +19 -50
- package/lib/index.js +68 -52
- package/lib/types/api.d.ts +0 -10
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +0 -2
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +9 -5
- package/lib/types/index.js.map +1 -1
- package/lib/types/server/exec/agent-runtime.d.ts +17 -0
- package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
- package/lib/types/server/exec/agent-runtime.js.map +1 -1
- package/lib/types/server/store.d.ts +2 -4
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +44 -21
- package/lib/types/servers/inbound-manager.d.ts.map +1 -1
- package/lib/types/servers/inbound-manager.js +67 -44
- package/lib/types/servers/inbound-manager.js.map +1 -1
- package/lib/types/service.d.ts +1 -3
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js.map +1 -1
- package/package.json +2 -2
- package/src/api.ts +2 -4
- package/src/client/index.ts +11 -48
- package/src/index.ts +13 -9
- package/src/server/exec/agent-runtime.ts +15 -0
- package/src/server/store.ts +2 -4
- package/src/servers/inbound-manager.ts +81 -50
- package/src/service.ts +3 -3
package/src/server/store.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type DomainFacility,
|
|
16
16
|
type KvTable,
|
|
17
17
|
} from '@deepseek-ai/dsh-storage-domain'
|
|
18
|
-
import { TaskState, type
|
|
18
|
+
import { TaskState, type Part } from '../protocol.ts'
|
|
19
19
|
|
|
20
20
|
/** One durable inbound/outbound task record. */
|
|
21
21
|
export interface TaskRecord {
|
|
@@ -59,11 +59,9 @@ export interface InboundServerRecord {
|
|
|
59
59
|
readonly description: string
|
|
60
60
|
readonly version: string
|
|
61
61
|
readonly endpointPath: string
|
|
62
|
-
/** Agent preset id composing inbound sessions; absent
|
|
62
|
+
/** Agent preset id composing inbound sessions; absent falls back to the deployment default. */
|
|
63
63
|
readonly preset?: string
|
|
64
64
|
readonly authTokenEnv?: string
|
|
65
|
-
/** Advertised skills (creator-entered declarations, default = preset name). */
|
|
66
|
-
readonly skills: readonly AgentSkill[]
|
|
67
65
|
readonly enabled: boolean
|
|
68
66
|
}
|
|
69
67
|
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inbound server manager: owns every inbound A2A server instance. Each
|
|
3
|
-
* instance binds one agent preset (its session pool is composed from
|
|
4
|
-
* preset)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* instance binds one concrete agent preset (its session pool is composed from
|
|
4
|
+
* that preset) and serves its own endpoint and AgentCard route. Instances are
|
|
5
|
+
* persisted in the `inbound_servers` domain table and assembled on boot; the
|
|
6
|
+
* manager owns route registration and the full lifecycle
|
|
7
|
+
* (add/enable/disable/update/remove) driven from the GUI and facade, and
|
|
8
|
+
* disposes every instance's environment on teardown.
|
|
9
9
|
*
|
|
10
|
-
* Skill declaration
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Skill declaration is DERIVED, never typed: "the preset decides its skills;
|
|
11
|
+
* everything is a plugin." An instance's AgentCard skills are the
|
|
12
|
+
* model-invocable entries of its preset's skill directory — the preset's
|
|
13
|
+
* standing scope key (`agentPresets.standingKeyFor`) plus
|
|
14
|
+
* `ctx.skills.list({ scope })` — resolved automatically at add/update/boot
|
|
15
|
+
* and cached on the live instance; the creator types no skill text. A missing
|
|
16
|
+
* skills service or standing mount falls back to a built-in `chat` skill so
|
|
17
|
+
* minimal compositions stay exercisable.
|
|
14
18
|
* @module dsh-a2a/servers/inbound-manager
|
|
15
19
|
*/
|
|
16
20
|
|
|
@@ -18,7 +22,7 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
|
18
22
|
import { buildCard } from '../server/card.ts'
|
|
19
23
|
import { A2AServer } from '../server/a2a-server.ts'
|
|
20
24
|
import { A2aRoutes } from '../server/routes.ts'
|
|
21
|
-
import { ContextSessionPool, type AgentPresetsLike, type AgentRegistryLike } from '../server/exec/agent-runtime.ts'
|
|
25
|
+
import { ContextSessionPool, type AgentPresetsLike, type AgentRegistryLike, type SkillsLike } from '../server/exec/agent-runtime.ts'
|
|
22
26
|
import { createSessionExecutor } from '../server/exec/session.ts'
|
|
23
27
|
import { createSubagentExecutor, type SubagentsLike } from '../server/exec/subagent.ts'
|
|
24
28
|
import { ExecutorSet } from '../server/executor.ts'
|
|
@@ -45,10 +49,13 @@ export interface InboundManagerHost {
|
|
|
45
49
|
readonly logger: { info(message: string): void; warn(message: string): void; error(message: string): void }
|
|
46
50
|
readonly agents?: AgentRegistryLike
|
|
47
51
|
readonly agentPresets?: AgentPresetsLike
|
|
52
|
+
readonly skills?: SkillsLike
|
|
48
53
|
readonly subagents?: SubagentsLike
|
|
49
54
|
readonly sessionCwd: string
|
|
50
55
|
readonly defaultBaseUrl: string
|
|
51
56
|
readonly subagentProvider: string
|
|
57
|
+
/** Deployment default preset id (from `agentPresets.defaultId`), when known. */
|
|
58
|
+
readonly defaultPresetId?: string
|
|
52
59
|
/** Resolve the deployment's default model options (may be undefined). */
|
|
53
60
|
readonly resolveDefaultModel?: () => { readonly provider?: string; readonly model?: string } | undefined
|
|
54
61
|
/** Allocate a fresh stable instance id. */
|
|
@@ -59,6 +66,10 @@ export interface InboundManagerHost {
|
|
|
59
66
|
export interface LiveInboundServer {
|
|
60
67
|
readonly id: string
|
|
61
68
|
readonly record: InboundServerRecord
|
|
69
|
+
/** Effective preset id (record's or the deployment default). */
|
|
70
|
+
readonly preset: string | undefined
|
|
71
|
+
/** Derived skill declarations (cached at assemble time). */
|
|
72
|
+
readonly skills: readonly AgentSkill[]
|
|
62
73
|
readonly card: ReturnType<typeof buildCard>
|
|
63
74
|
readonly server: A2AServer
|
|
64
75
|
readonly routes: A2aRoutes
|
|
@@ -75,10 +86,9 @@ export interface InboundCreateInput {
|
|
|
75
86
|
readonly description: string
|
|
76
87
|
readonly version: string
|
|
77
88
|
readonly endpointPath?: string
|
|
89
|
+
/** Preset id; absent = the deployment default preset. */
|
|
78
90
|
readonly preset?: string
|
|
79
91
|
readonly authTokenEnv?: string
|
|
80
|
-
/** Declared skills; empty defaults to the preset name (or `chat`). */
|
|
81
|
-
readonly skills?: readonly AgentSkill[]
|
|
82
92
|
readonly enabled?: boolean
|
|
83
93
|
}
|
|
84
94
|
|
|
@@ -96,23 +106,52 @@ function refuseExecutor(reason: string) {
|
|
|
96
106
|
}
|
|
97
107
|
}
|
|
98
108
|
|
|
99
|
-
/**
|
|
100
|
-
export function
|
|
101
|
-
skills: readonly AgentSkill[] | undefined,
|
|
102
|
-
presetName: string | undefined,
|
|
103
|
-
): readonly AgentSkill[] {
|
|
104
|
-
if (skills !== undefined && skills.length > 0) return skills
|
|
105
|
-
if (presetName !== undefined && presetName.length > 0) {
|
|
106
|
-
return [{ id: 'chat', name: presetName, description: `Compose inbound sessions from the "${presetName}" agent preset.`, tags: ['preset'] }]
|
|
107
|
-
}
|
|
109
|
+
/** Built-in fallback skill when no skills service / standing mount exists. */
|
|
110
|
+
export function chatFallbackSkills(): readonly AgentSkill[] {
|
|
108
111
|
return [{ id: 'chat', name: 'chat', description: 'Conversational assistance over a DSH agent session.', tags: ['chat'] }]
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Derive an instance's AgentCard skills from its preset's skill directory.
|
|
116
|
+
*
|
|
117
|
+
* `agentPresets.standingKeyFor(preset)` yields the preset's standing scope
|
|
118
|
+
* key (no agent required); `ctx.skills.list({ scope })` returns the catalogue
|
|
119
|
+
* that preset agent actually sees (its layer + the deployment global). Only
|
|
120
|
+
* model-invocable entries are advertised, mapped to `AgentSkill` (id = name,
|
|
121
|
+
* name = name). Any missing service, failed mount, or empty catalogue falls
|
|
122
|
+
* back to the built-in `chat` skill.
|
|
123
|
+
*
|
|
124
|
+
* @param agentPresets - the preset roster, or undefined.
|
|
125
|
+
* @param skills - the skill registry, or undefined.
|
|
126
|
+
* @param preset - the instance's preset id; undefined = deployment default.
|
|
127
|
+
*/
|
|
128
|
+
export async function derivePresetSkills(
|
|
129
|
+
agentPresets: AgentPresetsLike | undefined,
|
|
130
|
+
skills: SkillsLike | undefined,
|
|
131
|
+
preset: string | undefined,
|
|
132
|
+
): Promise<readonly AgentSkill[]> {
|
|
133
|
+
if (agentPresets?.standingKeyFor === undefined || skills === undefined) return chatFallbackSkills()
|
|
134
|
+
try {
|
|
135
|
+
const scope = await agentPresets.standingKeyFor(preset)
|
|
136
|
+
const rows = await skills.list({ scope })
|
|
137
|
+
const advertised: AgentSkill[] = rows
|
|
138
|
+
.filter((row) => row.invocation?.modelInvocable !== false)
|
|
139
|
+
.map((row) => ({
|
|
140
|
+
id: row.name,
|
|
141
|
+
name: row.name,
|
|
142
|
+
...(row.description !== undefined && row.description.length > 0 ? { description: row.description } : {}),
|
|
143
|
+
}))
|
|
144
|
+
return advertised.length > 0 ? advertised : chatFallbackSkills()
|
|
145
|
+
} catch {
|
|
146
|
+
return chatFallbackSkills()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Assemble one live instance (skills already derived from the preset). */
|
|
151
|
+
function assemble(record: InboundServerRecord, skills: readonly AgentSkill[], host: InboundManagerHost): LiveInboundServer {
|
|
114
152
|
const endpointPath = record.endpointPath
|
|
115
153
|
const cardPath = `${endpointPath.replace(/\/$/, '')}/agent-card.json`
|
|
154
|
+
const preset = record.preset ?? host.defaultPresetId
|
|
116
155
|
const card = buildCard({
|
|
117
156
|
baseUrl: host.defaultBaseUrl,
|
|
118
157
|
endpointPath,
|
|
@@ -130,7 +169,7 @@ function assemble(record: InboundServerRecord, host: InboundManagerHost): LiveIn
|
|
|
130
169
|
? new ContextSessionPool(host.agents, {
|
|
131
170
|
cwd: host.sessionCwd,
|
|
132
171
|
...(host.agentPresets !== undefined ? { agentPresets: host.agentPresets } : {}),
|
|
133
|
-
...(
|
|
172
|
+
...(preset !== undefined ? { presetId: () => preset } : {}),
|
|
134
173
|
// `?.()` yields `{ provider?, model? } | undefined`, matching the
|
|
135
174
|
// AgentRuntimeOptions.resolveAgentOptions signature under
|
|
136
175
|
// exactOptionalPropertyTypes.
|
|
@@ -184,6 +223,8 @@ function assemble(record: InboundServerRecord, host: InboundManagerHost): LiveIn
|
|
|
184
223
|
return {
|
|
185
224
|
id: record.id,
|
|
186
225
|
record,
|
|
226
|
+
preset,
|
|
227
|
+
skills,
|
|
187
228
|
card,
|
|
188
229
|
server,
|
|
189
230
|
routes,
|
|
@@ -242,7 +283,7 @@ export class DomainInboundStore {
|
|
|
242
283
|
function safeParse(raw: string): InboundServerRecord | undefined {
|
|
243
284
|
try {
|
|
244
285
|
const parsed = JSON.parse(raw) as InboundServerRecord
|
|
245
|
-
if (typeof parsed.id !== 'string' || typeof parsed.name !== 'string'
|
|
286
|
+
if (typeof parsed.id !== 'string' || typeof parsed.name !== 'string') return undefined
|
|
246
287
|
return parsed
|
|
247
288
|
} catch {
|
|
248
289
|
return undefined
|
|
@@ -258,11 +299,12 @@ export class InboundServerManager {
|
|
|
258
299
|
private readonly store: DomainInboundStore,
|
|
259
300
|
) {}
|
|
260
301
|
|
|
261
|
-
/** Assemble every persisted
|
|
262
|
-
boot(): void {
|
|
302
|
+
/** Assemble every persisted instance (skills derived per preset). */
|
|
303
|
+
async boot(): Promise<void> {
|
|
263
304
|
for (const record of this.store.list()) {
|
|
264
305
|
try {
|
|
265
|
-
const
|
|
306
|
+
const skills = await derivePresetSkills(this.host.agentPresets, this.host.skills, record.preset ?? this.host.defaultPresetId)
|
|
307
|
+
const live = assemble(record, skills, this.host)
|
|
266
308
|
this.live.set(record.id, live)
|
|
267
309
|
if (record.enabled) live.routes.enable()
|
|
268
310
|
} catch (err) {
|
|
@@ -279,35 +321,24 @@ export class InboundServerManager {
|
|
|
279
321
|
return this.live.get(id)
|
|
280
322
|
}
|
|
281
323
|
|
|
282
|
-
/** Resolve the display name of a preset id (undefined when unnamed/absent). */
|
|
283
|
-
private async presetDisplayName(preset: string | undefined): Promise<string | undefined> {
|
|
284
|
-
if (preset === undefined || this.host.agentPresets === undefined) return undefined
|
|
285
|
-
try {
|
|
286
|
-
const resolved = await this.host.agentPresets.resolve(preset)
|
|
287
|
-
return resolved.name ?? resolved.id
|
|
288
|
-
} catch {
|
|
289
|
-
return undefined
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
324
|
/** Create, persist, and assemble a new inbound instance. */
|
|
294
325
|
async add(input: InboundCreateInput): Promise<OpResult & { readonly id?: string }> {
|
|
295
326
|
const id = this.newId(input.name)
|
|
296
327
|
if (this.live.has(id)) return { ok: false, message: `inbound server ${id} already exists` }
|
|
297
|
-
const
|
|
328
|
+
const preset = input.preset ?? this.host.defaultPresetId
|
|
298
329
|
const record: InboundServerRecord = {
|
|
299
330
|
id,
|
|
300
331
|
name: input.name,
|
|
301
332
|
description: input.description,
|
|
302
333
|
version: input.version,
|
|
303
334
|
endpointPath: input.endpointPath ?? `/a2a/${id}`,
|
|
304
|
-
...(
|
|
335
|
+
...(preset !== undefined ? { preset } : {}),
|
|
305
336
|
...(input.authTokenEnv !== undefined ? { authTokenEnv: input.authTokenEnv } : {}),
|
|
306
|
-
skills: [...defaultSkillFor(input.skills, presetName)],
|
|
307
337
|
enabled: input.enabled ?? true,
|
|
308
338
|
}
|
|
309
339
|
try {
|
|
310
|
-
const
|
|
340
|
+
const skills = await derivePresetSkills(this.host.agentPresets, this.host.skills, preset)
|
|
341
|
+
const live = assemble(record, skills, this.host)
|
|
311
342
|
await this.store.save(record)
|
|
312
343
|
this.live.set(id, live)
|
|
313
344
|
if (record.enabled) live.routes.enable()
|
|
@@ -317,8 +348,8 @@ export class InboundServerManager {
|
|
|
317
348
|
}
|
|
318
349
|
}
|
|
319
350
|
|
|
320
|
-
/** Persist, rebuild, and apply a patch onto a live instance. */
|
|
321
|
-
async update(id: string, patch: Partial<Pick<InboundServerRecord, 'name' | 'description' | 'version' | 'endpointPath' | 'preset' | 'authTokenEnv'
|
|
351
|
+
/** Persist, rebuild (re-deriving skills), and apply a patch onto a live instance. */
|
|
352
|
+
async update(id: string, patch: Partial<Pick<InboundServerRecord, 'name' | 'description' | 'version' | 'endpointPath' | 'preset' | 'authTokenEnv'>>): Promise<OpResult> {
|
|
322
353
|
const live = this.live.get(id)
|
|
323
354
|
const stored = this.store.get(id)
|
|
324
355
|
if (live === undefined || stored === undefined) return { ok: false, message: `inbound server ${id} not found` }
|
|
@@ -330,12 +361,12 @@ export class InboundServerManager {
|
|
|
330
361
|
endpointPath: patch.endpointPath ?? stored.endpointPath,
|
|
331
362
|
...(patch.preset !== undefined ? { preset: patch.preset } : {}),
|
|
332
363
|
...(patch.authTokenEnv !== undefined ? { authTokenEnv: patch.authTokenEnv } : {}),
|
|
333
|
-
skills: patch.skills ?? stored.skills,
|
|
334
364
|
enabled: stored.enabled,
|
|
335
365
|
}
|
|
336
366
|
try {
|
|
337
367
|
await this.store.save(next)
|
|
338
|
-
const
|
|
368
|
+
const skills = await derivePresetSkills(this.host.agentPresets, this.host.skills, next.preset ?? this.host.defaultPresetId)
|
|
369
|
+
const replaced = assemble(next, skills, this.host)
|
|
339
370
|
// Preserve the route registration state while swapping runtime halves.
|
|
340
371
|
const wasEnabled = live.routes.active
|
|
341
372
|
live.dispose()
|
|
@@ -382,4 +413,4 @@ export class InboundServerManager {
|
|
|
382
413
|
}
|
|
383
414
|
}
|
|
384
415
|
|
|
385
|
-
export type { AgentSkill }
|
|
416
|
+
export type { AgentSkill }
|
package/src/service.ts
CHANGED
|
@@ -64,9 +64,9 @@ export interface InboundCreateInput {
|
|
|
64
64
|
readonly description: string
|
|
65
65
|
readonly version: string
|
|
66
66
|
readonly endpointPath?: string
|
|
67
|
+
/** Preset id; absent = the deployment default. Skills derive from it. */
|
|
67
68
|
readonly preset?: string
|
|
68
69
|
readonly authTokenEnv?: string
|
|
69
|
-
readonly skills?: readonly SkillView[]
|
|
70
70
|
readonly enabled?: boolean
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -91,7 +91,7 @@ export interface A2AServiceImpl {
|
|
|
91
91
|
createInboundServer(input: InboundCreateInput): Promise<OpResult>
|
|
92
92
|
removeInboundServer(id: string): Promise<OpResult>
|
|
93
93
|
setInboundServerEnabled(id: string, enabled: boolean): Promise<OpResult>
|
|
94
|
-
updateInboundServer(id: string, patch: { name?: string; description?: string; version?: string; endpointPath?: string; preset?: string; authTokenEnv?: string
|
|
94
|
+
updateInboundServer(id: string, patch: { name?: string; description?: string; version?: string; endpointPath?: string; preset?: string; authTokenEnv?: string }): Promise<OpResult>
|
|
95
95
|
// ── outbound server instances ───────────────────────────────────────
|
|
96
96
|
listOutboundServers(): OutboundServerView[]
|
|
97
97
|
createOutboundServer(input: OutboundCreateInput): Promise<OpResult>
|
|
@@ -140,7 +140,7 @@ export class A2AService extends Service {
|
|
|
140
140
|
return this.impl.setInboundServerEnabled(id, enabled)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
async updateInboundServer(id: string, patch: { name?: string; description?: string; version?: string; endpointPath?: string; preset?: string; authTokenEnv?: string
|
|
143
|
+
async updateInboundServer(id: string, patch: { name?: string; description?: string; version?: string; endpointPath?: string; preset?: string; authTokenEnv?: string }): Promise<OpResult> {
|
|
144
144
|
return this.impl.updateInboundServer(id, patch)
|
|
145
145
|
}
|
|
146
146
|
|