@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
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1562 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
// src/logger.ts
|
|
34
|
+
var LogLevel, Logger, logger;
|
|
35
|
+
var init_logger = __esm({
|
|
36
|
+
"src/logger.ts"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
39
|
+
LogLevel2[LogLevel2["DEBUG"] = 10] = "DEBUG";
|
|
40
|
+
LogLevel2[LogLevel2["INFO"] = 20] = "INFO";
|
|
41
|
+
LogLevel2[LogLevel2["WARN"] = 30] = "WARN";
|
|
42
|
+
LogLevel2[LogLevel2["ERROR"] = 40] = "ERROR";
|
|
43
|
+
return LogLevel2;
|
|
44
|
+
})(LogLevel || {});
|
|
45
|
+
Logger = class _Logger {
|
|
46
|
+
constructor(name) {
|
|
47
|
+
this.level = 20 /* INFO */;
|
|
48
|
+
this.name = name;
|
|
49
|
+
}
|
|
50
|
+
setLevel(level) {
|
|
51
|
+
this.level = level;
|
|
52
|
+
}
|
|
53
|
+
child(name) {
|
|
54
|
+
const childLogger = new _Logger(`${this.name}:${name}`);
|
|
55
|
+
childLogger.level = this.level;
|
|
56
|
+
return childLogger;
|
|
57
|
+
}
|
|
58
|
+
debug(msg, ctx) {
|
|
59
|
+
this.log(10 /* DEBUG */, msg, ctx);
|
|
60
|
+
}
|
|
61
|
+
info(msg, ctx) {
|
|
62
|
+
this.log(20 /* INFO */, msg, ctx);
|
|
63
|
+
}
|
|
64
|
+
warn(msg, ctx) {
|
|
65
|
+
this.log(30 /* WARN */, msg, ctx);
|
|
66
|
+
}
|
|
67
|
+
error(msg, ctx) {
|
|
68
|
+
this.log(40 /* ERROR */, msg, ctx);
|
|
69
|
+
}
|
|
70
|
+
log(level, msg, ctx) {
|
|
71
|
+
if (level < this.level) return;
|
|
72
|
+
const entry = {
|
|
73
|
+
level,
|
|
74
|
+
msg,
|
|
75
|
+
ctx: { name: this.name, ...ctx },
|
|
76
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
77
|
+
};
|
|
78
|
+
const output = process.env.NODE_ENV === "production" ? JSON.stringify(entry) : `${entry.ts} [${LogLevel[entry.level]}] ${entry.ctx.name}: ${msg}${ctx && Object.keys(ctx).length > 1 ? " " + JSON.stringify(ctx) : ""}`;
|
|
79
|
+
if (level >= 30 /* WARN */) {
|
|
80
|
+
process.stderr.write(output + "\n");
|
|
81
|
+
} else {
|
|
82
|
+
process.stdout.write(output + "\n");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
logger = new Logger("cli-a2a");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// src/executors/index.ts
|
|
91
|
+
var executors_exports = {};
|
|
92
|
+
__export(executors_exports, {
|
|
93
|
+
TaskState: () => import_sdk.TaskState,
|
|
94
|
+
createExecutor: () => createExecutor,
|
|
95
|
+
getExecutor: () => getExecutor,
|
|
96
|
+
registerExecutor: () => registerExecutor,
|
|
97
|
+
uuidv4: () => import_uuid.v4
|
|
98
|
+
});
|
|
99
|
+
function registerExecutor(provider, factory) {
|
|
100
|
+
registry.set(provider, factory);
|
|
101
|
+
}
|
|
102
|
+
function getExecutor(provider) {
|
|
103
|
+
return registry.get(provider);
|
|
104
|
+
}
|
|
105
|
+
function createExecutor(config) {
|
|
106
|
+
const factory = registry.get(config.provider);
|
|
107
|
+
if (!factory) {
|
|
108
|
+
const available = Array.from(registry.keys()).join(", ");
|
|
109
|
+
throw new Error(`Unknown provider: ${config.provider}. Available: ${available}`);
|
|
110
|
+
}
|
|
111
|
+
return factory(config);
|
|
112
|
+
}
|
|
113
|
+
var import_sdk, import_uuid, registry;
|
|
114
|
+
var init_executors = __esm({
|
|
115
|
+
"src/executors/index.ts"() {
|
|
116
|
+
"use strict";
|
|
117
|
+
import_sdk = require("@a2a-js/sdk");
|
|
118
|
+
import_uuid = require("uuid");
|
|
119
|
+
registry = /* @__PURE__ */ new Map();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// src/executors/opencode/client.ts
|
|
124
|
+
var import_node_child_process, log4, OpenCodeClient;
|
|
125
|
+
var init_client = __esm({
|
|
126
|
+
"src/executors/opencode/client.ts"() {
|
|
127
|
+
"use strict";
|
|
128
|
+
import_node_child_process = require("child_process");
|
|
129
|
+
init_logger();
|
|
130
|
+
log4 = logger.child("opencode:client");
|
|
131
|
+
OpenCodeClient = class {
|
|
132
|
+
constructor(config) {
|
|
133
|
+
this.config = config;
|
|
134
|
+
this.currentChild = null;
|
|
135
|
+
}
|
|
136
|
+
/** Abort the currently running child process (if any). */
|
|
137
|
+
abort() {
|
|
138
|
+
if (this.currentChild) {
|
|
139
|
+
this.currentChild.kill("SIGTERM");
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
if (this.currentChild?.exitCode === null) {
|
|
142
|
+
this.currentChild.kill("SIGKILL");
|
|
143
|
+
}
|
|
144
|
+
}, 5e3);
|
|
145
|
+
this.currentChild = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Execute a prompt via the OpenCode CLI and return the response text.
|
|
150
|
+
*/
|
|
151
|
+
async execute(prompt) {
|
|
152
|
+
const cliPath = this.config.cliPath || "opencode";
|
|
153
|
+
const timeout = this.config.timeout ?? 6e5;
|
|
154
|
+
const MAX_PROMPT = 1e5;
|
|
155
|
+
if (prompt.length > MAX_PROMPT) {
|
|
156
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages or use a provider with HTTP API transport.`);
|
|
157
|
+
}
|
|
158
|
+
const env = {
|
|
159
|
+
PATH: process.env.PATH ?? "",
|
|
160
|
+
HOME: process.env.HOME ?? ""
|
|
161
|
+
};
|
|
162
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
163
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
164
|
+
}
|
|
165
|
+
for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
|
|
166
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
167
|
+
}
|
|
168
|
+
const args = ["run", prompt];
|
|
169
|
+
if (this.config.model) {
|
|
170
|
+
args.push("--model", this.config.model);
|
|
171
|
+
}
|
|
172
|
+
if (this.config.agent) {
|
|
173
|
+
args.push("--agent", this.config.agent);
|
|
174
|
+
}
|
|
175
|
+
if (this.config.attachUrl) {
|
|
176
|
+
args.push("--attach", this.config.attachUrl);
|
|
177
|
+
}
|
|
178
|
+
args.push("--format", "default");
|
|
179
|
+
args.push("--dangerously-skip-permissions");
|
|
180
|
+
log4.info("Spawning opencode", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
181
|
+
return new Promise((resolve3, reject) => {
|
|
182
|
+
const child = (0, import_node_child_process.spawn)(cliPath, args, {
|
|
183
|
+
cwd: this.config.workdir || process.cwd(),
|
|
184
|
+
env,
|
|
185
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
186
|
+
});
|
|
187
|
+
this.currentChild = child;
|
|
188
|
+
let stdout = "";
|
|
189
|
+
let stderr = "";
|
|
190
|
+
const MAX_OUTPUT = 1e7;
|
|
191
|
+
const timer = setTimeout(() => {
|
|
192
|
+
child.kill("SIGTERM");
|
|
193
|
+
const forceTimer = setTimeout(() => {
|
|
194
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
195
|
+
}, 5e3);
|
|
196
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
197
|
+
reject(new Error(`OpenCode execution timed out after ${timeout}ms`));
|
|
198
|
+
}, timeout);
|
|
199
|
+
child.stdout?.on("data", (chunk) => {
|
|
200
|
+
stdout += chunk.toString();
|
|
201
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
202
|
+
child.kill("SIGTERM");
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
205
|
+
}, 5e3);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
child.stderr?.on("data", (chunk) => {
|
|
209
|
+
stderr += chunk.toString();
|
|
210
|
+
log4.debug("OpenCode stderr", { text: chunk.toString().trim() });
|
|
211
|
+
});
|
|
212
|
+
child.on("close", (code) => {
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
this.currentChild = null;
|
|
215
|
+
if (code === 0) {
|
|
216
|
+
resolve3(stdout.trim());
|
|
217
|
+
} else {
|
|
218
|
+
const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
|
|
219
|
+
log4.warn("OpenCode non-zero exit", { code, stderr: errMsg });
|
|
220
|
+
reject(new Error(errMsg));
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
child.on("error", (err) => {
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
log4.error("OpenCode spawn failed", { error: err.message });
|
|
226
|
+
reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// src/executors/events.ts
|
|
235
|
+
function publishTask(bus, taskId, contextId) {
|
|
236
|
+
bus.publish({
|
|
237
|
+
kind: "task",
|
|
238
|
+
taskId,
|
|
239
|
+
contextId
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function publishStatus(bus, taskId, contextId, state, messageText, final) {
|
|
243
|
+
const event = {
|
|
244
|
+
kind: "task-status-update",
|
|
245
|
+
taskId,
|
|
246
|
+
contextId,
|
|
247
|
+
status: {
|
|
248
|
+
state,
|
|
249
|
+
message: messageText ? { role: "agent", parts: [{ kind: "text", text: messageText }] } : void 0
|
|
250
|
+
},
|
|
251
|
+
final: final ?? false
|
|
252
|
+
};
|
|
253
|
+
bus.publish(event);
|
|
254
|
+
}
|
|
255
|
+
function publishFinalArtifact(bus, taskId, contextId, text) {
|
|
256
|
+
const artifactId = (0, import_uuid2.v4)();
|
|
257
|
+
const event = {
|
|
258
|
+
kind: "task-artifact-update",
|
|
259
|
+
taskId,
|
|
260
|
+
contextId,
|
|
261
|
+
artifact: {
|
|
262
|
+
artifactId,
|
|
263
|
+
parts: [{ kind: "text", text }]
|
|
264
|
+
},
|
|
265
|
+
lastChunk: true
|
|
266
|
+
};
|
|
267
|
+
bus.publish(event);
|
|
268
|
+
}
|
|
269
|
+
var import_uuid2;
|
|
270
|
+
var init_events = __esm({
|
|
271
|
+
"src/executors/events.ts"() {
|
|
272
|
+
"use strict";
|
|
273
|
+
import_uuid2 = require("uuid");
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// src/executors/opencode/executor.ts
|
|
278
|
+
var executor_exports = {};
|
|
279
|
+
__export(executor_exports, {
|
|
280
|
+
OpenCodeExecutor: () => OpenCodeExecutor,
|
|
281
|
+
createOpenCodeExecutor: () => createOpenCodeExecutor
|
|
282
|
+
});
|
|
283
|
+
function createOpenCodeExecutor(config) {
|
|
284
|
+
return new OpenCodeExecutor(config);
|
|
285
|
+
}
|
|
286
|
+
var log5, OpenCodeExecutor;
|
|
287
|
+
var init_executor = __esm({
|
|
288
|
+
"src/executors/opencode/executor.ts"() {
|
|
289
|
+
"use strict";
|
|
290
|
+
init_logger();
|
|
291
|
+
init_client();
|
|
292
|
+
init_events();
|
|
293
|
+
log5 = logger.child("opencode:executor");
|
|
294
|
+
OpenCodeExecutor = class {
|
|
295
|
+
constructor(config) {
|
|
296
|
+
this.client = null;
|
|
297
|
+
this.initialized = false;
|
|
298
|
+
this.config = config;
|
|
299
|
+
}
|
|
300
|
+
async initialize() {
|
|
301
|
+
if (this.initialized) return;
|
|
302
|
+
const oc = this.config.opencode;
|
|
303
|
+
this.client = new OpenCodeClient({
|
|
304
|
+
cliPath: "opencode",
|
|
305
|
+
workdir: oc.projectDirectory || process.cwd(),
|
|
306
|
+
model: oc.model || void 0,
|
|
307
|
+
agent: oc.agent || void 0,
|
|
308
|
+
attachUrl: oc.baseUrl || void 0,
|
|
309
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
310
|
+
});
|
|
311
|
+
this.initialized = true;
|
|
312
|
+
log5.info("Executor initialized", { workdir: oc.projectDirectory, model: oc.model });
|
|
313
|
+
}
|
|
314
|
+
async shutdown() {
|
|
315
|
+
this.client = null;
|
|
316
|
+
this.initialized = false;
|
|
317
|
+
log5.info("Executor shut down");
|
|
318
|
+
}
|
|
319
|
+
async execute(ctx, bus) {
|
|
320
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
321
|
+
await this.initialize();
|
|
322
|
+
try {
|
|
323
|
+
if (!task) {
|
|
324
|
+
publishTask(bus, taskId, contextId);
|
|
325
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
326
|
+
}
|
|
327
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
328
|
+
const promptText = this.extractText(userMessage);
|
|
329
|
+
log5.info("Sending prompt to OpenCode", { taskId, len: promptText.length });
|
|
330
|
+
const response = await this.client.execute(promptText);
|
|
331
|
+
const finalText = response || "No response from OpenCode.";
|
|
332
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
333
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
334
|
+
bus.finished();
|
|
335
|
+
log5.info("Task completed", { taskId, len: finalText.length });
|
|
336
|
+
} catch (error) {
|
|
337
|
+
const msg = error.message ?? String(error);
|
|
338
|
+
log5.error("Execution failed", { taskId, error: msg });
|
|
339
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
340
|
+
bus.finished();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async cancelTask(taskId, bus) {
|
|
344
|
+
log5.info("Cancel requested for OpenCode task", { taskId });
|
|
345
|
+
this.client?.abort();
|
|
346
|
+
publishStatus(bus, taskId, "", "canceled", "OpenCode task cancelled", true);
|
|
347
|
+
bus.finished();
|
|
348
|
+
}
|
|
349
|
+
extractText(message) {
|
|
350
|
+
return message.parts.filter((p) => {
|
|
351
|
+
const part = p;
|
|
352
|
+
const text = part.text;
|
|
353
|
+
return text !== void 0 && text !== null;
|
|
354
|
+
}).map((p) => p.text).join("\n");
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// src/executors/codex/client.ts
|
|
361
|
+
var import_node_child_process2, log6, CodexClient;
|
|
362
|
+
var init_client2 = __esm({
|
|
363
|
+
"src/executors/codex/client.ts"() {
|
|
364
|
+
"use strict";
|
|
365
|
+
import_node_child_process2 = require("child_process");
|
|
366
|
+
init_logger();
|
|
367
|
+
log6 = logger.child("codex:client");
|
|
368
|
+
CodexClient = class {
|
|
369
|
+
constructor(config) {
|
|
370
|
+
this.config = config;
|
|
371
|
+
this.currentChild = null;
|
|
372
|
+
}
|
|
373
|
+
abort() {
|
|
374
|
+
if (this.currentChild) {
|
|
375
|
+
this.currentChild.kill("SIGTERM");
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
if (this.currentChild?.exitCode === null) {
|
|
378
|
+
this.currentChild.kill("SIGKILL");
|
|
379
|
+
}
|
|
380
|
+
}, 5e3);
|
|
381
|
+
this.currentChild = null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Execute a prompt via the Codex CLI and return the response text.
|
|
386
|
+
*/
|
|
387
|
+
async execute(prompt) {
|
|
388
|
+
const cliPath = this.config.cliPath || "codex";
|
|
389
|
+
const timeout = this.config.timeout ?? 3e5;
|
|
390
|
+
const MAX_PROMPT = 1e5;
|
|
391
|
+
if (prompt.length > MAX_PROMPT) {
|
|
392
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
393
|
+
}
|
|
394
|
+
const env = {
|
|
395
|
+
PATH: process.env.PATH ?? "",
|
|
396
|
+
HOME: process.env.HOME ?? ""
|
|
397
|
+
};
|
|
398
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
399
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
400
|
+
}
|
|
401
|
+
if (this.config.apiKey) {
|
|
402
|
+
env["OPENAI_API_KEY"] = this.config.apiKey;
|
|
403
|
+
}
|
|
404
|
+
const args = [];
|
|
405
|
+
if (this.config.model) {
|
|
406
|
+
args.push("--model", this.config.model);
|
|
407
|
+
}
|
|
408
|
+
args.push(prompt);
|
|
409
|
+
log6.info("Spawning codex", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
410
|
+
return new Promise((resolve3, reject) => {
|
|
411
|
+
const child = (0, import_node_child_process2.spawn)(cliPath, args, {
|
|
412
|
+
cwd: this.config.workdir || process.cwd(),
|
|
413
|
+
env,
|
|
414
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
415
|
+
});
|
|
416
|
+
this.currentChild = child;
|
|
417
|
+
let stdout = "";
|
|
418
|
+
let stderr = "";
|
|
419
|
+
const MAX_OUTPUT = 1e7;
|
|
420
|
+
const timer = setTimeout(() => {
|
|
421
|
+
child.kill("SIGTERM");
|
|
422
|
+
const forceTimer = setTimeout(() => {
|
|
423
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
424
|
+
}, 5e3);
|
|
425
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
426
|
+
reject(new Error(`Codex execution timed out after ${timeout}ms`));
|
|
427
|
+
}, timeout);
|
|
428
|
+
child.stdout?.on("data", (chunk) => {
|
|
429
|
+
stdout += chunk.toString();
|
|
430
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
431
|
+
child.kill("SIGTERM");
|
|
432
|
+
setTimeout(() => {
|
|
433
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
434
|
+
}, 5e3);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
child.stderr?.on("data", (chunk) => {
|
|
438
|
+
stderr += chunk.toString();
|
|
439
|
+
log6.debug("Codex stderr", { text: chunk.toString().trim() });
|
|
440
|
+
});
|
|
441
|
+
child.on("close", (code) => {
|
|
442
|
+
clearTimeout(timer);
|
|
443
|
+
this.currentChild = null;
|
|
444
|
+
if (code === 0) {
|
|
445
|
+
resolve3(stdout.trim());
|
|
446
|
+
} else {
|
|
447
|
+
const errMsg = stderr.trim() || stdout.trim() || `Codex exited with code ${code}`;
|
|
448
|
+
log6.warn("Codex non-zero exit", { code, stderr: errMsg });
|
|
449
|
+
reject(new Error(errMsg));
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
child.on("error", (err) => {
|
|
453
|
+
clearTimeout(timer);
|
|
454
|
+
log6.error("Codex spawn failed", { error: err.message });
|
|
455
|
+
reject(new Error(`Failed to start codex: ${err.message}. Is it installed? (npm i -g @openai/codex)`));
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// src/executors/codex/executor.ts
|
|
464
|
+
var executor_exports2 = {};
|
|
465
|
+
__export(executor_exports2, {
|
|
466
|
+
CodexExecutor: () => CodexExecutor,
|
|
467
|
+
createCodexExecutor: () => createCodexExecutor
|
|
468
|
+
});
|
|
469
|
+
function createCodexExecutor(config) {
|
|
470
|
+
return new CodexExecutor(config);
|
|
471
|
+
}
|
|
472
|
+
var log7, CodexExecutor;
|
|
473
|
+
var init_executor2 = __esm({
|
|
474
|
+
"src/executors/codex/executor.ts"() {
|
|
475
|
+
"use strict";
|
|
476
|
+
init_logger();
|
|
477
|
+
init_client2();
|
|
478
|
+
init_events();
|
|
479
|
+
log7 = logger.child("codex:executor");
|
|
480
|
+
CodexExecutor = class {
|
|
481
|
+
constructor(config) {
|
|
482
|
+
this.client = null;
|
|
483
|
+
this.initialized = false;
|
|
484
|
+
this.config = config;
|
|
485
|
+
}
|
|
486
|
+
async initialize() {
|
|
487
|
+
if (this.initialized) return;
|
|
488
|
+
const cx = this.config.codex;
|
|
489
|
+
this.client = new CodexClient({
|
|
490
|
+
cliPath: cx.cliPath,
|
|
491
|
+
workdir: cx.workdir,
|
|
492
|
+
model: cx.model || void 0,
|
|
493
|
+
apiKey: cx.apiKey || void 0,
|
|
494
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
495
|
+
});
|
|
496
|
+
this.initialized = true;
|
|
497
|
+
log7.info("Executor initialized", { cliPath: cx.cliPath, workdir: cx.workdir });
|
|
498
|
+
}
|
|
499
|
+
async shutdown() {
|
|
500
|
+
this.client = null;
|
|
501
|
+
this.initialized = false;
|
|
502
|
+
log7.info("Executor shut down");
|
|
503
|
+
}
|
|
504
|
+
async execute(ctx, bus) {
|
|
505
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
506
|
+
await this.initialize();
|
|
507
|
+
try {
|
|
508
|
+
if (!task) {
|
|
509
|
+
publishTask(bus, taskId, contextId);
|
|
510
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
511
|
+
}
|
|
512
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
513
|
+
const promptText = this.extractText(userMessage);
|
|
514
|
+
log7.info("Sending prompt to Codex", { taskId, len: promptText.length });
|
|
515
|
+
const response = await this.client.execute(promptText);
|
|
516
|
+
const finalText = response || "No response from Codex.";
|
|
517
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
518
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
519
|
+
bus.finished();
|
|
520
|
+
log7.info("Task completed", { taskId, len: finalText.length });
|
|
521
|
+
} catch (error) {
|
|
522
|
+
const msg = error.message ?? String(error);
|
|
523
|
+
log7.error("Execution failed", { taskId, error: msg });
|
|
524
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
525
|
+
bus.finished();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async cancelTask(taskId, bus) {
|
|
529
|
+
log7.info("Cancel requested for Codex task", { taskId });
|
|
530
|
+
this.client?.abort();
|
|
531
|
+
publishStatus(bus, taskId, "", "canceled", "Codex task cancelled", true);
|
|
532
|
+
bus.finished();
|
|
533
|
+
}
|
|
534
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
535
|
+
extractText(message) {
|
|
536
|
+
return message.parts.filter((p) => {
|
|
537
|
+
const part = p;
|
|
538
|
+
const text = part.text;
|
|
539
|
+
return text !== void 0 && text !== null;
|
|
540
|
+
}).map((p) => p.text).join("\n");
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// src/executors/claude/client.ts
|
|
547
|
+
var import_node_child_process3, log8, ClaudeClient;
|
|
548
|
+
var init_client3 = __esm({
|
|
549
|
+
"src/executors/claude/client.ts"() {
|
|
550
|
+
"use strict";
|
|
551
|
+
import_node_child_process3 = require("child_process");
|
|
552
|
+
init_logger();
|
|
553
|
+
log8 = logger.child("claude:client");
|
|
554
|
+
ClaudeClient = class {
|
|
555
|
+
constructor(config) {
|
|
556
|
+
this.config = config;
|
|
557
|
+
this.currentChild = null;
|
|
558
|
+
}
|
|
559
|
+
abort() {
|
|
560
|
+
if (this.currentChild) {
|
|
561
|
+
this.currentChild.kill("SIGTERM");
|
|
562
|
+
setTimeout(() => {
|
|
563
|
+
if (this.currentChild?.exitCode === null) {
|
|
564
|
+
this.currentChild.kill("SIGKILL");
|
|
565
|
+
}
|
|
566
|
+
}, 5e3);
|
|
567
|
+
this.currentChild = null;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Execute a prompt via the Claude Code CLI and return the response text.
|
|
572
|
+
*/
|
|
573
|
+
async execute(prompt) {
|
|
574
|
+
const cliPath = this.config.cliPath || "claude";
|
|
575
|
+
const timeout = this.config.timeout ?? 3e5;
|
|
576
|
+
const MAX_PROMPT = 1e5;
|
|
577
|
+
if (prompt.length > MAX_PROMPT) {
|
|
578
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
579
|
+
}
|
|
580
|
+
const env = {
|
|
581
|
+
PATH: process.env.PATH ?? "",
|
|
582
|
+
HOME: process.env.HOME ?? ""
|
|
583
|
+
};
|
|
584
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
585
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
586
|
+
}
|
|
587
|
+
if (this.config.apiKey) {
|
|
588
|
+
env["ANTHROPIC_API_KEY"] = this.config.apiKey;
|
|
589
|
+
}
|
|
590
|
+
const args = ["-p"];
|
|
591
|
+
if (this.config.model) {
|
|
592
|
+
args.push("--model", this.config.model);
|
|
593
|
+
}
|
|
594
|
+
args.push(prompt);
|
|
595
|
+
log8.info("Spawning claude", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
596
|
+
return new Promise((resolve3, reject) => {
|
|
597
|
+
const child = (0, import_node_child_process3.spawn)(cliPath, args, {
|
|
598
|
+
cwd: this.config.workdir || process.cwd(),
|
|
599
|
+
env,
|
|
600
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
601
|
+
});
|
|
602
|
+
this.currentChild = child;
|
|
603
|
+
let stdout = "";
|
|
604
|
+
let stderr = "";
|
|
605
|
+
const MAX_OUTPUT = 1e7;
|
|
606
|
+
const timer = setTimeout(() => {
|
|
607
|
+
child.kill("SIGTERM");
|
|
608
|
+
const forceTimer = setTimeout(() => {
|
|
609
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
610
|
+
}, 5e3);
|
|
611
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
612
|
+
reject(new Error(`Claude Code execution timed out after ${timeout}ms`));
|
|
613
|
+
}, timeout);
|
|
614
|
+
child.stdout?.on("data", (chunk) => {
|
|
615
|
+
stdout += chunk.toString();
|
|
616
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
617
|
+
child.kill("SIGTERM");
|
|
618
|
+
setTimeout(() => {
|
|
619
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
620
|
+
}, 5e3);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
child.stderr?.on("data", (chunk) => {
|
|
624
|
+
stderr += chunk.toString();
|
|
625
|
+
log8.debug("Claude stderr", { text: chunk.toString().trim() });
|
|
626
|
+
});
|
|
627
|
+
child.on("close", (code) => {
|
|
628
|
+
clearTimeout(timer);
|
|
629
|
+
this.currentChild = null;
|
|
630
|
+
if (code === 0) {
|
|
631
|
+
resolve3(stdout.trim());
|
|
632
|
+
} else {
|
|
633
|
+
const errMsg = stderr.trim() || stdout.trim() || `Claude Code exited with code ${code}`;
|
|
634
|
+
log8.warn("Claude non-zero exit", { code, stderr: errMsg });
|
|
635
|
+
reject(new Error(errMsg));
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
child.on("error", (err) => {
|
|
639
|
+
clearTimeout(timer);
|
|
640
|
+
log8.error("Claude spawn failed", { error: err.message });
|
|
641
|
+
reject(new Error(`Failed to start claude: ${err.message}. Install: npm i -g @anthropic-ai/claude-code`));
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// src/executors/claude/executor.ts
|
|
650
|
+
var executor_exports3 = {};
|
|
651
|
+
__export(executor_exports3, {
|
|
652
|
+
ClaudeExecutor: () => ClaudeExecutor,
|
|
653
|
+
createClaudeExecutor: () => createClaudeExecutor
|
|
654
|
+
});
|
|
655
|
+
function createClaudeExecutor(config) {
|
|
656
|
+
return new ClaudeExecutor(config);
|
|
657
|
+
}
|
|
658
|
+
var log9, ClaudeExecutor;
|
|
659
|
+
var init_executor3 = __esm({
|
|
660
|
+
"src/executors/claude/executor.ts"() {
|
|
661
|
+
"use strict";
|
|
662
|
+
init_logger();
|
|
663
|
+
init_client3();
|
|
664
|
+
init_events();
|
|
665
|
+
log9 = logger.child("claude:executor");
|
|
666
|
+
ClaudeExecutor = class {
|
|
667
|
+
constructor(config) {
|
|
668
|
+
this.client = null;
|
|
669
|
+
this.initialized = false;
|
|
670
|
+
this.config = config;
|
|
671
|
+
}
|
|
672
|
+
async initialize() {
|
|
673
|
+
if (this.initialized) return;
|
|
674
|
+
const cc = this.config.claude;
|
|
675
|
+
this.client = new ClaudeClient({
|
|
676
|
+
cliPath: cc.cliPath,
|
|
677
|
+
workdir: cc.workdir,
|
|
678
|
+
model: cc.model || void 0,
|
|
679
|
+
apiKey: cc.apiKey || void 0,
|
|
680
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
681
|
+
});
|
|
682
|
+
this.initialized = true;
|
|
683
|
+
log9.info("Executor initialized", { cliPath: cc.cliPath, workdir: cc.workdir });
|
|
684
|
+
}
|
|
685
|
+
async shutdown() {
|
|
686
|
+
this.client = null;
|
|
687
|
+
this.initialized = false;
|
|
688
|
+
log9.info("Executor shut down");
|
|
689
|
+
}
|
|
690
|
+
async execute(ctx, bus) {
|
|
691
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
692
|
+
await this.initialize();
|
|
693
|
+
try {
|
|
694
|
+
if (!task) {
|
|
695
|
+
publishTask(bus, taskId, contextId);
|
|
696
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
697
|
+
}
|
|
698
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
699
|
+
const promptText = this.extractText(userMessage);
|
|
700
|
+
log9.info("Sending prompt to Claude Code", { taskId, len: promptText.length });
|
|
701
|
+
const response = await this.client.execute(promptText);
|
|
702
|
+
const finalText = response || "No response from Claude Code.";
|
|
703
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
704
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
705
|
+
bus.finished();
|
|
706
|
+
log9.info("Task completed", { taskId, len: finalText.length });
|
|
707
|
+
} catch (error) {
|
|
708
|
+
const msg = error.message ?? String(error);
|
|
709
|
+
log9.error("Execution failed", { taskId, error: msg });
|
|
710
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
711
|
+
bus.finished();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async cancelTask(taskId, bus) {
|
|
715
|
+
log9.info("Cancel requested for Claude Code task", { taskId });
|
|
716
|
+
this.client?.abort();
|
|
717
|
+
publishStatus(bus, taskId, "", "canceled", "Claude Code task cancelled", true);
|
|
718
|
+
bus.finished();
|
|
719
|
+
}
|
|
720
|
+
extractText(message) {
|
|
721
|
+
return message.parts.filter((p) => {
|
|
722
|
+
const part = p;
|
|
723
|
+
const text = part.text;
|
|
724
|
+
return text !== void 0 && text !== null;
|
|
725
|
+
}).map((p) => p.text).join("\n");
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// src/bridge.ts
|
|
732
|
+
var bridge_exports = {};
|
|
733
|
+
__export(bridge_exports, {
|
|
734
|
+
BridgeClient: () => BridgeClient
|
|
735
|
+
});
|
|
736
|
+
var import_node_net, import_node_crypto, log10, SimpleWs, BridgeClient;
|
|
737
|
+
var init_bridge = __esm({
|
|
738
|
+
"src/bridge.ts"() {
|
|
739
|
+
"use strict";
|
|
740
|
+
import_node_net = require("net");
|
|
741
|
+
import_node_crypto = require("crypto");
|
|
742
|
+
init_logger();
|
|
743
|
+
log10 = logger.child("bridge");
|
|
744
|
+
SimpleWs = class {
|
|
745
|
+
constructor() {
|
|
746
|
+
this.socket = null;
|
|
747
|
+
this.buffer = Buffer.alloc(0);
|
|
748
|
+
this.handshaking = true;
|
|
749
|
+
this.httpBuffer = "";
|
|
750
|
+
}
|
|
751
|
+
connect(urlStr, apiKey) {
|
|
752
|
+
const url = new URL(urlStr);
|
|
753
|
+
const host = url.hostname;
|
|
754
|
+
const port = parseInt(url.port) || (url.protocol === "wss:" ? 443 : 80);
|
|
755
|
+
const path = url.pathname + url.search;
|
|
756
|
+
const key = (0, import_node_crypto.randomBytes)(16).toString("base64");
|
|
757
|
+
const req = (0, import_node_net.connect)({ host, port }, () => {
|
|
758
|
+
const headers = [
|
|
759
|
+
`GET ${path} HTTP/1.1`,
|
|
760
|
+
`Host: ${host}:${port}`,
|
|
761
|
+
"Upgrade: websocket",
|
|
762
|
+
"Connection: Upgrade",
|
|
763
|
+
`Sec-WebSocket-Key: ${key}`,
|
|
764
|
+
"Sec-WebSocket-Version: 13",
|
|
765
|
+
`x-api-key: ${apiKey}`,
|
|
766
|
+
"",
|
|
767
|
+
""
|
|
768
|
+
].join("\r\n");
|
|
769
|
+
if (this.socket) {
|
|
770
|
+
this.socket.write(headers);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
this.socket = req;
|
|
774
|
+
req.on("data", (chunk) => {
|
|
775
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
776
|
+
if (this.handshaking) {
|
|
777
|
+
this.httpBuffer += chunk.toString();
|
|
778
|
+
const headerEnd = this.httpBuffer.indexOf("\r\n\r\n");
|
|
779
|
+
if (headerEnd === -1) return;
|
|
780
|
+
const header = this.httpBuffer.substring(0, headerEnd);
|
|
781
|
+
const statusLine = header.split("\r\n")[0] || "";
|
|
782
|
+
if (!statusLine.startsWith("HTTP/1.1 101")) {
|
|
783
|
+
const bodyStart = headerEnd + 4;
|
|
784
|
+
const bodyText = this.httpBuffer.substring(bodyStart).trim();
|
|
785
|
+
const errMsg = statusLine ? `WebSocket upgrade rejected: ${statusLine}` : "WebSocket upgrade failed: no status line";
|
|
786
|
+
this.onerror?.(new Error(errMsg + (bodyText ? ` (${bodyText.slice(0, 200)})` : "")));
|
|
787
|
+
this.socket?.destroy();
|
|
788
|
+
this.socket = null;
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
this.handshaking = false;
|
|
792
|
+
const remaining = this.buffer.subarray(headerEnd + 4);
|
|
793
|
+
this.buffer = remaining;
|
|
794
|
+
this.httpBuffer = "";
|
|
795
|
+
if (this.onopen) this.onopen();
|
|
796
|
+
if (remaining.length === 0) return;
|
|
797
|
+
}
|
|
798
|
+
while (this.buffer.length >= 2) {
|
|
799
|
+
const opcode = this.buffer[0] & 15;
|
|
800
|
+
const masked = (this.buffer[1] & 128) !== 0;
|
|
801
|
+
let offset = 2;
|
|
802
|
+
let payloadLen = this.buffer[1] & 127;
|
|
803
|
+
if (payloadLen === 126) {
|
|
804
|
+
if (this.buffer.length < 4) break;
|
|
805
|
+
payloadLen = this.buffer.readUInt16BE(2);
|
|
806
|
+
offset = 4;
|
|
807
|
+
} else if (payloadLen === 127) {
|
|
808
|
+
if (this.buffer.length < 10) break;
|
|
809
|
+
const hi = this.buffer.readUInt32BE(2);
|
|
810
|
+
const lo = this.buffer.readUInt32BE(6);
|
|
811
|
+
payloadLen = hi * 4294967296 + lo;
|
|
812
|
+
offset = 10;
|
|
813
|
+
}
|
|
814
|
+
if (this.buffer.length < offset + payloadLen) break;
|
|
815
|
+
const payload = this.buffer.subarray(offset, offset + payloadLen);
|
|
816
|
+
this.buffer = this.buffer.subarray(offset + payloadLen);
|
|
817
|
+
if (masked) {
|
|
818
|
+
const mask = payload.subarray(0, 4);
|
|
819
|
+
const data = payload.subarray(4);
|
|
820
|
+
for (let i = 0; i < data.length; i++) {
|
|
821
|
+
data[i] ^= mask[i % 4];
|
|
822
|
+
}
|
|
823
|
+
if (opcode === 1) {
|
|
824
|
+
this.onmessage?.(data.toString());
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
if (opcode === 1) {
|
|
828
|
+
this.onmessage?.(payload.toString());
|
|
829
|
+
} else if (opcode === 8) {
|
|
830
|
+
this.onclose?.(1e3);
|
|
831
|
+
return;
|
|
832
|
+
} else if (opcode === 9) {
|
|
833
|
+
const pong = Buffer.from([138, 0]);
|
|
834
|
+
this.socket?.write(pong);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
req.on("error", (err) => {
|
|
840
|
+
this.onerror?.(err);
|
|
841
|
+
});
|
|
842
|
+
req.on("close", () => {
|
|
843
|
+
this.onclose?.(1006);
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
send(data) {
|
|
847
|
+
if (!this.socket) return;
|
|
848
|
+
const payload = Buffer.from(data, "utf-8");
|
|
849
|
+
const len = payload.length;
|
|
850
|
+
const maskKey = (0, import_node_crypto.randomBytes)(4);
|
|
851
|
+
const masked = Buffer.alloc(len);
|
|
852
|
+
for (let i = 0; i < len; i++) {
|
|
853
|
+
masked[i] = payload[i] ^ maskKey[i % 4];
|
|
854
|
+
}
|
|
855
|
+
let header;
|
|
856
|
+
if (len < 126) {
|
|
857
|
+
header = Buffer.alloc(6);
|
|
858
|
+
header[0] = 129;
|
|
859
|
+
header[1] = 128 | len;
|
|
860
|
+
maskKey.copy(header, 2);
|
|
861
|
+
} else if (len < 65536) {
|
|
862
|
+
header = Buffer.alloc(8);
|
|
863
|
+
header[0] = 129;
|
|
864
|
+
header[1] = 128 | 126;
|
|
865
|
+
header.writeUInt16BE(len, 2);
|
|
866
|
+
maskKey.copy(header, 4);
|
|
867
|
+
} else {
|
|
868
|
+
header = Buffer.alloc(14);
|
|
869
|
+
header[0] = 129;
|
|
870
|
+
header[1] = 128 | 127;
|
|
871
|
+
header.writeUInt32BE(0, 2);
|
|
872
|
+
header.writeUInt32BE(len, 6);
|
|
873
|
+
maskKey.copy(header, 10);
|
|
874
|
+
}
|
|
875
|
+
this.socket.write(Buffer.concat([header, masked]));
|
|
876
|
+
}
|
|
877
|
+
close() {
|
|
878
|
+
if (!this.socket) return;
|
|
879
|
+
const frame = Buffer.from([136, 0]);
|
|
880
|
+
this.socket.write(frame);
|
|
881
|
+
this.socket.destroy();
|
|
882
|
+
this.socket = null;
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
BridgeClient = class {
|
|
886
|
+
constructor(config) {
|
|
887
|
+
this.config = config;
|
|
888
|
+
this.ws = null;
|
|
889
|
+
this.running = false;
|
|
890
|
+
this.reconnectTimer = null;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Start the bridge — connect WebSocket and begin serving proxy requests.
|
|
894
|
+
* Returns a promise that resolves when connected. Reconnects on disconnect.
|
|
895
|
+
*/
|
|
896
|
+
async start() {
|
|
897
|
+
this.running = true;
|
|
898
|
+
return new Promise((resolve3, reject) => {
|
|
899
|
+
this.ws = new SimpleWs();
|
|
900
|
+
let firstConnect = true;
|
|
901
|
+
this.ws.onopen = () => {
|
|
902
|
+
log10.info("Bridge connected");
|
|
903
|
+
this.ws.send(JSON.stringify({
|
|
904
|
+
type: "agent:register",
|
|
905
|
+
agentId: this.config.agentId,
|
|
906
|
+
agentCard: this.config.agentCard
|
|
907
|
+
}));
|
|
908
|
+
};
|
|
909
|
+
this.ws.onmessage = async (raw) => {
|
|
910
|
+
let msg;
|
|
911
|
+
try {
|
|
912
|
+
msg = JSON.parse(raw);
|
|
913
|
+
} catch {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (msg.type === "agent:registered") {
|
|
917
|
+
log10.info("Agent registered via bridge", { agentId: this.config.agentId });
|
|
918
|
+
if (firstConnect) {
|
|
919
|
+
firstConnect = false;
|
|
920
|
+
resolve3();
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (msg.type === "http:request") {
|
|
925
|
+
this.handleHttpRequest(msg);
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
this.ws.onclose = () => {
|
|
929
|
+
log10.warn("Bridge disconnected, reconnecting...");
|
|
930
|
+
if (this.running) {
|
|
931
|
+
this.reconnectTimer = setTimeout(
|
|
932
|
+
() => this.start().catch(() => {
|
|
933
|
+
}),
|
|
934
|
+
this.config.reconnectDelay ?? 5e3
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
this.ws.onerror = (err) => {
|
|
939
|
+
log10.error("Bridge error", { error: err.message });
|
|
940
|
+
if (firstConnect) {
|
|
941
|
+
firstConnect = false;
|
|
942
|
+
reject(err);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
log10.info("Connecting bridge", { url: this.config.bridgeUrl });
|
|
946
|
+
this.ws.connect(this.config.bridgeUrl, this.config.apiKey);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Stop the bridge.
|
|
951
|
+
*/
|
|
952
|
+
stop() {
|
|
953
|
+
this.running = false;
|
|
954
|
+
if (this.reconnectTimer) {
|
|
955
|
+
clearTimeout(this.reconnectTimer);
|
|
956
|
+
this.reconnectTimer = null;
|
|
957
|
+
}
|
|
958
|
+
this.ws?.close();
|
|
959
|
+
this.ws = null;
|
|
960
|
+
log10.info("Bridge stopped");
|
|
961
|
+
}
|
|
962
|
+
// ── Internal ────────────────────────────────────────────────────────────
|
|
963
|
+
async handleHttpRequest(msg) {
|
|
964
|
+
const requestId = msg.requestId;
|
|
965
|
+
const method = msg.method ?? "GET";
|
|
966
|
+
const path = msg.path ?? "/";
|
|
967
|
+
const headers = msg.headers ?? {};
|
|
968
|
+
const body = msg.body ?? "";
|
|
969
|
+
const url = `${this.config.localBaseUrl}${path}`;
|
|
970
|
+
try {
|
|
971
|
+
const fetchHeaders = { ...headers };
|
|
972
|
+
const fetchOpts = {
|
|
973
|
+
method,
|
|
974
|
+
headers: fetchHeaders,
|
|
975
|
+
signal: AbortSignal.timeout(3e5)
|
|
976
|
+
};
|
|
977
|
+
if (body && method !== "GET") {
|
|
978
|
+
fetchOpts.body = body;
|
|
979
|
+
}
|
|
980
|
+
const resp = await fetch(url, fetchOpts);
|
|
981
|
+
const responseBody = await resp.text();
|
|
982
|
+
const responseHeaders = {};
|
|
983
|
+
resp.headers.forEach((value, key) => {
|
|
984
|
+
responseHeaders[key] = value;
|
|
985
|
+
});
|
|
986
|
+
this.ws?.send(JSON.stringify({
|
|
987
|
+
type: "http:response",
|
|
988
|
+
requestId,
|
|
989
|
+
status: resp.status,
|
|
990
|
+
headers: responseHeaders,
|
|
991
|
+
body: responseBody
|
|
992
|
+
}));
|
|
993
|
+
} catch (err) {
|
|
994
|
+
log10.error("Proxy request failed", { url, error: err.message });
|
|
995
|
+
this.ws?.send(JSON.stringify({
|
|
996
|
+
type: "http:error",
|
|
997
|
+
requestId,
|
|
998
|
+
message: err.message
|
|
999
|
+
}));
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
// src/cli.ts
|
|
1007
|
+
var import_node_util = require("util");
|
|
1008
|
+
var import_node_path2 = require("path");
|
|
1009
|
+
|
|
1010
|
+
// src/config/loader.ts
|
|
1011
|
+
var import_node_fs = require("fs");
|
|
1012
|
+
var import_node_path = require("path");
|
|
1013
|
+
|
|
1014
|
+
// src/config/defaults.ts
|
|
1015
|
+
function withDefaults(partial, defaults) {
|
|
1016
|
+
const result = { ...defaults };
|
|
1017
|
+
for (const key of Object.keys(partial)) {
|
|
1018
|
+
const val = partial[key];
|
|
1019
|
+
if (val !== void 0 && val !== null) {
|
|
1020
|
+
if (typeof val === "object" && !Array.isArray(val) && typeof defaults[key] === "object" && !Array.isArray(defaults[key])) {
|
|
1021
|
+
result[key] = {
|
|
1022
|
+
...defaults[key],
|
|
1023
|
+
...val
|
|
1024
|
+
};
|
|
1025
|
+
} else {
|
|
1026
|
+
result[key] = val;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return result;
|
|
1031
|
+
}
|
|
1032
|
+
var DEFAULTS = Object.freeze({
|
|
1033
|
+
provider: "opencode",
|
|
1034
|
+
agentCard: {
|
|
1035
|
+
name: "CLI A2A Agent",
|
|
1036
|
+
description: "A multi-provider A2A agent driven by local CLI coding tools.",
|
|
1037
|
+
protocolVersion: "0.3.0",
|
|
1038
|
+
version: "0.1.0",
|
|
1039
|
+
skills: [],
|
|
1040
|
+
defaultInputModes: ["text"],
|
|
1041
|
+
defaultOutputModes: ["text"],
|
|
1042
|
+
streaming: true,
|
|
1043
|
+
pushNotifications: false
|
|
1044
|
+
},
|
|
1045
|
+
server: {
|
|
1046
|
+
port: 3e3,
|
|
1047
|
+
hostname: "0.0.0.0",
|
|
1048
|
+
advertiseHost: "localhost",
|
|
1049
|
+
advertiseProtocol: "http"
|
|
1050
|
+
},
|
|
1051
|
+
session: {
|
|
1052
|
+
titlePrefix: "A2A Session",
|
|
1053
|
+
reuseByContext: true,
|
|
1054
|
+
ttl: 36e5,
|
|
1055
|
+
cleanupInterval: 3e5
|
|
1056
|
+
},
|
|
1057
|
+
features: {
|
|
1058
|
+
autoApprovePermissions: true,
|
|
1059
|
+
autoAnswerQuestions: true,
|
|
1060
|
+
streamArtifactChunks: false,
|
|
1061
|
+
enablePollingFallback: true
|
|
1062
|
+
},
|
|
1063
|
+
timeouts: {
|
|
1064
|
+
prompt: 6e5,
|
|
1065
|
+
pollingInterval: 2e3,
|
|
1066
|
+
healthCheck: 3e4
|
|
1067
|
+
},
|
|
1068
|
+
logging: {
|
|
1069
|
+
level: "info"
|
|
1070
|
+
},
|
|
1071
|
+
opencode: {
|
|
1072
|
+
baseUrl: "http://localhost:4096",
|
|
1073
|
+
projectDirectory: "",
|
|
1074
|
+
model: "",
|
|
1075
|
+
agent: "",
|
|
1076
|
+
systemPrompt: "",
|
|
1077
|
+
systemPromptMode: "append",
|
|
1078
|
+
contextFile: "context.md",
|
|
1079
|
+
contextPrompt: ""
|
|
1080
|
+
},
|
|
1081
|
+
codex: {
|
|
1082
|
+
cliPath: "codex",
|
|
1083
|
+
workdir: process.cwd(),
|
|
1084
|
+
model: "",
|
|
1085
|
+
apiKey: ""
|
|
1086
|
+
},
|
|
1087
|
+
claude: {
|
|
1088
|
+
cliPath: "claude",
|
|
1089
|
+
workdir: process.cwd(),
|
|
1090
|
+
model: "",
|
|
1091
|
+
apiKey: ""
|
|
1092
|
+
},
|
|
1093
|
+
configDir: void 0
|
|
1094
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1095
|
+
});
|
|
1096
|
+
function resolveDefaults(overrides) {
|
|
1097
|
+
return withDefaults(overrides, DEFAULTS);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// src/config/loader.ts
|
|
1101
|
+
init_logger();
|
|
1102
|
+
var log = logger.child("config");
|
|
1103
|
+
function loadConfigFile(filePath) {
|
|
1104
|
+
const absPath = (0, import_node_path.resolve)(filePath);
|
|
1105
|
+
if (!(0, import_node_fs.existsSync)(absPath)) {
|
|
1106
|
+
throw new Error(`Config file not found: ${absPath}`);
|
|
1107
|
+
}
|
|
1108
|
+
try {
|
|
1109
|
+
const raw = (0, import_node_fs.readFileSync)(absPath, "utf-8");
|
|
1110
|
+
return JSON.parse(raw);
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
const msg = err instanceof SyntaxError ? `Invalid JSON in config file: ${err.message}` : `Failed to read config file: ${err.message}`;
|
|
1113
|
+
throw new Error(msg);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function loadEnvOverrides() {
|
|
1117
|
+
const overrides = {
|
|
1118
|
+
opencode: {},
|
|
1119
|
+
codex: {},
|
|
1120
|
+
claude: {}
|
|
1121
|
+
};
|
|
1122
|
+
if (process.env.OPENCODE_URL) overrides.opencode.baseUrl = process.env.OPENCODE_URL;
|
|
1123
|
+
if (process.env.OPENCODE_DIRECTORY) overrides.opencode.projectDirectory = process.env.OPENCODE_DIRECTORY;
|
|
1124
|
+
if (process.env.OPENCODE_MODEL) overrides.opencode.model = process.env.OPENCODE_MODEL;
|
|
1125
|
+
if (process.env.OPENCODE_AGENT) overrides.opencode.agent = process.env.OPENCODE_AGENT;
|
|
1126
|
+
if (process.env.OPENCODE_SYSTEM_PROMPT) overrides.opencode.systemPrompt = process.env.OPENCODE_SYSTEM_PROMPT;
|
|
1127
|
+
if (process.env.OPENAI_API_KEY) overrides.codex.apiKey = process.env.OPENAI_API_KEY;
|
|
1128
|
+
if (process.env.CODEX_MODEL) overrides.codex.model = process.env.CODEX_MODEL;
|
|
1129
|
+
if (process.env.ANTHROPIC_API_KEY) overrides.claude.apiKey = process.env.ANTHROPIC_API_KEY;
|
|
1130
|
+
if (process.env.CLAUDE_MODEL) overrides.claude.model = process.env.CLAUDE_MODEL;
|
|
1131
|
+
return overrides;
|
|
1132
|
+
}
|
|
1133
|
+
function resolveConfig(configPath, cliOverrides) {
|
|
1134
|
+
let fileConfig = {};
|
|
1135
|
+
if (configPath) {
|
|
1136
|
+
fileConfig = loadConfigFile(configPath);
|
|
1137
|
+
log.info("Loaded config file", { path: configPath });
|
|
1138
|
+
}
|
|
1139
|
+
const envOverrides = loadEnvOverrides();
|
|
1140
|
+
const cliConfig = cliOverrides ?? {};
|
|
1141
|
+
const cloned = structuredClone(resolveDefaults(fileConfig));
|
|
1142
|
+
const base = cloned;
|
|
1143
|
+
mergeOverrides(base, envOverrides);
|
|
1144
|
+
mergeOverrides(base, cliConfig);
|
|
1145
|
+
if (configPath) {
|
|
1146
|
+
base.configDir = (0, import_node_path.dirname)((0, import_node_path.resolve)(configPath));
|
|
1147
|
+
}
|
|
1148
|
+
return base;
|
|
1149
|
+
}
|
|
1150
|
+
function mergeOverrides(target, source) {
|
|
1151
|
+
for (const key of Object.keys(source)) {
|
|
1152
|
+
const srcVal = source[key];
|
|
1153
|
+
const tgtVal = target[key];
|
|
1154
|
+
if (srcVal !== void 0 && srcVal !== null && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== void 0 && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
1155
|
+
if (Object.keys(srcVal).length > 0) {
|
|
1156
|
+
mergeOverrides(tgtVal, srcVal);
|
|
1157
|
+
}
|
|
1158
|
+
} else if (srcVal !== void 0 && srcVal !== null) {
|
|
1159
|
+
target[key] = srcVal;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// src/server/index.ts
|
|
1165
|
+
var import_express = __toESM(require("express"));
|
|
1166
|
+
var import_sdk2 = require("@a2a-js/sdk");
|
|
1167
|
+
var import_server = require("@a2a-js/sdk/server");
|
|
1168
|
+
var import_express2 = require("@a2a-js/sdk/server/express");
|
|
1169
|
+
|
|
1170
|
+
// src/server/agent-card.ts
|
|
1171
|
+
init_logger();
|
|
1172
|
+
var log2 = logger.child("agent-card");
|
|
1173
|
+
function buildAgentCard(config) {
|
|
1174
|
+
const { agentCard, server } = config;
|
|
1175
|
+
const proto = server.advertiseProtocol ?? "http";
|
|
1176
|
+
const host = server.advertiseHost ?? "localhost";
|
|
1177
|
+
const port = server.port ?? 3e3;
|
|
1178
|
+
const baseUrl = `${proto}://${host}:${port}`;
|
|
1179
|
+
const skills = (agentCard.skills ?? []).map((s) => ({
|
|
1180
|
+
id: s.id,
|
|
1181
|
+
name: s.name,
|
|
1182
|
+
description: s.description,
|
|
1183
|
+
tags: s.tags ?? [],
|
|
1184
|
+
examples: s.examples ?? []
|
|
1185
|
+
}));
|
|
1186
|
+
const card = {
|
|
1187
|
+
name: agentCard.name,
|
|
1188
|
+
description: agentCard.description,
|
|
1189
|
+
protocolVersion: agentCard.protocolVersion ?? "0.3.0",
|
|
1190
|
+
version: agentCard.version ?? "1.0.0",
|
|
1191
|
+
url: `${baseUrl}/a2a/jsonrpc`,
|
|
1192
|
+
// Default input/output modes (A2A v1.0 normalized names)
|
|
1193
|
+
defaultInputModes: agentCard.defaultInputModes ?? ["text"],
|
|
1194
|
+
defaultOutputModes: agentCard.defaultOutputModes ?? ["text"],
|
|
1195
|
+
skills,
|
|
1196
|
+
capabilities: {
|
|
1197
|
+
streaming: agentCard.streaming ?? true,
|
|
1198
|
+
pushNotifications: agentCard.pushNotifications ?? false
|
|
1199
|
+
},
|
|
1200
|
+
// Additional interfaces advertised to orchestrators
|
|
1201
|
+
additionalInterfaces: [
|
|
1202
|
+
{ transport: "JSONRPC", url: `${baseUrl}/a2a/jsonrpc` },
|
|
1203
|
+
{ transport: "REST", url: `${baseUrl}/a2a/rest` }
|
|
1204
|
+
]
|
|
1205
|
+
};
|
|
1206
|
+
log2.info("Agent card built", { name: card.name, url: card.url, skills: card.skills?.length ?? 0 });
|
|
1207
|
+
return card;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/server/index.ts
|
|
1211
|
+
init_executors();
|
|
1212
|
+
init_logger();
|
|
1213
|
+
var log3 = logger.child("server");
|
|
1214
|
+
async function createA2AServer(config) {
|
|
1215
|
+
const srv = config.server;
|
|
1216
|
+
const port = srv.port ?? 3e3;
|
|
1217
|
+
const hostname = srv.hostname ?? "0.0.0.0";
|
|
1218
|
+
const advertiseHost = srv.advertiseHost ?? "localhost";
|
|
1219
|
+
const advertiseProto = srv.advertiseProtocol ?? "http";
|
|
1220
|
+
const executor = createExecutor(config);
|
|
1221
|
+
await executor.initialize();
|
|
1222
|
+
const agentCard = buildAgentCard(config);
|
|
1223
|
+
const taskStore = new import_server.InMemoryTaskStore();
|
|
1224
|
+
const requestHandler = new import_server.DefaultRequestHandler(agentCard, taskStore, executor);
|
|
1225
|
+
const app = (0, import_express.default)();
|
|
1226
|
+
app.use((_req, res, next) => {
|
|
1227
|
+
res.setHeader("A2A-Version", "0.3");
|
|
1228
|
+
next();
|
|
1229
|
+
});
|
|
1230
|
+
app.get("/health", (_req, res) => {
|
|
1231
|
+
res.json({ status: "healthy", agent: agentCard.name, provider: config.provider });
|
|
1232
|
+
});
|
|
1233
|
+
const serveAgentCard = (req, res) => {
|
|
1234
|
+
const host = req.headers.host || `${advertiseHost}:${port}`;
|
|
1235
|
+
const proto = req.headers["x-forwarded-proto"] || advertiseProto;
|
|
1236
|
+
const dynamicBase = `${proto}://${host}`;
|
|
1237
|
+
res.json({
|
|
1238
|
+
...agentCard,
|
|
1239
|
+
url: `${dynamicBase}/a2a/jsonrpc`,
|
|
1240
|
+
additionalInterfaces: [
|
|
1241
|
+
{ transport: "JSONRPC", url: `${dynamicBase}/a2a/jsonrpc` },
|
|
1242
|
+
{ transport: "REST", url: `${dynamicBase}/a2a/rest` }
|
|
1243
|
+
]
|
|
1244
|
+
});
|
|
1245
|
+
};
|
|
1246
|
+
app.get(`/${import_sdk2.AGENT_CARD_PATH}`, serveAgentCard);
|
|
1247
|
+
app.get("/.well-known/agent.json", serveAgentCard);
|
|
1248
|
+
app.get("/.well-known/agent-json", serveAgentCard);
|
|
1249
|
+
app.use("/a2a/jsonrpc", (0, import_express2.jsonRpcHandler)({ requestHandler, userBuilder: import_express2.UserBuilder.noAuthentication }));
|
|
1250
|
+
app.use("/a2a/rest", (0, import_express2.restHandler)({ requestHandler, userBuilder: import_express2.UserBuilder.noAuthentication }));
|
|
1251
|
+
const httpServer = app.listen(port, hostname, () => {
|
|
1252
|
+
log3.info("A2A server started", { bind: hostname, port, proto: advertiseProto });
|
|
1253
|
+
const banner = [
|
|
1254
|
+
"",
|
|
1255
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
1256
|
+
`\u2551 CLI A2A Gateway \u2014 ${config.provider.padEnd(28)}\u2551`,
|
|
1257
|
+
"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",
|
|
1258
|
+
`\u2551 Agent: ${agentCard.name}`,
|
|
1259
|
+
`\u2551 Bind: ${hostname}:${port}`,
|
|
1260
|
+
`\u2551 Agent Card: ${advertiseProto}://${advertiseHost}:${port}/${import_sdk2.AGENT_CARD_PATH}`,
|
|
1261
|
+
`\u2551 JSON-RPC: ${advertiseProto}://${advertiseHost}:${port}/a2a/jsonrpc`,
|
|
1262
|
+
`\u2551 REST: ${advertiseProto}://${advertiseHost}:${port}/a2a/rest`,
|
|
1263
|
+
`\u2551 Health: ${advertiseProto}://${advertiseHost}:${port}/health`,
|
|
1264
|
+
"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",
|
|
1265
|
+
"\u2551 Ready. Press Ctrl+C to stop. \u2551",
|
|
1266
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
1267
|
+
].join("\n");
|
|
1268
|
+
console.log(banner);
|
|
1269
|
+
});
|
|
1270
|
+
return {
|
|
1271
|
+
app,
|
|
1272
|
+
server: httpServer,
|
|
1273
|
+
async shutdown() {
|
|
1274
|
+
httpServer.close();
|
|
1275
|
+
await executor.shutdown();
|
|
1276
|
+
log3.info("Server shut down");
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/cli.ts
|
|
1282
|
+
init_logger();
|
|
1283
|
+
var log11 = logger.child("cli");
|
|
1284
|
+
var PKG_VERSION = "0.1.0";
|
|
1285
|
+
function printUsage() {
|
|
1286
|
+
console.log(`
|
|
1287
|
+
Usage: cli-a2a [options]
|
|
1288
|
+
|
|
1289
|
+
Options:
|
|
1290
|
+
--provider <name> Provider: opencode | codex | claude
|
|
1291
|
+
--config, -c <path> Path to agent JSON config file
|
|
1292
|
+
--port, -p <number> A2A server port (default: 3000)
|
|
1293
|
+
--hostname <addr> Bind address (default: 0.0.0.0)
|
|
1294
|
+
--advertise-host <host> Hostname for agent card URLs
|
|
1295
|
+
--agent-name <name> Agent display name
|
|
1296
|
+
--agent-description <desc> Agent description
|
|
1297
|
+
--log-level <level> debug | info | warn | error (default: info)
|
|
1298
|
+
|
|
1299
|
+
Gateway Registration:
|
|
1300
|
+
--register-url <url> Gateway URL to register this agent with
|
|
1301
|
+
--register-key <key> API key for gateway authentication
|
|
1302
|
+
--unregister-on-shutdown Remove agent from gateway on exit
|
|
1303
|
+
|
|
1304
|
+
Bridge (NAT traversal):
|
|
1305
|
+
--bridge-url <url> Gateway WebSocket bridge (ws://gateway:8080/api/a2a/bridge)
|
|
1306
|
+
--bridge-key <key> API key for bridge authentication
|
|
1307
|
+
|
|
1308
|
+
OpenCode Options:
|
|
1309
|
+
--opencode-url <url> OpenCode server URL (default: http://localhost:4096)
|
|
1310
|
+
--directory, -d <path> Project directory
|
|
1311
|
+
--model, -m <provider/model> LLM model (e.g. anthropic/claude-sonnet-4)
|
|
1312
|
+
--agent <name> OpenCode agent preset
|
|
1313
|
+
|
|
1314
|
+
Codex Options:
|
|
1315
|
+
--codex-model <model> Codex model ID
|
|
1316
|
+
--codex-api-key <key> OpenAI API key
|
|
1317
|
+
|
|
1318
|
+
Claude Options:
|
|
1319
|
+
--claude-model <model> Claude Code model ID
|
|
1320
|
+
--claude-api-key <key> Anthropic API key
|
|
1321
|
+
|
|
1322
|
+
--help, -h Show this help
|
|
1323
|
+
--version, -v Show version
|
|
1324
|
+
|
|
1325
|
+
Examples:
|
|
1326
|
+
npx @axiom-lattice/cli-a2a --provider opencode
|
|
1327
|
+
npx @axiom-lattice/cli-a2a --provider opencode --port 3001 --opencode-url http://localhost:4096
|
|
1328
|
+
npx @axiom-lattice/cli-a2a --config ./my-agent.json
|
|
1329
|
+
npx @axiom-lattice/cli-a2a --provider opencode --model anthropic/claude-sonnet-4
|
|
1330
|
+
npx @axiom-lattice/cli-a2a --provider opencode --register-url http://gateway:8080 --register-key sk-xxx --unregister-on-shutdown
|
|
1331
|
+
npx @axiom-lattice/cli-a2a --provider opencode --bridge-url ws://gateway:8080/api/a2a/bridge --bridge-key sk-xxx
|
|
1332
|
+
`);
|
|
1333
|
+
}
|
|
1334
|
+
function parseCliArgs() {
|
|
1335
|
+
const { values } = (0, import_node_util.parseArgs)({
|
|
1336
|
+
options: {
|
|
1337
|
+
provider: { type: "string" },
|
|
1338
|
+
config: { type: "string", short: "c" },
|
|
1339
|
+
port: { type: "string", short: "p" },
|
|
1340
|
+
hostname: { type: "string" },
|
|
1341
|
+
"advertise-host": { type: "string" },
|
|
1342
|
+
"agent-name": { type: "string" },
|
|
1343
|
+
"agent-description": { type: "string" },
|
|
1344
|
+
"opencode-url": { type: "string" },
|
|
1345
|
+
directory: { type: "string", short: "d" },
|
|
1346
|
+
model: { type: "string", short: "m" },
|
|
1347
|
+
agent: { type: "string" },
|
|
1348
|
+
"codex-model": { type: "string" },
|
|
1349
|
+
"codex-api-key": { type: "string" },
|
|
1350
|
+
"claude-model": { type: "string" },
|
|
1351
|
+
"claude-api-key": { type: "string" },
|
|
1352
|
+
"log-level": { type: "string" },
|
|
1353
|
+
"register-url": { type: "string" },
|
|
1354
|
+
"register-key": { type: "string" },
|
|
1355
|
+
"unregister-on-shutdown": { type: "boolean" },
|
|
1356
|
+
"bridge-url": { type: "string" },
|
|
1357
|
+
"bridge-key": { type: "string" },
|
|
1358
|
+
help: { type: "boolean", short: "h" },
|
|
1359
|
+
version: { type: "boolean", short: "v" }
|
|
1360
|
+
},
|
|
1361
|
+
strict: false
|
|
1362
|
+
});
|
|
1363
|
+
if (values.help) {
|
|
1364
|
+
printUsage();
|
|
1365
|
+
process.exit(0);
|
|
1366
|
+
}
|
|
1367
|
+
if (values.version) {
|
|
1368
|
+
console.log(PKG_VERSION);
|
|
1369
|
+
process.exit(0);
|
|
1370
|
+
}
|
|
1371
|
+
const overrides = {};
|
|
1372
|
+
if (values.provider) {
|
|
1373
|
+
overrides.provider = values.provider;
|
|
1374
|
+
}
|
|
1375
|
+
if (values.port || values.hostname || values["advertise-host"]) {
|
|
1376
|
+
overrides.server = {};
|
|
1377
|
+
if (values.port) overrides.server.port = parseInt(values.port, 10);
|
|
1378
|
+
if (values.hostname) overrides.server.hostname = values.hostname;
|
|
1379
|
+
if (values["advertise-host"]) overrides.server.advertiseHost = values["advertise-host"];
|
|
1380
|
+
}
|
|
1381
|
+
const agentCardOverrides = {};
|
|
1382
|
+
if (values["agent-name"]) agentCardOverrides.name = values["agent-name"];
|
|
1383
|
+
if (values["agent-description"]) agentCardOverrides.description = values["agent-description"];
|
|
1384
|
+
if (Object.keys(agentCardOverrides).length > 0) {
|
|
1385
|
+
overrides.agentCard = agentCardOverrides;
|
|
1386
|
+
}
|
|
1387
|
+
const hasOpenCode = !!(values["opencode-url"] || values.directory || values.model || values.agent);
|
|
1388
|
+
if (hasOpenCode) {
|
|
1389
|
+
overrides.opencode = {};
|
|
1390
|
+
if (values["opencode-url"]) overrides.opencode.baseUrl = values["opencode-url"];
|
|
1391
|
+
if (values.directory) overrides.opencode.projectDirectory = values.directory;
|
|
1392
|
+
if (values.model) overrides.opencode.model = values.model;
|
|
1393
|
+
if (values.agent) overrides.opencode.agent = values.agent;
|
|
1394
|
+
}
|
|
1395
|
+
const hasCodex = !!(values["codex-model"] || values["codex-api-key"]);
|
|
1396
|
+
if (hasCodex) {
|
|
1397
|
+
overrides.codex = {};
|
|
1398
|
+
if (values["codex-model"]) overrides.codex.model = values["codex-model"];
|
|
1399
|
+
if (values["codex-api-key"]) overrides.codex.apiKey = values["codex-api-key"];
|
|
1400
|
+
}
|
|
1401
|
+
const hasClaude = !!(values["claude-model"] || values["claude-api-key"]);
|
|
1402
|
+
if (hasClaude) {
|
|
1403
|
+
overrides.claude = {};
|
|
1404
|
+
if (values["claude-model"]) overrides.claude.model = values["claude-model"];
|
|
1405
|
+
if (values["claude-api-key"]) overrides.claude.apiKey = values["claude-api-key"];
|
|
1406
|
+
}
|
|
1407
|
+
if (values["log-level"]) {
|
|
1408
|
+
overrides.logging = { level: values["log-level"] };
|
|
1409
|
+
}
|
|
1410
|
+
const configPath = values.config;
|
|
1411
|
+
const registerUrl = values["register-url"];
|
|
1412
|
+
const registerKey = values["register-key"];
|
|
1413
|
+
const unregisterOnShutdown = values["unregister-on-shutdown"] ?? false;
|
|
1414
|
+
const bridgeUrl = values["bridge-url"];
|
|
1415
|
+
const bridgeKey = values["bridge-key"];
|
|
1416
|
+
return { configPath, overrides, registerUrl, registerKey, unregisterOnShutdown, bridgeUrl, bridgeKey };
|
|
1417
|
+
}
|
|
1418
|
+
async function registerWithGateway(registerUrl, registerKey, config, agentCardUrl) {
|
|
1419
|
+
const body = {
|
|
1420
|
+
name: config.agentCard.name,
|
|
1421
|
+
description: config.agentCard.description,
|
|
1422
|
+
graphDefinition: {
|
|
1423
|
+
type: "a2a_remote",
|
|
1424
|
+
key: `a2a-${config.provider}-${config.agentCard.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
1425
|
+
name: config.agentCard.name,
|
|
1426
|
+
description: config.agentCard.description,
|
|
1427
|
+
prompt: config.agentCard.description,
|
|
1428
|
+
agentCardUrl,
|
|
1429
|
+
timeout: config.timeouts.prompt
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
try {
|
|
1433
|
+
const url = registerUrl.replace(/\/$/, "") + "/api/assistants";
|
|
1434
|
+
log11.info("Registering with gateway", { url, agentCardUrl });
|
|
1435
|
+
const resp = await fetch(url, {
|
|
1436
|
+
method: "POST",
|
|
1437
|
+
headers: {
|
|
1438
|
+
"Content-Type": "application/json",
|
|
1439
|
+
"Authorization": `Bearer ${registerKey}`
|
|
1440
|
+
},
|
|
1441
|
+
body: JSON.stringify(body)
|
|
1442
|
+
});
|
|
1443
|
+
if (!resp.ok) {
|
|
1444
|
+
const text = await resp.text().catch(() => "");
|
|
1445
|
+
log11.error("Gateway registration failed", { status: resp.status, body: text });
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
const result = await resp.json();
|
|
1449
|
+
const assistantId = result.data?.id ?? body.graphDefinition.key;
|
|
1450
|
+
log11.info("Registered with gateway", { assistantId, agentCardUrl });
|
|
1451
|
+
console.log(`Registered as agent: ${assistantId}`);
|
|
1452
|
+
console.log(`Agent card: ${agentCardUrl}`);
|
|
1453
|
+
return { assistantId, agentCardUrl };
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
log11.error("Gateway registration error", { error: err.message });
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
async function unregisterFromGateway(registerUrl, registerKey, assistantId) {
|
|
1460
|
+
try {
|
|
1461
|
+
const url = `${registerUrl.replace(/\/$/, "")}/api/assistants/${assistantId}`;
|
|
1462
|
+
log11.info("Unregistering from gateway", { assistantId });
|
|
1463
|
+
const resp = await fetch(url, {
|
|
1464
|
+
method: "DELETE",
|
|
1465
|
+
headers: {
|
|
1466
|
+
"Authorization": `Bearer ${registerKey}`
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
if (!resp.ok && resp.status !== 404) {
|
|
1470
|
+
log11.warn("Gateway unregistration failed", { status: resp.status });
|
|
1471
|
+
} else {
|
|
1472
|
+
log11.info("Unregistered from gateway", { assistantId });
|
|
1473
|
+
}
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
log11.warn("Gateway unregistration error", { error: err.message });
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
async function main() {
|
|
1479
|
+
const { configPath, overrides, registerUrl, registerKey, unregisterOnShutdown, bridgeUrl, bridgeKey } = parseCliArgs();
|
|
1480
|
+
const config = resolveConfig(configPath, overrides);
|
|
1481
|
+
if (configPath) {
|
|
1482
|
+
config.configDir = (0, import_node_path2.dirname)((0, import_node_path2.resolve)(configPath));
|
|
1483
|
+
}
|
|
1484
|
+
const levelStr = config.logging.level ?? "info";
|
|
1485
|
+
const levelMap = {
|
|
1486
|
+
debug: 10 /* DEBUG */,
|
|
1487
|
+
info: 20 /* INFO */,
|
|
1488
|
+
warn: 30 /* WARN */,
|
|
1489
|
+
error: 40 /* ERROR */
|
|
1490
|
+
};
|
|
1491
|
+
logger.setLevel(levelMap[levelStr] ?? 20 /* INFO */);
|
|
1492
|
+
if (!configPath) {
|
|
1493
|
+
log11.info("No --config provided \u2014 running with defaults + CLI args.");
|
|
1494
|
+
}
|
|
1495
|
+
log11.info("Starting CLI A2A Gateway", {
|
|
1496
|
+
provider: config.provider,
|
|
1497
|
+
config: configPath ?? "(defaults)",
|
|
1498
|
+
agent: config.agentCard.name,
|
|
1499
|
+
port: config.server?.port
|
|
1500
|
+
});
|
|
1501
|
+
const { registerExecutor: registerExecutor2 } = await Promise.resolve().then(() => (init_executors(), executors_exports));
|
|
1502
|
+
const { createOpenCodeExecutor: createOpenCodeExecutor2 } = await Promise.resolve().then(() => (init_executor(), executor_exports));
|
|
1503
|
+
const { createCodexExecutor: createCodexExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2));
|
|
1504
|
+
const { createClaudeExecutor: createClaudeExecutor2 } = await Promise.resolve().then(() => (init_executor3(), executor_exports3));
|
|
1505
|
+
registerExecutor2("opencode", createOpenCodeExecutor2);
|
|
1506
|
+
registerExecutor2("codex", createCodexExecutor2);
|
|
1507
|
+
registerExecutor2("claude", createClaudeExecutor2);
|
|
1508
|
+
const server = await createA2AServer(config);
|
|
1509
|
+
const listenPort = config.server?.port ?? 3e3;
|
|
1510
|
+
const agentId = `a2a-${config.provider}-${listenPort}`;
|
|
1511
|
+
let bridgeClient = null;
|
|
1512
|
+
if (bridgeUrl && bridgeKey) {
|
|
1513
|
+
const { BridgeClient: BridgeClient2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
|
|
1514
|
+
const agentCardUrl = `${config.server?.advertiseProtocol ?? "http"}://${config.server?.advertiseHost ?? "localhost"}:${listenPort}/.well-known/agent-card.json`;
|
|
1515
|
+
bridgeClient = new BridgeClient2({
|
|
1516
|
+
bridgeUrl: bridgeUrl.replace(/^http/, "ws"),
|
|
1517
|
+
apiKey: bridgeKey,
|
|
1518
|
+
agentId,
|
|
1519
|
+
agentCard: {
|
|
1520
|
+
name: config.agentCard.name,
|
|
1521
|
+
description: config.agentCard.description,
|
|
1522
|
+
url: agentCardUrl
|
|
1523
|
+
},
|
|
1524
|
+
localBaseUrl: `http://localhost:${listenPort}`
|
|
1525
|
+
});
|
|
1526
|
+
bridgeClient.start().then(() => {
|
|
1527
|
+
log11.info("Bridge connected \u2014 agent reachable via gateway proxy");
|
|
1528
|
+
console.log(`Bridge: ws://gateway \u2192 localhost:${listenPort}`);
|
|
1529
|
+
}).catch((err) => {
|
|
1530
|
+
log11.error("Bridge connection failed", { error: err.message });
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
let registeredAssistantId = null;
|
|
1534
|
+
if (registerUrl && registerKey) {
|
|
1535
|
+
const agentCardUrl = bridgeUrl ? `${registerUrl.replace(/\/$/, "")}/api/a2a/bridge/proxy/${agentId}/.well-known/agent-card.json` : `${config.server?.advertiseProtocol ?? "http"}://${config.server?.advertiseHost ?? "localhost"}:${listenPort}/.well-known/agent-card.json`;
|
|
1536
|
+
const reg = await registerWithGateway(
|
|
1537
|
+
registerUrl,
|
|
1538
|
+
registerKey,
|
|
1539
|
+
config,
|
|
1540
|
+
agentCardUrl
|
|
1541
|
+
);
|
|
1542
|
+
if (reg) {
|
|
1543
|
+
registeredAssistantId = reg.assistantId;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
const shutdown = async (signal) => {
|
|
1547
|
+
log11.info(`${signal} received, shutting down...`);
|
|
1548
|
+
bridgeClient?.stop();
|
|
1549
|
+
if (registeredAssistantId && registerUrl && registerKey && unregisterOnShutdown) {
|
|
1550
|
+
await unregisterFromGateway(registerUrl, registerKey, registeredAssistantId);
|
|
1551
|
+
}
|
|
1552
|
+
await server.shutdown();
|
|
1553
|
+
process.exit(0);
|
|
1554
|
+
};
|
|
1555
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
1556
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
1557
|
+
}
|
|
1558
|
+
main().catch((err) => {
|
|
1559
|
+
log11.error("Fatal error", { error: err.message, stack: err.stack });
|
|
1560
|
+
process.exit(1);
|
|
1561
|
+
});
|
|
1562
|
+
//# sourceMappingURL=cli.js.map
|