@rk0429/agentic-relay 0.1.2 → 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.
- package/README.md +13 -4
- package/dist/relay.mjs +120 -21
- 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:
|
|
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:
|
|
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 (
|
|
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
|
|
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
|
|
266
|
-
|
|
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,
|
|
269
|
-
await this.processManager.spawnInteractive(
|
|
270
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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.
|
|
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",
|