@aramisfa/openclaw-a2a-outbound 0.1.1 → 0.2.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 +261 -16
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +214 -8
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +37 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -20
- package/dist/index.js.map +1 -1
- package/dist/request-normalization.d.ts +23 -0
- package/dist/request-normalization.d.ts.map +1 -0
- package/dist/request-normalization.js +109 -0
- package/dist/request-normalization.js.map +1 -0
- package/dist/result-shape.d.ts +68 -25
- package/dist/result-shape.d.ts.map +1 -1
- package/dist/result-shape.js +192 -59
- package/dist/result-shape.js.map +1 -1
- package/dist/schemas.d.ts +81 -779
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +308 -275
- package/dist/schemas.js.map +1 -1
- package/dist/sdk-client-pool.d.ts +8 -4
- package/dist/sdk-client-pool.d.ts.map +1 -1
- package/dist/sdk-client-pool.js +21 -1
- package/dist/sdk-client-pool.js.map +1 -1
- package/dist/service.d.ts +33 -4
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +381 -60
- package/dist/service.js.map +1 -1
- package/dist/target-catalog.d.ts +66 -0
- package/dist/target-catalog.d.ts.map +1 -0
- package/dist/target-catalog.js +309 -0
- package/dist/target-catalog.js.map +1 -0
- package/dist/task-handle-registry.d.ts +29 -0
- package/dist/task-handle-registry.d.ts.map +1 -0
- package/dist/task-handle-registry.js +141 -0
- package/dist/task-handle-registry.js.map +1 -0
- package/openclaw.plugin.json +68 -1
- package/package.json +9 -5
- package/skills/remote-agent/SKILL.md +93 -0
package/README.md
CHANGED
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Native OpenClaw outbound A2A delegation plugin.
|
|
4
4
|
|
|
5
|
-
This package registers
|
|
6
|
-
|
|
7
|
-
- `a2a_delegate`
|
|
8
|
-
- `a2a_task_status`
|
|
9
|
-
- `a2a_task_cancel`
|
|
5
|
+
This package registers exactly one optional OpenClaw tool, `remote_agent`. The tool exposes five actions: `list_targets`, `send`, `watch`, `status`, and `cancel`.
|
|
10
6
|
|
|
11
7
|
## Installation
|
|
12
8
|
|
|
@@ -21,7 +17,7 @@ npm install @aramisfa/openclaw-a2a-outbound
|
|
|
21
17
|
|
|
22
18
|
## OpenClaw Plugin Config
|
|
23
19
|
|
|
24
|
-
`@aramisfa/openclaw-a2a-outbound` is disabled by default. Enable
|
|
20
|
+
`@aramisfa/openclaw-a2a-outbound` is disabled by default. Enable plugin id `openclaw-a2a-outbound` in your OpenClaw plugin config:
|
|
25
21
|
|
|
26
22
|
```json
|
|
27
23
|
{
|
|
@@ -32,33 +28,263 @@ npm install @aramisfa/openclaw-a2a-outbound
|
|
|
32
28
|
"preferredTransports": ["JSONRPC", "HTTP+JSON"],
|
|
33
29
|
"serviceParameters": {}
|
|
34
30
|
},
|
|
31
|
+
"targets": [
|
|
32
|
+
{
|
|
33
|
+
"alias": "support",
|
|
34
|
+
"baseUrl": "https://support.example",
|
|
35
|
+
"description": "Primary support lane",
|
|
36
|
+
"tags": ["support"],
|
|
37
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
38
|
+
"default": true
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"taskHandles": {
|
|
42
|
+
"ttlMs": 86400000,
|
|
43
|
+
"maxEntries": 1000
|
|
44
|
+
},
|
|
35
45
|
"policy": {
|
|
36
46
|
"acceptedOutputModes": [],
|
|
37
47
|
"normalizeBaseUrl": true,
|
|
38
|
-
"enforceSupportedTransports": true
|
|
48
|
+
"enforceSupportedTransports": true,
|
|
49
|
+
"allowTargetUrlOverride": false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Call `list_targets` first to discover configured aliases and refreshed target-card metadata. Prefer `target_alias` over `target_url`; use `target_url` only when policy allows direct URL routing.
|
|
55
|
+
|
|
56
|
+
## Unified Tool Contract
|
|
57
|
+
|
|
58
|
+
`remote_agent` accepts a flattened request object with top-level fields:
|
|
59
|
+
|
|
60
|
+
- `action`: required for every request.
|
|
61
|
+
- `target_alias`: preferred routing key for `send`, `watch`, `status`, and `cancel`.
|
|
62
|
+
- `target_url`: explicit remote base URL when policy allows it or when it matches a configured target.
|
|
63
|
+
- `input`: required for `send`; becomes the single user text part sent to the peer.
|
|
64
|
+
- `attachments`: optional file or data attachments for `send`.
|
|
65
|
+
- `task_handle`: opaque follow-up handle returned after delegated tasks are created.
|
|
66
|
+
- `task_id`: fallback follow-up key when no live `task_handle` is available.
|
|
67
|
+
- `follow_updates`: stream live updates during `send`.
|
|
68
|
+
- `history_length`: optional history window for `send` and `status`.
|
|
69
|
+
- `timeout_ms`: per-request timeout override.
|
|
70
|
+
- `service_parameters`: optional outbound service parameters.
|
|
71
|
+
- `metadata`: optional metadata payload for `send`.
|
|
72
|
+
|
|
73
|
+
Action-specific validation rejects unsupported fields for each action, so keep requests flat and action-focused.
|
|
74
|
+
|
|
75
|
+
## Actions
|
|
76
|
+
|
|
77
|
+
- `list_targets`: discover configured targets, aliases, examples, and hydrated card metadata.
|
|
78
|
+
- `send`: send user input to a remote agent selected by `target_alias`, `target_url`, or a configured default target.
|
|
79
|
+
- `watch`: resubscribe to a running delegated task and stream updates.
|
|
80
|
+
- `status`: fetch the latest task snapshot.
|
|
81
|
+
- `cancel`: request cancellation for a delegated task.
|
|
82
|
+
|
|
83
|
+
For follow-up actions, prefer `task_handle` first. If the handle is expired or unavailable, fall back to `target_alias` + `task_id`.
|
|
84
|
+
|
|
85
|
+
## Examples
|
|
86
|
+
|
|
87
|
+
### Discover Available Targets
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "action": "list_targets" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"ok": true,
|
|
96
|
+
"operation": "remote_agent",
|
|
97
|
+
"action": "list_targets",
|
|
98
|
+
"summary": {
|
|
99
|
+
"targets": [
|
|
100
|
+
{
|
|
101
|
+
"target_alias": "support",
|
|
102
|
+
"target_url": "https://support.example/",
|
|
103
|
+
"default": true,
|
|
104
|
+
"tags": ["support"],
|
|
105
|
+
"examples": ["Summarize this incident and propose next steps."],
|
|
106
|
+
"target_name": "Support Agent",
|
|
107
|
+
"description": "Primary support lane",
|
|
108
|
+
"streaming_supported": true,
|
|
109
|
+
"skills": [
|
|
110
|
+
{
|
|
111
|
+
"id": "triage",
|
|
112
|
+
"name": "Incident Triage",
|
|
113
|
+
"description": "Summarize incidents and propose next actions.",
|
|
114
|
+
"tags": ["support"],
|
|
115
|
+
"examples": ["Summarize this incident and propose next steps."]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
"raw": [
|
|
122
|
+
{
|
|
123
|
+
"default": true,
|
|
124
|
+
"tags": ["support"],
|
|
125
|
+
"examples": ["Summarize this incident and propose next steps."]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Send To An Explicit `target_alias`
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"action": "send",
|
|
136
|
+
"target_alias": "support",
|
|
137
|
+
"input": "Summarize this bug report for triage.",
|
|
138
|
+
"metadata": {
|
|
139
|
+
"ticket_id": "INC-42"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"ok": true,
|
|
147
|
+
"operation": "remote_agent",
|
|
148
|
+
"action": "send",
|
|
149
|
+
"summary": {
|
|
150
|
+
"target_alias": "support",
|
|
151
|
+
"target_url": "https://support.example/",
|
|
152
|
+
"message_text": "Triage summary: reproduce, collect logs, and notify the on-call engineer."
|
|
153
|
+
},
|
|
154
|
+
"raw": {
|
|
155
|
+
"kind": "message"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Send Using The Configured Default Target
|
|
161
|
+
|
|
162
|
+
If one target is marked `"default": true`, `send` can omit `target_alias`:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"action": "send",
|
|
167
|
+
"input": "Draft a reply to the customer update.",
|
|
168
|
+
"follow_updates": true
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"ok": true,
|
|
175
|
+
"operation": "remote_agent",
|
|
176
|
+
"action": "send",
|
|
177
|
+
"summary": {
|
|
178
|
+
"target_alias": "support",
|
|
179
|
+
"target_url": "https://support.example/",
|
|
180
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
181
|
+
"task_id": "task-456",
|
|
182
|
+
"status": "completed",
|
|
183
|
+
"can_watch": true
|
|
184
|
+
},
|
|
185
|
+
"raw": {
|
|
186
|
+
"events": [
|
|
187
|
+
{
|
|
188
|
+
"kind": "task",
|
|
189
|
+
"id": "task-456",
|
|
190
|
+
"status": {
|
|
191
|
+
"state": "submitted"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"kind": "status-update",
|
|
196
|
+
"taskId": "task-456",
|
|
197
|
+
"status": {
|
|
198
|
+
"state": "completed"
|
|
199
|
+
},
|
|
200
|
+
"final": true
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"finalEvent": {
|
|
204
|
+
"kind": "status-update",
|
|
205
|
+
"taskId": "task-456",
|
|
206
|
+
"status": {
|
|
207
|
+
"state": "completed"
|
|
208
|
+
},
|
|
209
|
+
"final": true
|
|
210
|
+
}
|
|
39
211
|
}
|
|
40
212
|
}
|
|
41
213
|
```
|
|
42
214
|
|
|
43
|
-
|
|
215
|
+
### Check Task Status With `task_handle`
|
|
44
216
|
|
|
45
|
-
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"action": "status",
|
|
220
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
221
|
+
"history_length": 2
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"ok": true,
|
|
228
|
+
"operation": "remote_agent",
|
|
229
|
+
"action": "status",
|
|
230
|
+
"summary": {
|
|
231
|
+
"target_alias": "support",
|
|
232
|
+
"target_url": "https://support.example/",
|
|
233
|
+
"task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
234
|
+
"task_id": "task-456",
|
|
235
|
+
"status": "completed",
|
|
236
|
+
"can_watch": true
|
|
237
|
+
},
|
|
238
|
+
"raw": {
|
|
239
|
+
"kind": "task",
|
|
240
|
+
"id": "task-456",
|
|
241
|
+
"status": {
|
|
242
|
+
"state": "completed"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
When a handle is expired or unavailable, retry with `target_alias` + `task_id`:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"action": "status",
|
|
253
|
+
"target_alias": "support",
|
|
254
|
+
"task_id": "task-456"
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`watch` and `cancel` use the same follow-up targeting rules:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{ "action": "watch", "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0" }
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{ "action": "cancel", "task_handle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0" }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Validation And Actionable Errors
|
|
269
|
+
|
|
270
|
+
Tool input validation uses Ajv in strict mode. Validation failures use `operation: "remote_agent"` and include native-style Ajv error objects:
|
|
46
271
|
|
|
47
272
|
```json
|
|
48
273
|
{
|
|
49
274
|
"ok": false,
|
|
50
|
-
"operation": "
|
|
275
|
+
"operation": "remote_agent",
|
|
276
|
+
"action": "send",
|
|
51
277
|
"error": {
|
|
52
278
|
"code": "VALIDATION_ERROR",
|
|
53
|
-
"message": "
|
|
279
|
+
"message": "remote_agent input validation failed",
|
|
54
280
|
"details": {
|
|
55
281
|
"source": "ajv",
|
|
56
|
-
"tool": "
|
|
282
|
+
"tool": "remote_agent",
|
|
57
283
|
"errors": [
|
|
58
284
|
{
|
|
59
|
-
"keyword": "
|
|
60
|
-
"instancePath": "
|
|
61
|
-
"
|
|
285
|
+
"keyword": "anyOf",
|
|
286
|
+
"instancePath": "",
|
|
287
|
+
"message": "send requires target_alias, target_url, or a configured default target"
|
|
62
288
|
}
|
|
63
289
|
]
|
|
64
290
|
}
|
|
@@ -66,7 +292,26 @@ Tool input validation is powered by [Ajv](https://ajv.js.org/) in strict mode. W
|
|
|
66
292
|
}
|
|
67
293
|
```
|
|
68
294
|
|
|
69
|
-
|
|
295
|
+
Expired handles return an actionable recovery envelope:
|
|
296
|
+
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"ok": false,
|
|
300
|
+
"operation": "remote_agent",
|
|
301
|
+
"action": "status",
|
|
302
|
+
"error": {
|
|
303
|
+
"code": "EXPIRED_TASK_HANDLE",
|
|
304
|
+
"message": "task handle \"rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0\" has expired",
|
|
305
|
+
"details": {
|
|
306
|
+
"taskHandle": "rah_0a3ff8c2-4a6d-48cb-a57d-4ae6f3c589d0",
|
|
307
|
+
"retryHint": "Retry with explicit target plus taskId, or resend the original request after a restart to obtain a new handle.",
|
|
308
|
+
"restartInvalidatesHandles": true,
|
|
309
|
+
"suggested_actions": ["status", "send"],
|
|
310
|
+
"hint": "Retry with target_alias + task_id, or send a new request."
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
70
315
|
|
|
71
316
|
## Development
|
|
72
317
|
|
package/dist/config.d.ts
CHANGED
|
@@ -6,14 +6,31 @@ export interface A2AOutboundDefaultsConfig {
|
|
|
6
6
|
preferredTransports: A2ATransport[];
|
|
7
7
|
serviceParameters: Record<string, string>;
|
|
8
8
|
}
|
|
9
|
+
export interface A2AOutboundTargetConfig {
|
|
10
|
+
alias: string;
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
tags: string[];
|
|
14
|
+
cardPath: string;
|
|
15
|
+
preferredTransports: A2ATransport[];
|
|
16
|
+
examples: string[];
|
|
17
|
+
default: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface A2AOutboundTaskHandlesConfig {
|
|
20
|
+
ttlMs: number;
|
|
21
|
+
maxEntries: number;
|
|
22
|
+
}
|
|
9
23
|
export interface A2AOutboundPolicyConfig {
|
|
10
24
|
acceptedOutputModes: string[];
|
|
11
25
|
normalizeBaseUrl: boolean;
|
|
12
26
|
enforceSupportedTransports: boolean;
|
|
27
|
+
allowTargetUrlOverride: boolean;
|
|
13
28
|
}
|
|
14
29
|
export interface A2AOutboundPluginConfig {
|
|
15
30
|
enabled: boolean;
|
|
16
31
|
defaults: A2AOutboundDefaultsConfig;
|
|
32
|
+
targets: A2AOutboundTargetConfig[];
|
|
33
|
+
taskHandles: A2AOutboundTaskHandlesConfig;
|
|
17
34
|
policy: A2AOutboundPolicyConfig;
|
|
18
35
|
}
|
|
19
36
|
export declare const A2A_OUTBOUND_DEFAULT_CONFIG: A2AOutboundPluginConfig;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,YAAY,EAAE,CAAC;IACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0BAA0B,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,YAAY,EAAE,CAAC;IACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,YAAY,EAAE,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0BAA0B,EAAE,OAAO,CAAC;IACpC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,yBAAyB,CAAC;IACpC,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,eAAO,MAAM,2BAA2B,EAAE,uBAmBzC,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,WAAW,CACvD,0BAA0B,CAAC,YAAY,CAAC,CA2HzC,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,WAAW,CACpD,0BAA0B,CAAC,SAAS,CAAC,CA6DtC,CAAC;AAqPF,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,OAAO,GACb,uBAAuB,CA+DzB;AAED,eAAO,MAAM,mCAAmC,EAAE,0BAO/C,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -7,10 +7,16 @@ export const A2A_OUTBOUND_DEFAULT_CONFIG = {
|
|
|
7
7
|
preferredTransports: [...SUPPORTED_TRANSPORTS],
|
|
8
8
|
serviceParameters: {},
|
|
9
9
|
},
|
|
10
|
+
targets: [],
|
|
11
|
+
taskHandles: {
|
|
12
|
+
ttlMs: 86400000,
|
|
13
|
+
maxEntries: 1000,
|
|
14
|
+
},
|
|
10
15
|
policy: {
|
|
11
16
|
acceptedOutputModes: [],
|
|
12
17
|
normalizeBaseUrl: true,
|
|
13
18
|
enforceSupportedTransports: true,
|
|
19
|
+
allowTargetUrlOverride: false,
|
|
14
20
|
},
|
|
15
21
|
};
|
|
16
22
|
export const A2A_OUTBOUND_CONFIG_JSON_SCHEMA = {
|
|
@@ -49,6 +55,68 @@ export const A2A_OUTBOUND_CONFIG_JSON_SCHEMA = {
|
|
|
49
55
|
},
|
|
50
56
|
},
|
|
51
57
|
},
|
|
58
|
+
targets: {
|
|
59
|
+
type: "array",
|
|
60
|
+
default: [],
|
|
61
|
+
items: {
|
|
62
|
+
type: "object",
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
required: ["alias", "baseUrl"],
|
|
65
|
+
properties: {
|
|
66
|
+
alias: {
|
|
67
|
+
type: "string",
|
|
68
|
+
},
|
|
69
|
+
baseUrl: {
|
|
70
|
+
type: "string",
|
|
71
|
+
},
|
|
72
|
+
description: {
|
|
73
|
+
type: "string",
|
|
74
|
+
},
|
|
75
|
+
tags: {
|
|
76
|
+
type: "array",
|
|
77
|
+
items: { type: "string" },
|
|
78
|
+
default: [],
|
|
79
|
+
},
|
|
80
|
+
cardPath: {
|
|
81
|
+
type: "string",
|
|
82
|
+
default: A2A_OUTBOUND_DEFAULT_CONFIG.defaults.cardPath,
|
|
83
|
+
},
|
|
84
|
+
preferredTransports: {
|
|
85
|
+
type: "array",
|
|
86
|
+
items: {
|
|
87
|
+
type: "string",
|
|
88
|
+
enum: [...ALL_TRANSPORTS],
|
|
89
|
+
},
|
|
90
|
+
default: [...A2A_OUTBOUND_DEFAULT_CONFIG.defaults.preferredTransports],
|
|
91
|
+
},
|
|
92
|
+
examples: {
|
|
93
|
+
type: "array",
|
|
94
|
+
items: { type: "string" },
|
|
95
|
+
default: [],
|
|
96
|
+
},
|
|
97
|
+
default: {
|
|
98
|
+
type: "boolean",
|
|
99
|
+
default: false,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
taskHandles: {
|
|
105
|
+
type: "object",
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
properties: {
|
|
108
|
+
ttlMs: {
|
|
109
|
+
type: "integer",
|
|
110
|
+
minimum: 1,
|
|
111
|
+
default: A2A_OUTBOUND_DEFAULT_CONFIG.taskHandles.ttlMs,
|
|
112
|
+
},
|
|
113
|
+
maxEntries: {
|
|
114
|
+
type: "integer",
|
|
115
|
+
minimum: 1,
|
|
116
|
+
default: A2A_OUTBOUND_DEFAULT_CONFIG.taskHandles.maxEntries,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
52
120
|
policy: {
|
|
53
121
|
type: "object",
|
|
54
122
|
additionalProperties: false,
|
|
@@ -66,6 +134,10 @@ export const A2A_OUTBOUND_CONFIG_JSON_SCHEMA = {
|
|
|
66
134
|
type: "boolean",
|
|
67
135
|
default: A2A_OUTBOUND_DEFAULT_CONFIG.policy.enforceSupportedTransports,
|
|
68
136
|
},
|
|
137
|
+
allowTargetUrlOverride: {
|
|
138
|
+
type: "boolean",
|
|
139
|
+
default: A2A_OUTBOUND_DEFAULT_CONFIG.policy.allowTargetUrlOverride,
|
|
140
|
+
},
|
|
69
141
|
},
|
|
70
142
|
},
|
|
71
143
|
},
|
|
@@ -96,6 +168,20 @@ export const A2A_OUTBOUND_CONFIG_UI_HINTS = {
|
|
|
96
168
|
help: "Default headers or provider-specific request parameters.",
|
|
97
169
|
advanced: true,
|
|
98
170
|
},
|
|
171
|
+
targets: {
|
|
172
|
+
label: "Named Targets",
|
|
173
|
+
help: "Registry of reusable outbound A2A targets for future routing phases.",
|
|
174
|
+
},
|
|
175
|
+
"taskHandles.ttlMs": {
|
|
176
|
+
label: "Task Handle TTL (ms)",
|
|
177
|
+
help: "Retention window for cached delegated task handles.",
|
|
178
|
+
advanced: true,
|
|
179
|
+
},
|
|
180
|
+
"taskHandles.maxEntries": {
|
|
181
|
+
label: "Task Handle Cache Size",
|
|
182
|
+
help: "Maximum number of delegated task handles retained locally.",
|
|
183
|
+
advanced: true,
|
|
184
|
+
},
|
|
99
185
|
"policy.acceptedOutputModes": {
|
|
100
186
|
label: "Accepted Output Modes",
|
|
101
187
|
help: "Allowed response media types for outbound client requests.",
|
|
@@ -111,7 +197,24 @@ export const A2A_OUTBOUND_CONFIG_UI_HINTS = {
|
|
|
111
197
|
help: "Rejects targets requesting transports unsupported by this build.",
|
|
112
198
|
advanced: true,
|
|
113
199
|
},
|
|
200
|
+
"policy.allowTargetUrlOverride": {
|
|
201
|
+
label: "Allow Target URL Override",
|
|
202
|
+
help: "Permits explicit request URLs to bypass the named target registry.",
|
|
203
|
+
advanced: true,
|
|
204
|
+
},
|
|
114
205
|
};
|
|
206
|
+
function cloneTargetConfig(target) {
|
|
207
|
+
return {
|
|
208
|
+
alias: target.alias,
|
|
209
|
+
baseUrl: target.baseUrl,
|
|
210
|
+
...(target.description !== undefined ? { description: target.description } : {}),
|
|
211
|
+
tags: [...target.tags],
|
|
212
|
+
cardPath: target.cardPath,
|
|
213
|
+
preferredTransports: [...target.preferredTransports],
|
|
214
|
+
examples: [...target.examples],
|
|
215
|
+
default: target.default,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
115
218
|
function cloneConfig(config) {
|
|
116
219
|
return {
|
|
117
220
|
enabled: config.enabled,
|
|
@@ -121,10 +224,16 @@ function cloneConfig(config) {
|
|
|
121
224
|
preferredTransports: [...config.defaults.preferredTransports],
|
|
122
225
|
serviceParameters: { ...config.defaults.serviceParameters },
|
|
123
226
|
},
|
|
227
|
+
targets: config.targets.map(cloneTargetConfig),
|
|
228
|
+
taskHandles: {
|
|
229
|
+
ttlMs: config.taskHandles.ttlMs,
|
|
230
|
+
maxEntries: config.taskHandles.maxEntries,
|
|
231
|
+
},
|
|
124
232
|
policy: {
|
|
125
233
|
acceptedOutputModes: [...config.policy.acceptedOutputModes],
|
|
126
234
|
normalizeBaseUrl: config.policy.normalizeBaseUrl,
|
|
127
235
|
enforceSupportedTransports: config.policy.enforceSupportedTransports,
|
|
236
|
+
allowTargetUrlOverride: config.policy.allowTargetUrlOverride,
|
|
128
237
|
},
|
|
129
238
|
};
|
|
130
239
|
}
|
|
@@ -146,10 +255,33 @@ function normalizeStringArray(value, fallback = []) {
|
|
|
146
255
|
}
|
|
147
256
|
return normalized;
|
|
148
257
|
}
|
|
258
|
+
function normalizeTrimmedStringArray(value, fallback = []) {
|
|
259
|
+
if (!Array.isArray(value)) {
|
|
260
|
+
return [...fallback];
|
|
261
|
+
}
|
|
262
|
+
const normalized = [];
|
|
263
|
+
for (const entry of value) {
|
|
264
|
+
if (typeof entry !== "string") {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const trimmed = entry.trim();
|
|
268
|
+
if (trimmed === "") {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (!normalized.includes(trimmed)) {
|
|
272
|
+
normalized.push(trimmed);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return normalized;
|
|
276
|
+
}
|
|
149
277
|
function normalizeTransports(value, fallback) {
|
|
150
278
|
const normalized = normalizeStringArray(value).filter((entry) => ALL_TRANSPORTS.includes(entry));
|
|
151
279
|
return normalized.length > 0 ? normalized : [...fallback];
|
|
152
280
|
}
|
|
281
|
+
function normalizeTrimmedTransports(value, fallback) {
|
|
282
|
+
const normalized = normalizeTrimmedStringArray(value).filter((entry) => ALL_TRANSPORTS.includes(entry));
|
|
283
|
+
return normalized.length > 0 ? normalized : [...fallback];
|
|
284
|
+
}
|
|
153
285
|
function normalizeStringMap(value, fallback = {}) {
|
|
154
286
|
if (!isPlainObject(value)) {
|
|
155
287
|
return { ...fallback };
|
|
@@ -170,27 +302,101 @@ function readPositiveInteger(value, fallback) {
|
|
|
170
302
|
? value
|
|
171
303
|
: fallback;
|
|
172
304
|
}
|
|
305
|
+
function readString(value, fallback) {
|
|
306
|
+
return typeof value === "string" && value.trim() !== "" ? value : fallback;
|
|
307
|
+
}
|
|
308
|
+
function readTrimmedString(value, fallback) {
|
|
309
|
+
if (typeof value !== "string") {
|
|
310
|
+
return fallback;
|
|
311
|
+
}
|
|
312
|
+
const trimmed = value.trim();
|
|
313
|
+
return trimmed !== "" ? trimmed : fallback;
|
|
314
|
+
}
|
|
315
|
+
function readOptionalTrimmedString(value) {
|
|
316
|
+
if (typeof value !== "string") {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
const trimmed = value.trim();
|
|
320
|
+
return trimmed !== "" ? trimmed : undefined;
|
|
321
|
+
}
|
|
322
|
+
function invalidConfig(message) {
|
|
323
|
+
return new TypeError(`Invalid openclaw-a2a-outbound config: ${message}`);
|
|
324
|
+
}
|
|
325
|
+
function normalizeTargets(value, defaults) {
|
|
326
|
+
if (value === undefined) {
|
|
327
|
+
return A2A_OUTBOUND_DEFAULT_CONFIG.targets.map(cloneTargetConfig);
|
|
328
|
+
}
|
|
329
|
+
if (!Array.isArray(value)) {
|
|
330
|
+
throw invalidConfig("targets must be an array");
|
|
331
|
+
}
|
|
332
|
+
const aliases = new Set();
|
|
333
|
+
let defaultTargetAlias;
|
|
334
|
+
return value.map((entry, index) => {
|
|
335
|
+
if (!isPlainObject(entry)) {
|
|
336
|
+
throw invalidConfig(`targets[${index}] must be an object`);
|
|
337
|
+
}
|
|
338
|
+
const alias = typeof entry.alias === "string" ? entry.alias.trim() : "";
|
|
339
|
+
if (alias === "") {
|
|
340
|
+
throw invalidConfig(`targets[${index}].alias must be a non-empty string`);
|
|
341
|
+
}
|
|
342
|
+
const baseUrl = typeof entry.baseUrl === "string" ? entry.baseUrl.trim() : "";
|
|
343
|
+
if (baseUrl === "") {
|
|
344
|
+
throw invalidConfig(`targets[${index}].baseUrl must be a non-empty string`);
|
|
345
|
+
}
|
|
346
|
+
if (aliases.has(alias)) {
|
|
347
|
+
throw invalidConfig(`targets contains duplicate alias "${alias}"`);
|
|
348
|
+
}
|
|
349
|
+
aliases.add(alias);
|
|
350
|
+
const normalizedTarget = {
|
|
351
|
+
alias,
|
|
352
|
+
baseUrl,
|
|
353
|
+
tags: normalizeTrimmedStringArray(entry.tags),
|
|
354
|
+
cardPath: readTrimmedString(entry.cardPath, defaults.cardPath.trim()),
|
|
355
|
+
preferredTransports: normalizeTrimmedTransports(entry.preferredTransports, defaults.preferredTransports),
|
|
356
|
+
examples: normalizeTrimmedStringArray(entry.examples),
|
|
357
|
+
default: readBoolean(entry.default, false),
|
|
358
|
+
};
|
|
359
|
+
const description = readOptionalTrimmedString(entry.description);
|
|
360
|
+
if (description !== undefined) {
|
|
361
|
+
normalizedTarget.description = description;
|
|
362
|
+
}
|
|
363
|
+
if (normalizedTarget.default) {
|
|
364
|
+
if (defaultTargetAlias !== undefined) {
|
|
365
|
+
throw invalidConfig(`targets contains multiple default entries ("${defaultTargetAlias}" and "${alias}")`);
|
|
366
|
+
}
|
|
367
|
+
defaultTargetAlias = alias;
|
|
368
|
+
}
|
|
369
|
+
return normalizedTarget;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
173
372
|
export function parseA2AOutboundPluginConfig(input) {
|
|
174
373
|
if (!isPlainObject(input)) {
|
|
175
374
|
return cloneConfig(A2A_OUTBOUND_DEFAULT_CONFIG);
|
|
176
375
|
}
|
|
177
376
|
const rawDefaults = isPlainObject(input.defaults) ? input.defaults : {};
|
|
377
|
+
const normalizedDefaults = {
|
|
378
|
+
timeoutMs: readPositiveInteger(rawDefaults.timeoutMs, A2A_OUTBOUND_DEFAULT_CONFIG.defaults.timeoutMs),
|
|
379
|
+
cardPath: readString(rawDefaults.cardPath, A2A_OUTBOUND_DEFAULT_CONFIG.defaults.cardPath),
|
|
380
|
+
preferredTransports: normalizeTransports(rawDefaults.preferredTransports, A2A_OUTBOUND_DEFAULT_CONFIG.defaults.preferredTransports),
|
|
381
|
+
serviceParameters: normalizeStringMap(rawDefaults.serviceParameters, A2A_OUTBOUND_DEFAULT_CONFIG.defaults.serviceParameters),
|
|
382
|
+
};
|
|
383
|
+
const rawTaskHandles = isPlainObject(input.taskHandles)
|
|
384
|
+
? input.taskHandles
|
|
385
|
+
: {};
|
|
178
386
|
const rawPolicy = isPlainObject(input.policy) ? input.policy : {};
|
|
179
387
|
return {
|
|
180
388
|
enabled: readBoolean(input.enabled, A2A_OUTBOUND_DEFAULT_CONFIG.enabled),
|
|
181
|
-
defaults:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
: A2A_OUTBOUND_DEFAULT_CONFIG.defaults.cardPath,
|
|
187
|
-
preferredTransports: normalizeTransports(rawDefaults.preferredTransports, A2A_OUTBOUND_DEFAULT_CONFIG.defaults.preferredTransports),
|
|
188
|
-
serviceParameters: normalizeStringMap(rawDefaults.serviceParameters, A2A_OUTBOUND_DEFAULT_CONFIG.defaults.serviceParameters),
|
|
389
|
+
defaults: normalizedDefaults,
|
|
390
|
+
targets: normalizeTargets(input.targets, normalizedDefaults),
|
|
391
|
+
taskHandles: {
|
|
392
|
+
ttlMs: readPositiveInteger(rawTaskHandles.ttlMs, A2A_OUTBOUND_DEFAULT_CONFIG.taskHandles.ttlMs),
|
|
393
|
+
maxEntries: readPositiveInteger(rawTaskHandles.maxEntries, A2A_OUTBOUND_DEFAULT_CONFIG.taskHandles.maxEntries),
|
|
189
394
|
},
|
|
190
395
|
policy: {
|
|
191
396
|
acceptedOutputModes: normalizeStringArray(rawPolicy.acceptedOutputModes, A2A_OUTBOUND_DEFAULT_CONFIG.policy.acceptedOutputModes),
|
|
192
397
|
normalizeBaseUrl: readBoolean(rawPolicy.normalizeBaseUrl, A2A_OUTBOUND_DEFAULT_CONFIG.policy.normalizeBaseUrl),
|
|
193
398
|
enforceSupportedTransports: readBoolean(rawPolicy.enforceSupportedTransports, A2A_OUTBOUND_DEFAULT_CONFIG.policy.enforceSupportedTransports),
|
|
399
|
+
allowTargetUrlOverride: readBoolean(rawPolicy.allowTargetUrlOverride, A2A_OUTBOUND_DEFAULT_CONFIG.policy.allowTargetUrlOverride),
|
|
194
400
|
},
|
|
195
401
|
};
|
|
196
402
|
}
|