@axiom-lattice/cli-a2a 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +51 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +290 -0
- package/__tests__/opencode-executor.test.ts +159 -0
- package/agents/opencode-example/config.json +64 -0
- package/dist/bridge-7ZUDKCZT.mjs +271 -0
- package/dist/bridge-7ZUDKCZT.mjs.map +1 -0
- package/dist/chunk-35NFMGMS.mjs +27 -0
- package/dist/chunk-35NFMGMS.mjs.map +1 -0
- package/dist/chunk-G7AGL2QA.mjs +284 -0
- package/dist/chunk-G7AGL2QA.mjs.map +1 -0
- package/dist/chunk-LXL47XMZ.mjs +43 -0
- package/dist/chunk-LXL47XMZ.mjs.map +1 -0
- package/dist/chunk-NQIDRU47.mjs +178 -0
- package/dist/chunk-NQIDRU47.mjs.map +1 -0
- package/dist/chunk-VSZ3DACI.mjs +179 -0
- package/dist/chunk-VSZ3DACI.mjs.map +1 -0
- package/dist/chunk-VZEH3EPJ.mjs +56 -0
- package/dist/chunk-VZEH3EPJ.mjs.map +1 -0
- package/dist/chunk-WNCDOYZS.mjs +187 -0
- package/dist/chunk-WNCDOYZS.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1562 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +293 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/executor-OWPVDUXH.mjs +11 -0
- package/dist/executor-OWPVDUXH.mjs.map +1 -0
- package/dist/executor-RVBGAWUF.mjs +11 -0
- package/dist/executor-RVBGAWUF.mjs.map +1 -0
- package/dist/executor-XWHWUVQ3.mjs +11 -0
- package/dist/executor-XWHWUVQ3.mjs.map +1 -0
- package/dist/executors-QIIKBUMJ.mjs +15 -0
- package/dist/executors-QIIKBUMJ.mjs.map +1 -0
- package/dist/index.d.mts +295 -0
- package/dist/index.d.ts +295 -0
- package/dist/index.js +942 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +44 -0
- package/dist/index.mjs.map +1 -0
- package/jest.config.js +16 -0
- package/package.json +63 -0
- package/src/bridge.ts +355 -0
- package/src/cli.ts +384 -0
- package/src/config/defaults.ts +109 -0
- package/src/config/index.ts +16 -0
- package/src/config/loader.ts +121 -0
- package/src/config/types.ts +163 -0
- package/src/executors/claude/client.ts +138 -0
- package/src/executors/claude/executor.ts +111 -0
- package/src/executors/codex/client.ts +139 -0
- package/src/executors/codex/executor.ts +118 -0
- package/src/executors/events.ts +117 -0
- package/src/executors/index.ts +54 -0
- package/src/executors/opencode/client.ts +149 -0
- package/src/executors/opencode/executor.ts +112 -0
- package/src/index.ts +54 -0
- package/src/logger.ts +78 -0
- package/src/server/agent-card.ts +51 -0
- package/src/server/index.ts +124 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
publishFinalArtifact,
|
|
3
|
+
publishStatus,
|
|
4
|
+
publishTask
|
|
5
|
+
} from "./chunk-LXL47XMZ.mjs";
|
|
6
|
+
import {
|
|
7
|
+
logger
|
|
8
|
+
} from "./chunk-VZEH3EPJ.mjs";
|
|
9
|
+
|
|
10
|
+
// src/executors/codex/client.ts
|
|
11
|
+
import { spawn } from "child_process";
|
|
12
|
+
var log = logger.child("codex:client");
|
|
13
|
+
var CodexClient = class {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.currentChild = null;
|
|
17
|
+
}
|
|
18
|
+
abort() {
|
|
19
|
+
if (this.currentChild) {
|
|
20
|
+
this.currentChild.kill("SIGTERM");
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (this.currentChild?.exitCode === null) {
|
|
23
|
+
this.currentChild.kill("SIGKILL");
|
|
24
|
+
}
|
|
25
|
+
}, 5e3);
|
|
26
|
+
this.currentChild = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Execute a prompt via the Codex CLI and return the response text.
|
|
31
|
+
*/
|
|
32
|
+
async execute(prompt) {
|
|
33
|
+
const cliPath = this.config.cliPath || "codex";
|
|
34
|
+
const timeout = this.config.timeout ?? 3e5;
|
|
35
|
+
const MAX_PROMPT = 1e5;
|
|
36
|
+
if (prompt.length > MAX_PROMPT) {
|
|
37
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
|
|
38
|
+
}
|
|
39
|
+
const env = {
|
|
40
|
+
PATH: process.env.PATH ?? "",
|
|
41
|
+
HOME: process.env.HOME ?? ""
|
|
42
|
+
};
|
|
43
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
44
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
45
|
+
}
|
|
46
|
+
if (this.config.apiKey) {
|
|
47
|
+
env["OPENAI_API_KEY"] = this.config.apiKey;
|
|
48
|
+
}
|
|
49
|
+
const args = [];
|
|
50
|
+
if (this.config.model) {
|
|
51
|
+
args.push("--model", this.config.model);
|
|
52
|
+
}
|
|
53
|
+
args.push(prompt);
|
|
54
|
+
log.info("Spawning codex", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const child = spawn(cliPath, args, {
|
|
57
|
+
cwd: this.config.workdir || process.cwd(),
|
|
58
|
+
env,
|
|
59
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
60
|
+
});
|
|
61
|
+
this.currentChild = child;
|
|
62
|
+
let stdout = "";
|
|
63
|
+
let stderr = "";
|
|
64
|
+
const MAX_OUTPUT = 1e7;
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
child.kill("SIGTERM");
|
|
67
|
+
const forceTimer = setTimeout(() => {
|
|
68
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
69
|
+
}, 5e3);
|
|
70
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
71
|
+
reject(new Error(`Codex execution timed out after ${timeout}ms`));
|
|
72
|
+
}, timeout);
|
|
73
|
+
child.stdout?.on("data", (chunk) => {
|
|
74
|
+
stdout += chunk.toString();
|
|
75
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
76
|
+
child.kill("SIGTERM");
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
79
|
+
}, 5e3);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
child.stderr?.on("data", (chunk) => {
|
|
83
|
+
stderr += chunk.toString();
|
|
84
|
+
log.debug("Codex stderr", { text: chunk.toString().trim() });
|
|
85
|
+
});
|
|
86
|
+
child.on("close", (code) => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
this.currentChild = null;
|
|
89
|
+
if (code === 0) {
|
|
90
|
+
resolve(stdout.trim());
|
|
91
|
+
} else {
|
|
92
|
+
const errMsg = stderr.trim() || stdout.trim() || `Codex exited with code ${code}`;
|
|
93
|
+
log.warn("Codex non-zero exit", { code, stderr: errMsg });
|
|
94
|
+
reject(new Error(errMsg));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
child.on("error", (err) => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
log.error("Codex spawn failed", { error: err.message });
|
|
100
|
+
reject(new Error(`Failed to start codex: ${err.message}. Is it installed? (npm i -g @openai/codex)`));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/executors/codex/executor.ts
|
|
107
|
+
var log2 = logger.child("codex:executor");
|
|
108
|
+
var CodexExecutor = class {
|
|
109
|
+
constructor(config) {
|
|
110
|
+
this.client = null;
|
|
111
|
+
this.initialized = false;
|
|
112
|
+
this.config = config;
|
|
113
|
+
}
|
|
114
|
+
async initialize() {
|
|
115
|
+
if (this.initialized) return;
|
|
116
|
+
const cx = this.config.codex;
|
|
117
|
+
this.client = new CodexClient({
|
|
118
|
+
cliPath: cx.cliPath,
|
|
119
|
+
workdir: cx.workdir,
|
|
120
|
+
model: cx.model || void 0,
|
|
121
|
+
apiKey: cx.apiKey || void 0,
|
|
122
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
123
|
+
});
|
|
124
|
+
this.initialized = true;
|
|
125
|
+
log2.info("Executor initialized", { cliPath: cx.cliPath, workdir: cx.workdir });
|
|
126
|
+
}
|
|
127
|
+
async shutdown() {
|
|
128
|
+
this.client = null;
|
|
129
|
+
this.initialized = false;
|
|
130
|
+
log2.info("Executor shut down");
|
|
131
|
+
}
|
|
132
|
+
async execute(ctx, bus) {
|
|
133
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
134
|
+
await this.initialize();
|
|
135
|
+
try {
|
|
136
|
+
if (!task) {
|
|
137
|
+
publishTask(bus, taskId, contextId);
|
|
138
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
139
|
+
}
|
|
140
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
141
|
+
const promptText = this.extractText(userMessage);
|
|
142
|
+
log2.info("Sending prompt to Codex", { taskId, len: promptText.length });
|
|
143
|
+
const response = await this.client.execute(promptText);
|
|
144
|
+
const finalText = response || "No response from Codex.";
|
|
145
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
146
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
147
|
+
bus.finished();
|
|
148
|
+
log2.info("Task completed", { taskId, len: finalText.length });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const msg = error.message ?? String(error);
|
|
151
|
+
log2.error("Execution failed", { taskId, error: msg });
|
|
152
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
153
|
+
bus.finished();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async cancelTask(taskId, bus) {
|
|
157
|
+
log2.info("Cancel requested for Codex task", { taskId });
|
|
158
|
+
this.client?.abort();
|
|
159
|
+
publishStatus(bus, taskId, "", "canceled", "Codex task cancelled", true);
|
|
160
|
+
bus.finished();
|
|
161
|
+
}
|
|
162
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
163
|
+
extractText(message) {
|
|
164
|
+
return message.parts.filter((p) => {
|
|
165
|
+
const part = p;
|
|
166
|
+
const text = part.text;
|
|
167
|
+
return text !== void 0 && text !== null;
|
|
168
|
+
}).map((p) => p.text).join("\n");
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function createCodexExecutor(config) {
|
|
172
|
+
return new CodexExecutor(config);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export {
|
|
176
|
+
CodexExecutor,
|
|
177
|
+
createCodexExecutor
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=chunk-VSZ3DACI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/executors/codex/client.ts","../src/executors/codex/executor.ts"],"sourcesContent":["/**\n * Codex CLI client wrapper.\n *\n * Spawns the OpenAI Codex CLI as a child process, sends prompts via stdin,\n * captures stdout as the response. Supports configurable CLI path, working\n * directory, model selection, and timeout.\n */\n\nimport { spawn } from 'node:child_process';\nimport { logger } from '../../logger.js';\n\nconst log = logger.child('codex:client');\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface CodexClientConfig {\n /** Path to the codex binary (default: \"codex\") */\n cliPath: string;\n /** Working directory for the codex process */\n workdir: string;\n /** Model identifier (e.g. \"gpt-4o\") — passed as CODELLM env or --model flag */\n model?: string;\n /** OpenAI API key — passed as OPENAI_API_KEY env */\n apiKey?: string;\n /** Timeout in ms (default: 300_000 = 5 min) */\n timeout?: number;\n}\n\n// ─── Client ─────────────────────────────────────────────────────────────────\n\nexport class CodexClient {\n private currentChild: ReturnType<typeof spawn> | null = null;\n\n constructor(private config: CodexClientConfig) {}\n\n abort(): void {\n if (this.currentChild) {\n this.currentChild.kill('SIGTERM');\n setTimeout(() => {\n if (this.currentChild?.exitCode === null) {\n this.currentChild.kill('SIGKILL');\n }\n }, 5000);\n this.currentChild = null;\n }\n }\n\n /**\n * Execute a prompt via the Codex CLI and return the response text.\n */\n async execute(prompt: string): Promise<string> {\n const cliPath = this.config.cliPath || 'codex';\n const timeout = this.config.timeout ?? 300_000;\n const MAX_PROMPT = 100_000;\n\n if (prompt.length > MAX_PROMPT) {\n throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);\n }\n\n // Only pass relevant env vars to child process\n const env: Record<string, string> = {\n PATH: process.env.PATH ?? '',\n HOME: process.env.HOME ?? '',\n };\n // Forward common proxy/no-proxy settings\n for (const key of ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'no_proxy']) {\n if (process.env[key]) env[key] = process.env[key];\n }\n\n if (this.config.apiKey) {\n env['OPENAI_API_KEY'] = this.config.apiKey;\n }\n\n const args: string[] = [];\n // Some codex versions support --model flag\n if (this.config.model) {\n args.push('--model', this.config.model);\n }\n args.push(prompt);\n\n log.info('Spawning codex', { cliPath, workdir: this.config.workdir, model: this.config.model });\n\n return new Promise<string>((resolve, reject) => {\n const child = spawn(cliPath, args, {\n cwd: this.config.workdir || process.cwd(),\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.currentChild = child;\n\n let stdout = '';\n let stderr = '';\n const MAX_OUTPUT = 10_000_000;\n\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n // Force kill after 5s grace period\n const forceTimer = setTimeout(() => {\n if (child.exitCode === null) child.kill('SIGKILL');\n }, 5000);\n child.on('close', () => clearTimeout(forceTimer));\n reject(new Error(`Codex execution timed out after ${timeout}ms`));\n }, timeout);\n\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n if (stdout.length > MAX_OUTPUT) {\n child.kill('SIGTERM');\n setTimeout(() => { if (child.exitCode === null) child.kill('SIGKILL'); }, 5000);\n }\n });\n\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n log.debug('Codex stderr', { text: chunk.toString().trim() });\n });\n\n child.on('close', (code) => {\n clearTimeout(timer);\n this.currentChild = null;\n\n if (code === 0) {\n resolve(stdout.trim());\n } else {\n const errMsg = stderr.trim() || stdout.trim() || `Codex exited with code ${code}`;\n log.warn('Codex non-zero exit', { code, stderr: errMsg });\n reject(new Error(errMsg));\n }\n });\n\n child.on('error', (err) => {\n clearTimeout(timer);\n log.error('Codex spawn failed', { error: err.message });\n reject(new Error(`Failed to start codex: ${err.message}. Is it installed? (npm i -g @openai/codex)`));\n });\n });\n }\n}\n","/**\n * Codex A2A Executor\n *\n * Bridges the local Codex CLI to the A2A protocol. Spawns Codex as a\n * child process for each request and captures stdout as the response.\n */\n\nimport type { A2AExecutor } from '../index.js';\nimport type { RequestContext, ExecutionEventBus } from '@a2a-js/sdk/server';\nimport type { Message as A2AMessage } from '@a2a-js/sdk';\n\nimport type { AgentConfig } from '../../config/types.js';\nimport { logger } from '../../logger.js';\nimport { CodexClient } from './client.js';\nimport {\n publishTask,\n publishStatus,\n publishFinalArtifact,\n} from '../events.js';\n\nconst log = logger.child('codex:executor');\n\n// ─── Executor ───────────────────────────────────────────────────────────────\n\nexport class CodexExecutor implements A2AExecutor {\n private config: Required<AgentConfig>;\n private client: CodexClient | null = null;\n private initialized = false;\n\n constructor(config: Required<AgentConfig>) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n const cx = this.config.codex;\n this.client = new CodexClient({\n cliPath: cx.cliPath!,\n workdir: cx.workdir!,\n model: cx.model || undefined,\n apiKey: cx.apiKey || undefined,\n timeout: this.config.timeouts.prompt ?? 600_000,\n });\n\n this.initialized = true;\n log.info('Executor initialized', { cliPath: cx.cliPath, workdir: cx.workdir });\n }\n\n async shutdown(): Promise<void> {\n this.client = null;\n this.initialized = false;\n log.info('Executor shut down');\n }\n\n async execute(ctx: RequestContext, bus: ExecutionEventBus): Promise<void> {\n const { taskId, contextId, userMessage, task } = ctx;\n await this.initialize();\n\n try {\n // 1. Register task\n if (!task) {\n publishTask(bus, taskId, contextId);\n publishStatus(bus, taskId, contextId, 'submitted');\n }\n\n // 2. Working\n publishStatus(bus, taskId, contextId, 'working', 'Processing request...');\n\n // 3. Extract prompt text\n const promptText = this.extractText(userMessage);\n\n log.info('Sending prompt to Codex', { taskId, len: promptText.length });\n\n // 4. Execute\n const response = await this.client!.execute(promptText);\n\n // 5. Finalize\n const finalText = response || 'No response from Codex.';\n publishFinalArtifact(bus, taskId, contextId, finalText);\n publishStatus(bus, taskId, contextId, 'completed', undefined, true);\n bus.finished();\n log.info('Task completed', { taskId, len: finalText.length });\n\n } catch (error) {\n const msg = (error as Error).message ?? String(error);\n log.error('Execution failed', { taskId, error: msg });\n publishStatus(bus, taskId, contextId, 'failed', `Error: ${msg}`, true);\n bus.finished();\n }\n }\n\n async cancelTask(taskId: string, bus: ExecutionEventBus): Promise<void> {\n log.info('Cancel requested for Codex task', { taskId });\n this.client?.abort();\n publishStatus(bus, taskId, '', 'canceled', 'Codex task cancelled', true);\n bus.finished();\n }\n\n // ── Helpers ─────────────────────────────────────────────────────────────\n\n private extractText(message: A2AMessage): string {\n return message.parts\n .filter((p) => {\n const part = p as unknown as Record<string, unknown>;\n const text = part.text as string | undefined;\n return text !== undefined && text !== null;\n })\n .map((p) => (p as unknown as { text: string }).text)\n .join('\\n');\n }\n}\n\n// ─── Factory ────────────────────────────────────────────────────────────────\n\nexport function createCodexExecutor(config: Required<AgentConfig>): A2AExecutor {\n return new CodexExecutor(config);\n}\n"],"mappings":"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","names":["log"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
3
|
+
LogLevel2[LogLevel2["DEBUG"] = 10] = "DEBUG";
|
|
4
|
+
LogLevel2[LogLevel2["INFO"] = 20] = "INFO";
|
|
5
|
+
LogLevel2[LogLevel2["WARN"] = 30] = "WARN";
|
|
6
|
+
LogLevel2[LogLevel2["ERROR"] = 40] = "ERROR";
|
|
7
|
+
return LogLevel2;
|
|
8
|
+
})(LogLevel || {});
|
|
9
|
+
var Logger = class _Logger {
|
|
10
|
+
constructor(name) {
|
|
11
|
+
this.level = 20 /* INFO */;
|
|
12
|
+
this.name = name;
|
|
13
|
+
}
|
|
14
|
+
setLevel(level) {
|
|
15
|
+
this.level = level;
|
|
16
|
+
}
|
|
17
|
+
child(name) {
|
|
18
|
+
const childLogger = new _Logger(`${this.name}:${name}`);
|
|
19
|
+
childLogger.level = this.level;
|
|
20
|
+
return childLogger;
|
|
21
|
+
}
|
|
22
|
+
debug(msg, ctx) {
|
|
23
|
+
this.log(10 /* DEBUG */, msg, ctx);
|
|
24
|
+
}
|
|
25
|
+
info(msg, ctx) {
|
|
26
|
+
this.log(20 /* INFO */, msg, ctx);
|
|
27
|
+
}
|
|
28
|
+
warn(msg, ctx) {
|
|
29
|
+
this.log(30 /* WARN */, msg, ctx);
|
|
30
|
+
}
|
|
31
|
+
error(msg, ctx) {
|
|
32
|
+
this.log(40 /* ERROR */, msg, ctx);
|
|
33
|
+
}
|
|
34
|
+
log(level, msg, ctx) {
|
|
35
|
+
if (level < this.level) return;
|
|
36
|
+
const entry = {
|
|
37
|
+
level,
|
|
38
|
+
msg,
|
|
39
|
+
ctx: { name: this.name, ...ctx },
|
|
40
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
41
|
+
};
|
|
42
|
+
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) : ""}`;
|
|
43
|
+
if (level >= 30 /* WARN */) {
|
|
44
|
+
process.stderr.write(output + "\n");
|
|
45
|
+
} else {
|
|
46
|
+
process.stdout.write(output + "\n");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var logger = new Logger("cli-a2a");
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
LogLevel,
|
|
54
|
+
logger
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-VZEH3EPJ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts"],"sourcesContent":["/**\n * Lightweight structured logger for the CLI A2A gateway.\n *\n * Uses pino-compatible structured logging. In production mode the output is\n * JSON; in dev mode it is pretty-printed via pino-pretty.\n */\n\nexport enum LogLevel {\n DEBUG = 10,\n INFO = 20,\n WARN = 30,\n ERROR = 40,\n}\n\ninterface LogEntry {\n level: LogLevel;\n msg: string;\n ctx: Record<string, unknown>;\n ts: string;\n}\n\nclass Logger {\n private level: LogLevel = LogLevel.INFO;\n private name: string;\n\n constructor(name: string) {\n this.name = name;\n }\n\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n child(name: string): Logger {\n const childLogger = new Logger(`${this.name}:${name}`);\n childLogger.level = this.level;\n return childLogger;\n }\n\n debug(msg: string, ctx?: Record<string, unknown>): void {\n this.log(LogLevel.DEBUG, msg, ctx);\n }\n\n info(msg: string, ctx?: Record<string, unknown>): void {\n this.log(LogLevel.INFO, msg, ctx);\n }\n\n warn(msg: string, ctx?: Record<string, unknown>): void {\n this.log(LogLevel.WARN, msg, ctx);\n }\n\n error(msg: string, ctx?: Record<string, unknown>): void {\n this.log(LogLevel.ERROR, msg, ctx);\n }\n\n private log(level: LogLevel, msg: string, ctx?: Record<string, unknown>): void {\n if (level < this.level) return;\n\n const entry: LogEntry = {\n level,\n msg,\n ctx: { name: this.name, ...ctx },\n ts: new Date().toISOString(),\n };\n\n const output = process.env.NODE_ENV === 'production'\n ? JSON.stringify(entry)\n : `${entry.ts} [${LogLevel[entry.level]}] ${entry.ctx.name}: ${msg}${ctx && Object.keys(ctx).length > 1 ? ' ' + JSON.stringify(ctx) : ''}`;\n\n if (level >= LogLevel.WARN) {\n process.stderr.write(output + '\\n');\n } else {\n process.stdout.write(output + '\\n');\n }\n }\n}\n\nexport const logger = new Logger('cli-a2a');\n"],"mappings":";AAOO,IAAK,WAAL,kBAAKA,cAAL;AACL,EAAAA,oBAAA,WAAQ,MAAR;AACA,EAAAA,oBAAA,UAAO,MAAP;AACA,EAAAA,oBAAA,UAAO,MAAP;AACA,EAAAA,oBAAA,WAAQ,MAAR;AAJU,SAAAA;AAAA,GAAA;AAcZ,IAAM,SAAN,MAAM,QAAO;AAAA,EAIX,YAAY,MAAc;AAH1B,SAAQ,QAAkB;AAIxB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,SAAS,OAAuB;AAC9B,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,MAAsB;AAC1B,UAAM,cAAc,IAAI,QAAO,GAAG,KAAK,IAAI,IAAI,IAAI,EAAE;AACrD,gBAAY,QAAQ,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,KAAa,KAAqC;AACtD,SAAK,IAAI,gBAAgB,KAAK,GAAG;AAAA,EACnC;AAAA,EAEA,KAAK,KAAa,KAAqC;AACrD,SAAK,IAAI,eAAe,KAAK,GAAG;AAAA,EAClC;AAAA,EAEA,KAAK,KAAa,KAAqC;AACrD,SAAK,IAAI,eAAe,KAAK,GAAG;AAAA,EAClC;AAAA,EAEA,MAAM,KAAa,KAAqC;AACtD,SAAK,IAAI,gBAAgB,KAAK,GAAG;AAAA,EACnC;AAAA,EAEQ,IAAI,OAAiB,KAAa,KAAqC;AAC7E,QAAI,QAAQ,KAAK,MAAO;AAExB,UAAM,QAAkB;AAAA,MACtB;AAAA,MACA;AAAA,MACA,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI;AAAA,MAC/B,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC7B;AAEA,UAAM,SAAS,QAAQ,IAAI,aAAa,eACpC,KAAK,UAAU,KAAK,IACpB,GAAG,MAAM,EAAE,KAAK,SAAS,MAAM,KAAK,CAAC,KAAK,MAAM,IAAI,IAAI,KAAK,GAAG,GAAG,OAAO,OAAO,KAAK,GAAG,EAAE,SAAS,IAAI,MAAM,KAAK,UAAU,GAAG,IAAI,EAAE;AAE1I,QAAI,SAAS,eAAe;AAC1B,cAAQ,OAAO,MAAM,SAAS,IAAI;AAAA,IACpC,OAAO;AACL,cAAQ,OAAO,MAAM,SAAS,IAAI;AAAA,IACpC;AAAA,EACF;AACF;AAEO,IAAM,SAAS,IAAI,OAAO,SAAS;","names":["LogLevel"]}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
publishFinalArtifact,
|
|
3
|
+
publishStatus,
|
|
4
|
+
publishTask
|
|
5
|
+
} from "./chunk-LXL47XMZ.mjs";
|
|
6
|
+
import {
|
|
7
|
+
logger
|
|
8
|
+
} from "./chunk-VZEH3EPJ.mjs";
|
|
9
|
+
|
|
10
|
+
// src/executors/opencode/client.ts
|
|
11
|
+
import { spawn } from "child_process";
|
|
12
|
+
var log = logger.child("opencode:client");
|
|
13
|
+
var OpenCodeClient = class {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.currentChild = null;
|
|
17
|
+
}
|
|
18
|
+
/** Abort the currently running child process (if any). */
|
|
19
|
+
abort() {
|
|
20
|
+
if (this.currentChild) {
|
|
21
|
+
this.currentChild.kill("SIGTERM");
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
if (this.currentChild?.exitCode === null) {
|
|
24
|
+
this.currentChild.kill("SIGKILL");
|
|
25
|
+
}
|
|
26
|
+
}, 5e3);
|
|
27
|
+
this.currentChild = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Execute a prompt via the OpenCode CLI and return the response text.
|
|
32
|
+
*/
|
|
33
|
+
async execute(prompt) {
|
|
34
|
+
const cliPath = this.config.cliPath || "opencode";
|
|
35
|
+
const timeout = this.config.timeout ?? 6e5;
|
|
36
|
+
const MAX_PROMPT = 1e5;
|
|
37
|
+
if (prompt.length > MAX_PROMPT) {
|
|
38
|
+
throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages or use a provider with HTTP API transport.`);
|
|
39
|
+
}
|
|
40
|
+
const env = {
|
|
41
|
+
PATH: process.env.PATH ?? "",
|
|
42
|
+
HOME: process.env.HOME ?? ""
|
|
43
|
+
};
|
|
44
|
+
for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
|
|
45
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
46
|
+
}
|
|
47
|
+
for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
|
|
48
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
49
|
+
}
|
|
50
|
+
const args = ["run", prompt];
|
|
51
|
+
if (this.config.model) {
|
|
52
|
+
args.push("--model", this.config.model);
|
|
53
|
+
}
|
|
54
|
+
if (this.config.agent) {
|
|
55
|
+
args.push("--agent", this.config.agent);
|
|
56
|
+
}
|
|
57
|
+
if (this.config.attachUrl) {
|
|
58
|
+
args.push("--attach", this.config.attachUrl);
|
|
59
|
+
}
|
|
60
|
+
args.push("--format", "default");
|
|
61
|
+
args.push("--dangerously-skip-permissions");
|
|
62
|
+
log.info("Spawning opencode", { cliPath, workdir: this.config.workdir, model: this.config.model });
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const child = spawn(cliPath, args, {
|
|
65
|
+
cwd: this.config.workdir || process.cwd(),
|
|
66
|
+
env,
|
|
67
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
68
|
+
});
|
|
69
|
+
this.currentChild = child;
|
|
70
|
+
let stdout = "";
|
|
71
|
+
let stderr = "";
|
|
72
|
+
const MAX_OUTPUT = 1e7;
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
child.kill("SIGTERM");
|
|
75
|
+
const forceTimer = setTimeout(() => {
|
|
76
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
77
|
+
}, 5e3);
|
|
78
|
+
child.on("close", () => clearTimeout(forceTimer));
|
|
79
|
+
reject(new Error(`OpenCode execution timed out after ${timeout}ms`));
|
|
80
|
+
}, timeout);
|
|
81
|
+
child.stdout?.on("data", (chunk) => {
|
|
82
|
+
stdout += chunk.toString();
|
|
83
|
+
if (stdout.length > MAX_OUTPUT) {
|
|
84
|
+
child.kill("SIGTERM");
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
if (child.exitCode === null) child.kill("SIGKILL");
|
|
87
|
+
}, 5e3);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
child.stderr?.on("data", (chunk) => {
|
|
91
|
+
stderr += chunk.toString();
|
|
92
|
+
log.debug("OpenCode stderr", { text: chunk.toString().trim() });
|
|
93
|
+
});
|
|
94
|
+
child.on("close", (code) => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
this.currentChild = null;
|
|
97
|
+
if (code === 0) {
|
|
98
|
+
resolve(stdout.trim());
|
|
99
|
+
} else {
|
|
100
|
+
const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
|
|
101
|
+
log.warn("OpenCode non-zero exit", { code, stderr: errMsg });
|
|
102
|
+
reject(new Error(errMsg));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
child.on("error", (err) => {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
log.error("OpenCode spawn failed", { error: err.message });
|
|
108
|
+
reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/executors/opencode/executor.ts
|
|
115
|
+
var log2 = logger.child("opencode:executor");
|
|
116
|
+
var OpenCodeExecutor = class {
|
|
117
|
+
constructor(config) {
|
|
118
|
+
this.client = null;
|
|
119
|
+
this.initialized = false;
|
|
120
|
+
this.config = config;
|
|
121
|
+
}
|
|
122
|
+
async initialize() {
|
|
123
|
+
if (this.initialized) return;
|
|
124
|
+
const oc = this.config.opencode;
|
|
125
|
+
this.client = new OpenCodeClient({
|
|
126
|
+
cliPath: "opencode",
|
|
127
|
+
workdir: oc.projectDirectory || process.cwd(),
|
|
128
|
+
model: oc.model || void 0,
|
|
129
|
+
agent: oc.agent || void 0,
|
|
130
|
+
attachUrl: oc.baseUrl || void 0,
|
|
131
|
+
timeout: this.config.timeouts.prompt ?? 6e5
|
|
132
|
+
});
|
|
133
|
+
this.initialized = true;
|
|
134
|
+
log2.info("Executor initialized", { workdir: oc.projectDirectory, model: oc.model });
|
|
135
|
+
}
|
|
136
|
+
async shutdown() {
|
|
137
|
+
this.client = null;
|
|
138
|
+
this.initialized = false;
|
|
139
|
+
log2.info("Executor shut down");
|
|
140
|
+
}
|
|
141
|
+
async execute(ctx, bus) {
|
|
142
|
+
const { taskId, contextId, userMessage, task } = ctx;
|
|
143
|
+
await this.initialize();
|
|
144
|
+
try {
|
|
145
|
+
if (!task) {
|
|
146
|
+
publishTask(bus, taskId, contextId);
|
|
147
|
+
publishStatus(bus, taskId, contextId, "submitted");
|
|
148
|
+
}
|
|
149
|
+
publishStatus(bus, taskId, contextId, "working", "Processing request...");
|
|
150
|
+
const promptText = this.extractText(userMessage);
|
|
151
|
+
log2.info("Sending prompt to OpenCode", { taskId, len: promptText.length });
|
|
152
|
+
const response = await this.client.execute(promptText);
|
|
153
|
+
const finalText = response || "No response from OpenCode.";
|
|
154
|
+
publishFinalArtifact(bus, taskId, contextId, finalText);
|
|
155
|
+
publishStatus(bus, taskId, contextId, "completed", void 0, true);
|
|
156
|
+
bus.finished();
|
|
157
|
+
log2.info("Task completed", { taskId, len: finalText.length });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const msg = error.message ?? String(error);
|
|
160
|
+
log2.error("Execution failed", { taskId, error: msg });
|
|
161
|
+
publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
|
|
162
|
+
bus.finished();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async cancelTask(taskId, bus) {
|
|
166
|
+
log2.info("Cancel requested for OpenCode task", { taskId });
|
|
167
|
+
this.client?.abort();
|
|
168
|
+
publishStatus(bus, taskId, "", "canceled", "OpenCode task cancelled", true);
|
|
169
|
+
bus.finished();
|
|
170
|
+
}
|
|
171
|
+
extractText(message) {
|
|
172
|
+
return message.parts.filter((p) => {
|
|
173
|
+
const part = p;
|
|
174
|
+
const text = part.text;
|
|
175
|
+
return text !== void 0 && text !== null;
|
|
176
|
+
}).map((p) => p.text).join("\n");
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
function createOpenCodeExecutor(config) {
|
|
180
|
+
return new OpenCodeExecutor(config);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
OpenCodeExecutor,
|
|
185
|
+
createOpenCodeExecutor
|
|
186
|
+
};
|
|
187
|
+
//# sourceMappingURL=chunk-WNCDOYZS.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/executors/opencode/client.ts","../src/executors/opencode/executor.ts"],"sourcesContent":["/**\n * OpenCode CLI client wrapper.\n *\n * Spawns the OpenCode CLI as a child process. Uses `opencode run` with\n * optional `--attach` to connect to an existing server. Captures stdout\n * as the response. Same pattern as Codex and Claude Code clients.\n */\n\nimport { spawn } from 'node:child_process';\nimport { logger } from '../../logger.js';\n\nconst log = logger.child('opencode:client');\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface OpenCodeClientConfig {\n /** Path to the opencode binary (default: \"opencode\") */\n cliPath: string;\n /** Working directory for the process */\n workdir: string;\n /** Model identifier (e.g. \"anthropic/claude-sonnet-4\") */\n model?: string;\n /** Agent preset name */\n agent?: string;\n /** Optional URL of a running OpenCode server to attach to */\n attachUrl?: string;\n /** Timeout in ms (default: 600_000 = 10 min) */\n timeout?: number;\n}\n\n// ─── Client ─────────────────────────────────────────────────────────────────\n\nexport class OpenCodeClient {\n private currentChild: ReturnType<typeof spawn> | null = null;\n\n constructor(private config: OpenCodeClientConfig) {}\n\n /** Abort the currently running child process (if any). */\n abort(): void {\n if (this.currentChild) {\n this.currentChild.kill('SIGTERM');\n setTimeout(() => {\n if (this.currentChild?.exitCode === null) {\n this.currentChild.kill('SIGKILL');\n }\n }, 5000);\n this.currentChild = null;\n }\n }\n\n /**\n * Execute a prompt via the OpenCode CLI and return the response text.\n */\n async execute(prompt: string): Promise<string> {\n const cliPath = this.config.cliPath || 'opencode';\n const timeout = this.config.timeout ?? 600_000;\n const MAX_PROMPT = 100_000; // safe below typical ARG_MAX\n\n if (prompt.length > MAX_PROMPT) {\n throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}). Split into smaller messages or use a provider with HTTP API transport.`);\n }\n\n // Only pass essential env vars\n const env: Record<string, string> = {\n PATH: process.env.PATH ?? '',\n HOME: process.env.HOME ?? '',\n };\n for (const key of ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'no_proxy']) {\n if (process.env[key]) env[key] = process.env[key];\n }\n // Forward auth env vars if present\n for (const key of ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OPENCODE_SERVER_PASSWORD', 'OPENCODE_SERVER_USERNAME']) {\n if (process.env[key]) env[key] = process.env[key];\n }\n\n const args: string[] = ['run', prompt];\n\n if (this.config.model) {\n args.push('--model', this.config.model);\n }\n if (this.config.agent) {\n args.push('--agent', this.config.agent);\n }\n if (this.config.attachUrl) {\n args.push('--attach', this.config.attachUrl);\n }\n // Default format captures plain text output\n args.push('--format', 'default');\n // Never go interactive\n args.push('--dangerously-skip-permissions');\n\n log.info('Spawning opencode', { cliPath, workdir: this.config.workdir, model: this.config.model });\n\n return new Promise<string>((resolve, reject) => {\n const child = spawn(cliPath, args, {\n cwd: this.config.workdir || process.cwd(),\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n this.currentChild = child;\n\n let stdout = '';\n let stderr = '';\n const MAX_OUTPUT = 10_000_000; // 10MB\n\n const timer = setTimeout(() => {\n child.kill('SIGTERM');\n const forceTimer = setTimeout(() => {\n if (child.exitCode === null) child.kill('SIGKILL');\n }, 5000);\n child.on('close', () => clearTimeout(forceTimer));\n reject(new Error(`OpenCode execution timed out after ${timeout}ms`));\n }, timeout);\n\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n if (stdout.length > MAX_OUTPUT) {\n child.kill('SIGTERM');\n setTimeout(() => { if (child.exitCode === null) child.kill('SIGKILL'); }, 5000);\n }\n });\n\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n log.debug('OpenCode stderr', { text: chunk.toString().trim() });\n });\n\n child.on('close', (code) => {\n clearTimeout(timer);\n this.currentChild = null;\n\n if (code === 0) {\n resolve(stdout.trim());\n } else {\n const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;\n log.warn('OpenCode non-zero exit', { code, stderr: errMsg });\n reject(new Error(errMsg));\n }\n });\n\n child.on('error', (err) => {\n clearTimeout(timer);\n log.error('OpenCode spawn failed', { error: err.message });\n reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));\n });\n });\n }\n}\n","/**\n * OpenCode A2A Executor\n *\n * Bridges the local OpenCode CLI to the A2A protocol. Spawns `opencode run`\n * as a child process for each request and captures stdout as the response.\n * Same spawn pattern as Codex and Claude Code executors.\n */\n\nimport type { A2AExecutor } from '../index.js';\nimport type { RequestContext, ExecutionEventBus } from '@a2a-js/sdk/server';\nimport type { Message as A2AMessage } from '@a2a-js/sdk';\n\nimport type { AgentConfig } from '../../config/types.js';\nimport { logger } from '../../logger.js';\nimport { OpenCodeClient } from './client.js';\nimport {\n publishTask,\n publishStatus,\n publishFinalArtifact,\n} from '../events.js';\n\nconst log = logger.child('opencode:executor');\n\n// ─── Executor ───────────────────────────────────────────────────────────────\n\nexport class OpenCodeExecutor implements A2AExecutor {\n private config: Required<AgentConfig>;\n private client: OpenCodeClient | null = null;\n private initialized = false;\n\n constructor(config: Required<AgentConfig>) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n const oc = this.config.opencode;\n this.client = new OpenCodeClient({\n cliPath: 'opencode',\n workdir: oc.projectDirectory || process.cwd(),\n model: oc.model || undefined,\n agent: oc.agent || undefined,\n attachUrl: oc.baseUrl || undefined,\n timeout: this.config.timeouts.prompt ?? 600_000,\n });\n\n this.initialized = true;\n log.info('Executor initialized', { workdir: oc.projectDirectory, model: oc.model });\n }\n\n async shutdown(): Promise<void> {\n this.client = null;\n this.initialized = false;\n log.info('Executor shut down');\n }\n\n async execute(ctx: RequestContext, bus: ExecutionEventBus): Promise<void> {\n const { taskId, contextId, userMessage, task } = ctx;\n await this.initialize();\n\n try {\n if (!task) {\n publishTask(bus, taskId, contextId);\n publishStatus(bus, taskId, contextId, 'submitted');\n }\n\n publishStatus(bus, taskId, contextId, 'working', 'Processing request...');\n\n const promptText = this.extractText(userMessage);\n log.info('Sending prompt to OpenCode', { taskId, len: promptText.length });\n\n const response = await this.client!.execute(promptText);\n\n const finalText = response || 'No response from OpenCode.';\n publishFinalArtifact(bus, taskId, contextId, finalText);\n publishStatus(bus, taskId, contextId, 'completed', undefined, true);\n bus.finished();\n log.info('Task completed', { taskId, len: finalText.length });\n\n } catch (error) {\n const msg = (error as Error).message ?? String(error);\n log.error('Execution failed', { taskId, error: msg });\n publishStatus(bus, taskId, contextId, 'failed', `Error: ${msg}`, true);\n bus.finished();\n }\n }\n\n async cancelTask(taskId: string, bus: ExecutionEventBus): Promise<void> {\n log.info('Cancel requested for OpenCode task', { taskId });\n this.client?.abort();\n publishStatus(bus, taskId, '', 'canceled', 'OpenCode task cancelled', true);\n bus.finished();\n }\n\n private extractText(message: A2AMessage): string {\n return message.parts\n .filter((p) => {\n const part = p as unknown as Record<string, unknown>;\n const text = part.text as string | undefined;\n return text !== undefined && text !== null;\n })\n .map((p) => (p as unknown as { text: string }).text)\n .join('\\n');\n }\n}\n\n// ─── Factory ────────────────────────────────────────────────────────────────\n\nexport function createOpenCodeExecutor(config: Required<AgentConfig>): A2AExecutor {\n return new OpenCodeExecutor(config);\n}\n"],"mappings":"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","names":["log"]}
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|