@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.
Files changed (63) hide show
  1. package/.turbo/turbo-build.log +51 -0
  2. package/CHANGELOG.md +7 -0
  3. package/LICENSE +201 -0
  4. package/README.md +290 -0
  5. package/__tests__/opencode-executor.test.ts +159 -0
  6. package/agents/opencode-example/config.json +64 -0
  7. package/dist/bridge-7ZUDKCZT.mjs +271 -0
  8. package/dist/bridge-7ZUDKCZT.mjs.map +1 -0
  9. package/dist/chunk-35NFMGMS.mjs +27 -0
  10. package/dist/chunk-35NFMGMS.mjs.map +1 -0
  11. package/dist/chunk-G7AGL2QA.mjs +284 -0
  12. package/dist/chunk-G7AGL2QA.mjs.map +1 -0
  13. package/dist/chunk-LXL47XMZ.mjs +43 -0
  14. package/dist/chunk-LXL47XMZ.mjs.map +1 -0
  15. package/dist/chunk-NQIDRU47.mjs +178 -0
  16. package/dist/chunk-NQIDRU47.mjs.map +1 -0
  17. package/dist/chunk-VSZ3DACI.mjs +179 -0
  18. package/dist/chunk-VSZ3DACI.mjs.map +1 -0
  19. package/dist/chunk-VZEH3EPJ.mjs +56 -0
  20. package/dist/chunk-VZEH3EPJ.mjs.map +1 -0
  21. package/dist/chunk-WNCDOYZS.mjs +187 -0
  22. package/dist/chunk-WNCDOYZS.mjs.map +1 -0
  23. package/dist/cli.d.mts +1 -0
  24. package/dist/cli.d.ts +1 -0
  25. package/dist/cli.js +1562 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/cli.mjs +293 -0
  28. package/dist/cli.mjs.map +1 -0
  29. package/dist/executor-OWPVDUXH.mjs +11 -0
  30. package/dist/executor-OWPVDUXH.mjs.map +1 -0
  31. package/dist/executor-RVBGAWUF.mjs +11 -0
  32. package/dist/executor-RVBGAWUF.mjs.map +1 -0
  33. package/dist/executor-XWHWUVQ3.mjs +11 -0
  34. package/dist/executor-XWHWUVQ3.mjs.map +1 -0
  35. package/dist/executors-QIIKBUMJ.mjs +15 -0
  36. package/dist/executors-QIIKBUMJ.mjs.map +1 -0
  37. package/dist/index.d.mts +295 -0
  38. package/dist/index.d.ts +295 -0
  39. package/dist/index.js +942 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/index.mjs +44 -0
  42. package/dist/index.mjs.map +1 -0
  43. package/jest.config.js +16 -0
  44. package/package.json +63 -0
  45. package/src/bridge.ts +355 -0
  46. package/src/cli.ts +384 -0
  47. package/src/config/defaults.ts +109 -0
  48. package/src/config/index.ts +16 -0
  49. package/src/config/loader.ts +121 -0
  50. package/src/config/types.ts +163 -0
  51. package/src/executors/claude/client.ts +138 -0
  52. package/src/executors/claude/executor.ts +111 -0
  53. package/src/executors/codex/client.ts +139 -0
  54. package/src/executors/codex/executor.ts +118 -0
  55. package/src/executors/events.ts +117 -0
  56. package/src/executors/index.ts +54 -0
  57. package/src/executors/opencode/client.ts +149 -0
  58. package/src/executors/opencode/executor.ts +112 -0
  59. package/src/index.ts +54 -0
  60. package/src/logger.ts +78 -0
  61. package/src/server/agent-card.ts +51 -0
  62. package/src/server/index.ts +124 -0
  63. 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