@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/index.js
ADDED
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ClaudeExecutor: () => ClaudeExecutor,
|
|
34
|
+
CodexExecutor: () => CodexExecutor,
|
|
35
|
+
LogLevel: () => LogLevel,
|
|
36
|
+
OpenCodeExecutor: () => OpenCodeExecutor,
|
|
37
|
+
createA2AServer: () => createA2AServer,
|
|
38
|
+
createClaudeExecutor: () => createClaudeExecutor,
|
|
39
|
+
createCodexExecutor: () => createCodexExecutor,
|
|
40
|
+
createExecutor: () => createExecutor,
|
|
41
|
+
createOpenCodeExecutor: () => createOpenCodeExecutor,
|
|
42
|
+
getExecutor: () => getExecutor,
|
|
43
|
+
loadConfigFile: () => loadConfigFile,
|
|
44
|
+
logger: () => logger,
|
|
45
|
+
registerExecutor: () => registerExecutor,
|
|
46
|
+
resolveConfig: () => resolveConfig
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/config/loader.ts
|
|
51
|
+
var import_node_fs = require("fs");
|
|
52
|
+
var import_node_path = require("path");
|
|
53
|
+
|
|
54
|
+
// src/config/defaults.ts
|
|
55
|
+
function withDefaults(partial, defaults) {
|
|
56
|
+
const result = { ...defaults };
|
|
57
|
+
for (const key of Object.keys(partial)) {
|
|
58
|
+
const val = partial[key];
|
|
59
|
+
if (val !== void 0 && val !== null) {
|
|
60
|
+
if (typeof val === "object" && !Array.isArray(val) && typeof defaults[key] === "object" && !Array.isArray(defaults[key])) {
|
|
61
|
+
result[key] = {
|
|
62
|
+
...defaults[key],
|
|
63
|
+
...val
|
|
64
|
+
};
|
|
65
|
+
} else {
|
|
66
|
+
result[key] = val;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
var DEFAULTS = Object.freeze({
|
|
73
|
+
provider: "opencode",
|
|
74
|
+
agentCard: {
|
|
75
|
+
name: "CLI A2A Agent",
|
|
76
|
+
description: "A multi-provider A2A agent driven by local CLI coding tools.",
|
|
77
|
+
protocolVersion: "0.3.0",
|
|
78
|
+
version: "0.1.0",
|
|
79
|
+
skills: [],
|
|
80
|
+
defaultInputModes: ["text"],
|
|
81
|
+
defaultOutputModes: ["text"],
|
|
82
|
+
streaming: true,
|
|
83
|
+
pushNotifications: false
|
|
84
|
+
},
|
|
85
|
+
server: {
|
|
86
|
+
port: 3e3,
|
|
87
|
+
hostname: "0.0.0.0",
|
|
88
|
+
advertiseHost: "localhost",
|
|
89
|
+
advertiseProtocol: "http"
|
|
90
|
+
},
|
|
91
|
+
session: {
|
|
92
|
+
titlePrefix: "A2A Session",
|
|
93
|
+
reuseByContext: true,
|
|
94
|
+
ttl: 36e5,
|
|
95
|
+
cleanupInterval: 3e5
|
|
96
|
+
},
|
|
97
|
+
features: {
|
|
98
|
+
autoApprovePermissions: true,
|
|
99
|
+
autoAnswerQuestions: true,
|
|
100
|
+
streamArtifactChunks: false,
|
|
101
|
+
enablePollingFallback: true
|
|
102
|
+
},
|
|
103
|
+
timeouts: {
|
|
104
|
+
prompt: 6e5,
|
|
105
|
+
pollingInterval: 2e3,
|
|
106
|
+
healthCheck: 3e4
|
|
107
|
+
},
|
|
108
|
+
logging: {
|
|
109
|
+
level: "info"
|
|
110
|
+
},
|
|
111
|
+
opencode: {
|
|
112
|
+
baseUrl: "http://localhost:4096",
|
|
113
|
+
projectDirectory: "",
|
|
114
|
+
model: "",
|
|
115
|
+
agent: "",
|
|
116
|
+
systemPrompt: "",
|
|
117
|
+
systemPromptMode: "append",
|
|
118
|
+
contextFile: "context.md",
|
|
119
|
+
contextPrompt: ""
|
|
120
|
+
},
|
|
121
|
+
codex: {
|
|
122
|
+
cliPath: "codex",
|
|
123
|
+
workdir: process.cwd(),
|
|
124
|
+
model: "",
|
|
125
|
+
apiKey: ""
|
|
126
|
+
},
|
|
127
|
+
claude: {
|
|
128
|
+
cliPath: "claude",
|
|
129
|
+
workdir: process.cwd(),
|
|
130
|
+
model: "",
|
|
131
|
+
apiKey: ""
|
|
132
|
+
},
|
|
133
|
+
configDir: void 0
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
|
+
});
|
|
136
|
+
function resolveDefaults(overrides) {
|
|
137
|
+
return withDefaults(overrides, DEFAULTS);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/logger.ts
|
|
141
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
142
|
+
LogLevel2[LogLevel2["DEBUG"] = 10] = "DEBUG";
|
|
143
|
+
LogLevel2[LogLevel2["INFO"] = 20] = "INFO";
|
|
144
|
+
LogLevel2[LogLevel2["WARN"] = 30] = "WARN";
|
|
145
|
+
LogLevel2[LogLevel2["ERROR"] = 40] = "ERROR";
|
|
146
|
+
return LogLevel2;
|
|
147
|
+
})(LogLevel || {});
|
|
148
|
+
var Logger = class _Logger {
|
|
149
|
+
constructor(name) {
|
|
150
|
+
this.level = 20 /* INFO */;
|
|
151
|
+
this.name = name;
|
|
152
|
+
}
|
|
153
|
+
setLevel(level) {
|
|
154
|
+
this.level = level;
|
|
155
|
+
}
|
|
156
|
+
child(name) {
|
|
157
|
+
const childLogger = new _Logger(`${this.name}:${name}`);
|
|
158
|
+
childLogger.level = this.level;
|
|
159
|
+
return childLogger;
|
|
160
|
+
}
|
|
161
|
+
debug(msg, ctx) {
|
|
162
|
+
this.log(10 /* DEBUG */, msg, ctx);
|
|
163
|
+
}
|
|
164
|
+
info(msg, ctx) {
|
|
165
|
+
this.log(20 /* INFO */, msg, ctx);
|
|
166
|
+
}
|
|
167
|
+
warn(msg, ctx) {
|
|
168
|
+
this.log(30 /* WARN */, msg, ctx);
|
|
169
|
+
}
|
|
170
|
+
error(msg, ctx) {
|
|
171
|
+
this.log(40 /* ERROR */, msg, ctx);
|
|
172
|
+
}
|
|
173
|
+
log(level, msg, ctx) {
|
|
174
|
+
if (level < this.level) return;
|
|
175
|
+
const entry = {
|
|
176
|
+
level,
|
|
177
|
+
msg,
|
|
178
|
+
ctx: { name: this.name, ...ctx },
|
|
179
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
180
|
+
};
|
|
181
|
+
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) : ""}`;
|
|
182
|
+
if (level >= 30 /* WARN */) {
|
|
183
|
+
process.stderr.write(output + "\n");
|
|
184
|
+
} else {
|
|
185
|
+
process.stdout.write(output + "\n");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
var logger = new Logger("cli-a2a");
|
|
190
|
+
|
|
191
|
+
// src/config/loader.ts
|
|
192
|
+
var log = logger.child("config");
|
|
193
|
+
function loadConfigFile(filePath) {
|
|
194
|
+
const absPath = (0, import_node_path.resolve)(filePath);
|
|
195
|
+
if (!(0, import_node_fs.existsSync)(absPath)) {
|
|
196
|
+
throw new Error(`Config file not found: ${absPath}`);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const raw = (0, import_node_fs.readFileSync)(absPath, "utf-8");
|
|
200
|
+
return JSON.parse(raw);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
const msg = err instanceof SyntaxError ? `Invalid JSON in config file: ${err.message}` : `Failed to read config file: ${err.message}`;
|
|
203
|
+
throw new Error(msg);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function loadEnvOverrides() {
|
|
207
|
+
const overrides = {
|
|
208
|
+
opencode: {},
|
|
209
|
+
codex: {},
|
|
210
|
+
claude: {}
|
|
211
|
+
};
|
|
212
|
+
if (process.env.OPENCODE_URL) overrides.opencode.baseUrl = process.env.OPENCODE_URL;
|
|
213
|
+
if (process.env.OPENCODE_DIRECTORY) overrides.opencode.projectDirectory = process.env.OPENCODE_DIRECTORY;
|
|
214
|
+
if (process.env.OPENCODE_MODEL) overrides.opencode.model = process.env.OPENCODE_MODEL;
|
|
215
|
+
if (process.env.OPENCODE_AGENT) overrides.opencode.agent = process.env.OPENCODE_AGENT;
|
|
216
|
+
if (process.env.OPENCODE_SYSTEM_PROMPT) overrides.opencode.systemPrompt = process.env.OPENCODE_SYSTEM_PROMPT;
|
|
217
|
+
if (process.env.OPENAI_API_KEY) overrides.codex.apiKey = process.env.OPENAI_API_KEY;
|
|
218
|
+
if (process.env.CODEX_MODEL) overrides.codex.model = process.env.CODEX_MODEL;
|
|
219
|
+
if (process.env.ANTHROPIC_API_KEY) overrides.claude.apiKey = process.env.ANTHROPIC_API_KEY;
|
|
220
|
+
if (process.env.CLAUDE_MODEL) overrides.claude.model = process.env.CLAUDE_MODEL;
|
|
221
|
+
return overrides;
|
|
222
|
+
}
|
|
223
|
+
function resolveConfig(configPath, cliOverrides) {
|
|
224
|
+
let fileConfig = {};
|
|
225
|
+
if (configPath) {
|
|
226
|
+
fileConfig = loadConfigFile(configPath);
|
|
227
|
+
log.info("Loaded config file", { path: configPath });
|
|
228
|
+
}
|
|
229
|
+
const envOverrides = loadEnvOverrides();
|
|
230
|
+
const cliConfig = cliOverrides ?? {};
|
|
231
|
+
const cloned = structuredClone(resolveDefaults(fileConfig));
|
|
232
|
+
const base = cloned;
|
|
233
|
+
mergeOverrides(base, envOverrides);
|
|
234
|
+
mergeOverrides(base, cliConfig);
|
|
235
|
+
if (configPath) {
|
|
236
|
+
base.configDir = (0, import_node_path.dirname)((0, import_node_path.resolve)(configPath));
|
|
237
|
+
}
|
|
238
|
+
return base;
|
|
239
|
+
}
|
|
240
|
+
function mergeOverrides(target, source) {
|
|
241
|
+
for (const key of Object.keys(source)) {
|
|
242
|
+
const srcVal = source[key];
|
|
243
|
+
const tgtVal = target[key];
|
|
244
|
+
if (srcVal !== void 0 && srcVal !== null && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== void 0 && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
|
|
245
|
+
if (Object.keys(srcVal).length > 0) {
|
|
246
|
+
mergeOverrides(tgtVal, srcVal);
|
|
247
|
+
}
|
|
248
|
+
} else if (srcVal !== void 0 && srcVal !== null) {
|
|
249
|
+
target[key] = srcVal;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/server/index.ts
|
|
255
|
+
var import_express = __toESM(require("express"));
|
|
256
|
+
var import_sdk2 = require("@a2a-js/sdk");
|
|
257
|
+
var import_server = require("@a2a-js/sdk/server");
|
|
258
|
+
var import_express2 = require("@a2a-js/sdk/server/express");
|
|
259
|
+
|
|
260
|
+
// src/server/agent-card.ts
|
|
261
|
+
var log2 = logger.child("agent-card");
|
|
262
|
+
function buildAgentCard(config) {
|
|
263
|
+
const { agentCard, server } = config;
|
|
264
|
+
const proto = server.advertiseProtocol ?? "http";
|
|
265
|
+
const host = server.advertiseHost ?? "localhost";
|
|
266
|
+
const port = server.port ?? 3e3;
|
|
267
|
+
const baseUrl = `${proto}://${host}:${port}`;
|
|
268
|
+
const skills = (agentCard.skills ?? []).map((s) => ({
|
|
269
|
+
id: s.id,
|
|
270
|
+
name: s.name,
|
|
271
|
+
description: s.description,
|
|
272
|
+
tags: s.tags ?? [],
|
|
273
|
+
examples: s.examples ?? []
|
|
274
|
+
}));
|
|
275
|
+
const card = {
|
|
276
|
+
name: agentCard.name,
|
|
277
|
+
description: agentCard.description,
|
|
278
|
+
protocolVersion: agentCard.protocolVersion ?? "0.3.0",
|
|
279
|
+
version: agentCard.version ?? "1.0.0",
|
|
280
|
+
url: `${baseUrl}/a2a/jsonrpc`,
|
|
281
|
+
// Default input/output modes (A2A v1.0 normalized names)
|
|
282
|
+
defaultInputModes: agentCard.defaultInputModes ?? ["text"],
|
|
283
|
+
defaultOutputModes: agentCard.defaultOutputModes ?? ["text"],
|
|
284
|
+
skills,
|
|
285
|
+
capabilities: {
|
|
286
|
+
streaming: agentCard.streaming ?? true,
|
|
287
|
+
pushNotifications: agentCard.pushNotifications ?? false
|
|
288
|
+
},
|
|
289
|
+
// Additional interfaces advertised to orchestrators
|
|
290
|
+
additionalInterfaces: [
|
|
291
|
+
{ transport: "JSONRPC", url: `${baseUrl}/a2a/jsonrpc` },
|
|
292
|
+
{ transport: "REST", url: `${baseUrl}/a2a/rest` }
|
|
293
|
+
]
|
|
294
|
+
};
|
|
295
|
+
log2.info("Agent card built", { name: card.name, url: card.url, skills: card.skills?.length ?? 0 });
|
|
296
|
+
return card;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/executors/index.ts
|
|
300
|
+
var import_sdk = require("@a2a-js/sdk");
|
|
301
|
+
var import_uuid = require("uuid");
|
|
302
|
+
var registry = /* @__PURE__ */ new Map();
|
|
303
|
+
function registerExecutor(provider, factory) {
|
|
304
|
+
registry.set(provider, factory);
|
|
305
|
+
}
|
|
306
|
+
function getExecutor(provider) {
|
|
307
|
+
return registry.get(provider);
|
|
308
|
+
}
|
|
309
|
+
function createExecutor(config) {
|
|
310
|
+
const factory = registry.get(config.provider);
|
|
311
|
+
if (!factory) {
|
|
312
|
+
const available = Array.from(registry.keys()).join(", ");
|
|
313
|
+
throw new Error(`Unknown provider: ${config.provider}. Available: ${available}`);
|
|
314
|
+
}
|
|
315
|
+
return factory(config);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/server/index.ts
|
|
319
|
+
var log3 = logger.child("server");
|
|
320
|
+
async function createA2AServer(config) {
|
|
321
|
+
const srv = config.server;
|
|
322
|
+
const port = srv.port ?? 3e3;
|
|
323
|
+
const hostname = srv.hostname ?? "0.0.0.0";
|
|
324
|
+
const advertiseHost = srv.advertiseHost ?? "localhost";
|
|
325
|
+
const advertiseProto = srv.advertiseProtocol ?? "http";
|
|
326
|
+
const executor = createExecutor(config);
|
|
327
|
+
await executor.initialize();
|
|
328
|
+
const agentCard = buildAgentCard(config);
|
|
329
|
+
const taskStore = new import_server.InMemoryTaskStore();
|
|
330
|
+
const requestHandler = new import_server.DefaultRequestHandler(agentCard, taskStore, executor);
|
|
331
|
+
const app = (0, import_express.default)();
|
|
332
|
+
app.use((_req, res, next) => {
|
|
333
|
+
res.setHeader("A2A-Version", "0.3");
|
|
334
|
+
next();
|
|
335
|
+
});
|
|
336
|
+
app.get("/health", (_req, res) => {
|
|
337
|
+
res.json({ status: "healthy", agent: agentCard.name, provider: config.provider });
|
|
338
|
+
});
|
|
339
|
+
const serveAgentCard = (req, res) => {
|
|
340
|
+
const host = req.headers.host || `${advertiseHost}:${port}`;
|
|
341
|
+
const proto = req.headers["x-forwarded-proto"] || advertiseProto;
|
|
342
|
+
const dynamicBase = `${proto}://${host}`;
|
|
343
|
+
res.json({
|
|
344
|
+
...agentCard,
|
|
345
|
+
url: `${dynamicBase}/a2a/jsonrpc`,
|
|
346
|
+
additionalInterfaces: [
|
|
347
|
+
{ transport: "JSONRPC", url: `${dynamicBase}/a2a/jsonrpc` },
|
|
348
|
+
{ transport: "REST", url: `${dynamicBase}/a2a/rest` }
|
|
349
|
+
]
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
app.get(`/${import_sdk2.AGENT_CARD_PATH}`, serveAgentCard);
|
|
353
|
+
app.get("/.well-known/agent.json", serveAgentCard);
|
|
354
|
+
app.get("/.well-known/agent-json", serveAgentCard);
|
|
355
|
+
app.use("/a2a/jsonrpc", (0, import_express2.jsonRpcHandler)({ requestHandler, userBuilder: import_express2.UserBuilder.noAuthentication }));
|
|
356
|
+
app.use("/a2a/rest", (0, import_express2.restHandler)({ requestHandler, userBuilder: import_express2.UserBuilder.noAuthentication }));
|
|
357
|
+
const httpServer = app.listen(port, hostname, () => {
|
|
358
|
+
log3.info("A2A server started", { bind: hostname, port, proto: advertiseProto });
|
|
359
|
+
const banner = [
|
|
360
|
+
"",
|
|
361
|
+
"\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",
|
|
362
|
+
`\u2551 CLI A2A Gateway \u2014 ${config.provider.padEnd(28)}\u2551`,
|
|
363
|
+
"\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",
|
|
364
|
+
`\u2551 Agent: ${agentCard.name}`,
|
|
365
|
+
`\u2551 Bind: ${hostname}:${port}`,
|
|
366
|
+
`\u2551 Agent Card: ${advertiseProto}://${advertiseHost}:${port}/${import_sdk2.AGENT_CARD_PATH}`,
|
|
367
|
+
`\u2551 JSON-RPC: ${advertiseProto}://${advertiseHost}:${port}/a2a/jsonrpc`,
|
|
368
|
+
`\u2551 REST: ${advertiseProto}://${advertiseHost}:${port}/a2a/rest`,
|
|
369
|
+
`\u2551 Health: ${advertiseProto}://${advertiseHost}:${port}/health`,
|
|
370
|
+
"\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",
|
|
371
|
+
"\u2551 Ready. Press Ctrl+C to stop. \u2551",
|
|
372
|
+
"\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"
|
|
373
|
+
].join("\n");
|
|
374
|
+
console.log(banner);
|
|
375
|
+
});
|
|
376
|
+
return {
|
|
377
|
+
app,
|
|
378
|
+
server: httpServer,
|
|
379
|
+
async shutdown() {
|
|
380
|
+
httpServer.close();
|
|
381
|
+
await executor.shutdown();
|
|
382
|
+
log3.info("Server shut down");
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/executors/opencode/client.ts
|
|
388
|
+
var import_node_child_process = require("child_process");
|
|
389
|
+
var log4 = logger.child("opencode:client");
|
|
390
|
+
var OpenCodeClient = class {
|
|
391
|
+
constructor(config) {
|
|
392
|
+
this.config = config;
|
|
393
|
+
this.currentChild = null;
|
|
394
|
+
}
|
|
395
|
+
/** Abort the currently running child process (if any). */
|
|
396
|
+
abort() {
|
|
397
|
+
if (this.currentChild) {
|
|
398
|
+
this.currentChild.kill("SIGTERM");
|
|
399
|
+
setTimeout(() => {
|
|
400
|
+
if (this.currentChild?.exitCode === null) {
|
|
401
|
+
this.currentChild.kill("SIGKILL");
|
|
402
|
+
}
|
|
403
|
+
}, 5e3);
|
|
404
|
+
this.currentChild = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Execute a prompt via the OpenCode CLI and return the response text.
|
|
409
|
+
*/
|
|
410
|
+
async execute(prompt) {
|
|
411
|
+
const cliPath = this.config.cliPath || "opencode";
|
|
412
|
+
const timeout = this.config.timeout ?? 6e5;
|
|
413
|
+
const MAX_PROMPT = 1e5;
|
|
414
|
+
if (prompt.length > MAX_PROMPT) {
|
|
415
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages or use a provider with HTTP API transport.`);
|
|
416
|
+
}
|
|
417
|
+
const env = {
|
|
418
|
+
PATH: process.env.PATH ?? "",
|
|
419
|
+
HOME: process.env.HOME ?? ""
|
|
420
|
+
};
|
|
421
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
422
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
423
|
+
}
|
|
424
|
+
for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
|
|
425
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
426
|
+
}
|
|
427
|
+
const args = ["run", prompt];
|
|
428
|
+
if (this.config.model) {
|
|
429
|
+
args.push("--model", this.config.model);
|
|
430
|
+
}
|
|
431
|
+
if (this.config.agent) {
|
|
432
|
+
args.push("--agent", this.config.agent);
|
|
433
|
+
}
|
|
434
|
+
if (this.config.attachUrl) {
|
|
435
|
+
args.push("--attach", this.config.attachUrl);
|
|
436
|
+
}
|
|
437
|
+
args.push("--format", "default");
|
|
438
|
+
args.push("--dangerously-skip-permissions");
|
|
439
|
+
log4.info("Spawning opencode", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
440
|
+
return new Promise((resolve2, reject) => {
|
|
441
|
+
const child = (0, import_node_child_process.spawn)(cliPath, args, {
|
|
442
|
+
cwd: this.config.workdir || process.cwd(),
|
|
443
|
+
env,
|
|
444
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
445
|
+
});
|
|
446
|
+
this.currentChild = child;
|
|
447
|
+
let stdout = "";
|
|
448
|
+
let stderr = "";
|
|
449
|
+
const MAX_OUTPUT = 1e7;
|
|
450
|
+
const timer = setTimeout(() => {
|
|
451
|
+
child.kill("SIGTERM");
|
|
452
|
+
const forceTimer = setTimeout(() => {
|
|
453
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
454
|
+
}, 5e3);
|
|
455
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
456
|
+
reject(new Error(`OpenCode execution timed out after ${timeout}ms`));
|
|
457
|
+
}, timeout);
|
|
458
|
+
child.stdout?.on("data", (chunk) => {
|
|
459
|
+
stdout += chunk.toString();
|
|
460
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
461
|
+
child.kill("SIGTERM");
|
|
462
|
+
setTimeout(() => {
|
|
463
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
464
|
+
}, 5e3);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
child.stderr?.on("data", (chunk) => {
|
|
468
|
+
stderr += chunk.toString();
|
|
469
|
+
log4.debug("OpenCode stderr", { text: chunk.toString().trim() });
|
|
470
|
+
});
|
|
471
|
+
child.on("close", (code) => {
|
|
472
|
+
clearTimeout(timer);
|
|
473
|
+
this.currentChild = null;
|
|
474
|
+
if (code === 0) {
|
|
475
|
+
resolve2(stdout.trim());
|
|
476
|
+
} else {
|
|
477
|
+
const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
|
|
478
|
+
log4.warn("OpenCode non-zero exit", { code, stderr: errMsg });
|
|
479
|
+
reject(new Error(errMsg));
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
child.on("error", (err) => {
|
|
483
|
+
clearTimeout(timer);
|
|
484
|
+
log4.error("OpenCode spawn failed", { error: err.message });
|
|
485
|
+
reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/executors/events.ts
|
|
492
|
+
var import_uuid2 = require("uuid");
|
|
493
|
+
function publishTask(bus, taskId, contextId) {
|
|
494
|
+
bus.publish({
|
|
495
|
+
kind: "task",
|
|
496
|
+
taskId,
|
|
497
|
+
contextId
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
function publishStatus(bus, taskId, contextId, state, messageText, final) {
|
|
501
|
+
const event = {
|
|
502
|
+
kind: "task-status-update",
|
|
503
|
+
taskId,
|
|
504
|
+
contextId,
|
|
505
|
+
status: {
|
|
506
|
+
state,
|
|
507
|
+
message: messageText ? { role: "agent", parts: [{ kind: "text", text: messageText }] } : void 0
|
|
508
|
+
},
|
|
509
|
+
final: final ?? false
|
|
510
|
+
};
|
|
511
|
+
bus.publish(event);
|
|
512
|
+
}
|
|
513
|
+
function publishFinalArtifact(bus, taskId, contextId, text) {
|
|
514
|
+
const artifactId = (0, import_uuid2.v4)();
|
|
515
|
+
const event = {
|
|
516
|
+
kind: "task-artifact-update",
|
|
517
|
+
taskId,
|
|
518
|
+
contextId,
|
|
519
|
+
artifact: {
|
|
520
|
+
artifactId,
|
|
521
|
+
parts: [{ kind: "text", text }]
|
|
522
|
+
},
|
|
523
|
+
lastChunk: true
|
|
524
|
+
};
|
|
525
|
+
bus.publish(event);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/executors/opencode/executor.ts
|
|
529
|
+
var log5 = logger.child("opencode:executor");
|
|
530
|
+
var OpenCodeExecutor = class {
|
|
531
|
+
constructor(config) {
|
|
532
|
+
this.client = null;
|
|
533
|
+
this.initialized = false;
|
|
534
|
+
this.config = config;
|
|
535
|
+
}
|
|
536
|
+
async initialize() {
|
|
537
|
+
if (this.initialized) return;
|
|
538
|
+
const oc = this.config.opencode;
|
|
539
|
+
this.client = new OpenCodeClient({
|
|
540
|
+
cliPath: "opencode",
|
|
541
|
+
workdir: oc.projectDirectory || process.cwd(),
|
|
542
|
+
model: oc.model || void 0,
|
|
543
|
+
agent: oc.agent || void 0,
|
|
544
|
+
attachUrl: oc.baseUrl || void 0,
|
|
545
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
546
|
+
});
|
|
547
|
+
this.initialized = true;
|
|
548
|
+
log5.info("Executor initialized", { workdir: oc.projectDirectory, model: oc.model });
|
|
549
|
+
}
|
|
550
|
+
async shutdown() {
|
|
551
|
+
this.client = null;
|
|
552
|
+
this.initialized = false;
|
|
553
|
+
log5.info("Executor shut down");
|
|
554
|
+
}
|
|
555
|
+
async execute(ctx, bus) {
|
|
556
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
557
|
+
await this.initialize();
|
|
558
|
+
try {
|
|
559
|
+
if (!task) {
|
|
560
|
+
publishTask(bus, taskId, contextId);
|
|
561
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
562
|
+
}
|
|
563
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
564
|
+
const promptText = this.extractText(userMessage);
|
|
565
|
+
log5.info("Sending prompt to OpenCode", { taskId, len: promptText.length });
|
|
566
|
+
const response = await this.client.execute(promptText);
|
|
567
|
+
const finalText = response || "No response from OpenCode.";
|
|
568
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
569
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
570
|
+
bus.finished();
|
|
571
|
+
log5.info("Task completed", { taskId, len: finalText.length });
|
|
572
|
+
} catch (error) {
|
|
573
|
+
const msg = error.message ?? String(error);
|
|
574
|
+
log5.error("Execution failed", { taskId, error: msg });
|
|
575
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
576
|
+
bus.finished();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
async cancelTask(taskId, bus) {
|
|
580
|
+
log5.info("Cancel requested for OpenCode task", { taskId });
|
|
581
|
+
this.client?.abort();
|
|
582
|
+
publishStatus(bus, taskId, "", "canceled", "OpenCode task cancelled", true);
|
|
583
|
+
bus.finished();
|
|
584
|
+
}
|
|
585
|
+
extractText(message) {
|
|
586
|
+
return message.parts.filter((p) => {
|
|
587
|
+
const part = p;
|
|
588
|
+
const text = part.text;
|
|
589
|
+
return text !== void 0 && text !== null;
|
|
590
|
+
}).map((p) => p.text).join("\n");
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
function createOpenCodeExecutor(config) {
|
|
594
|
+
return new OpenCodeExecutor(config);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// src/executors/codex/client.ts
|
|
598
|
+
var import_node_child_process2 = require("child_process");
|
|
599
|
+
var log6 = logger.child("codex:client");
|
|
600
|
+
var CodexClient = class {
|
|
601
|
+
constructor(config) {
|
|
602
|
+
this.config = config;
|
|
603
|
+
this.currentChild = null;
|
|
604
|
+
}
|
|
605
|
+
abort() {
|
|
606
|
+
if (this.currentChild) {
|
|
607
|
+
this.currentChild.kill("SIGTERM");
|
|
608
|
+
setTimeout(() => {
|
|
609
|
+
if (this.currentChild?.exitCode === null) {
|
|
610
|
+
this.currentChild.kill("SIGKILL");
|
|
611
|
+
}
|
|
612
|
+
}, 5e3);
|
|
613
|
+
this.currentChild = null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Execute a prompt via the Codex CLI and return the response text.
|
|
618
|
+
*/
|
|
619
|
+
async execute(prompt) {
|
|
620
|
+
const cliPath = this.config.cliPath || "codex";
|
|
621
|
+
const timeout = this.config.timeout ?? 3e5;
|
|
622
|
+
const MAX_PROMPT = 1e5;
|
|
623
|
+
if (prompt.length > MAX_PROMPT) {
|
|
624
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
625
|
+
}
|
|
626
|
+
const env = {
|
|
627
|
+
PATH: process.env.PATH ?? "",
|
|
628
|
+
HOME: process.env.HOME ?? ""
|
|
629
|
+
};
|
|
630
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
631
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
632
|
+
}
|
|
633
|
+
if (this.config.apiKey) {
|
|
634
|
+
env["OPENAI_API_KEY"] = this.config.apiKey;
|
|
635
|
+
}
|
|
636
|
+
const args = [];
|
|
637
|
+
if (this.config.model) {
|
|
638
|
+
args.push("--model", this.config.model);
|
|
639
|
+
}
|
|
640
|
+
args.push(prompt);
|
|
641
|
+
log6.info("Spawning codex", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
642
|
+
return new Promise((resolve2, reject) => {
|
|
643
|
+
const child = (0, import_node_child_process2.spawn)(cliPath, args, {
|
|
644
|
+
cwd: this.config.workdir || process.cwd(),
|
|
645
|
+
env,
|
|
646
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
647
|
+
});
|
|
648
|
+
this.currentChild = child;
|
|
649
|
+
let stdout = "";
|
|
650
|
+
let stderr = "";
|
|
651
|
+
const MAX_OUTPUT = 1e7;
|
|
652
|
+
const timer = setTimeout(() => {
|
|
653
|
+
child.kill("SIGTERM");
|
|
654
|
+
const forceTimer = setTimeout(() => {
|
|
655
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
656
|
+
}, 5e3);
|
|
657
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
658
|
+
reject(new Error(`Codex execution timed out after ${timeout}ms`));
|
|
659
|
+
}, timeout);
|
|
660
|
+
child.stdout?.on("data", (chunk) => {
|
|
661
|
+
stdout += chunk.toString();
|
|
662
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
663
|
+
child.kill("SIGTERM");
|
|
664
|
+
setTimeout(() => {
|
|
665
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
666
|
+
}, 5e3);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
child.stderr?.on("data", (chunk) => {
|
|
670
|
+
stderr += chunk.toString();
|
|
671
|
+
log6.debug("Codex stderr", { text: chunk.toString().trim() });
|
|
672
|
+
});
|
|
673
|
+
child.on("close", (code) => {
|
|
674
|
+
clearTimeout(timer);
|
|
675
|
+
this.currentChild = null;
|
|
676
|
+
if (code === 0) {
|
|
677
|
+
resolve2(stdout.trim());
|
|
678
|
+
} else {
|
|
679
|
+
const errMsg = stderr.trim() || stdout.trim() || `Codex exited with code ${code}`;
|
|
680
|
+
log6.warn("Codex non-zero exit", { code, stderr: errMsg });
|
|
681
|
+
reject(new Error(errMsg));
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
child.on("error", (err) => {
|
|
685
|
+
clearTimeout(timer);
|
|
686
|
+
log6.error("Codex spawn failed", { error: err.message });
|
|
687
|
+
reject(new Error(`Failed to start codex: ${err.message}. Is it installed? (npm i -g @openai/codex)`));
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/executors/codex/executor.ts
|
|
694
|
+
var log7 = logger.child("codex:executor");
|
|
695
|
+
var CodexExecutor = class {
|
|
696
|
+
constructor(config) {
|
|
697
|
+
this.client = null;
|
|
698
|
+
this.initialized = false;
|
|
699
|
+
this.config = config;
|
|
700
|
+
}
|
|
701
|
+
async initialize() {
|
|
702
|
+
if (this.initialized) return;
|
|
703
|
+
const cx = this.config.codex;
|
|
704
|
+
this.client = new CodexClient({
|
|
705
|
+
cliPath: cx.cliPath,
|
|
706
|
+
workdir: cx.workdir,
|
|
707
|
+
model: cx.model || void 0,
|
|
708
|
+
apiKey: cx.apiKey || void 0,
|
|
709
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
710
|
+
});
|
|
711
|
+
this.initialized = true;
|
|
712
|
+
log7.info("Executor initialized", { cliPath: cx.cliPath, workdir: cx.workdir });
|
|
713
|
+
}
|
|
714
|
+
async shutdown() {
|
|
715
|
+
this.client = null;
|
|
716
|
+
this.initialized = false;
|
|
717
|
+
log7.info("Executor shut down");
|
|
718
|
+
}
|
|
719
|
+
async execute(ctx, bus) {
|
|
720
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
721
|
+
await this.initialize();
|
|
722
|
+
try {
|
|
723
|
+
if (!task) {
|
|
724
|
+
publishTask(bus, taskId, contextId);
|
|
725
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
726
|
+
}
|
|
727
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
728
|
+
const promptText = this.extractText(userMessage);
|
|
729
|
+
log7.info("Sending prompt to Codex", { taskId, len: promptText.length });
|
|
730
|
+
const response = await this.client.execute(promptText);
|
|
731
|
+
const finalText = response || "No response from Codex.";
|
|
732
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
733
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
734
|
+
bus.finished();
|
|
735
|
+
log7.info("Task completed", { taskId, len: finalText.length });
|
|
736
|
+
} catch (error) {
|
|
737
|
+
const msg = error.message ?? String(error);
|
|
738
|
+
log7.error("Execution failed", { taskId, error: msg });
|
|
739
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
740
|
+
bus.finished();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async cancelTask(taskId, bus) {
|
|
744
|
+
log7.info("Cancel requested for Codex task", { taskId });
|
|
745
|
+
this.client?.abort();
|
|
746
|
+
publishStatus(bus, taskId, "", "canceled", "Codex task cancelled", true);
|
|
747
|
+
bus.finished();
|
|
748
|
+
}
|
|
749
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
750
|
+
extractText(message) {
|
|
751
|
+
return message.parts.filter((p) => {
|
|
752
|
+
const part = p;
|
|
753
|
+
const text = part.text;
|
|
754
|
+
return text !== void 0 && text !== null;
|
|
755
|
+
}).map((p) => p.text).join("\n");
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
function createCodexExecutor(config) {
|
|
759
|
+
return new CodexExecutor(config);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/executors/claude/client.ts
|
|
763
|
+
var import_node_child_process3 = require("child_process");
|
|
764
|
+
var log8 = logger.child("claude:client");
|
|
765
|
+
var ClaudeClient = class {
|
|
766
|
+
constructor(config) {
|
|
767
|
+
this.config = config;
|
|
768
|
+
this.currentChild = null;
|
|
769
|
+
}
|
|
770
|
+
abort() {
|
|
771
|
+
if (this.currentChild) {
|
|
772
|
+
this.currentChild.kill("SIGTERM");
|
|
773
|
+
setTimeout(() => {
|
|
774
|
+
if (this.currentChild?.exitCode === null) {
|
|
775
|
+
this.currentChild.kill("SIGKILL");
|
|
776
|
+
}
|
|
777
|
+
}, 5e3);
|
|
778
|
+
this.currentChild = null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Execute a prompt via the Claude Code CLI and return the response text.
|
|
783
|
+
*/
|
|
784
|
+
async execute(prompt) {
|
|
785
|
+
const cliPath = this.config.cliPath || "claude";
|
|
786
|
+
const timeout = this.config.timeout ?? 3e5;
|
|
787
|
+
const MAX_PROMPT = 1e5;
|
|
788
|
+
if (prompt.length > MAX_PROMPT) {
|
|
789
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
790
|
+
}
|
|
791
|
+
const env = {
|
|
792
|
+
PATH: process.env.PATH ?? "",
|
|
793
|
+
HOME: process.env.HOME ?? ""
|
|
794
|
+
};
|
|
795
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
796
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
797
|
+
}
|
|
798
|
+
if (this.config.apiKey) {
|
|
799
|
+
env["ANTHROPIC_API_KEY"] = this.config.apiKey;
|
|
800
|
+
}
|
|
801
|
+
const args = ["-p"];
|
|
802
|
+
if (this.config.model) {
|
|
803
|
+
args.push("--model", this.config.model);
|
|
804
|
+
}
|
|
805
|
+
args.push(prompt);
|
|
806
|
+
log8.info("Spawning claude", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
807
|
+
return new Promise((resolve2, reject) => {
|
|
808
|
+
const child = (0, import_node_child_process3.spawn)(cliPath, args, {
|
|
809
|
+
cwd: this.config.workdir || process.cwd(),
|
|
810
|
+
env,
|
|
811
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
812
|
+
});
|
|
813
|
+
this.currentChild = child;
|
|
814
|
+
let stdout = "";
|
|
815
|
+
let stderr = "";
|
|
816
|
+
const MAX_OUTPUT = 1e7;
|
|
817
|
+
const timer = setTimeout(() => {
|
|
818
|
+
child.kill("SIGTERM");
|
|
819
|
+
const forceTimer = setTimeout(() => {
|
|
820
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
821
|
+
}, 5e3);
|
|
822
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
823
|
+
reject(new Error(`Claude Code execution timed out after ${timeout}ms`));
|
|
824
|
+
}, timeout);
|
|
825
|
+
child.stdout?.on("data", (chunk) => {
|
|
826
|
+
stdout += chunk.toString();
|
|
827
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
828
|
+
child.kill("SIGTERM");
|
|
829
|
+
setTimeout(() => {
|
|
830
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
831
|
+
}, 5e3);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
child.stderr?.on("data", (chunk) => {
|
|
835
|
+
stderr += chunk.toString();
|
|
836
|
+
log8.debug("Claude stderr", { text: chunk.toString().trim() });
|
|
837
|
+
});
|
|
838
|
+
child.on("close", (code) => {
|
|
839
|
+
clearTimeout(timer);
|
|
840
|
+
this.currentChild = null;
|
|
841
|
+
if (code === 0) {
|
|
842
|
+
resolve2(stdout.trim());
|
|
843
|
+
} else {
|
|
844
|
+
const errMsg = stderr.trim() || stdout.trim() || `Claude Code exited with code ${code}`;
|
|
845
|
+
log8.warn("Claude non-zero exit", { code, stderr: errMsg });
|
|
846
|
+
reject(new Error(errMsg));
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
child.on("error", (err) => {
|
|
850
|
+
clearTimeout(timer);
|
|
851
|
+
log8.error("Claude spawn failed", { error: err.message });
|
|
852
|
+
reject(new Error(`Failed to start claude: ${err.message}. Install: npm i -g @anthropic-ai/claude-code`));
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// src/executors/claude/executor.ts
|
|
859
|
+
var log9 = logger.child("claude:executor");
|
|
860
|
+
var ClaudeExecutor = class {
|
|
861
|
+
constructor(config) {
|
|
862
|
+
this.client = null;
|
|
863
|
+
this.initialized = false;
|
|
864
|
+
this.config = config;
|
|
865
|
+
}
|
|
866
|
+
async initialize() {
|
|
867
|
+
if (this.initialized) return;
|
|
868
|
+
const cc = this.config.claude;
|
|
869
|
+
this.client = new ClaudeClient({
|
|
870
|
+
cliPath: cc.cliPath,
|
|
871
|
+
workdir: cc.workdir,
|
|
872
|
+
model: cc.model || void 0,
|
|
873
|
+
apiKey: cc.apiKey || void 0,
|
|
874
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
875
|
+
});
|
|
876
|
+
this.initialized = true;
|
|
877
|
+
log9.info("Executor initialized", { cliPath: cc.cliPath, workdir: cc.workdir });
|
|
878
|
+
}
|
|
879
|
+
async shutdown() {
|
|
880
|
+
this.client = null;
|
|
881
|
+
this.initialized = false;
|
|
882
|
+
log9.info("Executor shut down");
|
|
883
|
+
}
|
|
884
|
+
async execute(ctx, bus) {
|
|
885
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
886
|
+
await this.initialize();
|
|
887
|
+
try {
|
|
888
|
+
if (!task) {
|
|
889
|
+
publishTask(bus, taskId, contextId);
|
|
890
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
891
|
+
}
|
|
892
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
893
|
+
const promptText = this.extractText(userMessage);
|
|
894
|
+
log9.info("Sending prompt to Claude Code", { taskId, len: promptText.length });
|
|
895
|
+
const response = await this.client.execute(promptText);
|
|
896
|
+
const finalText = response || "No response from Claude Code.";
|
|
897
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
898
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
899
|
+
bus.finished();
|
|
900
|
+
log9.info("Task completed", { taskId, len: finalText.length });
|
|
901
|
+
} catch (error) {
|
|
902
|
+
const msg = error.message ?? String(error);
|
|
903
|
+
log9.error("Execution failed", { taskId, error: msg });
|
|
904
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
905
|
+
bus.finished();
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async cancelTask(taskId, bus) {
|
|
909
|
+
log9.info("Cancel requested for Claude Code task", { taskId });
|
|
910
|
+
this.client?.abort();
|
|
911
|
+
publishStatus(bus, taskId, "", "canceled", "Claude Code task cancelled", true);
|
|
912
|
+
bus.finished();
|
|
913
|
+
}
|
|
914
|
+
extractText(message) {
|
|
915
|
+
return message.parts.filter((p) => {
|
|
916
|
+
const part = p;
|
|
917
|
+
const text = part.text;
|
|
918
|
+
return text !== void 0 && text !== null;
|
|
919
|
+
}).map((p) => p.text).join("\n");
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
function createClaudeExecutor(config) {
|
|
923
|
+
return new ClaudeExecutor(config);
|
|
924
|
+
}
|
|
925
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
926
|
+
0 && (module.exports = {
|
|
927
|
+
ClaudeExecutor,
|
|
928
|
+
CodexExecutor,
|
|
929
|
+
LogLevel,
|
|
930
|
+
OpenCodeExecutor,
|
|
931
|
+
createA2AServer,
|
|
932
|
+
createClaudeExecutor,
|
|
933
|
+
createCodexExecutor,
|
|
934
|
+
createExecutor,
|
|
935
|
+
createOpenCodeExecutor,
|
|
936
|
+
getExecutor,
|
|
937
|
+
loadConfigFile,
|
|
938
|
+
logger,
|
|
939
|
+
registerExecutor,
|
|
940
|
+
resolveConfig
|
|
941
|
+
});
|
|
942
|
+
//# sourceMappingURL=index.js.map
|