@axiom-lattice/cli-a2a 0.1.4 → 0.1.6

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 (65) hide show
  1. package/.turbo/turbo-build.log +34 -32
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +85 -2
  4. package/__tests__/opencode-executor.test.ts +631 -31
  5. package/__tests__/session-store-path.test.ts +40 -0
  6. package/dist/{chunk-NQIDRU47.mjs → chunk-54SKDK3D.mjs} +78 -32
  7. package/dist/chunk-54SKDK3D.mjs.map +1 -0
  8. package/dist/chunk-BSNQOP2W.mjs +350 -0
  9. package/dist/chunk-BSNQOP2W.mjs.map +1 -0
  10. package/dist/chunk-DRFFQJII.mjs +195 -0
  11. package/dist/chunk-DRFFQJII.mjs.map +1 -0
  12. package/dist/chunk-URJDRXJW.mjs +378 -0
  13. package/dist/chunk-URJDRXJW.mjs.map +1 -0
  14. package/dist/{chunk-G7AGL2QA.mjs → chunk-WZB7FBSX.mjs} +23 -5
  15. package/dist/chunk-WZB7FBSX.mjs.map +1 -0
  16. package/dist/{chunk-LXL47XMZ.mjs → chunk-YGUPSPZO.mjs} +9 -1
  17. package/dist/chunk-YGUPSPZO.mjs.map +1 -0
  18. package/dist/{chunk-35NFMGMS.mjs → chunk-ZP3JNJQJ.mjs} +3 -3
  19. package/dist/chunk-ZP3JNJQJ.mjs.map +1 -0
  20. package/dist/cli.js +769 -156
  21. package/dist/cli.js.map +1 -1
  22. package/dist/cli.mjs +7 -6
  23. package/dist/cli.mjs.map +1 -1
  24. package/dist/executor-LLE5VEFJ.mjs +12 -0
  25. package/dist/executor-LO7P3KUW.mjs +12 -0
  26. package/dist/{executor-RVBGAWUF.mjs → executor-OOLGGXHS.mjs} +4 -3
  27. package/dist/{executors-QIIKBUMJ.mjs → executors-AQHOPGSR.mjs} +2 -2
  28. package/dist/index.d.mts +92 -21
  29. package/dist/index.d.ts +92 -21
  30. package/dist/index.js +731 -123
  31. package/dist/index.js.map +1 -1
  32. package/dist/index.mjs +15 -7
  33. package/package.json +11 -4
  34. package/runtime/cli-path.cjs +3 -0
  35. package/runtime/cli-path.d.ts +1 -0
  36. package/runtime/cli-path.mjs +5 -0
  37. package/src/config/defaults.ts +3 -1
  38. package/src/config/types.ts +8 -3
  39. package/src/executors/claude/client.ts +44 -14
  40. package/src/executors/claude/executor.ts +65 -30
  41. package/src/executors/codex/client.ts +239 -16
  42. package/src/executors/codex/executor.ts +70 -37
  43. package/src/executors/events.ts +15 -1
  44. package/src/executors/index.ts +13 -6
  45. package/src/executors/opencode/client.ts +192 -18
  46. package/src/executors/opencode/executor.ts +65 -30
  47. package/src/index.ts +8 -0
  48. package/src/local-state.ts +53 -0
  49. package/src/server/agent-card.ts +1 -0
  50. package/src/server/index.ts +22 -7
  51. package/src/session-store.ts +262 -0
  52. package/dist/chunk-35NFMGMS.mjs.map +0 -1
  53. package/dist/chunk-G7AGL2QA.mjs.map +0 -1
  54. package/dist/chunk-LXL47XMZ.mjs.map +0 -1
  55. package/dist/chunk-NQIDRU47.mjs.map +0 -1
  56. package/dist/chunk-VSZ3DACI.mjs +0 -179
  57. package/dist/chunk-VSZ3DACI.mjs.map +0 -1
  58. package/dist/chunk-WNCDOYZS.mjs +0 -187
  59. package/dist/chunk-WNCDOYZS.mjs.map +0 -1
  60. package/dist/executor-OWPVDUXH.mjs +0 -11
  61. package/dist/executor-XWHWUVQ3.mjs +0 -11
  62. /package/dist/{executor-OWPVDUXH.mjs.map → executor-LLE5VEFJ.mjs.map} +0 -0
  63. /package/dist/{executor-RVBGAWUF.mjs.map → executor-LO7P3KUW.mjs.map} +0 -0
  64. /package/dist/{executor-XWHWUVQ3.mjs.map → executor-OOLGGXHS.mjs.map} +0 -0
  65. /package/dist/{executors-QIIKBUMJ.mjs.map → executors-AQHOPGSR.mjs.map} +0 -0
@@ -0,0 +1,40 @@
1
+ import { join } from 'node:path';
2
+ import { resolveSessionStorePath } from '../src/session-store';
3
+
4
+ describe('resolveSessionStorePath', () => {
5
+ const storeDir = '/tmp/cli-a2a-session-tests';
6
+
7
+ it('isolates default stores by provider and port', () => {
8
+ const codex = resolveSessionStorePath({
9
+ provider: 'codex',
10
+ port: 4301,
11
+ storeDir,
12
+ });
13
+ const claude = resolveSessionStorePath({
14
+ provider: 'claude',
15
+ port: 4302,
16
+ storeDir,
17
+ });
18
+
19
+ expect(codex).toBe(join(storeDir, 'sessions-codex-4301.json'));
20
+ expect(claude).toBe(join(storeDir, 'sessions-claude-4302.json'));
21
+ expect(codex).not.toBe(claude);
22
+ });
23
+
24
+ it('returns the same path for the same runtime identity', () => {
25
+ const options = { provider: 'opencode' as const, port: 4303, storeDir };
26
+
27
+ expect(resolveSessionStorePath(options)).toBe(resolveSessionStorePath(options));
28
+ });
29
+
30
+ it('preserves an explicitly configured store path', () => {
31
+ const configuredPath = join(storeDir, 'custom.json');
32
+
33
+ expect(resolveSessionStorePath({
34
+ configuredPath,
35
+ provider: 'codex',
36
+ port: 4301,
37
+ storeDir,
38
+ })).toBe(configuredPath);
39
+ });
40
+ });
@@ -1,14 +1,19 @@
1
1
  import {
2
+ extractText,
2
3
  publishFinalArtifact,
3
4
  publishStatus,
4
5
  publishTask
5
- } from "./chunk-LXL47XMZ.mjs";
6
+ } from "./chunk-YGUPSPZO.mjs";
7
+ import {
8
+ generateContextId
9
+ } from "./chunk-DRFFQJII.mjs";
6
10
  import {
7
11
  logger
8
12
  } from "./chunk-VZEH3EPJ.mjs";
9
13
 
10
14
  // src/executors/claude/client.ts
11
15
  import { spawn } from "child_process";
16
+ import { v4 as uuidv4 } from "uuid";
12
17
  var log = logger.child("claude:client");
13
18
  var ClaudeClient = class {
14
19
  constructor(config) {
@@ -27,14 +32,31 @@ var ClaudeClient = class {
27
32
  }
28
33
  }
29
34
  /**
30
- * Execute a prompt via the Claude Code CLI and return the response text.
35
+ * Execute a prompt via the Claude Code CLI.
36
+ *
37
+ * @param prompt - The user prompt text.
38
+ * @param sessionId - If provided, resumes the session with `--resume`.
39
+ * Otherwise creates a new session with `--session-id <new-uuid>`.
40
+ * @param workingDirectory - Optional per-request working directory override.
41
+ * @returns The response text and the session ID (new or existing).
31
42
  */
32
- async execute(prompt) {
43
+ async execute(prompt, sessionId, workingDirectory) {
44
+ if (sessionId) {
45
+ const text2 = await this.run(["-p", "--resume", sessionId, prompt], workingDirectory);
46
+ return { text: text2, sessionId };
47
+ }
48
+ const newSessionId = uuidv4();
49
+ const text = await this.run(["-p", "--session-id", newSessionId, prompt], workingDirectory);
50
+ return { text, sessionId: newSessionId };
51
+ }
52
+ // ── Internal ──────────────────────────────────────────────────────────
53
+ async run(args, workingDirectory) {
33
54
  const cliPath = this.config.cliPath || "claude";
34
55
  const timeout = this.config.timeout ?? 3e5;
35
56
  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.`);
57
+ const promptArg = args[args.length - 1];
58
+ if (promptArg && promptArg.length > MAX_PROMPT) {
59
+ throw new Error(`Prompt too large (${promptArg.length} chars, max ${MAX_PROMPT}). Split into smaller messages.`);
38
60
  }
39
61
  const env = {
40
62
  PATH: process.env.PATH ?? "",
@@ -46,15 +68,19 @@ var ClaudeClient = class {
46
68
  if (this.config.apiKey) {
47
69
  env["ANTHROPIC_API_KEY"] = this.config.apiKey;
48
70
  }
49
- const args = ["-p"];
71
+ const fullArgs = [];
72
+ for (const arg of args.slice(0, -1)) {
73
+ fullArgs.push(arg);
74
+ }
50
75
  if (this.config.model) {
51
- args.push("--model", this.config.model);
76
+ fullArgs.push("--model", this.config.model);
52
77
  }
53
- args.push(prompt);
54
- log.info("Spawning claude", { cliPath, workdir: this.config.workdir, model: this.config.model });
78
+ fullArgs.push(promptArg);
79
+ const workdir = workingDirectory || this.config.workdir || process.cwd();
80
+ log.info("Spawning claude", { cliPath, workdir, model: this.config.model });
55
81
  return new Promise((resolve, reject) => {
56
- const child = spawn(cliPath, args, {
57
- cwd: this.config.workdir || process.cwd(),
82
+ const child = spawn(cliPath, fullArgs, {
83
+ cwd: workdir,
58
84
  env,
59
85
  stdio: ["ignore", "pipe", "pipe"]
60
86
  });
@@ -106,10 +132,11 @@ var ClaudeClient = class {
106
132
  // src/executors/claude/executor.ts
107
133
  var log2 = logger.child("claude:executor");
108
134
  var ClaudeExecutor = class {
109
- constructor(config) {
135
+ constructor(config, sessionStore) {
110
136
  this.client = null;
111
137
  this.initialized = false;
112
138
  this.config = config;
139
+ this.sessionStore = sessionStore;
113
140
  }
114
141
  async initialize() {
115
142
  if (this.initialized) return;
@@ -130,25 +157,51 @@ var ClaudeExecutor = class {
130
157
  log2.info("Executor shut down");
131
158
  }
132
159
  async execute(ctx, bus) {
133
- const { taskId, contextId, userMessage, task } = ctx;
160
+ const { taskId, userMessage } = ctx;
134
161
  await this.initialize();
162
+ const reuseByContext = this.config.session?.reuseByContext ?? true;
163
+ let contextId = ctx.contextId;
164
+ if (!contextId) {
165
+ contextId = generateContextId();
166
+ log2.info("Generated new contextId", { contextId, taskId });
167
+ }
168
+ const binding = reuseByContext ? this.sessionStore.get(contextId, "claude") : null;
169
+ const sessionId = binding?.sessionId;
135
170
  try {
136
- if (!task) {
171
+ const existingTask = ctx.task;
172
+ if (!existingTask) {
137
173
  publishTask(bus, taskId, contextId);
138
174
  publishStatus(bus, taskId, contextId, "submitted");
139
175
  }
140
- publishStatus(bus, taskId, contextId, "working", "Processing request...");
141
- const promptText = this.extractText(userMessage);
142
- log2.info("Sending prompt to Claude Code", { taskId, len: promptText.length });
143
- const response = await this.client.execute(promptText);
144
- const finalText = response || "No response from Claude Code.";
145
- publishFinalArtifact(bus, taskId, contextId, finalText);
176
+ publishStatus(bus, taskId, contextId, "working");
177
+ const promptText = extractText(userMessage);
178
+ const userMsg = userMessage;
179
+ const workingDirectory = userMsg.metadata && typeof userMsg.metadata === "object" ? userMsg.metadata.workingDirectory : void 0;
180
+ log2.info("Sending prompt to Claude Code", {
181
+ taskId,
182
+ contextId,
183
+ sessionId: sessionId || "(new)",
184
+ len: promptText.length,
185
+ workingDirectory: workingDirectory || "(default)"
186
+ });
187
+ const result = await this.client.execute(promptText, sessionId, workingDirectory);
188
+ this.sessionStore.set({
189
+ provider: "claude",
190
+ contextId,
191
+ sessionId: result.sessionId,
192
+ activeTaskId: void 0,
193
+ activeTaskState: void 0,
194
+ createdAt: binding?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
195
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
196
+ expiresAt: new Date(Date.now() + (this.config.session?.ttl ?? 24 * 60 * 60 * 1e3)).toISOString()
197
+ });
198
+ publishFinalArtifact(bus, taskId, contextId, result.text || "No response from Claude Code.");
146
199
  publishStatus(bus, taskId, contextId, "completed", void 0, true);
147
200
  bus.finished();
148
- log2.info("Task completed", { taskId, len: finalText.length });
201
+ log2.info("Task completed", { taskId, contextId, sessionId: result.sessionId });
149
202
  } catch (error) {
150
203
  const msg = error.message ?? String(error);
151
- log2.error("Execution failed", { taskId, error: msg });
204
+ log2.error("Execution failed", { taskId, contextId, error: msg });
152
205
  publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
153
206
  bus.finished();
154
207
  }
@@ -159,20 +212,13 @@ var ClaudeExecutor = class {
159
212
  publishStatus(bus, taskId, "", "canceled", "Claude Code task cancelled", true);
160
213
  bus.finished();
161
214
  }
162
- extractText(message) {
163
- return message.parts.filter((p) => {
164
- const part = p;
165
- const text = part.text;
166
- return text !== void 0 && text !== null;
167
- }).map((p) => p.text).join("\n");
168
- }
169
215
  };
170
- function createClaudeExecutor(config) {
171
- return new ClaudeExecutor(config);
216
+ function createClaudeExecutor(config, sessionStore) {
217
+ return new ClaudeExecutor(config, sessionStore);
172
218
  }
173
219
 
174
220
  export {
175
221
  ClaudeExecutor,
176
222
  createClaudeExecutor
177
223
  };
178
- //# sourceMappingURL=chunk-NQIDRU47.mjs.map
224
+ //# sourceMappingURL=chunk-54SKDK3D.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/executors/claude/client.ts","../src/executors/claude/executor.ts"],"sourcesContent":["/**\n * Claude Code CLI client wrapper.\n *\n * Spawns the Anthropic Claude Code CLI as a child process in non-interactive\n * print mode (`-p`). Sends prompts, captures stdout as the response.\n * Supports session persistence: first call uses `--session-id <uuid>` to\n * create a named session; subsequent calls use `--resume <uuid>` to continue.\n */\n\nimport { spawn } from 'node:child_process';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../../logger.js';\n\nconst log = logger.child('claude:client');\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ClaudeClientConfig {\n /** Path to the claude binary (default: \"claude\") */\n cliPath: string;\n /** Working directory for the process */\n workdir: string;\n /** Model identifier (e.g. \"claude-sonnet-4-20250514\") — passed via --model */\n model?: string;\n /** Anthropic API key — passed as ANTHROPIC_API_KEY env */\n apiKey?: string;\n /** Timeout in ms (default: 300_000 = 5 min) */\n timeout?: number;\n}\n\nexport interface ClaudeExecuteResult {\n text: string;\n sessionId: string;\n}\n\n// ─── Client ─────────────────────────────────────────────────────────────────\n\nexport class ClaudeClient {\n private currentChild: ReturnType<typeof spawn> | null = null;\n\n constructor(private config: ClaudeClientConfig) {}\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 Claude Code CLI.\n *\n * @param prompt - The user prompt text.\n * @param sessionId - If provided, resumes the session with `--resume`.\n * Otherwise creates a new session with `--session-id <new-uuid>`.\n * @param workingDirectory - Optional per-request working directory override.\n * @returns The response text and the session ID (new or existing).\n */\n async execute(prompt: string, sessionId?: string, workingDirectory?: string): Promise<ClaudeExecuteResult> {\n if (sessionId) {\n const text = await this.run(['-p', '--resume', sessionId, prompt], workingDirectory);\n return { text, sessionId };\n }\n\n const newSessionId = uuidv4();\n const text = await this.run(['-p', '--session-id', newSessionId, prompt], workingDirectory);\n return { text, sessionId: newSessionId };\n }\n\n // ── Internal ──────────────────────────────────────────────────────────\n\n private async run(args: string[], workingDirectory?: string): Promise<string> {\n const cliPath = this.config.cliPath || 'claude';\n const timeout = this.config.timeout ?? 300_000;\n const MAX_PROMPT = 100_000;\n\n // Validate prompt size\n const promptArg = args[args.length - 1];\n if (promptArg && promptArg.length > MAX_PROMPT) {\n throw new Error(`Prompt too large (${promptArg.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 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 if (this.config.apiKey) {\n env['ANTHROPIC_API_KEY'] = this.config.apiKey;\n }\n\n // Build full args: base args + model + prompt\n const fullArgs: string[] = [];\n for (const arg of args.slice(0, -1)) {\n fullArgs.push(arg);\n }\n if (this.config.model) {\n fullArgs.push('--model', this.config.model);\n }\n fullArgs.push(promptArg);\n\n const workdir = workingDirectory || this.config.workdir || process.cwd();\n log.info('Spawning claude', { cliPath, workdir, model: this.config.model });\n\n return new Promise<string>((resolve, reject) => {\n const child = spawn(cliPath, fullArgs, {\n cwd: workdir,\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 const forceTimer = setTimeout(() => {\n if (child.exitCode === null) child.kill('SIGKILL');\n }, 5000);\n child.on('close', () => clearTimeout(forceTimer));\n reject(new Error(`Claude Code 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('Claude 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() || `Claude Code exited with code ${code}`;\n log.warn('Claude 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('Claude spawn failed', { error: err.message });\n reject(new Error(`Failed to start claude: ${err.message}. Install: npm i -g @anthropic-ai/claude-code`));\n });\n });\n }\n}\n","/**\n * Claude Code A2A Executor\n *\n * Bridges the local Claude Code CLI to the A2A protocol. Spawns Claude Code\n * in non-interactive print mode (`-p`) for each request. Supports multi-turn\n * conversation via contextId → sessionId binding through SessionBindingStore.\n */\n\nimport type { A2AExecutor } from '../index.js';\nimport type { RequestContext, ExecutionEventBus } from '@a2a-js/sdk/server';\n\nimport type { AgentConfig } from '../../config/types.js';\nimport type { SessionBindingStore } from '../../session-store.js';\nimport { generateContextId } from '../../session-store.js';\nimport { logger } from '../../logger.js';\nimport { ClaudeClient } from './client.js';\nimport {\n publishTask,\n publishStatus,\n publishFinalArtifact,\n extractText,\n} from '../events.js';\n\nconst log = logger.child('claude:executor');\n\n// ─── Executor ───────────────────────────────────────────────────────────────\n\nexport class ClaudeExecutor implements A2AExecutor {\n private config: Required<AgentConfig>;\n private client: ClaudeClient | null = null;\n private initialized = false;\n private sessionStore: SessionBindingStore;\n\n constructor(config: Required<AgentConfig>, sessionStore: SessionBindingStore) {\n this.config = config;\n this.sessionStore = sessionStore;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n const cc = this.config.claude;\n this.client = new ClaudeClient({\n cliPath: cc.cliPath!,\n workdir: cc.workdir!,\n model: cc.model || undefined,\n apiKey: cc.apiKey || undefined,\n timeout: this.config.timeouts.prompt ?? 600_000,\n });\n\n this.initialized = true;\n log.info('Executor initialized', { cliPath: cc.cliPath, workdir: cc.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, userMessage } = ctx;\n await this.initialize();\n\n const reuseByContext = this.config.session?.reuseByContext ?? true;\n\n let contextId = ctx.contextId;\n if (!contextId) {\n contextId = generateContextId();\n log.info('Generated new contextId', { contextId, taskId });\n }\n\n const binding = reuseByContext\n ? this.sessionStore.get(contextId, 'claude')\n : null;\n\n const sessionId = binding?.sessionId;\n\n try {\n const existingTask = ctx.task;\n if (!existingTask) {\n publishTask(bus, taskId, contextId);\n publishStatus(bus, taskId, contextId, 'submitted');\n }\n\n publishStatus(bus, taskId, contextId, 'working');\n\n const promptText = extractText(userMessage);\n\n // Extract per-request workingDirectory from A2A message metadata\n const userMsg = userMessage as unknown as Record<string, unknown>;\n const workingDirectory = userMsg.metadata &&\n typeof userMsg.metadata === 'object'\n ? (userMsg.metadata as Record<string, unknown>).workingDirectory as string | undefined\n : undefined;\n\n log.info('Sending prompt to Claude Code', {\n taskId,\n contextId,\n sessionId: sessionId || '(new)',\n len: promptText.length,\n workingDirectory: workingDirectory || '(default)',\n });\n\n const result = await this.client!.execute(promptText, sessionId, workingDirectory);\n\n this.sessionStore.set({\n provider: 'claude',\n contextId,\n sessionId: result.sessionId,\n activeTaskId: undefined,\n activeTaskState: undefined,\n createdAt: binding?.createdAt ?? new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n expiresAt: new Date(Date.now() + (this.config.session?.ttl ?? 24 * 60 * 60 * 1000)).toISOString(),\n });\n\n publishFinalArtifact(bus, taskId, contextId, result.text || 'No response from Claude Code.');\n publishStatus(bus, taskId, contextId, 'completed', undefined, true);\n bus.finished();\n log.info('Task completed', { taskId, contextId, sessionId: result.sessionId });\n\n } catch (error) {\n const msg = (error as Error).message ?? String(error);\n log.error('Execution failed', { taskId, contextId, 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 Claude Code task', { taskId });\n this.client?.abort();\n publishStatus(bus, taskId, '', 'canceled', 'Claude Code task cancelled', true);\n bus.finished();\n }\n}\n\n// ─── Factory ────────────────────────────────────────────────────────────────\n\nexport function createClaudeExecutor(\n config: Required<AgentConfig>,\n sessionStore: SessionBindingStore,\n): A2AExecutor {\n return new ClaudeExecutor(config, sessionStore);\n}\n"],"mappings":"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","names":["text","log"]}
@@ -0,0 +1,350 @@
1
+ import {
2
+ extractText,
3
+ publishFinalArtifact,
4
+ publishStatus,
5
+ publishTask
6
+ } from "./chunk-YGUPSPZO.mjs";
7
+ import {
8
+ generateContextId
9
+ } from "./chunk-DRFFQJII.mjs";
10
+ import {
11
+ logger
12
+ } from "./chunk-VZEH3EPJ.mjs";
13
+
14
+ // src/executors/opencode/client.ts
15
+ import { spawn } from "child_process";
16
+ var log = logger.child("opencode:client");
17
+ var OpenCodeClient = class {
18
+ constructor(config) {
19
+ this.config = config;
20
+ this.currentChild = null;
21
+ }
22
+ abort() {
23
+ if (this.currentChild) {
24
+ this.currentChild.kill("SIGTERM");
25
+ setTimeout(() => {
26
+ if (this.currentChild?.exitCode === null) {
27
+ this.currentChild.kill("SIGKILL");
28
+ }
29
+ }, 5e3);
30
+ this.currentChild = null;
31
+ }
32
+ }
33
+ /**
34
+ * Execute a prompt via the OpenCode CLI.
35
+ *
36
+ * @param prompt - The user prompt text.
37
+ * @param sessionId - If provided, continues the session with `--session`.
38
+ * Otherwise creates a new session using `--format json` to capture the
39
+ * session ID.
40
+ * @param workingDirectory - Optional per-request working directory override.
41
+ * Falls back to config.workdir if not provided.
42
+ */
43
+ async execute(prompt, sessionId, workingDirectory) {
44
+ if (sessionId) {
45
+ const text = await this.runResume(prompt, sessionId, workingDirectory);
46
+ return { text, sessionId };
47
+ }
48
+ return this.runNew(prompt, workingDirectory);
49
+ }
50
+ resolveWorkdir(override) {
51
+ return override || this.config.workdir || process.cwd();
52
+ }
53
+ // ── New session (first turn) ──────────────────────────────────────────
54
+ async runNew(prompt, workingDirectory) {
55
+ const cliPath = this.config.cliPath || "opencode";
56
+ const timeout = this.config.timeout ?? 6e5;
57
+ const MAX_PROMPT = 1e5;
58
+ if (prompt.length > MAX_PROMPT) {
59
+ throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}).`);
60
+ }
61
+ const env = {
62
+ PATH: process.env.PATH ?? "",
63
+ HOME: process.env.HOME ?? ""
64
+ };
65
+ for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
66
+ if (process.env[key]) env[key] = process.env[key];
67
+ }
68
+ for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
69
+ if (process.env[key]) env[key] = process.env[key];
70
+ }
71
+ const args = ["run", "--format", "json"];
72
+ if (this.config.model) {
73
+ args.push("--model", this.config.model);
74
+ }
75
+ if (this.config.agent) {
76
+ args.push("--agent", this.config.agent);
77
+ }
78
+ if (this.config.attachUrl) {
79
+ args.push("--attach", this.config.attachUrl);
80
+ }
81
+ args.push("--dangerously-skip-permissions", prompt);
82
+ const workdir = this.resolveWorkdir(workingDirectory);
83
+ log.info("Spawning opencode (new session, json)", { cliPath, workdir });
84
+ return new Promise((resolve, reject) => {
85
+ const child = spawn(cliPath, args, {
86
+ cwd: workdir,
87
+ env,
88
+ stdio: ["ignore", "pipe", "pipe"]
89
+ });
90
+ this.currentChild = child;
91
+ let stdout = "";
92
+ let stderr = "";
93
+ const MAX_OUTPUT = 1e7;
94
+ const timer = setTimeout(() => {
95
+ child.kill("SIGTERM");
96
+ const forceTimer = setTimeout(() => {
97
+ if (child.exitCode === null) child.kill("SIGKILL");
98
+ }, 5e3);
99
+ child.on("close", () => clearTimeout(forceTimer));
100
+ reject(new Error(`OpenCode execution timed out after ${timeout}ms`));
101
+ }, timeout);
102
+ child.stdout?.on("data", (chunk) => {
103
+ stdout += chunk.toString();
104
+ if (stdout.length > MAX_OUTPUT) {
105
+ child.kill("SIGTERM");
106
+ setTimeout(() => {
107
+ if (child.exitCode === null) child.kill("SIGKILL");
108
+ }, 5e3);
109
+ }
110
+ });
111
+ child.stderr?.on("data", (chunk) => {
112
+ stderr += chunk.toString();
113
+ log.debug("OpenCode stderr", { text: chunk.toString().trim() });
114
+ });
115
+ child.on("close", (code) => {
116
+ clearTimeout(timer);
117
+ this.currentChild = null;
118
+ if (code !== 0) {
119
+ const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
120
+ log.warn("OpenCode non-zero exit", { code, stderr: errMsg });
121
+ reject(new Error(errMsg));
122
+ return;
123
+ }
124
+ try {
125
+ const { text, sessionId } = this.parseJsonEvents(stdout);
126
+ resolve({ text, sessionId });
127
+ } catch (err) {
128
+ reject(new Error(`Failed to parse OpenCode JSON output: ${err.message}`));
129
+ }
130
+ });
131
+ child.on("error", (err) => {
132
+ clearTimeout(timer);
133
+ log.error("OpenCode spawn failed", { error: err.message });
134
+ reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
135
+ });
136
+ });
137
+ }
138
+ // ── Resume session ────────────────────────────────────────────────────
139
+ async runResume(prompt, sessionId, workingDirectory) {
140
+ const cliPath = this.config.cliPath || "opencode";
141
+ const timeout = this.config.timeout ?? 6e5;
142
+ const MAX_PROMPT = 1e5;
143
+ if (prompt.length > MAX_PROMPT) {
144
+ throw new Error(`Prompt too large (${prompt.length} chars, max ${MAX_PROMPT}).`);
145
+ }
146
+ const env = {
147
+ PATH: process.env.PATH ?? "",
148
+ HOME: process.env.HOME ?? ""
149
+ };
150
+ for (const key of ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "no_proxy"]) {
151
+ if (process.env[key]) env[key] = process.env[key];
152
+ }
153
+ for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENCODE_SERVER_PASSWORD", "OPENCODE_SERVER_USERNAME"]) {
154
+ if (process.env[key]) env[key] = process.env[key];
155
+ }
156
+ const args = ["run", "--session", sessionId];
157
+ if (this.config.model) {
158
+ args.push("--model", this.config.model);
159
+ }
160
+ if (this.config.agent) {
161
+ args.push("--agent", this.config.agent);
162
+ }
163
+ if (this.config.attachUrl) {
164
+ args.push("--attach", this.config.attachUrl);
165
+ }
166
+ args.push("--format", "default", "--dangerously-skip-permissions", prompt);
167
+ const workdir = this.resolveWorkdir(workingDirectory);
168
+ log.info("Spawning opencode (resume)", { sessionId, workdir });
169
+ return new Promise((resolve, reject) => {
170
+ const child = spawn(cliPath, args, {
171
+ cwd: workdir,
172
+ env,
173
+ stdio: ["ignore", "pipe", "pipe"]
174
+ });
175
+ this.currentChild = child;
176
+ let stdout = "";
177
+ let stderr = "";
178
+ const MAX_OUTPUT = 1e7;
179
+ const timer = setTimeout(() => {
180
+ child.kill("SIGTERM");
181
+ const forceTimer = setTimeout(() => {
182
+ if (child.exitCode === null) child.kill("SIGKILL");
183
+ }, 5e3);
184
+ child.on("close", () => clearTimeout(forceTimer));
185
+ reject(new Error(`OpenCode execution timed out after ${timeout}ms`));
186
+ }, timeout);
187
+ child.stdout?.on("data", (chunk) => {
188
+ stdout += chunk.toString();
189
+ if (stdout.length > MAX_OUTPUT) {
190
+ child.kill("SIGTERM");
191
+ setTimeout(() => {
192
+ if (child.exitCode === null) child.kill("SIGKILL");
193
+ }, 5e3);
194
+ }
195
+ });
196
+ child.stderr?.on("data", (chunk) => {
197
+ stderr += chunk.toString();
198
+ log.debug("OpenCode stderr", { text: chunk.toString().trim() });
199
+ });
200
+ child.on("close", (code) => {
201
+ clearTimeout(timer);
202
+ this.currentChild = null;
203
+ if (code === 0) {
204
+ resolve(stdout.trim());
205
+ } else {
206
+ const errMsg = stderr.trim() || stdout.trim() || `OpenCode exited with code ${code}`;
207
+ log.warn("OpenCode non-zero exit", { code, stderr: errMsg });
208
+ reject(new Error(errMsg));
209
+ }
210
+ });
211
+ child.on("error", (err) => {
212
+ clearTimeout(timer);
213
+ log.error("OpenCode spawn failed", { error: err.message });
214
+ reject(new Error(`Failed to start opencode: ${err.message}. Is it installed?`));
215
+ });
216
+ });
217
+ }
218
+ // ── Parser ────────────────────────────────────────────────────────────
219
+ /**
220
+ * Parse newline-delimited JSON events from OpenCode's `--format json` output.
221
+ * Extracts the sessionID from the first event that contains it, and
222
+ * accumulates text content from relevant events.
223
+ */
224
+ parseJsonEvents(stdout) {
225
+ const lines = stdout.split("\n").filter((l) => l.trim());
226
+ let sessionId = "";
227
+ const textParts = [];
228
+ for (const line of lines) {
229
+ let event;
230
+ try {
231
+ event = JSON.parse(line);
232
+ } catch {
233
+ continue;
234
+ }
235
+ if (!sessionId && event.sessionID) {
236
+ sessionId = event.sessionID;
237
+ }
238
+ if (event.type === "error") continue;
239
+ const text = event.text || event.content || event.message;
240
+ if (text) {
241
+ textParts.push(text);
242
+ }
243
+ }
244
+ if (!sessionId) {
245
+ return {
246
+ text: textParts.join("\n").trim() || stdout.trim(),
247
+ sessionId: ""
248
+ };
249
+ }
250
+ return {
251
+ text: textParts.join("\n").trim() || "No response from OpenCode.",
252
+ sessionId
253
+ };
254
+ }
255
+ };
256
+
257
+ // src/executors/opencode/executor.ts
258
+ var log2 = logger.child("opencode:executor");
259
+ var OpenCodeExecutor = class {
260
+ constructor(config, sessionStore) {
261
+ this.client = null;
262
+ this.initialized = false;
263
+ this.config = config;
264
+ this.sessionStore = sessionStore;
265
+ }
266
+ async initialize() {
267
+ if (this.initialized) return;
268
+ const oc = this.config.opencode;
269
+ this.client = new OpenCodeClient({
270
+ cliPath: "opencode",
271
+ workdir: oc.projectDirectory || process.cwd(),
272
+ model: oc.model || void 0,
273
+ agent: oc.agent || void 0,
274
+ attachUrl: oc.baseUrl || void 0,
275
+ timeout: this.config.timeouts.prompt ?? 6e5
276
+ });
277
+ this.initialized = true;
278
+ log2.info("Executor initialized", { workdir: oc.projectDirectory, model: oc.model });
279
+ }
280
+ async shutdown() {
281
+ this.client = null;
282
+ this.initialized = false;
283
+ log2.info("Executor shut down");
284
+ }
285
+ async execute(ctx, bus) {
286
+ const { taskId, userMessage } = ctx;
287
+ await this.initialize();
288
+ const reuseByContext = this.config.session?.reuseByContext ?? true;
289
+ let contextId = ctx.contextId;
290
+ if (!contextId) {
291
+ contextId = generateContextId();
292
+ log2.info("Generated new contextId", { contextId, taskId });
293
+ }
294
+ const binding = reuseByContext ? this.sessionStore.get(contextId, "opencode") : null;
295
+ const sessionId = binding?.sessionId;
296
+ try {
297
+ const existingTask = ctx.task;
298
+ if (!existingTask) {
299
+ publishTask(bus, taskId, contextId);
300
+ publishStatus(bus, taskId, contextId, "submitted");
301
+ }
302
+ publishStatus(bus, taskId, contextId, "working");
303
+ const promptText = extractText(userMessage);
304
+ const userMsg = userMessage;
305
+ const workingDirectory = userMsg.metadata && typeof userMsg.metadata === "object" ? userMsg.metadata.workingDirectory : void 0;
306
+ log2.info("Sending prompt to OpenCode", {
307
+ taskId,
308
+ contextId,
309
+ sessionId: sessionId || "(new)",
310
+ len: promptText.length,
311
+ workingDirectory: workingDirectory || "(default)"
312
+ });
313
+ const result = await this.client.execute(promptText, sessionId, workingDirectory);
314
+ this.sessionStore.set({
315
+ provider: "opencode",
316
+ contextId,
317
+ sessionId: result.sessionId,
318
+ activeTaskId: void 0,
319
+ activeTaskState: void 0,
320
+ createdAt: binding?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
321
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
322
+ expiresAt: new Date(Date.now() + (this.config.session?.ttl ?? 24 * 60 * 60 * 1e3)).toISOString()
323
+ });
324
+ publishFinalArtifact(bus, taskId, contextId, result.text || "No response from OpenCode.");
325
+ publishStatus(bus, taskId, contextId, "completed", void 0, true);
326
+ bus.finished();
327
+ log2.info("Task completed", { taskId, contextId, sessionId: result.sessionId });
328
+ } catch (error) {
329
+ const msg = error.message ?? String(error);
330
+ log2.error("Execution failed", { taskId, contextId, error: msg });
331
+ publishStatus(bus, taskId, contextId, "failed", `Error: ${msg}`, true);
332
+ bus.finished();
333
+ }
334
+ }
335
+ async cancelTask(taskId, bus) {
336
+ log2.info("Cancel requested for OpenCode task", { taskId });
337
+ this.client?.abort();
338
+ publishStatus(bus, taskId, "", "canceled", "OpenCode task cancelled", true);
339
+ bus.finished();
340
+ }
341
+ };
342
+ function createOpenCodeExecutor(config, sessionStore) {
343
+ return new OpenCodeExecutor(config, sessionStore);
344
+ }
345
+
346
+ export {
347
+ OpenCodeExecutor,
348
+ createOpenCodeExecutor
349
+ };
350
+ //# sourceMappingURL=chunk-BSNQOP2W.mjs.map