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