agent-relay 3.2.3 → 3.2.4
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/dist/index.cjs +265 -108
- package/package.json +11 -10
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/ADAPTER_REVIEW.md +109 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.js +220 -0
- package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
- package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.js +209 -0
- package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.js +7 -0
- package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
- package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
- package/packages/sdk/dist/communicate/core.d.ts +58 -0
- package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/core.js +128 -0
- package/packages/sdk/dist/communicate/core.js.map +1 -0
- package/packages/sdk/dist/communicate/index.d.ts +4 -0
- package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/index.js +4 -0
- package/packages/sdk/dist/communicate/index.js.map +1 -0
- package/packages/sdk/dist/communicate/transport.d.ts +36 -0
- package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/transport.js +371 -0
- package/packages/sdk/dist/communicate/transport.js.map +1 -0
- package/packages/sdk/dist/communicate/types.d.ts +58 -0
- package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/types.js +66 -0
- package/packages/sdk/dist/communicate/types.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +35 -5
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +81 -7
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +14 -1
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +95 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +11 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
- package/packages/sdk/examples/communicate/pi_example.ts +8 -0
- package/packages/sdk/package.json +48 -2
- package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
- package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
- package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
- package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
- package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
- package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
- package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
- package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
- package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
- package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
- package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
- package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
- package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
- package/packages/sdk/src/communicate/a2a-server.ts +277 -0
- package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
- package/packages/sdk/src/communicate/a2a-types.ts +338 -0
- package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
- package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
- package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
- package/packages/sdk/src/communicate/adapters/index.ts +6 -0
- package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
- package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
- package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
- package/packages/sdk/src/communicate/core.ts +157 -0
- package/packages/sdk/src/communicate/index.ts +3 -0
- package/packages/sdk/src/communicate/transport.ts +489 -0
- package/packages/sdk/src/communicate/types.ts +106 -0
- package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
- package/packages/sdk/src/workflows/builder.ts +97 -9
- package/packages/sdk/src/workflows/cli.ts +16 -1
- package/packages/sdk/src/workflows/runner.ts +110 -1
- package/packages/sdk/src/workflows/types.ts +14 -0
- package/packages/sdk/tsconfig.build.json +1 -7
- package/packages/sdk/tsconfig.json +1 -7
- package/packages/sdk-py/README.md +67 -25
- package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
- package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
- package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
- package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
- package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
- package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
- package/packages/sdk-py/pyproject.toml +12 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
- package/packages/sdk-py/src/agent_relay/builder.py +65 -26
- package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
- package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
- package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
- package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
- package/packages/sdk-py/src/agent_relay/types.py +2 -1
- package/packages/sdk-py/tests/communicate/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
- package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
- package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
- package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
- package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
- package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
- package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
- package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
- package/packages/sdk-py/tests/communicate/conftest.py +555 -0
- package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
- package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
- package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
- package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
- package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
- package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
- package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
- package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
- package/packages/sdk-py/tests/communicate/test_core.py +331 -0
- package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
- package/packages/sdk-py/tests/communicate/test_types.py +285 -0
- package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.js +0 -305
- package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.js +0 -205
- package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.js +0 -20
- package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
- package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.js +0 -357
- package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
- package/packages/sdk-py/agent_relay/__init__.py +0 -21
- package/packages/sdk-py/agent_relay/models.py +0 -398
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A-compliant HTTP server that exposes a Relay agent as an A2A endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Routes:
|
|
5
|
+
* GET /.well-known/agent.json -> Agent Card
|
|
6
|
+
* POST / -> JSON-RPC 2.0 dispatcher
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import * as http from 'node:http';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type A2AAgentCard,
|
|
14
|
+
type A2AMessage,
|
|
15
|
+
type A2APart,
|
|
16
|
+
type A2ASkill,
|
|
17
|
+
type A2ATask,
|
|
18
|
+
type A2ATaskStatus,
|
|
19
|
+
a2aAgentCardToDict,
|
|
20
|
+
a2aMessageToDict,
|
|
21
|
+
createA2AAgentCard,
|
|
22
|
+
createA2ATaskStatus,
|
|
23
|
+
} from './a2a-types.js';
|
|
24
|
+
|
|
25
|
+
export type A2AMessageHandler = (
|
|
26
|
+
msg: A2AMessage,
|
|
27
|
+
) => A2AMessage | null | Promise<A2AMessage | null>;
|
|
28
|
+
|
|
29
|
+
export class A2AServer {
|
|
30
|
+
readonly agentName: string;
|
|
31
|
+
readonly port: number;
|
|
32
|
+
readonly host: string;
|
|
33
|
+
readonly skills: A2ASkill[];
|
|
34
|
+
readonly tasks: Map<string, A2ATask> = new Map();
|
|
35
|
+
|
|
36
|
+
private _onMessage?: A2AMessageHandler;
|
|
37
|
+
private _server?: http.Server;
|
|
38
|
+
private _actualPort?: number;
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
agentName: string,
|
|
42
|
+
port: number = 5000,
|
|
43
|
+
host: string = '0.0.0.0',
|
|
44
|
+
skills: A2ASkill[] = [],
|
|
45
|
+
) {
|
|
46
|
+
this.agentName = agentName;
|
|
47
|
+
this.port = port;
|
|
48
|
+
this.host = host;
|
|
49
|
+
this.skills = skills;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get url(): string {
|
|
53
|
+
const p = this._actualPort ?? this.port;
|
|
54
|
+
return `http://${this.host}:${p}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMessage(callback: A2AMessageHandler): void {
|
|
58
|
+
this._onMessage = callback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getAgentCard(): A2AAgentCard {
|
|
62
|
+
return createA2AAgentCard(
|
|
63
|
+
this.agentName,
|
|
64
|
+
`Agent Relay agent: ${this.agentName}`,
|
|
65
|
+
this.url,
|
|
66
|
+
[...this.skills],
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async handleMessageSend(params: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
71
|
+
const messageData = (params.message ?? {}) as Record<string, unknown>;
|
|
72
|
+
const rawParts = (messageData.parts ?? []) as Record<string, unknown>[];
|
|
73
|
+
const parts: A2APart[] = rawParts.map((p) => ({ text: p.text as string | undefined }));
|
|
74
|
+
|
|
75
|
+
const incoming: A2AMessage = {
|
|
76
|
+
role: (messageData.role as 'user' | 'agent') ?? 'user',
|
|
77
|
+
parts,
|
|
78
|
+
messageId: (messageData.messageId as string) ?? randomUUID(),
|
|
79
|
+
contextId: messageData.contextId as string | undefined,
|
|
80
|
+
taskId: messageData.taskId as string | undefined,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Create or find task
|
|
84
|
+
const taskId = incoming.taskId ?? randomUUID();
|
|
85
|
+
const contextId = incoming.contextId ?? randomUUID();
|
|
86
|
+
|
|
87
|
+
let task: A2ATask;
|
|
88
|
+
if (this.tasks.has(taskId)) {
|
|
89
|
+
task = this.tasks.get(taskId)!;
|
|
90
|
+
task.messages.push(incoming);
|
|
91
|
+
task.status = createA2ATaskStatus('working');
|
|
92
|
+
} else {
|
|
93
|
+
task = {
|
|
94
|
+
id: taskId,
|
|
95
|
+
contextId,
|
|
96
|
+
status: createA2ATaskStatus('working'),
|
|
97
|
+
messages: [incoming],
|
|
98
|
+
artifacts: [],
|
|
99
|
+
};
|
|
100
|
+
this.tasks.set(taskId, task);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Invoke callback
|
|
104
|
+
let responseMsg: A2AMessage | null = null;
|
|
105
|
+
if (this._onMessage) {
|
|
106
|
+
const result = this._onMessage(incoming);
|
|
107
|
+
responseMsg = result instanceof Promise ? await result : result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (responseMsg) {
|
|
111
|
+
task.messages.push(responseMsg);
|
|
112
|
+
task.status = createA2ATaskStatus('completed', responseMsg);
|
|
113
|
+
} else {
|
|
114
|
+
task.status = createA2ATaskStatus('completed');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return taskToDict(task);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async handleTasksGet(taskId: string): Promise<Record<string, unknown>> {
|
|
121
|
+
const task = this.tasks.get(taskId);
|
|
122
|
+
if (!task) {
|
|
123
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
124
|
+
}
|
|
125
|
+
return taskToDict(task);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async handleTasksCancel(taskId: string): Promise<Record<string, unknown>> {
|
|
129
|
+
const task = this.tasks.get(taskId);
|
|
130
|
+
if (!task) {
|
|
131
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
132
|
+
}
|
|
133
|
+
task.status = createA2ATaskStatus('canceled');
|
|
134
|
+
return taskToDict(task);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async start(): Promise<void> {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const server = http.createServer((req, res) => {
|
|
140
|
+
void this._handleRequest(req, res);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
server.on('error', reject);
|
|
144
|
+
|
|
145
|
+
server.listen(this.port, this.host, () => {
|
|
146
|
+
this._server = server;
|
|
147
|
+
const addr = server.address();
|
|
148
|
+
if (typeof addr === 'object' && addr) {
|
|
149
|
+
this._actualPort = addr.port;
|
|
150
|
+
}
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async stop(): Promise<void> {
|
|
157
|
+
if (this._server) {
|
|
158
|
+
await new Promise<void>((resolve, reject) => {
|
|
159
|
+
this._server!.close((err) => (err ? reject(err) : resolve()));
|
|
160
|
+
});
|
|
161
|
+
this._server = undefined;
|
|
162
|
+
this._actualPort = undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- HTTP handlers ---
|
|
167
|
+
|
|
168
|
+
private async _handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
169
|
+
if (req.method === 'GET' && req.url === '/.well-known/agent.json') {
|
|
170
|
+
const card = this.getAgentCard();
|
|
171
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
172
|
+
res.end(JSON.stringify(a2aAgentCardToDict(card)));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (req.method === 'POST' && (req.url === '/' || req.url === '')) {
|
|
177
|
+
await this._handleJsonRpc(req, res);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
182
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async _handleJsonRpc(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
186
|
+
let body: Record<string, unknown>;
|
|
187
|
+
try {
|
|
188
|
+
const raw = await readBody(req);
|
|
189
|
+
body = JSON.parse(raw) as Record<string, unknown>;
|
|
190
|
+
} catch {
|
|
191
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
192
|
+
res.end(
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
jsonrpc: '2.0',
|
|
195
|
+
error: { code: -32700, message: 'Parse error' },
|
|
196
|
+
id: null,
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const method = (body.method ?? '') as string;
|
|
203
|
+
const params = (body.params ?? {}) as Record<string, unknown>;
|
|
204
|
+
const rpcId = body.id ?? null;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
let result: Record<string, unknown>;
|
|
208
|
+
|
|
209
|
+
if (method === 'message/send') {
|
|
210
|
+
result = await this.handleMessageSend(params);
|
|
211
|
+
} else if (method === 'tasks/get') {
|
|
212
|
+
const id = (params.id ?? params.taskId ?? '') as string;
|
|
213
|
+
result = await this.handleTasksGet(id);
|
|
214
|
+
} else if (method === 'tasks/cancel') {
|
|
215
|
+
const id = (params.id ?? params.taskId ?? '') as string;
|
|
216
|
+
result = await this.handleTasksCancel(id);
|
|
217
|
+
} else {
|
|
218
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
219
|
+
res.end(
|
|
220
|
+
JSON.stringify({
|
|
221
|
+
jsonrpc: '2.0',
|
|
222
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
223
|
+
id: rpcId,
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
230
|
+
res.end(JSON.stringify({ jsonrpc: '2.0', result, id: rpcId }));
|
|
231
|
+
} catch (err) {
|
|
232
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
233
|
+
res.end(
|
|
234
|
+
JSON.stringify({
|
|
235
|
+
jsonrpc: '2.0',
|
|
236
|
+
error: { code: -32602, message: String(err) },
|
|
237
|
+
id: rpcId,
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// --- Helpers ---
|
|
245
|
+
|
|
246
|
+
function readBody(req: http.IncomingMessage): Promise<string> {
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
const chunks: Buffer[] = [];
|
|
249
|
+
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
250
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
251
|
+
req.on('error', reject);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function taskToDict(task: A2ATask): Record<string, unknown> {
|
|
256
|
+
const statusDict: Record<string, unknown> = { state: task.status.state };
|
|
257
|
+
if (task.status.message) {
|
|
258
|
+
statusDict.message = a2aMessageToDict(task.status.message);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const messages = task.messages.map((m) => {
|
|
262
|
+
const md: Record<string, unknown> = {
|
|
263
|
+
role: m.role,
|
|
264
|
+
parts: m.parts.map((p) => ({ text: p.text })),
|
|
265
|
+
};
|
|
266
|
+
if (m.messageId) md.messageId = m.messageId;
|
|
267
|
+
return md;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
id: task.id,
|
|
272
|
+
contextId: task.contextId,
|
|
273
|
+
status: statusDict,
|
|
274
|
+
messages,
|
|
275
|
+
artifacts: task.artifacts,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A (Agent-to-Agent) protocol transport implementation.
|
|
3
|
+
*
|
|
4
|
+
* Client side: sends JSON-RPC 2.0 to external A2A agent endpoints.
|
|
5
|
+
* Server side: runs a local HTTP server accepting A2A JSON-RPC calls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import * as http from 'node:http';
|
|
10
|
+
|
|
11
|
+
import type { Message, MessageCallback } from './types.js';
|
|
12
|
+
import {
|
|
13
|
+
type A2AAgentCard,
|
|
14
|
+
type A2AConfig,
|
|
15
|
+
type A2AMessage,
|
|
16
|
+
type A2ATask,
|
|
17
|
+
type A2ATaskStatus,
|
|
18
|
+
type JsonRpcResponse,
|
|
19
|
+
A2A_TASK_NOT_CANCELABLE,
|
|
20
|
+
A2A_TASK_NOT_FOUND,
|
|
21
|
+
JSONRPC_INTERNAL_ERROR,
|
|
22
|
+
JSONRPC_INVALID_PARAMS,
|
|
23
|
+
JSONRPC_METHOD_NOT_FOUND,
|
|
24
|
+
JSONRPC_PARSE_ERROR,
|
|
25
|
+
a2aAgentCardFromDict,
|
|
26
|
+
a2aAgentCardToDict,
|
|
27
|
+
a2aMessageFromDict,
|
|
28
|
+
a2aMessageGetText,
|
|
29
|
+
a2aMessageToDict,
|
|
30
|
+
a2aTaskToDict,
|
|
31
|
+
createA2AAgentCard,
|
|
32
|
+
createA2ATaskStatus,
|
|
33
|
+
makeJsonRpcError,
|
|
34
|
+
makeJsonRpcRequest,
|
|
35
|
+
makeJsonRpcResponse,
|
|
36
|
+
} from './a2a-types.js';
|
|
37
|
+
|
|
38
|
+
export class A2AError extends Error {
|
|
39
|
+
readonly code: number;
|
|
40
|
+
|
|
41
|
+
constructor(code: number, message: string) {
|
|
42
|
+
super(`A2A error ${code}: ${message}`);
|
|
43
|
+
this.name = 'A2AError';
|
|
44
|
+
this.code = code;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class A2ATransport {
|
|
49
|
+
readonly config: A2AConfig;
|
|
50
|
+
|
|
51
|
+
agentName?: string;
|
|
52
|
+
agentCard?: A2AAgentCard;
|
|
53
|
+
tasks: Map<string, A2ATask> = new Map();
|
|
54
|
+
|
|
55
|
+
private _messageCallbacks: MessageCallback[] = [];
|
|
56
|
+
private _server?: http.Server;
|
|
57
|
+
private _discoveredCards: Map<string, A2AAgentCard> = new Map();
|
|
58
|
+
private _closing = false;
|
|
59
|
+
|
|
60
|
+
constructor(config: A2AConfig) {
|
|
61
|
+
this.config = config;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// === Transport interface ===
|
|
65
|
+
|
|
66
|
+
async register(name: string): Promise<{ name: string; url: string; type: string }> {
|
|
67
|
+
this.agentName = name;
|
|
68
|
+
const host = this.config.serverHost ?? '0.0.0.0';
|
|
69
|
+
const port = this.config.serverPort ?? 5000;
|
|
70
|
+
|
|
71
|
+
this.agentCard = createA2AAgentCard(
|
|
72
|
+
name,
|
|
73
|
+
this.config.agentDescription ?? `Agent Relay agent: ${name}`,
|
|
74
|
+
`http://${host}:{PORT}`,
|
|
75
|
+
this.config.skills ?? [],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const server = http.createServer((req, res) => {
|
|
80
|
+
void this._handleRequest(req, res);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
server.on('error', reject);
|
|
84
|
+
|
|
85
|
+
server.listen(port, host, () => {
|
|
86
|
+
this._server = server;
|
|
87
|
+
const addr = server.address();
|
|
88
|
+
const actualPort = typeof addr === 'object' && addr ? addr.port : port;
|
|
89
|
+
this.agentCard!.url = `http://${host}:${actualPort}`;
|
|
90
|
+
resolve({ name, url: this.agentCard!.url, type: 'a2a' });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async unregister(): Promise<void> {
|
|
96
|
+
this._closing = true;
|
|
97
|
+
if (this._server) {
|
|
98
|
+
await new Promise<void>((resolve, reject) => {
|
|
99
|
+
this._server!.close((err) => (err ? reject(err) : resolve()));
|
|
100
|
+
});
|
|
101
|
+
this._server = undefined;
|
|
102
|
+
}
|
|
103
|
+
this._closing = false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async sendDm(target: string, text: string): Promise<Record<string, unknown>> {
|
|
107
|
+
const card = await this._discoverAgent(target);
|
|
108
|
+
|
|
109
|
+
const message: A2AMessage = {
|
|
110
|
+
role: 'user',
|
|
111
|
+
parts: [{ text }],
|
|
112
|
+
messageId: randomUUID(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const rpcRequest = makeJsonRpcRequest('message/send', {
|
|
116
|
+
message: a2aMessageToDict(message),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const headers: Record<string, string> = {
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
...this._authHeaders(),
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const response = await fetch(card.url, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers,
|
|
127
|
+
body: JSON.stringify(rpcRequest),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const body = (await response.json()) as JsonRpcResponse;
|
|
131
|
+
|
|
132
|
+
if (body.error) {
|
|
133
|
+
throw new A2AError(body.error.code, body.error.message);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const result = (body.result ?? {}) as Record<string, unknown>;
|
|
137
|
+
return this._a2aResultToRelay(result, card.name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async listAgents(): Promise<Record<string, unknown>[]> {
|
|
141
|
+
const agents: Record<string, unknown>[] = [];
|
|
142
|
+
for (const url of this.config.registry ?? []) {
|
|
143
|
+
try {
|
|
144
|
+
const card = await this._discoverAgent(url);
|
|
145
|
+
agents.push({
|
|
146
|
+
name: card.name,
|
|
147
|
+
url: card.url,
|
|
148
|
+
description: card.description,
|
|
149
|
+
skills: card.skills.map((s) => ({ id: s.id, name: s.name, description: s.description })),
|
|
150
|
+
});
|
|
151
|
+
} catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return agents;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onMessage(callback: MessageCallback): void {
|
|
159
|
+
this._messageCallbacks.push(callback);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async connectWs(): Promise<void> {
|
|
163
|
+
// A2A uses HTTP, not WebSocket. No-op.
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// === HTTP request handler ===
|
|
167
|
+
|
|
168
|
+
private async _handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
169
|
+
if (req.method === 'GET' && req.url === '/.well-known/agent.json') {
|
|
170
|
+
await this._handleAgentCard(res);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (req.method === 'POST' && (req.url === '/' || req.url === '')) {
|
|
175
|
+
await this._handleJsonRpcHttp(req, res);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
180
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private async _handleAgentCard(res: http.ServerResponse): Promise<void> {
|
|
184
|
+
if (!this.agentCard) {
|
|
185
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
186
|
+
res.end(JSON.stringify({ error: 'Not registered' }));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
190
|
+
res.end(JSON.stringify(a2aAgentCardToDict(this.agentCard)));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private async _handleJsonRpcHttp(
|
|
194
|
+
req: http.IncomingMessage,
|
|
195
|
+
res: http.ServerResponse,
|
|
196
|
+
): Promise<void> {
|
|
197
|
+
let body: Record<string, unknown>;
|
|
198
|
+
try {
|
|
199
|
+
const raw = await this._readBody(req);
|
|
200
|
+
body = JSON.parse(raw) as Record<string, unknown>;
|
|
201
|
+
} catch {
|
|
202
|
+
const error = makeJsonRpcError(JSONRPC_PARSE_ERROR, 'Parse error', null);
|
|
203
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
204
|
+
res.end(JSON.stringify(error));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = await this._dispatchJsonRpc(body);
|
|
209
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
210
|
+
res.end(JSON.stringify(result));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private _readBody(req: http.IncomingMessage): Promise<string> {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const chunks: Buffer[] = [];
|
|
216
|
+
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
217
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
218
|
+
req.on('error', reject);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// === JSON-RPC dispatch ===
|
|
223
|
+
|
|
224
|
+
private async _dispatchJsonRpc(request: Record<string, unknown>): Promise<JsonRpcResponse> {
|
|
225
|
+
const rpcId = (request.id ?? null) as string | number | null;
|
|
226
|
+
const method = (request.method ?? '') as string;
|
|
227
|
+
const params = (request.params ?? {}) as Record<string, unknown>;
|
|
228
|
+
|
|
229
|
+
const handlers: Record<string, (p: Record<string, unknown>) => Promise<Record<string, unknown>>> = {
|
|
230
|
+
'message/send': (p) => this._handleMessageSend(p),
|
|
231
|
+
'tasks/get': (p) => this._handleTasksGet(p),
|
|
232
|
+
'tasks/cancel': (p) => this._handleTasksCancel(p),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handler = handlers[method];
|
|
236
|
+
if (!handler) {
|
|
237
|
+
return makeJsonRpcError(JSONRPC_METHOD_NOT_FOUND, `Method not found: ${method}`, rpcId);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const result = await handler(params);
|
|
242
|
+
return makeJsonRpcResponse(result, rpcId as string | number);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
if (err instanceof A2AError) {
|
|
245
|
+
return makeJsonRpcError(err.code, err.message, rpcId);
|
|
246
|
+
}
|
|
247
|
+
return makeJsonRpcError(JSONRPC_INTERNAL_ERROR, String(err), rpcId);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async _handleMessageSend(params: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
252
|
+
const msgData = params.message as Record<string, unknown> | undefined;
|
|
253
|
+
if (!msgData) {
|
|
254
|
+
throw new A2AError(JSONRPC_INVALID_PARAMS, "Missing 'message' in params");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const a2aMsg = a2aMessageFromDict(msgData);
|
|
258
|
+
const taskId = a2aMsg.taskId ?? randomUUID();
|
|
259
|
+
const contextId = a2aMsg.contextId ?? randomUUID();
|
|
260
|
+
|
|
261
|
+
let task: A2ATask;
|
|
262
|
+
if (this.tasks.has(taskId)) {
|
|
263
|
+
task = this.tasks.get(taskId)!;
|
|
264
|
+
task.messages.push(a2aMsg);
|
|
265
|
+
task.status = createA2ATaskStatus('working');
|
|
266
|
+
} else {
|
|
267
|
+
task = {
|
|
268
|
+
id: taskId,
|
|
269
|
+
contextId,
|
|
270
|
+
status: createA2ATaskStatus('working'),
|
|
271
|
+
messages: [a2aMsg],
|
|
272
|
+
artifacts: [],
|
|
273
|
+
};
|
|
274
|
+
this.tasks.set(taskId, task);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Convert to Relay message and invoke callbacks
|
|
278
|
+
const relayMsg = A2ATransport._a2aToRelayMsg(a2aMsg, 'a2a-client');
|
|
279
|
+
await this._invokeCallbacks(relayMsg);
|
|
280
|
+
|
|
281
|
+
// Mark completed
|
|
282
|
+
task.status = createA2ATaskStatus('completed');
|
|
283
|
+
|
|
284
|
+
return a2aTaskToDict(task);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private async _handleTasksGet(params: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
288
|
+
const taskId = (params.id as string) ?? '';
|
|
289
|
+
if (!taskId || !this.tasks.has(taskId)) {
|
|
290
|
+
throw new A2AError(A2A_TASK_NOT_FOUND, `Task not found: ${taskId}`);
|
|
291
|
+
}
|
|
292
|
+
return a2aTaskToDict(this.tasks.get(taskId)!);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private async _handleTasksCancel(params: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
296
|
+
const taskId = (params.id as string) ?? '';
|
|
297
|
+
if (!taskId || !this.tasks.has(taskId)) {
|
|
298
|
+
throw new A2AError(A2A_TASK_NOT_FOUND, `Task not found: ${taskId}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const task = this.tasks.get(taskId)!;
|
|
302
|
+
if (['completed', 'failed', 'canceled'].includes(task.status.state)) {
|
|
303
|
+
throw new A2AError(
|
|
304
|
+
A2A_TASK_NOT_CANCELABLE,
|
|
305
|
+
`Task ${taskId} is already ${task.status.state}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
task.status = createA2ATaskStatus('canceled');
|
|
310
|
+
return a2aTaskToDict(task);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// === Agent discovery ===
|
|
314
|
+
|
|
315
|
+
private async _discoverAgent(url: string): Promise<A2AAgentCard> {
|
|
316
|
+
const normalizedUrl = url.replace(/\/+$/, '');
|
|
317
|
+
|
|
318
|
+
if (this._discoveredCards.has(normalizedUrl)) {
|
|
319
|
+
return this._discoveredCards.get(normalizedUrl)!;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const cardUrl = `${normalizedUrl}/.well-known/agent.json`;
|
|
323
|
+
const response = await fetch(cardUrl);
|
|
324
|
+
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
throw new A2AError(-1, `Failed to discover agent at ${cardUrl}: HTTP ${response.status}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const data = (await response.json()) as Record<string, unknown>;
|
|
330
|
+
const card = a2aAgentCardFromDict(data);
|
|
331
|
+
this._discoveredCards.set(normalizedUrl, card);
|
|
332
|
+
return card;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// === Message conversion ===
|
|
336
|
+
|
|
337
|
+
static _relayMsgToA2A(text: string, _sender: string): A2AMessage {
|
|
338
|
+
return {
|
|
339
|
+
role: 'user',
|
|
340
|
+
parts: [{ text }],
|
|
341
|
+
messageId: randomUUID(),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
static _a2aToRelayMsg(msg: A2AMessage, sender: string = 'unknown'): Message {
|
|
346
|
+
const text = a2aMessageGetText(msg);
|
|
347
|
+
return {
|
|
348
|
+
sender,
|
|
349
|
+
text,
|
|
350
|
+
channel: undefined,
|
|
351
|
+
threadId: msg.contextId,
|
|
352
|
+
messageId: msg.messageId,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private _a2aResultToRelay(result: Record<string, unknown>, sender: string): Record<string, unknown> {
|
|
357
|
+
const messages = (result.messages as Record<string, unknown>[] | undefined) ?? [];
|
|
358
|
+
let text = '';
|
|
359
|
+
if (messages.length > 0) {
|
|
360
|
+
const lastMsg = messages[messages.length - 1];
|
|
361
|
+
const parts = (lastMsg.parts as Record<string, unknown>[] | undefined) ?? [];
|
|
362
|
+
text = parts
|
|
363
|
+
.filter((p) => p.text)
|
|
364
|
+
.map((p) => p.text as string)
|
|
365
|
+
.join(' ');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
sender,
|
|
370
|
+
text,
|
|
371
|
+
task_id: result.id,
|
|
372
|
+
status: (result.status as Record<string, unknown> | undefined)?.state,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// === Internal helpers ===
|
|
377
|
+
|
|
378
|
+
private async _invokeCallbacks(msg: Message): Promise<void> {
|
|
379
|
+
for (const cb of this._messageCallbacks) {
|
|
380
|
+
await cb(msg);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _authHeaders(): Record<string, string> {
|
|
385
|
+
const headers: Record<string, string> = {};
|
|
386
|
+
if (this.config.authToken) {
|
|
387
|
+
if (this.config.authScheme === 'api_key') {
|
|
388
|
+
headers['X-API-Key'] = this.config.authToken;
|
|
389
|
+
} else {
|
|
390
|
+
headers['Authorization'] = `Bearer ${this.config.authToken}`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return headers;
|
|
394
|
+
}
|
|
395
|
+
}
|