@hanphone/dsh-a2a 0.2.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.
Files changed (78) hide show
  1. package/README.md +106 -106
  2. package/README.zh.md +50 -74
  3. package/cordis.patch.yml +4 -7
  4. package/lib/client.js +186 -73
  5. package/lib/index.js +1275 -868
  6. package/lib/types/api.d.ts +30 -23
  7. package/lib/types/api.d.ts.map +1 -1
  8. package/lib/types/api.js +56 -23
  9. package/lib/types/api.js.map +1 -1
  10. package/lib/types/client/index.d.ts +50 -19
  11. package/lib/types/client/index.d.ts.map +1 -1
  12. package/lib/types/commands.d.ts +4 -2
  13. package/lib/types/commands.d.ts.map +1 -1
  14. package/lib/types/commands.js +59 -46
  15. package/lib/types/commands.js.map +1 -1
  16. package/lib/types/events.d.ts +32 -9
  17. package/lib/types/events.d.ts.map +1 -1
  18. package/lib/types/index.d.ts +21 -32
  19. package/lib/types/index.d.ts.map +1 -1
  20. package/lib/types/index.js +177 -272
  21. package/lib/types/index.js.map +1 -1
  22. package/lib/types/outbound/calls.d.ts.map +1 -1
  23. package/lib/types/outbound/calls.js +8 -6
  24. package/lib/types/outbound/calls.js.map +1 -1
  25. package/lib/types/protocol.d.ts +174 -109
  26. package/lib/types/protocol.d.ts.map +1 -1
  27. package/lib/types/protocol.js +58 -34
  28. package/lib/types/protocol.js.map +1 -1
  29. package/lib/types/server/a2a-server.d.ts +4 -0
  30. package/lib/types/server/a2a-server.d.ts.map +1 -1
  31. package/lib/types/server/a2a-server.js +6 -2
  32. package/lib/types/server/a2a-server.js.map +1 -1
  33. package/lib/types/server/card.d.ts +6 -30
  34. package/lib/types/server/card.d.ts.map +1 -1
  35. package/lib/types/server/card.js +13 -49
  36. package/lib/types/server/card.js.map +1 -1
  37. package/lib/types/server/exec/agent-runtime.d.ts +20 -0
  38. package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
  39. package/lib/types/server/exec/agent-runtime.js +2 -1
  40. package/lib/types/server/exec/agent-runtime.js.map +1 -1
  41. package/lib/types/server/routes.d.ts +5 -1
  42. package/lib/types/server/routes.d.ts.map +1 -1
  43. package/lib/types/server/routes.js +9 -5
  44. package/lib/types/server/routes.js.map +1 -1
  45. package/lib/types/server/store.d.ts +32 -0
  46. package/lib/types/server/store.d.ts.map +1 -1
  47. package/lib/types/server/store.js +10 -0
  48. package/lib/types/server/store.js.map +1 -1
  49. package/lib/types/servers/inbound-manager.d.ts +154 -0
  50. package/lib/types/servers/inbound-manager.d.ts.map +1 -0
  51. package/lib/types/servers/inbound-manager.js +335 -0
  52. package/lib/types/servers/inbound-manager.js.map +1 -0
  53. package/lib/types/servers/outbound-manager.d.ts +129 -0
  54. package/lib/types/servers/outbound-manager.d.ts.map +1 -0
  55. package/lib/types/servers/outbound-manager.js +238 -0
  56. package/lib/types/servers/outbound-manager.js.map +1 -0
  57. package/lib/types/service.d.ts +104 -40
  58. package/lib/types/service.d.ts.map +1 -1
  59. package/lib/types/service.js +36 -27
  60. package/lib/types/service.js.map +1 -1
  61. package/package.json +2 -2
  62. package/src/api.ts +65 -53
  63. package/src/client/index.ts +272 -148
  64. package/src/commands.ts +59 -43
  65. package/src/events.ts +14 -7
  66. package/src/index.ts +203 -310
  67. package/src/outbound/calls.ts +8 -6
  68. package/src/protocol.ts +204 -95
  69. package/src/server/a2a-server.ts +9 -2
  70. package/src/server/card.ts +13 -62
  71. package/src/server/exec/agent-runtime.ts +20 -2
  72. package/src/server/routes.ts +6 -4
  73. package/src/server/store.ts +42 -3
  74. package/src/servers/inbound-manager.ts +416 -0
  75. package/src/servers/outbound-manager.ts +289 -0
  76. package/src/service.ts +125 -40
  77. package/lib/tsconfig.client.tsbuildinfo +0 -1
  78. package/lib/tsconfig.tsbuildinfo +0 -1
package/src/protocol.ts CHANGED
@@ -1,23 +1,24 @@
1
1
  /**
2
- * Agent2Agent (A2A) Protocol v1.0 — types, task states, JSON-RPC methods and
3
- * error codes used by this plugin's JSON-RPC over HTTP binding.
4
- *
5
- * The surface mirrors the normative `a2a.proto` of the A2A project (Apache-2.0);
6
- * this file is the plugin's own transcription and the single source of protocol
7
- * truth. Bindings other than JSON-RPC over HTTP (gRPC, REST) are not provided.
2
+ * Agent2Agent (A2A) Protocol v1.0.1 — types, JSON-RPC methods, task states,
3
+ * error codes and JSON serialization, transcribed from the official
4
+ * `a2aproject/A2A` specification (Apache-2.0), cross-checked against
5
+ * `specification/a2a.proto` and `docs/specification.md` (JSON-RPC binding,
6
+ * ADR-001 ProtoJSON). This file is the plugin's single source of protocol
7
+ * truth for the JSON-RPC over HTTP binding. Other bindings (gRPC, REST) are
8
+ * not provided.
8
9
  * @module dsh-a2a/protocol
9
10
  */
10
11
 
11
- /** Task lifecycle states (spec `TaskState`). */
12
+ /** Task lifecycle states (spec `TaskState`; JSON = SCREAMING_SNAKE_CASE per ADR-001). */
12
13
  export enum TaskState {
13
- SUBMITTED = 'SUBMITTED',
14
- WORKING = 'WORKING',
15
- INPUT_REQUIRED = 'INPUT_REQUIRED',
16
- AUTH_REQUIRED = 'AUTH_REQUIRED',
17
- COMPLETED = 'COMPLETED',
18
- FAILED = 'FAILED',
19
- CANCELED = 'CANCELED',
20
- REJECTED = 'REJECTED',
14
+ SUBMITTED = 'TASK_STATE_SUBMITTED',
15
+ WORKING = 'TASK_STATE_WORKING',
16
+ INPUT_REQUIRED = 'TASK_STATE_INPUT_REQUIRED',
17
+ AUTH_REQUIRED = 'TASK_STATE_AUTH_REQUIRED',
18
+ COMPLETED = 'TASK_STATE_COMPLETED',
19
+ FAILED = 'TASK_STATE_FAILED',
20
+ CANCELED = 'TASK_STATE_CANCELED',
21
+ REJECTED = 'TASK_STATE_REJECTED',
21
22
  }
22
23
 
23
24
  /** States that settle a task; a settled task no longer transitions. */
@@ -28,72 +29,150 @@ export const TERMINAL_STATES: ReadonlySet<TaskState> = new Set([
28
29
  TaskState.REJECTED,
29
30
  ])
30
31
 
32
+ /** Interrupted (non-terminal, still owed) states. */
33
+ export const INTERRUPTED_STATES: ReadonlySet<TaskState> = new Set([
34
+ TaskState.INPUT_REQUIRED,
35
+ TaskState.AUTH_REQUIRED,
36
+ ])
37
+
31
38
  /** @returns whether the state settles the task. */
32
39
  export function isTerminal(state: TaskState): boolean {
33
40
  return TERMINAL_STATES.has(state)
34
41
  }
35
42
 
36
- /** Message sender role (spec `Role`). */
43
+ /** Message sender role (spec `Role`; JSON = `ROLE_*` per ADR-001). */
37
44
  export enum Role {
38
- USER = 'user',
39
- AGENT = 'agent',
40
- }
41
-
42
- /** One message part: text, file (bytes or uri), or structured data. */
43
- export type Part =
44
- | { readonly text: string; readonly metadata?: Record<string, unknown> }
45
- | {
46
- readonly file: {
47
- readonly mimeType?: string
48
- readonly name?: string
49
- readonly bytes?: string
50
- readonly uri?: string
51
- }
52
- readonly metadata?: Record<string, unknown>
53
- }
54
- | { readonly data: unknown; readonly metadata?: Record<string, unknown> }
55
-
56
- /** An interaction payload exchanged between agents. */
45
+ USER = 'ROLE_USER',
46
+ AGENT = 'ROLE_AGENT',
47
+ }
48
+
49
+ /** Base properties common to every message part. */
50
+ export interface PartBase {
51
+ readonly metadata?: Record<string, unknown> | null
52
+ }
53
+
54
+ /** Conveys plain textual content. */
55
+ export interface TextPart extends PartBase {
56
+ readonly text: string
57
+ }
58
+
59
+ /** Conveys a file: raw bytes (base64 in JSON), a URL, or both. */
60
+ export interface FilePart extends PartBase {
61
+ readonly raw?: string
62
+ readonly url?: string
63
+ readonly filename?: string
64
+ readonly mediaType?: string
65
+ }
66
+
67
+ /** Conveys structured data (a JSON value). */
68
+ export interface DataPart extends PartBase {
69
+ readonly data: unknown
70
+ }
71
+
72
+ /** A container for a section of communication content (spec `Part`). */
73
+ export type Part = TextPart | FilePart | DataPart
74
+
75
+ /** A message in a task's conversation (spec `Message`). */
57
76
  export interface Message {
77
+ /** Unique id created by the message creator. */
58
78
  readonly messageId: string
59
- readonly role: Role
79
+ /** Associates this message with a context. */
60
80
  readonly contextId?: string
61
- /** Continuation: attaches this message to an existing task's conversation. */
81
+ /** Associates this message with a task. */
62
82
  readonly taskId?: string
83
+ readonly role: Role
63
84
  readonly parts: readonly Part[]
64
- readonly metadata?: Record<string, unknown>
85
+ readonly metadata?: Record<string, unknown> | null
86
+ /** Extension URIs present or contributed. */
87
+ readonly extensions?: readonly string[]
88
+ /** Task ids this message references for additional context. */
89
+ readonly referenceTaskIds?: readonly string[]
90
+ }
91
+
92
+ /** Configuration of a send-message request (spec `SendMessageConfiguration`). */
93
+ export interface SendMessageConfiguration {
94
+ /** Media types the client accepts for response parts. */
95
+ readonly acceptedOutputModes?: readonly string[]
96
+ /** Push notification config; task id empty when sent with SendMessage. */
97
+ readonly taskPushNotificationConfig?: TaskPushNotificationConfig
98
+ /** Max history messages to return; 0 = none; unset = no limit. */
99
+ readonly historyLength?: number
100
+ /** true = return immediately after task creation; false (default) = block to terminal/interrupted. */
101
+ readonly returnImmediately?: boolean
65
102
  }
66
103
 
67
- /** Current state of a task, with an optional explanatory message. */
104
+ /** A container for the status of a task. */
68
105
  export interface TaskStatus {
69
106
  readonly state: TaskState
70
- readonly message?: Message
107
+ readonly message?: Message | null
108
+ /** ISO 8601 timestamp when the status was recorded. */
71
109
  readonly timestamp: string
72
110
  }
73
111
 
74
- /** A chunk of task output. */
112
+ /** A chunk of task output (spec `Artifact`). */
75
113
  export interface Artifact {
76
114
  readonly name?: string
77
- readonly parts: readonly Part[]
78
115
  readonly artifactId?: string
116
+ readonly parts: readonly Part[]
117
+ readonly metadata?: Record<string, unknown> | null
79
118
  }
80
119
 
81
- /** A task: the durable work unit of A2A. */
120
+ /** The core unit of action for A2A (spec `Task`). */
82
121
  export interface Task {
122
+ /** Server-generated id for a new task. */
83
123
  readonly id: string
84
124
  readonly contextId?: string
85
125
  readonly status: TaskStatus
86
126
  readonly artifacts?: readonly Artifact[]
87
127
  readonly history?: readonly Message[]
88
- readonly metadata?: Record<string, unknown>
128
+ readonly metadata?: Record<string, unknown> | null
129
+ }
130
+
131
+ /** Task status transition event (spec `TaskStatusUpdateEvent`). */
132
+ export interface TaskStatusUpdateEvent {
133
+ readonly taskId: string
134
+ readonly contextId?: string
135
+ readonly status: TaskStatus
136
+ readonly metadata?: Record<string, unknown> | null
137
+ }
138
+
139
+ /** Task artifact update event (spec `TaskArtifactUpdateEvent`). */
140
+ export interface TaskArtifactUpdateEvent {
141
+ readonly taskId: string
142
+ readonly contextId?: string
143
+ readonly artifact: Artifact
144
+ readonly lastChunk?: boolean
145
+ readonly metadata?: Record<string, unknown> | null
89
146
  }
90
147
 
91
- /** JSON-RPC 2.0 request as used by the A2A binding. */
148
+ /** Streaming payload variants (spec `StreamResponse` oneof). */
149
+ export type StreamResponse =
150
+ | { readonly task: Task }
151
+ | { readonly message: Message }
152
+ | { readonly statusUpdate: TaskStatusUpdateEvent }
153
+ | { readonly artifactUpdate: TaskArtifactUpdateEvent }
154
+
155
+ // ── push notifications (spec §Push Notification Objects) ──────────────────
156
+
157
+ /** Push notification configuration (spec `TaskPushNotificationConfig`). */
158
+ export interface TaskPushNotificationConfig {
159
+ readonly id?: string
160
+ readonly url: string
161
+ readonly token?: string | null
162
+ readonly authentication?: {
163
+ readonly schemes: readonly string[]
164
+ readonly credentials?: string | null
165
+ } | null
166
+ }
167
+
168
+ // ── JSON-RPC 2.0 frames ───────────────────────────────────────────────────
169
+
170
+ /** JSON-RPC 2.0 message base. */
92
171
  export interface JsonRpcRequest {
93
172
  readonly jsonrpc: '2.0'
94
173
  readonly id?: string | number | null
95
174
  readonly method: string
96
- readonly params?: unknown
175
+ readonly params?: Record<string, unknown> | null
97
176
  }
98
177
 
99
178
  export interface JsonRpcSuccess {
@@ -116,64 +195,91 @@ export interface JsonRpcError {
116
195
 
117
196
  export type JsonRpcResponse = JsonRpcSuccess | JsonRpcError
118
197
 
119
- /** A skill advertised by an agent. */
198
+ // ── Agent Card (spec §8 Agent Discovery) ──────────────────────────────────
199
+
200
+ /** A skill advertised by an agent (spec `AgentSkill`). */
120
201
  export interface AgentSkill {
121
202
  readonly id: string
122
- readonly name?: string
123
- readonly description?: string
124
- readonly tags?: readonly string[]
125
- readonly examples?: readonly string[]
126
- readonly inputModes?: readonly string[]
127
- readonly outputModes?: readonly string[]
203
+ /** Human-readable name. Required by the protocol. */
204
+ readonly name: string
205
+ /** Detailed description (CommonMark may be used). */
206
+ readonly description?: string | null
207
+ readonly tags?: readonly string[] | null
208
+ readonly examples?: readonly string[] | null
209
+ readonly inputModes?: readonly string[] | null
210
+ readonly outputModes?: readonly string[] | null
128
211
  }
129
212
 
213
+ /** Optional A2A protocol features (spec `AgentCapabilities`). */
130
214
  export interface AgentCapabilities {
215
+ /** `SendStreamingMessage` + `SubscribeToTask` support. */
131
216
  readonly streaming?: boolean
217
+ /** Push notification webhooks. */
132
218
  readonly pushNotifications?: boolean
219
+ /** Extended Agent Card support. */
220
+ readonly extendedAgentCard?: boolean
133
221
  readonly stateTransitionHistory?: boolean
134
- readonly extensions?: readonly string[]
135
222
  }
136
223
 
137
- /** One supported interface (transport binding) of an agent. */
224
+ /** Information about the providing organization (spec `AgentProvider`). */
225
+ export interface AgentProvider {
226
+ readonly organization: string
227
+ readonly url?: string | null
228
+ }
229
+
230
+ /** Authentication requirements of the agent's endpoint (spec `AgentAuthentication`). */
231
+ export interface AgentAuthentication {
232
+ /** Scheme names, e.g. "Bearer", "OAuth2", "ApiKey". Empty = no A2A-level auth. */
233
+ readonly schemes: readonly string[]
234
+ /** Non-secret scheme configuration; MUST NOT contain plaintext secrets. */
235
+ readonly credentials?: string | null
236
+ }
237
+
238
+ /** One supported interface of an agent (spec `AgentInterface`). */
138
239
  export interface AgentInterface {
139
240
  readonly url: string
140
- readonly protocolBinding?: 'JSONRPC' | 'REST' | 'gRPC' | string
241
+ readonly protocolBinding?: 'JSONRPC' | 'GRPC' | 'HTTP+JSON' | string
141
242
  readonly protocolVersion?: string
142
- readonly authSchemes?: readonly string[]
143
- }
144
-
145
- /** Bearer-token security scheme advertised by the AgentCard. */
146
- export interface AgentSecurityScheme {
147
- readonly type: 'http'
148
- readonly scheme: 'bearer'
149
- readonly description?: string
243
+ readonly tenant?: string
150
244
  }
151
245
 
152
- /** The discovery manifest of an A2A agent. */
246
+ /** The discovery manifest of an A2A agent (spec `AgentCard`). */
153
247
  export interface AgentCard {
154
248
  readonly name: string
155
- readonly description: string
249
+ readonly description?: string | null
250
+ readonly supportedInterfaces?: readonly AgentInterface[]
251
+ readonly provider?: AgentProvider | null
252
+ readonly iconUrl?: string | null
156
253
  readonly version: string
157
- readonly url?: string
158
- readonly provider?: { readonly url: string; readonly organization: string }
159
- readonly skills?: readonly AgentSkill[]
160
- readonly capabilities?: AgentCapabilities
254
+ readonly documentationUrl?: string | null
255
+ readonly capabilities: AgentCapabilities
256
+ readonly securitySchemes?: Record<string, unknown> | null
257
+ readonly securityRequirements?: readonly unknown[] | null
161
258
  readonly defaultInputModes?: readonly string[]
162
259
  readonly defaultOutputModes?: readonly string[]
163
- readonly securitySchemes?: Record<string, AgentSecurityScheme>
164
- readonly securityRequirements?: readonly Record<string, readonly string[]>[]
165
- readonly supportedInterfaces?: readonly AgentInterface[]
166
- readonly custom?: Record<string, unknown>
260
+ readonly skills: readonly AgentSkill[]
167
261
  }
168
262
 
169
- /** Streamed updates during a task run (SSE payloads). */
170
- export type StreamResponse =
171
- | { readonly statusUpdate: { readonly taskId: string; readonly contextId?: string; readonly status: TaskStatus } }
172
- | { readonly artifactUpdate: { readonly taskId: string; readonly contextId?: string; readonly artifact: Artifact; readonly lastChunk?: boolean } }
173
- | { readonly task: Task }
174
- | { readonly error: { readonly code: number; readonly message: string } }
263
+ // ── JSON-RPC error codes (spec §error codes + §9.5) ───────────────────────
175
264
 
176
- /** A2A v1.0 JSON-RPC method names (canonical PascalCase as in a2a.proto). */
265
+ /** JSON-RPC + A2A error codes. */
266
+ export const A2A_ERROR_CODES = {
267
+ PARSE_ERROR: -32700,
268
+ INVALID_REQUEST: -32600,
269
+ METHOD_NOT_FOUND: -32601,
270
+ INVALID_PARAMS: -32602,
271
+ INTERNAL_ERROR: -32603,
272
+ TASK_NOT_FOUND: -32001,
273
+ TASK_CANCEL_NOT_ALLOWED: -32002,
274
+ PUSH_NOTIFICATION_NOT_SUPPORTED: -32003,
275
+ UNSUPPORTED_OPERATION: -32004,
276
+ CONTENT_TYPE_NOT_SUPPORTED: -32005,
277
+ STREAMING_NOT_SUPPORTED: -32006,
278
+ VERSION_NOT_SUPPORTED: -32007,
279
+ INVALID_AGENT_RESPONSE: -32008,
280
+ } as const
281
+
282
+ /** A2A v1.0 JSON-RPC method names (spec §5.3 Method Mapping Reference). */
177
283
  export const A2A_METHODS = {
178
284
  sendMessage: 'SendMessage',
179
285
  sendStreamingMessage: 'SendStreamingMessage',
@@ -181,30 +287,33 @@ export const A2A_METHODS = {
181
287
  listTasks: 'ListTasks',
182
288
  cancelTask: 'CancelTask',
183
289
  subscribeToTask: 'SubscribeToTask',
290
+ createTaskPushNotificationConfig: 'CreateTaskPushNotificationConfig',
291
+ getTaskPushNotificationConfig: 'GetTaskPushNotificationConfig',
292
+ listTaskPushNotificationConfigs: 'ListTaskPushNotificationConfigs',
293
+ deleteTaskPushNotificationConfig: 'DeleteTaskPushNotificationConfig',
184
294
  getExtendedAgentCard: 'GetExtendedAgentCard',
185
295
  } as const
186
296
 
187
- /** JSON-RPC error codes for the A2A binding (spec §error codes). */
188
- export const A2A_ERROR_CODES = {
189
- INVALID_REQUEST: -32600,
190
- METHOD_NOT_FOUND: -32601,
191
- INVALID_PARAMS: -32602,
192
- INTERNAL_ERROR: -32603,
193
- UNAUTHORIZED: -32000,
194
- TASK_NOT_FOUND: -32001,
195
- TASK_CANCEL_NOT_ALLOWED: -32002,
196
- AGENT_CARD_NOT_FOUND: -32004,
197
- AGENT_CARD_SIGNATURE_INVALID: -32005,
198
- } as const
297
+ /** The A2A protocol version this plugin implements (header + interface). */
298
+ export const PROTOCOL_VERSION = '1.0'
299
+
300
+ /** The well-known AgentCard content type (spec §14.1). */
301
+ export const A2A_JSON_MEDIA_TYPE = 'application/a2a+json'
199
302
 
200
- /** Read a message's text parts as a single string (model-facing convenience). */
303
+ /**
304
+ * Read a message's text parts as a single string (model-facing convenience).
305
+ * File parts render as their URL/name; data parts as JSON.
306
+ */
201
307
  export function partsToText(parts: readonly Part[] | undefined): string {
202
308
  if (!parts) return ''
203
309
  return parts
204
310
  .map((p) => {
205
311
  if ('text' in p && p.text) return p.text
206
312
  if ('data' in p && p.data !== undefined) return JSON.stringify(p.data)
207
- if ('file' in p) return p.file.uri ?? `[file ${p.file.name ?? p.file.mimeType ?? 'binary'}]`
313
+ if ('raw' in p || 'url' in p || 'filename' in p) {
314
+ const f = p as FilePart
315
+ return f.url ?? `[file ${f.filename ?? f.mediaType ?? 'binary'}]`
316
+ }
208
317
  return ''
209
318
  })
210
319
  .filter(Boolean)
@@ -51,6 +51,8 @@ export interface A2AServerOptions {
51
51
  readonly gate: (input: GateInput) => Promise<GateResult>
52
52
  readonly onInbound?: (facts: InboundFacts) => void
53
53
  readonly onTaskSettled?: (taskId: string) => void
54
+ /** AgentCard route path for this instance; default = well-known A2A path. */
55
+ readonly cardPath?: string
54
56
  }
55
57
 
56
58
  /** Structural request the server handles (HTTP adapter fills it). */
@@ -84,6 +86,11 @@ export class A2AServer {
84
86
  this.card = next
85
87
  }
86
88
 
89
+ /** The AgentCard route path for this instance. */
90
+ cardPath(): string {
91
+ return this.opts.cardPath ?? '/.well-known/agent-card.json'
92
+ }
93
+
87
94
  /**
88
95
  * Abort a running task by control path (facade /a2a task cancel): abort the
89
96
  * executor's signal, settle the task CANCELED, and wake stream waiters.
@@ -110,7 +117,7 @@ export class A2AServer {
110
117
  /** Route one inbound HTTP request (GET card, POST JSON-RPC). */
111
118
  async handle(req: ServerRequest, body: string): Promise<ServerResponseSpec> {
112
119
  const path = (req.url ?? '').split('?')[0] ?? ''
113
- if (req.method === 'GET' && path === '/.well-known/agent-card.json') {
120
+ if (req.method === 'GET' && path === this.cardPath()) {
114
121
  return { status: 200, contentType: 'application/json', body: JSON.stringify(this.card) }
115
122
  }
116
123
  if (req.method !== 'POST' || path !== endpointOf(this.card)) {
@@ -120,7 +127,7 @@ export class A2AServer {
120
127
  return {
121
128
  status: 401,
122
129
  contentType: 'application/json',
123
- body: JSON.stringify(rpcError(null, A2A_ERROR_CODES.UNAUTHORIZED, 'Unauthorized')),
130
+ body: JSON.stringify(rpcError(null, -32040, 'Unauthorized')),
124
131
  headers: { 'WWW-Authenticate': 'Bearer' },
125
132
  }
126
133
  }
@@ -1,7 +1,10 @@
1
1
  /**
2
- * AgentCard assembly for the inbound half: skills are DERIVED from the live
3
- * tool registry by an explicit id list (never invented), plus a built-in
4
- * `chat` skill so a fresh install is immediately exercisable end-to-end.
2
+ * AgentCard assembly for the inbound half: identity and skill DECLARATION at
3
+ * the instance level. The skill list is exactly the creator-declared
4
+ * `AgentSkill[]` stored on the inbound server instance no derivation from
5
+ * the live tool registry (the v0.2 white-list mechanism is removed). The
6
+ * advertised card is a pure function of the instance record plus its base URL
7
+ * and endpoint path.
5
8
  * @module dsh-a2a/server/card
6
9
  */
7
10
 
@@ -11,62 +14,6 @@ import {
11
14
  type AgentSkill,
12
15
  } from '../protocol.ts'
13
16
 
14
- /** Structural slice of `ctx.tools` used for derivation. */
15
- export interface ToolGetter {
16
- get(name: string): { readonly name: string; readonly description?: string } | undefined
17
- }
18
-
19
- /** Derivation policy for inbound skills. */
20
- export interface CardSkillPolicy {
21
- /** Tool ids (as registered on ctx.tools) exposed as inbound skills. */
22
- readonly ids: readonly string[]
23
- /** Ids to remove after derivation (defense in depth). */
24
- readonly exclude: readonly string[]
25
- }
26
-
27
- /**
28
- * Derive the inbound skill list from the live tool registry.
29
- *
30
- * Every configured id must resolve to a registered tool — a missing referent
31
- * fails the derivation loudly (the plugin's misconfiguration contract) with
32
- * all missing ids listed. The built-in `chat` skill is always present so the
33
- * server answers before any tool is exposed.
34
- *
35
- * @param tools - the tool registry getter.
36
- * @param policy - explicit-id derivation policy.
37
- * @returns the derived skills, `chat` first, configured tools after, excludes applied.
38
- */
39
- export function deriveSkills(tools: ToolGetter, policy: CardSkillPolicy): AgentSkill[] {
40
- const excluded = new Set(policy.exclude)
41
- const skills: AgentSkill[] = [
42
- {
43
- id: 'chat',
44
- name: 'chat',
45
- description: 'Conversational assistance over a DSH agent session.',
46
- tags: ['chat'],
47
- },
48
- ]
49
- const missing: string[] = []
50
- for (const id of policy.ids) {
51
- if (excluded.has(id)) continue
52
- const tool = tools.get(id)
53
- if (tool === undefined) {
54
- missing.push(id)
55
- continue
56
- }
57
- skills.push({
58
- id,
59
- name: id,
60
- description: tool.description ?? `Expose the DSH tool \`${id}\` over A2A.`,
61
- tags: ['tool'],
62
- })
63
- }
64
- if (missing.length > 0) {
65
- throw new Error(`dsh-a2a: server.skills.ids reference unregistered tools: ${missing.join(', ')}`)
66
- }
67
- return skills
68
- }
69
-
70
17
  /** Identity options for the advertised AgentCard. */
71
18
  export interface CardOptions {
72
19
  readonly baseUrl: string
@@ -91,15 +38,19 @@ export function buildCard(options: CardOptions): AgentCard {
91
38
  description: options.description,
92
39
  version: options.version,
93
40
  provider: { url: 'https://deepseek.com', organization: 'DeepSeek' },
94
- capabilities: { streaming: true, pushNotifications: false, extensions: [] },
41
+ capabilities: { streaming: true, pushNotifications: false, extendedAgentCard: true, stateTransitionHistory: false },
95
42
  defaultInputModes: ['text/plain'],
96
43
  defaultOutputModes: ['text/plain'],
97
44
  skills: options.skills,
98
45
  supportedInterfaces: [iface],
99
46
  ...(options.authToken !== undefined
100
47
  ? {
101
- securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', description: 'Shared bearer token' } },
102
- securityRequirements: [{ bearerAuth: [] }],
48
+ securitySchemes: {
49
+ bearer: {
50
+ httpAuthSecurityScheme: { scheme: 'bearer', description: 'Shared bearer token' },
51
+ },
52
+ },
53
+ securityRequirements: [{ schemes: { bearer: { list: ['bearer'] } } }],
103
54
  }
104
55
  : {}),
105
56
  }
@@ -54,8 +54,23 @@ export interface AgentRegistryLike {
54
54
 
55
55
  /** Structural slice of `ctx.agentPresets` (preset roster). */
56
56
  export interface AgentPresetsLike {
57
- resolve(id?: string): Promise<{ readonly id: string }>
57
+ resolve(id?: string): Promise<{ readonly id: string; readonly name?: string }>
58
58
  mount(agentCtx: unknown, id: string): Promise<unknown>
59
+ /** Standing scope key of a preset's mount (no agent required; undefined = default preset). */
60
+ standingKeyFor?(id?: string): Promise<unknown>
61
+ }
62
+
63
+ /** One skill-catalogue row as `ctx.skills.list()` returns it. */
64
+ export interface SkillRowLike {
65
+ readonly name: string
66
+ readonly description?: string
67
+ readonly whenToUse?: string
68
+ readonly invocation?: { readonly modelInvocable?: boolean }
69
+ }
70
+
71
+ /** Structural slice of `ctx.skills` (the skill registry). */
72
+ export interface SkillsLike {
73
+ list(options: { readonly scope?: unknown }): Promise<readonly SkillRowLike[]>
59
74
  }
60
75
 
61
76
  /** Runtime options shared by the executors. */
@@ -66,6 +81,8 @@ export interface AgentRuntimeOptions {
66
81
  readonly resolveAgentOptions?: () => { readonly provider?: string; readonly model?: string; readonly maxTokens?: number } | undefined
67
82
  /** Preset roster; when present every spawned session joins the default preset. */
68
83
  readonly agentPresets?: AgentPresetsLike
84
+ /** When set, every spawned session joins THIS preset id instead of the deployment default. */
85
+ readonly presetId?: () => string | undefined
69
86
  /** Once-per-context hook after the first prompt lands (cosmetic naming). */
70
87
  readonly onSessionOpened?: (info: { readonly sessionId: string; readonly contextId: string; readonly firstPrompt: string }) => void | Promise<void>
71
88
  }
@@ -100,7 +117,8 @@ export class ContextSessionPool {
100
117
  let presetId: string | undefined
101
118
  let setup: ((agentCtx: unknown) => Promise<void>) | undefined
102
119
  if (presets !== undefined) {
103
- presetId = (await presets.resolve()).id
120
+ const wanted = this.opts.presetId?.() ?? undefined
121
+ presetId = (wanted !== undefined ? await presets.resolve(wanted) : await presets.resolve()).id
104
122
  setup = async (agentCtx: unknown): Promise<void> => {
105
123
  await presets.mount(agentCtx, presetId!)
106
124
  }
@@ -17,7 +17,7 @@ export interface WebServerLike {
17
17
  }): () => void
18
18
  }
19
19
 
20
- const CARD_PATH = '/.well-known/agent-card.json'
20
+ const DEFAULT_CARD_PATH = '/.well-known/agent-card.json'
21
21
 
22
22
  /** Runtime-registered HTTP routes for one A2A server. */
23
23
  export class A2aRoutes {
@@ -27,6 +27,8 @@ export class A2aRoutes {
27
27
  constructor(
28
28
  private readonly webServer: WebServerLike,
29
29
  private readonly server: A2AServer,
30
+ /** AgentCard route path; default = the well-known A2A path. */
31
+ private readonly cardPath: string = DEFAULT_CARD_PATH,
30
32
  ) {}
31
33
 
32
34
  get active(): boolean {
@@ -39,7 +41,7 @@ export class A2aRoutes {
39
41
  this.disposers.push(
40
42
  this.webServer.register({
41
43
  kind: 'exact',
42
- path: CARD_PATH,
44
+ path: this.cardPath,
43
45
  handler: (_req, res) => {
44
46
  res.writeHead(200, { 'content-type': 'application/json' })
45
47
  res.end(JSON.stringify(this.server.card))
@@ -100,8 +102,8 @@ export class A2aRoutes {
100
102
  return
101
103
  }
102
104
 
103
- // GET AgentCard at the well-known path; anything else is the server's call.
104
- const out = await this.server.handle(toServerReq(req), path === CARD_PATH ? '' : '')
105
+ // GET AgentCard at this instance's card path; anything else is the server's call.
106
+ const out = await this.server.handle(toServerReq(req), path === this.cardPath ? '' : '')
105
107
  res.writeHead(out.status, { 'content-type': out.contentType, ...(out.headers ?? {}) })
106
108
  res.end(out.body)
107
109
  }