@rk0429/agentic-relay 0.1.0 → 0.2.0

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 (3) hide show
  1. package/README.md +13 -4
  2. package/dist/relay.mjs +120 -21
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -199,10 +199,10 @@ sequenceDiagram
199
199
  User->>Parent: Task request
200
200
  Parent->>Relay: spawn_agent(codex, prompt)
201
201
  Note over Relay: RecursionGuard check<br>depth=1, calls=1
202
- Relay->>Child: relay codex -p "prompt"<br>RELAY_DEPTH=1
202
+ Relay->>Child: Codex SDK thread.run("prompt")<br>RELAY_DEPTH=1
203
203
  Child->>Relay2: spawn_agent(gemini, sub-prompt)
204
204
  Note over Relay2: RecursionGuard check<br>depth=2, calls=1
205
- Relay2->>Grandchild: relay gemini -p "sub-prompt"<br>RELAY_DEPTH=2
205
+ Relay2->>Grandchild: gemini -p "sub-prompt"<br>RELAY_DEPTH=2
206
206
  Grandchild-->>Relay2: Result
207
207
  Relay2-->>Child: Result
208
208
  Child-->>Relay: Result
@@ -229,6 +229,14 @@ graph TD
229
229
 
230
230
  Each backend CLI has different flags, output formats, and session handling. The adapter layer normalizes these differences behind a common `BackendAdapter` interface. Adding a new backend means implementing this interface and registering it with the `AdapterRegistry`.
231
231
 
232
+ Non-interactive execution uses official SDKs where available:
233
+
234
+ | Backend | Non-interactive (`-p`) | Interactive | Session listing |
235
+ |---|---|---|---|
236
+ | Claude Code | Agent SDK `query()` | CLI spawn | Agent SDK `listSessions()` |
237
+ | Codex CLI | Codex SDK `thread.run()` | CLI spawn | -- |
238
+ | Gemini CLI | CLI spawn | CLI spawn | CLI `--list-sessions` |
239
+
232
240
  ### Hooks Engine
233
241
 
234
242
  An event-driven hook system that executes external commands via stdin/stdout JSON pipes.
@@ -296,11 +304,12 @@ src/
296
304
  - **Package manager**: pnpm
297
305
  - **CLI framework**: citty
298
306
  - **Bundler**: tsup (esbuild-based)
307
+ - **Backend SDKs**: @anthropic-ai/claude-agent-sdk, @openai/codex-sdk
299
308
  - **MCP**: @modelcontextprotocol/sdk
300
- - **Process management**: execa
309
+ - **Process management**: execa (interactive modes, Gemini CLI)
301
310
  - **Validation**: zod
302
311
  - **Logging**: consola
303
- - **Testing**: vitest (274 tests across 18 files)
312
+ - **Testing**: vitest (283 tests across 18 files)
304
313
 
305
314
  ## License
306
315
 
package/dist/relay.mjs CHANGED
@@ -48,6 +48,7 @@ var ProcessManager = class {
48
48
  stdio: "inherit",
49
49
  cwd: options?.cwd,
50
50
  env: options?.env,
51
+ extendEnv: options?.env ? false : true,
51
52
  timeout: options?.timeout,
52
53
  reject: false
53
54
  };
@@ -61,10 +62,13 @@ var ProcessManager = class {
61
62
  }
62
63
  async execute(command, args, options) {
63
64
  logger.debug(`Executing: ${command} ${args.join(" ")}`);
65
+ const stdinMode = options?.stdinMode ?? "pipe";
66
+ const stdio = stdinMode === "pipe" ? "pipe" : [stdinMode, "pipe", "pipe"];
64
67
  const execaOptions = {
65
- stdio: "pipe",
68
+ stdio,
66
69
  cwd: options?.cwd,
67
70
  env: options?.env,
71
+ extendEnv: options?.env ? false : true,
68
72
  timeout: options?.timeout,
69
73
  reject: false
70
74
  };
@@ -246,6 +250,15 @@ function mapCommonToNative(backendId, flags) {
246
250
  }
247
251
 
248
252
  // src/adapters/claude-adapter.ts
253
+ import {
254
+ query,
255
+ listSessions
256
+ } from "@anthropic-ai/claude-agent-sdk";
257
+ var CLAUDE_NESTING_ENV_VARS = [
258
+ "CLAUDECODE",
259
+ "CLAUDE_CODE_SSE_PORT",
260
+ "CLAUDE_CODE_ENTRYPOINT"
261
+ ];
249
262
  var ClaudeAdapter = class extends BaseAdapter {
250
263
  id = "claude";
251
264
  command = "claude";
@@ -256,26 +269,91 @@ var ClaudeAdapter = class extends BaseAdapter {
256
269
  }
257
270
  async startInteractive(flags) {
258
271
  const { args } = this.mapFlags(flags);
259
- await this.processManager.spawnInteractive(this.command, args);
272
+ await this.processManager.spawnInteractive(this.command, args, {
273
+ env: this.buildCleanEnv(flags)
274
+ });
260
275
  }
261
276
  async execute(flags) {
262
277
  if (!flags.prompt) {
263
278
  throw new Error("execute requires a prompt (-p flag)");
264
279
  }
265
- const { args } = this.mapFlags(flags);
266
- return this.processManager.execute(this.command, args);
280
+ const env = this.buildCleanEnv(flags);
281
+ try {
282
+ const q = query({
283
+ prompt: flags.prompt,
284
+ options: {
285
+ env,
286
+ cwd: process.cwd(),
287
+ ...flags.model ? { model: flags.model } : {},
288
+ ...flags.maxTurns ? { maxTurns: flags.maxTurns } : {},
289
+ permissionMode: "bypassPermissions",
290
+ allowDangerouslySkipPermissions: true
291
+ }
292
+ });
293
+ let resultText = "";
294
+ let sessionId = "";
295
+ let isError = false;
296
+ let errorMessages = [];
297
+ for await (const message of q) {
298
+ if (message.type === "result") {
299
+ sessionId = message.session_id;
300
+ if (message.subtype === "success") {
301
+ resultText = message.result;
302
+ } else {
303
+ isError = true;
304
+ errorMessages = message.errors;
305
+ }
306
+ }
307
+ }
308
+ logger.debug(`Claude SDK session: ${sessionId}`);
309
+ return {
310
+ exitCode: isError ? 1 : 0,
311
+ stdout: resultText,
312
+ stderr: errorMessages.join("\n")
313
+ };
314
+ } catch (error) {
315
+ return {
316
+ exitCode: 1,
317
+ stdout: "",
318
+ stderr: error instanceof Error ? error.message : String(error)
319
+ };
320
+ }
267
321
  }
268
- async resumeSession(sessionId, _flags) {
269
- await this.processManager.spawnInteractive(this.command, [
270
- "-r",
271
- sessionId
272
- ]);
322
+ async resumeSession(sessionId, flags) {
323
+ await this.processManager.spawnInteractive(
324
+ this.command,
325
+ ["-r", sessionId],
326
+ { env: this.buildCleanEnv(flags) }
327
+ );
328
+ }
329
+ buildCleanEnv(flags) {
330
+ const env = {};
331
+ for (const [key, value] of Object.entries(process.env)) {
332
+ if (value !== void 0 && !CLAUDE_NESTING_ENV_VARS.includes(key)) {
333
+ env[key] = value;
334
+ }
335
+ }
336
+ if (flags.mcpContext) {
337
+ env.RELAY_TRACE_ID = flags.mcpContext.traceId;
338
+ env.RELAY_PARENT_SESSION_ID = flags.mcpContext.parentSessionId;
339
+ env.RELAY_DEPTH = String(flags.mcpContext.depth);
340
+ }
341
+ return env;
273
342
  }
274
343
  async listNativeSessions() {
275
- logger.debug(
276
- "listNativeSessions: claude --resume is interactive, returning empty array"
277
- );
278
- return [];
344
+ try {
345
+ const sessions = await listSessions({ limit: 20 });
346
+ return sessions.map((s) => ({
347
+ nativeId: s.sessionId,
348
+ startedAt: new Date(s.lastModified),
349
+ lastMessage: s.summary
350
+ }));
351
+ } catch (error) {
352
+ logger.debug(
353
+ `listNativeSessions failed: ${error instanceof Error ? error.message : String(error)}`
354
+ );
355
+ return [];
356
+ }
279
357
  }
280
358
  async auth(action) {
281
359
  await this.processManager.spawnInteractive(this.command, ["auth", action]);
@@ -292,6 +370,7 @@ var ClaudeAdapter = class extends BaseAdapter {
292
370
  };
293
371
 
294
372
  // src/adapters/codex-adapter.ts
373
+ import { Codex } from "@openai/codex-sdk";
295
374
  var CodexAdapter = class extends BaseAdapter {
296
375
  id = "codex";
297
376
  command = "codex";
@@ -321,15 +400,35 @@ var CodexAdapter = class extends BaseAdapter {
321
400
  `Codex CLI does not support --agent flag. Ignoring agent "${flags.agent}".`
322
401
  );
323
402
  }
324
- const args = [];
325
- if (flags.model) {
326
- args.push("--model", flags.model);
327
- }
328
- if (flags.outputFormat === "json") {
329
- args.push("--json");
403
+ try {
404
+ const codexOptions = {};
405
+ if (flags.mcpContext) {
406
+ codexOptions.env = {
407
+ ...process.env,
408
+ RELAY_TRACE_ID: flags.mcpContext.traceId,
409
+ RELAY_PARENT_SESSION_ID: flags.mcpContext.parentSessionId,
410
+ RELAY_DEPTH: String(flags.mcpContext.depth)
411
+ };
412
+ }
413
+ const codex = new Codex(codexOptions);
414
+ const thread = codex.startThread({
415
+ ...flags.model ? { model: flags.model } : {},
416
+ workingDirectory: process.cwd(),
417
+ approvalPolicy: "never"
418
+ });
419
+ const result = await thread.run(flags.prompt);
420
+ return {
421
+ exitCode: 0,
422
+ stdout: result.finalResponse,
423
+ stderr: ""
424
+ };
425
+ } catch (error) {
426
+ return {
427
+ exitCode: 1,
428
+ stdout: "",
429
+ stderr: error instanceof Error ? error.message : String(error)
430
+ };
330
431
  }
331
- args.push("exec", flags.prompt);
332
- return this.processManager.execute(this.command, args);
333
432
  }
334
433
  async resumeSession(sessionId, flags) {
335
434
  const args = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI with MCP-based multi-layer sub-agent orchestration",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -46,7 +46,9 @@
46
46
  "node": ">=22"
47
47
  },
48
48
  "dependencies": {
49
+ "@anthropic-ai/claude-agent-sdk": "^0.2.59",
49
50
  "@modelcontextprotocol/sdk": "^1.27.1",
51
+ "@openai/codex-sdk": "^0.105.0",
50
52
  "citty": "^0.1.6",
51
53
  "consola": "^3.4.0",
52
54
  "execa": "^9.5.2",