@axiom-lattice/cli-a2a 0.1.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/.turbo/turbo-build.log +51 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +290 -0
- package/__tests__/opencode-executor.test.ts +159 -0
- package/agents/opencode-example/config.json +64 -0
- package/dist/bridge-7ZUDKCZT.mjs +271 -0
- package/dist/bridge-7ZUDKCZT.mjs.map +1 -0
- package/dist/chunk-35NFMGMS.mjs +27 -0
- package/dist/chunk-35NFMGMS.mjs.map +1 -0
- package/dist/chunk-G7AGL2QA.mjs +284 -0
- package/dist/chunk-G7AGL2QA.mjs.map +1 -0
- package/dist/chunk-LXL47XMZ.mjs +43 -0
- package/dist/chunk-LXL47XMZ.mjs.map +1 -0
- package/dist/chunk-NQIDRU47.mjs +178 -0
- package/dist/chunk-NQIDRU47.mjs.map +1 -0
- package/dist/chunk-VSZ3DACI.mjs +179 -0
- package/dist/chunk-VSZ3DACI.mjs.map +1 -0
- package/dist/chunk-VZEH3EPJ.mjs +56 -0
- package/dist/chunk-VZEH3EPJ.mjs.map +1 -0
- package/dist/chunk-WNCDOYZS.mjs +187 -0
- package/dist/chunk-WNCDOYZS.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1562 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +293 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/executor-OWPVDUXH.mjs +11 -0
- package/dist/executor-OWPVDUXH.mjs.map +1 -0
- package/dist/executor-RVBGAWUF.mjs +11 -0
- package/dist/executor-RVBGAWUF.mjs.map +1 -0
- package/dist/executor-XWHWUVQ3.mjs +11 -0
- package/dist/executor-XWHWUVQ3.mjs.map +1 -0
- package/dist/executors-QIIKBUMJ.mjs +15 -0
- package/dist/executors-QIIKBUMJ.mjs.map +1 -0
- package/dist/index.d.mts +295 -0
- package/dist/index.d.ts +295 -0
- package/dist/index.js +942 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +44 -0
- package/dist/index.mjs.map +1 -0
- package/jest.config.js +16 -0
- package/package.json +63 -0
- package/src/bridge.ts +355 -0
- package/src/cli.ts +384 -0
- package/src/config/defaults.ts +109 -0
- package/src/config/index.ts +16 -0
- package/src/config/loader.ts +121 -0
- package/src/config/types.ts +163 -0
- package/src/executors/claude/client.ts +138 -0
- package/src/executors/claude/executor.ts +111 -0
- package/src/executors/codex/client.ts +139 -0
- package/src/executors/codex/executor.ts +118 -0
- package/src/executors/events.ts +117 -0
- package/src/executors/index.ts +54 -0
- package/src/executors/opencode/client.ts +149 -0
- package/src/executors/opencode/executor.ts +112 -0
- package/src/index.ts +54 -0
- package/src/logger.ts +78 -0
- package/src/server/agent-card.ts +51 -0
- package/src/server/index.ts +124 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for OpenCode A2A Executor.
|
|
3
|
+
*
|
|
4
|
+
* These are unit tests that mock the OpenCode SDK.
|
|
5
|
+
* Integration tests require a running opencode server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { OpenCodeExecutor } from '../src/executors/opencode/executor';
|
|
9
|
+
import type { AgentConfig } from '../src/config/types';
|
|
10
|
+
import { resolveDefaults } from '../src/config/defaults';
|
|
11
|
+
|
|
12
|
+
function createMockConfig(overrides?: Partial<AgentConfig>): Required<AgentConfig> {
|
|
13
|
+
const base: Partial<AgentConfig> = {
|
|
14
|
+
provider: 'opencode',
|
|
15
|
+
agentCard: {
|
|
16
|
+
name: 'Test Agent',
|
|
17
|
+
description: 'Test agent for unit tests',
|
|
18
|
+
},
|
|
19
|
+
opencode: {
|
|
20
|
+
baseUrl: 'http://localhost:4096',
|
|
21
|
+
projectDirectory: '/tmp/test-workspace',
|
|
22
|
+
model: 'anthropic/claude-sonnet-4',
|
|
23
|
+
agent: 'code-assistant',
|
|
24
|
+
},
|
|
25
|
+
session: {
|
|
26
|
+
ttl: 3_600_000,
|
|
27
|
+
cleanupInterval: 300_000,
|
|
28
|
+
reuseByContext: true,
|
|
29
|
+
titlePrefix: 'A2A Test',
|
|
30
|
+
},
|
|
31
|
+
features: {
|
|
32
|
+
autoApprovePermissions: true,
|
|
33
|
+
autoAnswerQuestions: true,
|
|
34
|
+
streamArtifactChunks: false,
|
|
35
|
+
enablePollingFallback: true,
|
|
36
|
+
},
|
|
37
|
+
timeouts: {
|
|
38
|
+
prompt: 300_000,
|
|
39
|
+
pollingInterval: 1_000,
|
|
40
|
+
healthCheck: 30_000,
|
|
41
|
+
},
|
|
42
|
+
logging: {
|
|
43
|
+
level: 'info',
|
|
44
|
+
},
|
|
45
|
+
server: {
|
|
46
|
+
port: 3000,
|
|
47
|
+
hostname: '0.0.0.0',
|
|
48
|
+
advertiseHost: 'localhost',
|
|
49
|
+
advertiseProtocol: 'http',
|
|
50
|
+
},
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
return resolveDefaults(base);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('OpenCodeExecutor', () => {
|
|
57
|
+
it('should create an executor with valid config', () => {
|
|
58
|
+
const config = createMockConfig();
|
|
59
|
+
const executor = new OpenCodeExecutor(config);
|
|
60
|
+
expect(executor).toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should have initialize method', () => {
|
|
64
|
+
const config = createMockConfig();
|
|
65
|
+
const executor = new OpenCodeExecutor(config);
|
|
66
|
+
expect(typeof executor.initialize).toBe('function');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should have shutdown method', () => {
|
|
70
|
+
const config = createMockConfig();
|
|
71
|
+
const executor = new OpenCodeExecutor(config);
|
|
72
|
+
expect(typeof executor.shutdown).toBe('function');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should have execute method', () => {
|
|
76
|
+
const config = createMockConfig();
|
|
77
|
+
const executor = new OpenCodeExecutor(config);
|
|
78
|
+
expect(typeof executor.execute).toBe('function');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should have cancelTask method', () => {
|
|
82
|
+
const config = createMockConfig();
|
|
83
|
+
const executor = new OpenCodeExecutor(config);
|
|
84
|
+
expect(typeof executor.cancelTask).toBe('function');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should not be initialized until initialize is called', () => {
|
|
88
|
+
const config = createMockConfig();
|
|
89
|
+
const executor = new OpenCodeExecutor(config);
|
|
90
|
+
// @ts-expect-error testing private field
|
|
91
|
+
expect(executor.initialized).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle config with empty model', () => {
|
|
95
|
+
const config = createMockConfig({
|
|
96
|
+
opencode: { model: '' },
|
|
97
|
+
});
|
|
98
|
+
const executor = new OpenCodeExecutor(config);
|
|
99
|
+
expect(executor).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should handle config with system prompt', () => {
|
|
103
|
+
const config = createMockConfig({
|
|
104
|
+
opencode: {
|
|
105
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
106
|
+
systemPromptMode: 'replace',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const executor = new OpenCodeExecutor(config);
|
|
110
|
+
expect(executor).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should cleanup on shutdown', async () => {
|
|
114
|
+
const config = createMockConfig();
|
|
115
|
+
const executor = new OpenCodeExecutor(config);
|
|
116
|
+
await executor.shutdown();
|
|
117
|
+
// @ts-expect-error testing private field
|
|
118
|
+
expect(executor.initialized).toBe(false);
|
|
119
|
+
// @ts-expect-error testing private field
|
|
120
|
+
expect(executor.client).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should allow double initialization without error', async () => {
|
|
124
|
+
const config = createMockConfig({
|
|
125
|
+
features: {
|
|
126
|
+
autoApprovePermissions: false,
|
|
127
|
+
autoAnswerQuestions: false,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const executor = new OpenCodeExecutor(config);
|
|
131
|
+
|
|
132
|
+
// First call will fail because no server, but should set initialized=false
|
|
133
|
+
try { await executor.initialize(); } catch { /* no server */ }
|
|
134
|
+
// Second call should be safe
|
|
135
|
+
try { await executor.initialize(); } catch { /* no server */ }
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('Config resolution', () => {
|
|
140
|
+
it('should resolve defaults when no config file provided', () => {
|
|
141
|
+
const { resolveConfig } = require('../src/config/loader');
|
|
142
|
+
const config = resolveConfig(undefined);
|
|
143
|
+
expect(config.provider).toBe('opencode');
|
|
144
|
+
expect(config.agentCard.name).toBeDefined();
|
|
145
|
+
expect(config.server?.port).toBeDefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should merge CLI overrides into defaults', () => {
|
|
149
|
+
const { resolveConfig } = require('../src/config/loader');
|
|
150
|
+
const config = resolveConfig(undefined, {
|
|
151
|
+
provider: 'opencode',
|
|
152
|
+
server: { port: 8080 },
|
|
153
|
+
agentCard: { name: 'Custom Agent', description: 'Custom desc' },
|
|
154
|
+
});
|
|
155
|
+
expect(config.provider).toBe('opencode');
|
|
156
|
+
expect(config.server?.port).toBe(8080);
|
|
157
|
+
expect(config.agentCard.name).toBe('Custom Agent');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"provider": "opencode",
|
|
3
|
+
"agentCard": {
|
|
4
|
+
"name": "OpenCode A2A Agent",
|
|
5
|
+
"description": "An A2A agent powered by OpenCode — a multi-provider coding assistant.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"skills": [
|
|
8
|
+
{
|
|
9
|
+
"id": "code-generation",
|
|
10
|
+
"name": "Code Generation",
|
|
11
|
+
"description": "Generate, modify, and refactor code across multiple languages",
|
|
12
|
+
"tags": ["coding", "typescript", "python", "refactoring"]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "file-operations",
|
|
16
|
+
"name": "File Operations",
|
|
17
|
+
"description": "Read, write, edit, and search files in the workspace",
|
|
18
|
+
"tags": ["filesystem", "search"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "shell-commands",
|
|
22
|
+
"name": "Shell Commands",
|
|
23
|
+
"description": "Execute shell commands in a sandboxed environment",
|
|
24
|
+
"tags": ["bash", "terminal"]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"server": {
|
|
29
|
+
"port": 3000,
|
|
30
|
+
"hostname": "0.0.0.0",
|
|
31
|
+
"advertiseHost": "localhost",
|
|
32
|
+
"advertiseProtocol": "http"
|
|
33
|
+
},
|
|
34
|
+
"session": {
|
|
35
|
+
"titlePrefix": "A2A",
|
|
36
|
+
"reuseByContext": true,
|
|
37
|
+
"ttl": 3600000,
|
|
38
|
+
"cleanupInterval": 300000
|
|
39
|
+
},
|
|
40
|
+
"features": {
|
|
41
|
+
"autoApprovePermissions": true,
|
|
42
|
+
"autoAnswerQuestions": true,
|
|
43
|
+
"streamArtifactChunks": false,
|
|
44
|
+
"enablePollingFallback": true
|
|
45
|
+
},
|
|
46
|
+
"timeouts": {
|
|
47
|
+
"prompt": 600000,
|
|
48
|
+
"pollingInterval": 2000,
|
|
49
|
+
"healthCheck": 30000
|
|
50
|
+
},
|
|
51
|
+
"logging": {
|
|
52
|
+
"level": "info"
|
|
53
|
+
},
|
|
54
|
+
"opencode": {
|
|
55
|
+
"baseUrl": "http://localhost:4096",
|
|
56
|
+
"projectDirectory": "",
|
|
57
|
+
"model": "",
|
|
58
|
+
"agent": "",
|
|
59
|
+
"systemPrompt": "",
|
|
60
|
+
"systemPromptMode": "append",
|
|
61
|
+
"contextFile": "context.md",
|
|
62
|
+
"contextPrompt": ""
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-VZEH3EPJ.mjs";
|
|
4
|
+
|
|
5
|
+
// src/bridge.ts
|
|
6
|
+
import { connect } from "net";
|
|
7
|
+
import { randomBytes } from "crypto";
|
|
8
|
+
var log = logger.child("bridge");
|
|
9
|
+
var SimpleWs = class {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.socket = null;
|
|
12
|
+
this.buffer = Buffer.alloc(0);
|
|
13
|
+
this.handshaking = true;
|
|
14
|
+
this.httpBuffer = "";
|
|
15
|
+
}
|
|
16
|
+
connect(urlStr, apiKey) {
|
|
17
|
+
const url = new URL(urlStr);
|
|
18
|
+
const host = url.hostname;
|
|
19
|
+
const port = parseInt(url.port) || (url.protocol === "wss:" ? 443 : 80);
|
|
20
|
+
const path = url.pathname + url.search;
|
|
21
|
+
const key = randomBytes(16).toString("base64");
|
|
22
|
+
const req = connect({ host, port }, () => {
|
|
23
|
+
const headers = [
|
|
24
|
+
`GET ${path} HTTP/1.1`,
|
|
25
|
+
`Host: ${host}:${port}`,
|
|
26
|
+
"Upgrade: websocket",
|
|
27
|
+
"Connection: Upgrade",
|
|
28
|
+
`Sec-WebSocket-Key: ${key}`,
|
|
29
|
+
"Sec-WebSocket-Version: 13",
|
|
30
|
+
`x-api-key: ${apiKey}`,
|
|
31
|
+
"",
|
|
32
|
+
""
|
|
33
|
+
].join("\r\n");
|
|
34
|
+
if (this.socket) {
|
|
35
|
+
this.socket.write(headers);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this.socket = req;
|
|
39
|
+
req.on("data", (chunk) => {
|
|
40
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
41
|
+
if (this.handshaking) {
|
|
42
|
+
this.httpBuffer += chunk.toString();
|
|
43
|
+
const headerEnd = this.httpBuffer.indexOf("\r\n\r\n");
|
|
44
|
+
if (headerEnd === -1) return;
|
|
45
|
+
const header = this.httpBuffer.substring(0, headerEnd);
|
|
46
|
+
const statusLine = header.split("\r\n")[0] || "";
|
|
47
|
+
if (!statusLine.startsWith("HTTP/1.1 101")) {
|
|
48
|
+
const bodyStart = headerEnd + 4;
|
|
49
|
+
const bodyText = this.httpBuffer.substring(bodyStart).trim();
|
|
50
|
+
const errMsg = statusLine ? `WebSocket upgrade rejected: ${statusLine}` : "WebSocket upgrade failed: no status line";
|
|
51
|
+
this.onerror?.(new Error(errMsg + (bodyText ? ` (${bodyText.slice(0, 200)})` : "")));
|
|
52
|
+
this.socket?.destroy();
|
|
53
|
+
this.socket = null;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.handshaking = false;
|
|
57
|
+
const remaining = this.buffer.subarray(headerEnd + 4);
|
|
58
|
+
this.buffer = remaining;
|
|
59
|
+
this.httpBuffer = "";
|
|
60
|
+
if (this.onopen) this.onopen();
|
|
61
|
+
if (remaining.length === 0) return;
|
|
62
|
+
}
|
|
63
|
+
while (this.buffer.length >= 2) {
|
|
64
|
+
const opcode = this.buffer[0] & 15;
|
|
65
|
+
const masked = (this.buffer[1] & 128) !== 0;
|
|
66
|
+
let offset = 2;
|
|
67
|
+
let payloadLen = this.buffer[1] & 127;
|
|
68
|
+
if (payloadLen === 126) {
|
|
69
|
+
if (this.buffer.length < 4) break;
|
|
70
|
+
payloadLen = this.buffer.readUInt16BE(2);
|
|
71
|
+
offset = 4;
|
|
72
|
+
} else if (payloadLen === 127) {
|
|
73
|
+
if (this.buffer.length < 10) break;
|
|
74
|
+
const hi = this.buffer.readUInt32BE(2);
|
|
75
|
+
const lo = this.buffer.readUInt32BE(6);
|
|
76
|
+
payloadLen = hi * 4294967296 + lo;
|
|
77
|
+
offset = 10;
|
|
78
|
+
}
|
|
79
|
+
if (this.buffer.length < offset + payloadLen) break;
|
|
80
|
+
const payload = this.buffer.subarray(offset, offset + payloadLen);
|
|
81
|
+
this.buffer = this.buffer.subarray(offset + payloadLen);
|
|
82
|
+
if (masked) {
|
|
83
|
+
const mask = payload.subarray(0, 4);
|
|
84
|
+
const data = payload.subarray(4);
|
|
85
|
+
for (let i = 0; i < data.length; i++) {
|
|
86
|
+
data[i] ^= mask[i % 4];
|
|
87
|
+
}
|
|
88
|
+
if (opcode === 1) {
|
|
89
|
+
this.onmessage?.(data.toString());
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
if (opcode === 1) {
|
|
93
|
+
this.onmessage?.(payload.toString());
|
|
94
|
+
} else if (opcode === 8) {
|
|
95
|
+
this.onclose?.(1e3);
|
|
96
|
+
return;
|
|
97
|
+
} else if (opcode === 9) {
|
|
98
|
+
const pong = Buffer.from([138, 0]);
|
|
99
|
+
this.socket?.write(pong);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
req.on("error", (err) => {
|
|
105
|
+
this.onerror?.(err);
|
|
106
|
+
});
|
|
107
|
+
req.on("close", () => {
|
|
108
|
+
this.onclose?.(1006);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
send(data) {
|
|
112
|
+
if (!this.socket) return;
|
|
113
|
+
const payload = Buffer.from(data, "utf-8");
|
|
114
|
+
const len = payload.length;
|
|
115
|
+
const maskKey = randomBytes(4);
|
|
116
|
+
const masked = Buffer.alloc(len);
|
|
117
|
+
for (let i = 0; i < len; i++) {
|
|
118
|
+
masked[i] = payload[i] ^ maskKey[i % 4];
|
|
119
|
+
}
|
|
120
|
+
let header;
|
|
121
|
+
if (len < 126) {
|
|
122
|
+
header = Buffer.alloc(6);
|
|
123
|
+
header[0] = 129;
|
|
124
|
+
header[1] = 128 | len;
|
|
125
|
+
maskKey.copy(header, 2);
|
|
126
|
+
} else if (len < 65536) {
|
|
127
|
+
header = Buffer.alloc(8);
|
|
128
|
+
header[0] = 129;
|
|
129
|
+
header[1] = 128 | 126;
|
|
130
|
+
header.writeUInt16BE(len, 2);
|
|
131
|
+
maskKey.copy(header, 4);
|
|
132
|
+
} else {
|
|
133
|
+
header = Buffer.alloc(14);
|
|
134
|
+
header[0] = 129;
|
|
135
|
+
header[1] = 128 | 127;
|
|
136
|
+
header.writeUInt32BE(0, 2);
|
|
137
|
+
header.writeUInt32BE(len, 6);
|
|
138
|
+
maskKey.copy(header, 10);
|
|
139
|
+
}
|
|
140
|
+
this.socket.write(Buffer.concat([header, masked]));
|
|
141
|
+
}
|
|
142
|
+
close() {
|
|
143
|
+
if (!this.socket) return;
|
|
144
|
+
const frame = Buffer.from([136, 0]);
|
|
145
|
+
this.socket.write(frame);
|
|
146
|
+
this.socket.destroy();
|
|
147
|
+
this.socket = null;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var BridgeClient = class {
|
|
151
|
+
constructor(config) {
|
|
152
|
+
this.config = config;
|
|
153
|
+
this.ws = null;
|
|
154
|
+
this.running = false;
|
|
155
|
+
this.reconnectTimer = null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Start the bridge — connect WebSocket and begin serving proxy requests.
|
|
159
|
+
* Returns a promise that resolves when connected. Reconnects on disconnect.
|
|
160
|
+
*/
|
|
161
|
+
async start() {
|
|
162
|
+
this.running = true;
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
this.ws = new SimpleWs();
|
|
165
|
+
let firstConnect = true;
|
|
166
|
+
this.ws.onopen = () => {
|
|
167
|
+
log.info("Bridge connected");
|
|
168
|
+
this.ws.send(JSON.stringify({
|
|
169
|
+
type: "agent:register",
|
|
170
|
+
agentId: this.config.agentId,
|
|
171
|
+
agentCard: this.config.agentCard
|
|
172
|
+
}));
|
|
173
|
+
};
|
|
174
|
+
this.ws.onmessage = async (raw) => {
|
|
175
|
+
let msg;
|
|
176
|
+
try {
|
|
177
|
+
msg = JSON.parse(raw);
|
|
178
|
+
} catch {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (msg.type === "agent:registered") {
|
|
182
|
+
log.info("Agent registered via bridge", { agentId: this.config.agentId });
|
|
183
|
+
if (firstConnect) {
|
|
184
|
+
firstConnect = false;
|
|
185
|
+
resolve();
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (msg.type === "http:request") {
|
|
190
|
+
this.handleHttpRequest(msg);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
this.ws.onclose = () => {
|
|
194
|
+
log.warn("Bridge disconnected, reconnecting...");
|
|
195
|
+
if (this.running) {
|
|
196
|
+
this.reconnectTimer = setTimeout(
|
|
197
|
+
() => this.start().catch(() => {
|
|
198
|
+
}),
|
|
199
|
+
this.config.reconnectDelay ?? 5e3
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
this.ws.onerror = (err) => {
|
|
204
|
+
log.error("Bridge error", { error: err.message });
|
|
205
|
+
if (firstConnect) {
|
|
206
|
+
firstConnect = false;
|
|
207
|
+
reject(err);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
log.info("Connecting bridge", { url: this.config.bridgeUrl });
|
|
211
|
+
this.ws.connect(this.config.bridgeUrl, this.config.apiKey);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Stop the bridge.
|
|
216
|
+
*/
|
|
217
|
+
stop() {
|
|
218
|
+
this.running = false;
|
|
219
|
+
if (this.reconnectTimer) {
|
|
220
|
+
clearTimeout(this.reconnectTimer);
|
|
221
|
+
this.reconnectTimer = null;
|
|
222
|
+
}
|
|
223
|
+
this.ws?.close();
|
|
224
|
+
this.ws = null;
|
|
225
|
+
log.info("Bridge stopped");
|
|
226
|
+
}
|
|
227
|
+
// ── Internal ────────────────────────────────────────────────────────────
|
|
228
|
+
async handleHttpRequest(msg) {
|
|
229
|
+
const requestId = msg.requestId;
|
|
230
|
+
const method = msg.method ?? "GET";
|
|
231
|
+
const path = msg.path ?? "/";
|
|
232
|
+
const headers = msg.headers ?? {};
|
|
233
|
+
const body = msg.body ?? "";
|
|
234
|
+
const url = `${this.config.localBaseUrl}${path}`;
|
|
235
|
+
try {
|
|
236
|
+
const fetchHeaders = { ...headers };
|
|
237
|
+
const fetchOpts = {
|
|
238
|
+
method,
|
|
239
|
+
headers: fetchHeaders,
|
|
240
|
+
signal: AbortSignal.timeout(3e5)
|
|
241
|
+
};
|
|
242
|
+
if (body && method !== "GET") {
|
|
243
|
+
fetchOpts.body = body;
|
|
244
|
+
}
|
|
245
|
+
const resp = await fetch(url, fetchOpts);
|
|
246
|
+
const responseBody = await resp.text();
|
|
247
|
+
const responseHeaders = {};
|
|
248
|
+
resp.headers.forEach((value, key) => {
|
|
249
|
+
responseHeaders[key] = value;
|
|
250
|
+
});
|
|
251
|
+
this.ws?.send(JSON.stringify({
|
|
252
|
+
type: "http:response",
|
|
253
|
+
requestId,
|
|
254
|
+
status: resp.status,
|
|
255
|
+
headers: responseHeaders,
|
|
256
|
+
body: responseBody
|
|
257
|
+
}));
|
|
258
|
+
} catch (err) {
|
|
259
|
+
log.error("Proxy request failed", { url, error: err.message });
|
|
260
|
+
this.ws?.send(JSON.stringify({
|
|
261
|
+
type: "http:error",
|
|
262
|
+
requestId,
|
|
263
|
+
message: err.message
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
export {
|
|
269
|
+
BridgeClient
|
|
270
|
+
};
|
|
271
|
+
//# sourceMappingURL=bridge-7ZUDKCZT.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * A2A Bridge Client — connects to the gateway's WebSocket bridge and\n * proxies HTTP requests from the gateway to the local A2A Express server.\n *\n * Enables agents behind NAT/firewall to receive A2A requests without\n * a public IP or SSH tunnel. The agent opens an outbound WebSocket (which\n * is almost never blocked), and the gateway pushes requests over it.\n */\n\nimport { connect } from 'node:net';\nimport { randomBytes } from 'node:crypto';\nimport { logger } from './logger.js';\n\nconst log = logger.child('bridge');\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface BridgeConfig {\n /** Gateway WebSocket URL (e.g. ws://gateway:8080/api/a2a/bridge) */\n bridgeUrl: string;\n /** API key for gateway auth (sent as x-api-key header during WebSocket upgrade) */\n apiKey: string;\n /** Agent ID to register as on the gateway */\n agentId: string;\n /** Full agent card object to advertise */\n agentCard: Record<string, unknown>;\n /** Local A2A server base URL to forward requests to */\n localBaseUrl: string;\n /** Reconnect delay in ms (default: 5000) */\n reconnectDelay?: number;\n}\n\n// ─── Very lightweight WS client (no dependency) ─────────────────────────────\n// Uses Node.js built-in HTTP upgrade → WebSocket framing (RFC 6455 minimal).\n// Enough for JSON text frames. Avoids pulling in the ws library.\n\nclass SimpleWs {\n private socket: ReturnType<typeof connect> | null = null;\n private buffer = Buffer.alloc(0);\n private handshaking = true;\n private httpBuffer = '';\n\n onopen?: () => void;\n onmessage?: (data: string) => void;\n onclose?: (code: number) => void;\n onerror?: (err: Error) => void;\n\n connect(urlStr: string, apiKey: string): void {\n const url = new URL(urlStr);\n const host = url.hostname;\n const port = parseInt(url.port) || (url.protocol === 'wss:' ? 443 : 80);\n const path = url.pathname + url.search;\n\n const key = randomBytes(16).toString('base64');\n\n const req = connect({ host, port }, () => {\n // Send WebSocket upgrade handshake\n const headers = [\n `GET ${path} HTTP/1.1`,\n `Host: ${host}:${port}`,\n 'Upgrade: websocket',\n 'Connection: Upgrade',\n `Sec-WebSocket-Key: ${key}`,\n 'Sec-WebSocket-Version: 13',\n `x-api-key: ${apiKey}`,\n '',\n '',\n ].join('\\r\\n');\n\n if (this.socket) {\n this.socket.write(headers);\n }\n });\n\n this.socket = req;\n\n req.on('data', (chunk: Buffer) => {\n this.buffer = Buffer.concat([this.buffer, chunk]);\n\n // First, validate HTTP upgrade response\n if (this.handshaking) {\n this.httpBuffer += chunk.toString();\n const headerEnd = this.httpBuffer.indexOf('\\r\\n\\r\\n');\n if (headerEnd === -1) return; // not complete yet\n\n const header = this.httpBuffer.substring(0, headerEnd);\n const statusLine = header.split('\\r\\n')[0] || '';\n\n if (!statusLine.startsWith('HTTP/1.1 101')) {\n // Extract response body for diagnostics\n const bodyStart = headerEnd + 4;\n const bodyText = this.httpBuffer.substring(bodyStart).trim();\n const errMsg = statusLine\n ? `WebSocket upgrade rejected: ${statusLine}`\n : 'WebSocket upgrade failed: no status line';\n this.onerror?.(new Error(errMsg + (bodyText ? ` (${bodyText.slice(0, 200)})` : '')));\n this.socket?.destroy();\n this.socket = null;\n return;\n }\n\n // Upgrade successful — discard HTTP header, keep any remaining data\n this.handshaking = false;\n const remaining = this.buffer.subarray(headerEnd + 4);\n this.buffer = remaining;\n this.httpBuffer = '';\n if (this.onopen) this.onopen();\n\n if (remaining.length === 0) return;\n // Fall through to frame parsing below\n }\n\n // Parse WebSocket frames\n while (this.buffer.length >= 2) {\n const opcode = this.buffer[0] & 0x0f;\n const masked = (this.buffer[1] & 0x80) !== 0;\n let offset = 2;\n let payloadLen = this.buffer[1] & 0x7f;\n\n if (payloadLen === 126) {\n if (this.buffer.length < 4) break;\n payloadLen = this.buffer.readUInt16BE(2);\n offset = 4;\n } else if (payloadLen === 127) {\n if (this.buffer.length < 10) break;\n // readUInt32BE for high 32 bits (should be 0 for realistic payloads)\n const hi = this.buffer.readUInt32BE(2);\n const lo = this.buffer.readUInt32BE(6);\n payloadLen = hi * 0x100000000 + lo;\n offset = 10;\n }\n\n if (this.buffer.length < offset + payloadLen) break;\n\n const payload = this.buffer.subarray(offset, offset + payloadLen);\n this.buffer = this.buffer.subarray(offset + payloadLen);\n\n if (masked) {\n // Server→client frames shouldn't be masked per spec, but handle anyway\n const mask = payload.subarray(0, 4);\n const data = payload.subarray(4);\n for (let i = 0; i < data.length; i++) {\n data[i] ^= mask[i % 4];\n }\n if (opcode === 1) {\n this.onmessage?.(data.toString());\n }\n } else {\n if (opcode === 1) {\n this.onmessage?.(payload.toString());\n } else if (opcode === 8) {\n this.onclose?.(1000);\n return;\n } else if (opcode === 9) {\n // Ping — respond with pong\n const pong = Buffer.from([0x8a, 0]);\n this.socket?.write(pong);\n }\n }\n }\n });\n\n req.on('error', (err: Error) => {\n this.onerror?.(err);\n });\n\n req.on('close', () => {\n this.onclose?.(1006);\n });\n }\n\n send(data: string): void {\n if (!this.socket) return;\n const payload = Buffer.from(data, 'utf-8');\n const len = payload.length;\n const maskKey = randomBytes(4);\n\n // RFC 6455: client MUST mask frames to server\n const masked = Buffer.alloc(len);\n for (let i = 0; i < len; i++) {\n masked[i] = payload[i] ^ maskKey[i % 4];\n }\n\n let header: Buffer;\n if (len < 126) {\n header = Buffer.alloc(6);\n header[0] = 0x81; // FIN + text opcode\n header[1] = 0x80 | len; // MASK bit set + length\n maskKey.copy(header, 2);\n } else if (len < 65536) {\n header = Buffer.alloc(8);\n header[0] = 0x81;\n header[1] = 0x80 | 126; // MASK + 126 marker\n header.writeUInt16BE(len, 2);\n maskKey.copy(header, 4);\n } else {\n header = Buffer.alloc(14);\n header[0] = 0x81;\n header[1] = 0x80 | 127; // MASK + 127 marker\n header.writeUInt32BE(0, 2);\n header.writeUInt32BE(len, 6);\n maskKey.copy(header, 10);\n }\n\n this.socket.write(Buffer.concat([header, masked]));\n }\n\n close(): void {\n if (!this.socket) return;\n const frame = Buffer.from([0x88, 0x00]); // close frame\n this.socket.write(frame);\n this.socket.destroy();\n this.socket = null;\n }\n}\n\n// ─── Bridge Client ──────────────────────────────────────────────────────────\n\nexport class BridgeClient {\n private ws: SimpleWs | null = null;\n private running = false;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private config: BridgeConfig) {}\n\n /**\n * Start the bridge — connect WebSocket and begin serving proxy requests.\n * Returns a promise that resolves when connected. Reconnects on disconnect.\n */\n async start(): Promise<void> {\n this.running = true;\n\n return new Promise((resolve, reject) => {\n this.ws = new SimpleWs();\n\n let firstConnect = true;\n\n this.ws.onopen = () => {\n log.info('Bridge connected');\n // Register with the gateway\n this.ws!.send(JSON.stringify({\n type: 'agent:register',\n agentId: this.config.agentId,\n agentCard: this.config.agentCard,\n }));\n };\n\n this.ws.onmessage = async (raw: string) => {\n let msg: Record<string, unknown>;\n try {\n msg = JSON.parse(raw);\n } catch {\n return;\n }\n\n if (msg.type === 'agent:registered') {\n log.info('Agent registered via bridge', { agentId: this.config.agentId });\n if (firstConnect) {\n firstConnect = false;\n resolve();\n }\n return;\n }\n\n if (msg.type === 'http:request') {\n this.handleHttpRequest(msg);\n }\n };\n\n this.ws.onclose = () => {\n log.warn('Bridge disconnected, reconnecting...');\n if (this.running) {\n this.reconnectTimer = setTimeout(\n () => this.start().catch(() => {}),\n this.config.reconnectDelay ?? 5000,\n );\n }\n };\n\n this.ws.onerror = (err: Error) => {\n log.error('Bridge error', { error: err.message });\n if (firstConnect) {\n firstConnect = false;\n reject(err);\n }\n };\n\n log.info('Connecting bridge', { url: this.config.bridgeUrl });\n this.ws.connect(this.config.bridgeUrl, this.config.apiKey);\n });\n }\n\n /**\n * Stop the bridge.\n */\n stop(): void {\n this.running = false;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n this.ws?.close();\n this.ws = null;\n log.info('Bridge stopped');\n }\n\n // ── Internal ────────────────────────────────────────────────────────────\n\n private async handleHttpRequest(msg: Record<string, unknown>): Promise<void> {\n const requestId = msg.requestId as string;\n const method = (msg.method as string) ?? 'GET';\n const path = (msg.path as string) ?? '/';\n const headers = (msg.headers as Record<string, string>) ?? {};\n const body = (msg.body as string) ?? '';\n\n const url = `${this.config.localBaseUrl}${path}`;\n\n try {\n const fetchHeaders: Record<string, string> = { ...headers };\n const fetchOpts: RequestInit = {\n method,\n headers: fetchHeaders,\n signal: AbortSignal.timeout(300_000),\n };\n\n if (body && method !== 'GET') {\n fetchOpts.body = body;\n }\n\n const resp = await fetch(url, fetchOpts);\n\n const responseBody = await resp.text();\n\n const responseHeaders: Record<string, string> = {};\n resp.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n this.ws?.send(JSON.stringify({\n type: 'http:response',\n requestId,\n status: resp.status,\n headers: responseHeaders,\n body: responseBody,\n }));\n } catch (err) {\n log.error('Proxy request failed', { url, error: (err as Error).message });\n this.ws?.send(JSON.stringify({\n type: 'http:error',\n requestId,\n message: (err as Error).message,\n }));\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/executors/index.ts
|
|
2
|
+
import { TaskState } from "@a2a-js/sdk";
|
|
3
|
+
import { v4 } from "uuid";
|
|
4
|
+
var registry = /* @__PURE__ */ new Map();
|
|
5
|
+
function registerExecutor(provider, factory) {
|
|
6
|
+
registry.set(provider, factory);
|
|
7
|
+
}
|
|
8
|
+
function getExecutor(provider) {
|
|
9
|
+
return registry.get(provider);
|
|
10
|
+
}
|
|
11
|
+
function createExecutor(config) {
|
|
12
|
+
const factory = registry.get(config.provider);
|
|
13
|
+
if (!factory) {
|
|
14
|
+
const available = Array.from(registry.keys()).join(", ");
|
|
15
|
+
throw new Error(`Unknown provider: ${config.provider}. Available: ${available}`);
|
|
16
|
+
}
|
|
17
|
+
return factory(config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
registerExecutor,
|
|
22
|
+
getExecutor,
|
|
23
|
+
createExecutor,
|
|
24
|
+
TaskState,
|
|
25
|
+
v4
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=chunk-35NFMGMS.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/executors/index.ts"],"sourcesContent":["/**\n * Executor types and common interfaces for CLI A2A providers.\n *\n * Each provider (OpenCode, Codex, Claude) implements its own executor\n * conforming to the A2AExecutor interface which extends the SDK's\n * AgentExecutor with lifecycle methods (initialize, shutdown).\n */\n\nimport type { AgentExecutor } from '@a2a-js/sdk/server';\n\n/**\n * Extended executor interface with lifecycle methods.\n *\n * The base AgentExecutor from @a2a-js/sdk only defines execute() and\n * cancelTask(). We add initialize() for server setup and shutdown()\n * for graceful cleanup.\n */\nexport interface A2AExecutor extends AgentExecutor {\n initialize(): Promise<void>;\n shutdown(): Promise<void>;\n}\n\nexport type { AgentExecutor, RequestContext, ExecutionEventBus } from '@a2a-js/sdk/server';\nexport { TaskState } from '@a2a-js/sdk';\nexport { v4 as uuidv4 } from 'uuid';\n\nimport type { AgentConfig, ProviderType } from '../config/types.js';\n\n/**\n * Factory function type: given resolved config, produce an executor.\n */\nexport type ExecutorFactory = (config: Required<AgentConfig>) => A2AExecutor;\n\n/**\n * Map provider type to executor factory.\n */\nconst registry = new Map<ProviderType, ExecutorFactory>();\n\nexport function registerExecutor(provider: ProviderType, factory: ExecutorFactory): void {\n registry.set(provider, factory);\n}\n\nexport function getExecutor(provider: ProviderType): ExecutorFactory | undefined {\n return registry.get(provider);\n}\n\nexport function createExecutor(config: Required<AgentConfig>): A2AExecutor {\n const factory = registry.get(config.provider);\n if (!factory) {\n const available = Array.from(registry.keys()).join(', ');\n throw new Error(`Unknown provider: ${config.provider}. Available: ${available}`);\n }\n return factory(config);\n}\n"],"mappings":";AAuBA,SAAS,iBAAiB;AAC1B,SAAe,UAAc;AAY7B,IAAM,WAAW,oBAAI,IAAmC;AAEjD,SAAS,iBAAiB,UAAwB,SAAgC;AACvF,WAAS,IAAI,UAAU,OAAO;AAChC;AAEO,SAAS,YAAY,UAAqD;AAC/E,SAAO,SAAS,IAAI,QAAQ;AAC9B;AAEO,SAAS,eAAe,QAA4C;AACzE,QAAM,UAAU,SAAS,IAAI,OAAO,QAAQ;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,YAAY,MAAM,KAAK,SAAS,KAAK,CAAC,EAAE,KAAK,IAAI;AACvD,UAAM,IAAI,MAAM,qBAAqB,OAAO,QAAQ,gBAAgB,SAAS,EAAE;AAAA,EACjF;AACA,SAAO,QAAQ,MAAM;AACvB;","names":[]}
|