@rk0429/agentic-relay 0.4.0 → 0.5.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/dist/relay.mjs +461 -63
- package/package.json +1 -1
package/dist/relay.mjs
CHANGED
|
@@ -190,7 +190,7 @@ async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooks
|
|
|
190
190
|
sessionId: "",
|
|
191
191
|
exitCode: 1,
|
|
192
192
|
stdout: "",
|
|
193
|
-
stderr: `Backend "${input.backend}" is not
|
|
193
|
+
stderr: `Backend "${input.backend}" is not available. Use list_available_backends to see available options.`
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
const session = await sessionManager2.create({
|
|
@@ -219,29 +219,40 @@ async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooks
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
try {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
maxDepth: guard.getConfig().maxDepth,
|
|
232
|
-
traceId: envContext.traceId
|
|
222
|
+
let result;
|
|
223
|
+
if (input.resumeSessionId) {
|
|
224
|
+
if (!adapter.continueSession) {
|
|
225
|
+
return {
|
|
226
|
+
sessionId: session.relaySessionId,
|
|
227
|
+
exitCode: 1,
|
|
228
|
+
stdout: "",
|
|
229
|
+
stderr: `Backend "${input.backend}" does not support session continuation (continueSession).`
|
|
230
|
+
};
|
|
233
231
|
}
|
|
234
|
-
|
|
232
|
+
result = await adapter.continueSession(input.resumeSessionId, input.prompt);
|
|
233
|
+
} else {
|
|
234
|
+
result = await adapter.execute({
|
|
235
|
+
prompt: input.prompt,
|
|
236
|
+
agent: input.agent,
|
|
237
|
+
systemPrompt: input.systemPrompt,
|
|
238
|
+
model: input.model,
|
|
239
|
+
maxTurns: input.maxTurns,
|
|
240
|
+
mcpContext: {
|
|
241
|
+
parentSessionId: session.relaySessionId,
|
|
242
|
+
depth: envContext.depth + 1,
|
|
243
|
+
maxDepth: guard.getConfig().maxDepth,
|
|
244
|
+
traceId: envContext.traceId
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
235
248
|
if (contextMonitor2) {
|
|
236
249
|
const estimatedTokens = Math.ceil(
|
|
237
250
|
(result.stdout.length + result.stderr.length) / 4
|
|
238
251
|
);
|
|
239
|
-
const maxTokens = input.backend === "gemini" ? 128e3 : 2e5;
|
|
240
252
|
contextMonitor2.updateUsage(
|
|
241
253
|
session.relaySessionId,
|
|
242
254
|
input.backend,
|
|
243
|
-
estimatedTokens
|
|
244
|
-
maxTokens
|
|
255
|
+
estimatedTokens
|
|
245
256
|
);
|
|
246
257
|
}
|
|
247
258
|
guard.recordSpawn(context);
|
|
@@ -270,7 +281,8 @@ async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooks
|
|
|
270
281
|
sessionId: session.relaySessionId,
|
|
271
282
|
exitCode: result.exitCode,
|
|
272
283
|
stdout: result.stdout,
|
|
273
|
-
stderr: result.stderr
|
|
284
|
+
stderr: result.stderr,
|
|
285
|
+
nativeSessionId: result.nativeSessionId
|
|
274
286
|
};
|
|
275
287
|
} catch (error) {
|
|
276
288
|
await sessionManager2.update(session.relaySessionId, { status: "error" });
|
|
@@ -293,6 +305,7 @@ var init_spawn_agent = __esm({
|
|
|
293
305
|
backend: z2.enum(["claude", "codex", "gemini"]),
|
|
294
306
|
prompt: z2.string(),
|
|
295
307
|
agent: z2.string().optional(),
|
|
308
|
+
systemPrompt: z2.string().optional(),
|
|
296
309
|
resumeSessionId: z2.string().optional(),
|
|
297
310
|
model: z2.string().optional(),
|
|
298
311
|
maxTurns: z2.number().optional()
|
|
@@ -339,8 +352,14 @@ async function executeGetContextStatus(input, sessionManager2, contextMonitor2)
|
|
|
339
352
|
if (usage) {
|
|
340
353
|
return {
|
|
341
354
|
sessionId: input.sessionId,
|
|
355
|
+
backendId: usage.backendId,
|
|
342
356
|
usagePercent: usage.usagePercent,
|
|
343
|
-
isEstimated: usage.isEstimated
|
|
357
|
+
isEstimated: usage.isEstimated,
|
|
358
|
+
contextWindow: usage.contextWindow,
|
|
359
|
+
compactThreshold: usage.compactThreshold,
|
|
360
|
+
estimatedTokens: usage.estimatedTokens,
|
|
361
|
+
remainingBeforeCompact: usage.remainingBeforeCompact,
|
|
362
|
+
notifyThreshold: usage.notifyThreshold
|
|
344
363
|
};
|
|
345
364
|
}
|
|
346
365
|
}
|
|
@@ -360,6 +379,24 @@ var init_get_context_status = __esm({
|
|
|
360
379
|
}
|
|
361
380
|
});
|
|
362
381
|
|
|
382
|
+
// src/mcp-server/tools/list-available-backends.ts
|
|
383
|
+
async function executeListAvailableBackends(registry2) {
|
|
384
|
+
const backends = [];
|
|
385
|
+
for (const adapter of registry2.list()) {
|
|
386
|
+
const health = await adapter.checkHealth();
|
|
387
|
+
backends.push({
|
|
388
|
+
id: adapter.id,
|
|
389
|
+
...health
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return backends;
|
|
393
|
+
}
|
|
394
|
+
var init_list_available_backends = __esm({
|
|
395
|
+
"src/mcp-server/tools/list-available-backends.ts"() {
|
|
396
|
+
"use strict";
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
363
400
|
// src/mcp-server/server.ts
|
|
364
401
|
var server_exports = {};
|
|
365
402
|
__export(server_exports, {
|
|
@@ -379,6 +416,7 @@ var init_server = __esm({
|
|
|
379
416
|
init_spawn_agent();
|
|
380
417
|
init_list_sessions();
|
|
381
418
|
init_get_context_status();
|
|
419
|
+
init_list_available_backends();
|
|
382
420
|
init_logger();
|
|
383
421
|
RelayMCPServer = class {
|
|
384
422
|
constructor(registry2, sessionManager2, guardConfig, hooksEngine2, contextMonitor2) {
|
|
@@ -398,11 +436,14 @@ var init_server = __esm({
|
|
|
398
436
|
registerTools() {
|
|
399
437
|
this.server.tool(
|
|
400
438
|
"spawn_agent",
|
|
401
|
-
"Spawn a sub-agent on the specified backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result.",
|
|
439
|
+
"Spawn a sub-agent on the specified backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Use 'agent' for named agent configurations (Claude only), or 'systemPrompt' for custom role instructions (all backends).",
|
|
402
440
|
{
|
|
403
441
|
backend: z5.enum(["claude", "codex", "gemini"]),
|
|
404
442
|
prompt: z5.string(),
|
|
405
|
-
agent: z5.string().optional(),
|
|
443
|
+
agent: z5.string().optional().describe("Named agent configuration (Claude only)"),
|
|
444
|
+
systemPrompt: z5.string().optional().describe(
|
|
445
|
+
"System prompt / role instructions for the sub-agent (all backends)"
|
|
446
|
+
),
|
|
406
447
|
resumeSessionId: z5.string().optional(),
|
|
407
448
|
model: z5.string().optional(),
|
|
408
449
|
maxTurns: z5.number().optional()
|
|
@@ -494,6 +535,30 @@ ${result.stdout}`;
|
|
|
494
535
|
}
|
|
495
536
|
}
|
|
496
537
|
);
|
|
538
|
+
this.server.tool(
|
|
539
|
+
"list_available_backends",
|
|
540
|
+
"List all registered backends with their health status. Use this before spawn_agent to check which backends are available.",
|
|
541
|
+
{},
|
|
542
|
+
async () => {
|
|
543
|
+
try {
|
|
544
|
+
const result = await executeListAvailableBackends(this.registry);
|
|
545
|
+
return {
|
|
546
|
+
content: [
|
|
547
|
+
{
|
|
548
|
+
type: "text",
|
|
549
|
+
text: JSON.stringify(result, null, 2)
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
};
|
|
553
|
+
} catch (error) {
|
|
554
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
555
|
+
return {
|
|
556
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
557
|
+
isError: true
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
);
|
|
497
562
|
}
|
|
498
563
|
async start(options) {
|
|
499
564
|
const transportType = options?.transport ?? "stdio";
|
|
@@ -698,6 +763,42 @@ var BaseAdapter = class {
|
|
|
698
763
|
}
|
|
699
764
|
return result.stdout.trim();
|
|
700
765
|
}
|
|
766
|
+
async continueSession(_nativeSessionId, _prompt) {
|
|
767
|
+
return {
|
|
768
|
+
exitCode: 1,
|
|
769
|
+
stdout: "",
|
|
770
|
+
stderr: `continueSession not supported for ${this.id}`
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
async checkHealth() {
|
|
774
|
+
const HEALTH_TIMEOUT = 5e3;
|
|
775
|
+
const installed = await Promise.race([
|
|
776
|
+
this.isInstalled(),
|
|
777
|
+
new Promise(
|
|
778
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
779
|
+
)
|
|
780
|
+
]).catch(() => false);
|
|
781
|
+
if (!installed) {
|
|
782
|
+
return {
|
|
783
|
+
installed: false,
|
|
784
|
+
authenticated: false,
|
|
785
|
+
healthy: false,
|
|
786
|
+
message: `${this.id} is not installed`
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
const version = await Promise.race([
|
|
790
|
+
this.getVersion(),
|
|
791
|
+
new Promise(
|
|
792
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
793
|
+
)
|
|
794
|
+
]).catch(() => void 0);
|
|
795
|
+
return {
|
|
796
|
+
installed: true,
|
|
797
|
+
authenticated: true,
|
|
798
|
+
healthy: true,
|
|
799
|
+
version
|
|
800
|
+
};
|
|
801
|
+
}
|
|
701
802
|
async getMCPConfig() {
|
|
702
803
|
logger.warn(`getMCPConfig not implemented for ${this.id}`);
|
|
703
804
|
return [];
|
|
@@ -799,6 +900,48 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
799
900
|
getConfigPath() {
|
|
800
901
|
return join(homedir(), ".claude.json");
|
|
801
902
|
}
|
|
903
|
+
async checkHealth() {
|
|
904
|
+
const HEALTH_TIMEOUT = 5e3;
|
|
905
|
+
const installed = await Promise.race([
|
|
906
|
+
this.isInstalled(),
|
|
907
|
+
new Promise(
|
|
908
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
909
|
+
)
|
|
910
|
+
]).catch(() => false);
|
|
911
|
+
if (!installed) {
|
|
912
|
+
return {
|
|
913
|
+
installed: false,
|
|
914
|
+
authenticated: false,
|
|
915
|
+
healthy: false,
|
|
916
|
+
message: "claude is not installed"
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
const version = await Promise.race([
|
|
920
|
+
this.getVersion(),
|
|
921
|
+
new Promise(
|
|
922
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
923
|
+
)
|
|
924
|
+
]).catch(() => void 0);
|
|
925
|
+
let authenticated = true;
|
|
926
|
+
try {
|
|
927
|
+
const result = await Promise.race([
|
|
928
|
+
this.processManager.execute(this.command, ["auth", "status"]),
|
|
929
|
+
new Promise(
|
|
930
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
931
|
+
)
|
|
932
|
+
]);
|
|
933
|
+
authenticated = result.exitCode === 0;
|
|
934
|
+
} catch {
|
|
935
|
+
authenticated = true;
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
installed: true,
|
|
939
|
+
authenticated,
|
|
940
|
+
healthy: authenticated,
|
|
941
|
+
version,
|
|
942
|
+
...!authenticated ? { message: "claude authentication not configured" } : {}
|
|
943
|
+
};
|
|
944
|
+
}
|
|
802
945
|
mapFlags(flags) {
|
|
803
946
|
return {
|
|
804
947
|
args: mapCommonToNative("claude", flags)
|
|
@@ -825,7 +968,8 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
825
968
|
env,
|
|
826
969
|
cwd: process.cwd(),
|
|
827
970
|
...flags.model ? { model: flags.model } : {},
|
|
828
|
-
...flags.maxTurns ? { maxTurns: flags.maxTurns } : {}
|
|
971
|
+
...flags.maxTurns ? { maxTurns: flags.maxTurns } : {},
|
|
972
|
+
...flags.systemPrompt ? { systemPrompt: flags.systemPrompt } : {}
|
|
829
973
|
};
|
|
830
974
|
if (permissionMode === "bypassPermissions") {
|
|
831
975
|
options.permissionMode = "bypassPermissions";
|
|
@@ -877,7 +1021,8 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
877
1021
|
env,
|
|
878
1022
|
cwd: process.cwd(),
|
|
879
1023
|
...flags.model ? { model: flags.model } : {},
|
|
880
|
-
...flags.maxTurns ? { maxTurns: flags.maxTurns } : {}
|
|
1024
|
+
...flags.maxTurns ? { maxTurns: flags.maxTurns } : {},
|
|
1025
|
+
...flags.systemPrompt ? { systemPrompt: flags.systemPrompt } : {}
|
|
881
1026
|
};
|
|
882
1027
|
if (permissionMode === "bypassPermissions") {
|
|
883
1028
|
options.permissionMode = "bypassPermissions";
|
|
@@ -942,6 +1087,44 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
942
1087
|
};
|
|
943
1088
|
}
|
|
944
1089
|
}
|
|
1090
|
+
async continueSession(nativeSessionId, prompt) {
|
|
1091
|
+
try {
|
|
1092
|
+
const { query } = await loadClaudeSDK();
|
|
1093
|
+
const permissionMode = this.getPermissionMode();
|
|
1094
|
+
const options = {
|
|
1095
|
+
resume: nativeSessionId,
|
|
1096
|
+
maxTurns: 1,
|
|
1097
|
+
cwd: process.cwd()
|
|
1098
|
+
};
|
|
1099
|
+
if (permissionMode === "bypassPermissions") {
|
|
1100
|
+
options.permissionMode = "bypassPermissions";
|
|
1101
|
+
options.allowDangerouslySkipPermissions = true;
|
|
1102
|
+
}
|
|
1103
|
+
const q = query({
|
|
1104
|
+
prompt,
|
|
1105
|
+
options
|
|
1106
|
+
});
|
|
1107
|
+
let resultText = "";
|
|
1108
|
+
for await (const message of q) {
|
|
1109
|
+
if (message.type === "result") {
|
|
1110
|
+
if (message.subtype === "success") {
|
|
1111
|
+
resultText = message.result;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
exitCode: 0,
|
|
1117
|
+
stdout: resultText,
|
|
1118
|
+
stderr: ""
|
|
1119
|
+
};
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
return {
|
|
1122
|
+
exitCode: 1,
|
|
1123
|
+
stdout: "",
|
|
1124
|
+
stderr: error instanceof Error ? error.message : String(error)
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
945
1128
|
async resumeSession(sessionId, flags) {
|
|
946
1129
|
await this.processManager.spawnInteractive(
|
|
947
1130
|
this.command,
|
|
@@ -1157,6 +1340,48 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1157
1340
|
getConfigPath() {
|
|
1158
1341
|
return join2(homedir2(), ".codex", "config.toml");
|
|
1159
1342
|
}
|
|
1343
|
+
async checkHealth() {
|
|
1344
|
+
const HEALTH_TIMEOUT = 5e3;
|
|
1345
|
+
const installed = await Promise.race([
|
|
1346
|
+
this.isInstalled(),
|
|
1347
|
+
new Promise(
|
|
1348
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
1349
|
+
)
|
|
1350
|
+
]).catch(() => false);
|
|
1351
|
+
if (!installed) {
|
|
1352
|
+
return {
|
|
1353
|
+
installed: false,
|
|
1354
|
+
authenticated: false,
|
|
1355
|
+
healthy: false,
|
|
1356
|
+
message: "codex is not installed"
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
const version = await Promise.race([
|
|
1360
|
+
this.getVersion(),
|
|
1361
|
+
new Promise(
|
|
1362
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
1363
|
+
)
|
|
1364
|
+
]).catch(() => void 0);
|
|
1365
|
+
let authenticated = true;
|
|
1366
|
+
try {
|
|
1367
|
+
const result = await Promise.race([
|
|
1368
|
+
this.processManager.execute(this.command, ["login", "status"]),
|
|
1369
|
+
new Promise(
|
|
1370
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
1371
|
+
)
|
|
1372
|
+
]);
|
|
1373
|
+
authenticated = result.exitCode === 0;
|
|
1374
|
+
} catch {
|
|
1375
|
+
authenticated = true;
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
installed: true,
|
|
1379
|
+
authenticated,
|
|
1380
|
+
healthy: authenticated,
|
|
1381
|
+
version,
|
|
1382
|
+
...!authenticated ? { message: "codex authentication not configured" } : {}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1160
1385
|
mapFlags(flags) {
|
|
1161
1386
|
const args = mapCommonToNative("codex", flags);
|
|
1162
1387
|
if (flags.outputFormat === "json") {
|
|
@@ -1174,15 +1399,39 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1174
1399
|
}
|
|
1175
1400
|
await this.processManager.spawnInteractive(this.command, args);
|
|
1176
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Resolve the effective system prompt from flags.
|
|
1404
|
+
* Priority: systemPrompt > agent fallback > none
|
|
1405
|
+
*/
|
|
1406
|
+
resolveSystemPrompt(flags) {
|
|
1407
|
+
if (flags.systemPrompt) return flags.systemPrompt;
|
|
1408
|
+
if (flags.agent) {
|
|
1409
|
+
return `You are acting as the "${flags.agent}" agent. Follow the instructions and role defined for this agent.`;
|
|
1410
|
+
}
|
|
1411
|
+
return void 0;
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Build the effective prompt with system instructions prepended if needed.
|
|
1415
|
+
* Codex SDK does not support a native instructions/systemPrompt parameter,
|
|
1416
|
+
* so we inject role context via a prompt prefix.
|
|
1417
|
+
*/
|
|
1418
|
+
buildEffectivePrompt(prompt, systemPrompt) {
|
|
1419
|
+
if (!systemPrompt) return prompt;
|
|
1420
|
+
return `[System Instructions]
|
|
1421
|
+
${systemPrompt}
|
|
1422
|
+
|
|
1423
|
+
[User Request]
|
|
1424
|
+
${prompt}`;
|
|
1425
|
+
}
|
|
1177
1426
|
async execute(flags) {
|
|
1178
1427
|
if (!flags.prompt) {
|
|
1179
1428
|
throw new Error("execute requires a prompt (-p flag)");
|
|
1180
1429
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1430
|
+
const systemPrompt = this.resolveSystemPrompt(flags);
|
|
1431
|
+
const effectivePrompt = this.buildEffectivePrompt(
|
|
1432
|
+
flags.prompt,
|
|
1433
|
+
systemPrompt
|
|
1434
|
+
);
|
|
1186
1435
|
try {
|
|
1187
1436
|
const { Codex } = await loadCodexSDK();
|
|
1188
1437
|
const codexOptions = {};
|
|
@@ -1200,11 +1449,12 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1200
1449
|
workingDirectory: process.cwd(),
|
|
1201
1450
|
approvalPolicy: "never"
|
|
1202
1451
|
});
|
|
1203
|
-
const result = await thread.run(
|
|
1452
|
+
const result = await thread.run(effectivePrompt);
|
|
1204
1453
|
return {
|
|
1205
1454
|
exitCode: 0,
|
|
1206
1455
|
stdout: result.finalResponse,
|
|
1207
|
-
stderr: ""
|
|
1456
|
+
stderr: "",
|
|
1457
|
+
...thread.id ? { nativeSessionId: thread.id } : {}
|
|
1208
1458
|
};
|
|
1209
1459
|
} catch (error) {
|
|
1210
1460
|
return {
|
|
@@ -1218,11 +1468,11 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1218
1468
|
if (!flags.prompt) {
|
|
1219
1469
|
throw new Error("executeStreaming requires a prompt (-p flag)");
|
|
1220
1470
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1471
|
+
const systemPrompt = this.resolveSystemPrompt(flags);
|
|
1472
|
+
const effectivePrompt = this.buildEffectivePrompt(
|
|
1473
|
+
flags.prompt,
|
|
1474
|
+
systemPrompt
|
|
1475
|
+
);
|
|
1226
1476
|
try {
|
|
1227
1477
|
const { Codex } = await loadCodexSDK();
|
|
1228
1478
|
const codexOptions = {};
|
|
@@ -1240,10 +1490,13 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1240
1490
|
workingDirectory: process.cwd(),
|
|
1241
1491
|
approvalPolicy: "never"
|
|
1242
1492
|
});
|
|
1243
|
-
const streamedTurn = await thread.runStreamed(
|
|
1493
|
+
const streamedTurn = await thread.runStreamed(effectivePrompt);
|
|
1244
1494
|
const completedMessages = [];
|
|
1495
|
+
let threadId;
|
|
1245
1496
|
for await (const event of streamedTurn.events) {
|
|
1246
|
-
if (event.type === "
|
|
1497
|
+
if (event.type === "thread.started") {
|
|
1498
|
+
threadId = event.thread_id;
|
|
1499
|
+
} else if (event.type === "item.started") {
|
|
1247
1500
|
const item = event.item;
|
|
1248
1501
|
if (item?.type === "agent_message" && item.text) {
|
|
1249
1502
|
yield { type: "text", text: item.text };
|
|
@@ -1291,13 +1544,15 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1291
1544
|
const finalResponse = completedMessages.join("\n");
|
|
1292
1545
|
yield {
|
|
1293
1546
|
type: "done",
|
|
1294
|
-
result: { exitCode: 0, stdout: finalResponse, stderr: "" }
|
|
1547
|
+
result: { exitCode: 0, stdout: finalResponse, stderr: "" },
|
|
1548
|
+
nativeSessionId: threadId ?? thread.id ?? void 0
|
|
1295
1549
|
};
|
|
1296
1550
|
} else if (event.type === "turn.failed") {
|
|
1297
1551
|
const errorMessage = event.error?.message ?? "Turn failed";
|
|
1298
1552
|
yield {
|
|
1299
1553
|
type: "done",
|
|
1300
|
-
result: { exitCode: 1, stdout: "", stderr: errorMessage }
|
|
1554
|
+
result: { exitCode: 1, stdout: "", stderr: errorMessage },
|
|
1555
|
+
nativeSessionId: threadId ?? thread.id ?? void 0
|
|
1301
1556
|
};
|
|
1302
1557
|
} else if (event.type === "error") {
|
|
1303
1558
|
yield {
|
|
@@ -1315,6 +1570,28 @@ var CodexAdapter = class extends BaseAdapter {
|
|
|
1315
1570
|
};
|
|
1316
1571
|
}
|
|
1317
1572
|
}
|
|
1573
|
+
async continueSession(nativeSessionId, prompt) {
|
|
1574
|
+
try {
|
|
1575
|
+
const { Codex } = await loadCodexSDK();
|
|
1576
|
+
const codex = new Codex();
|
|
1577
|
+
const thread = codex.resumeThread(nativeSessionId, {
|
|
1578
|
+
workingDirectory: process.cwd(),
|
|
1579
|
+
approvalPolicy: "never"
|
|
1580
|
+
});
|
|
1581
|
+
const result = await thread.run(prompt);
|
|
1582
|
+
return {
|
|
1583
|
+
exitCode: 0,
|
|
1584
|
+
stdout: result.finalResponse,
|
|
1585
|
+
stderr: ""
|
|
1586
|
+
};
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
return {
|
|
1589
|
+
exitCode: 1,
|
|
1590
|
+
stdout: "",
|
|
1591
|
+
stderr: error instanceof Error ? error.message : String(error)
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1318
1595
|
async resumeSession(sessionId, flags) {
|
|
1319
1596
|
const args = [];
|
|
1320
1597
|
if (flags.model) {
|
|
@@ -1408,6 +1685,38 @@ var GeminiAdapter = class extends BaseAdapter {
|
|
|
1408
1685
|
getConfigPath() {
|
|
1409
1686
|
return join3(homedir3(), ".gemini", "settings.json");
|
|
1410
1687
|
}
|
|
1688
|
+
async checkHealth() {
|
|
1689
|
+
const HEALTH_TIMEOUT = 5e3;
|
|
1690
|
+
const installed = await Promise.race([
|
|
1691
|
+
this.isInstalled(),
|
|
1692
|
+
new Promise(
|
|
1693
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
1694
|
+
)
|
|
1695
|
+
]).catch(() => false);
|
|
1696
|
+
if (!installed) {
|
|
1697
|
+
return {
|
|
1698
|
+
installed: false,
|
|
1699
|
+
authenticated: false,
|
|
1700
|
+
healthy: false,
|
|
1701
|
+
message: "gemini is not installed"
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
const version = await Promise.race([
|
|
1705
|
+
this.getVersion(),
|
|
1706
|
+
new Promise(
|
|
1707
|
+
(_, reject) => setTimeout(() => reject(new Error("timeout")), HEALTH_TIMEOUT)
|
|
1708
|
+
)
|
|
1709
|
+
]).catch(() => void 0);
|
|
1710
|
+
const hasApiKey = !!process.env["GEMINI_API_KEY"];
|
|
1711
|
+
const hasGoogleAdc = !!process.env["GOOGLE_APPLICATION_CREDENTIALS"] || !!process.env["CLOUDSDK_CONFIG"];
|
|
1712
|
+
const authenticated = hasApiKey || hasGoogleAdc || true;
|
|
1713
|
+
return {
|
|
1714
|
+
installed: true,
|
|
1715
|
+
authenticated,
|
|
1716
|
+
healthy: true,
|
|
1717
|
+
version
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1411
1720
|
mapFlags(flags) {
|
|
1412
1721
|
const args = mapCommonToNative("gemini", flags);
|
|
1413
1722
|
if (flags.outputFormat) {
|
|
@@ -1428,15 +1737,34 @@ var GeminiAdapter = class extends BaseAdapter {
|
|
|
1428
1737
|
}
|
|
1429
1738
|
await this.processManager.spawnInteractive(this.command, args);
|
|
1430
1739
|
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Resolve the effective prompt with system instructions prepended if needed.
|
|
1742
|
+
* Gemini CLI has no native system prompt flag, so we use a prompt prefix.
|
|
1743
|
+
* Priority: systemPrompt > agent fallback > none
|
|
1744
|
+
*/
|
|
1745
|
+
buildEffectivePrompt(flags) {
|
|
1746
|
+
const prompt = flags.prompt;
|
|
1747
|
+
if (flags.systemPrompt) {
|
|
1748
|
+
return `[System Instructions]
|
|
1749
|
+
${flags.systemPrompt}
|
|
1750
|
+
|
|
1751
|
+
[User Request]
|
|
1752
|
+
${prompt}`;
|
|
1753
|
+
}
|
|
1754
|
+
if (flags.agent) {
|
|
1755
|
+
return `[System Instructions]
|
|
1756
|
+
You are acting as the "${flags.agent}" agent.
|
|
1757
|
+
|
|
1758
|
+
[User Request]
|
|
1759
|
+
${prompt}`;
|
|
1760
|
+
}
|
|
1761
|
+
return prompt;
|
|
1762
|
+
}
|
|
1431
1763
|
async execute(flags) {
|
|
1432
1764
|
if (!flags.prompt) {
|
|
1433
1765
|
throw new Error("execute requires a prompt (-p flag)");
|
|
1434
1766
|
}
|
|
1435
|
-
|
|
1436
|
-
logger.warn(
|
|
1437
|
-
`Gemini CLI does not support --agent flag. Ignoring agent "${flags.agent}".`
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1767
|
+
const effectivePrompt = this.buildEffectivePrompt(flags);
|
|
1440
1768
|
const args = [];
|
|
1441
1769
|
if (flags.model) {
|
|
1442
1770
|
args.push("--model", flags.model);
|
|
@@ -1447,7 +1775,7 @@ var GeminiAdapter = class extends BaseAdapter {
|
|
|
1447
1775
|
if (flags.verbose) {
|
|
1448
1776
|
args.push("--verbose");
|
|
1449
1777
|
}
|
|
1450
|
-
args.push("-p",
|
|
1778
|
+
args.push("-p", effectivePrompt);
|
|
1451
1779
|
return this.processManager.execute(this.command, args);
|
|
1452
1780
|
}
|
|
1453
1781
|
async resumeSession(sessionId, flags) {
|
|
@@ -1721,6 +2049,10 @@ var hookDefinitionSchema = z.object({
|
|
|
1721
2049
|
var hooksConfigSchema = z.object({
|
|
1722
2050
|
definitions: z.array(hookDefinitionSchema)
|
|
1723
2051
|
});
|
|
2052
|
+
var backendContextConfigSchema = z.object({
|
|
2053
|
+
contextWindow: z.number().positive().optional(),
|
|
2054
|
+
compactThreshold: z.number().positive().optional()
|
|
2055
|
+
}).optional();
|
|
1724
2056
|
var relayConfigSchema = z.object({
|
|
1725
2057
|
defaultBackend: backendIdSchema.optional(),
|
|
1726
2058
|
mcpServers: z.record(mcpServerConfigSchema).optional(),
|
|
@@ -1731,9 +2063,16 @@ var relayConfigSchema = z.object({
|
|
|
1731
2063
|
}).optional(),
|
|
1732
2064
|
hooks: hooksConfigSchema.optional(),
|
|
1733
2065
|
contextMonitor: z.object({
|
|
1734
|
-
enabled: z.boolean(),
|
|
1735
|
-
thresholdPercent: z.number().min(0).max(100),
|
|
1736
|
-
|
|
2066
|
+
enabled: z.boolean().optional(),
|
|
2067
|
+
thresholdPercent: z.number().min(0).max(100).optional(),
|
|
2068
|
+
notifyThreshold: z.number().positive().optional(),
|
|
2069
|
+
notifyPercent: z.number().min(0).max(100).optional(),
|
|
2070
|
+
notifyMethod: z.enum(["stderr", "hook"]).optional(),
|
|
2071
|
+
backends: z.object({
|
|
2072
|
+
claude: backendContextConfigSchema,
|
|
2073
|
+
codex: backendContextConfigSchema,
|
|
2074
|
+
gemini: backendContextConfigSchema
|
|
2075
|
+
}).optional()
|
|
1737
2076
|
}).optional(),
|
|
1738
2077
|
mcpServerMode: z.object({
|
|
1739
2078
|
maxDepth: z.number().int().positive(),
|
|
@@ -2129,34 +2468,75 @@ var HooksEngine = class _HooksEngine {
|
|
|
2129
2468
|
};
|
|
2130
2469
|
|
|
2131
2470
|
// src/core/context-monitor.ts
|
|
2471
|
+
var DEFAULT_BACKEND_CONTEXT = {
|
|
2472
|
+
claude: { contextWindow: 2e5, compactThreshold: 19e4 },
|
|
2473
|
+
codex: { contextWindow: 272e3, compactThreshold: 258400 },
|
|
2474
|
+
gemini: { contextWindow: 1048576, compactThreshold: 524288 }
|
|
2475
|
+
};
|
|
2476
|
+
var DEFAULT_NOTIFY_PERCENT = 70;
|
|
2132
2477
|
var DEFAULT_CONFIG = {
|
|
2133
2478
|
enabled: true,
|
|
2134
|
-
|
|
2135
|
-
notifyMethod: "stderr"
|
|
2479
|
+
notifyMethod: "hook"
|
|
2136
2480
|
};
|
|
2137
2481
|
var ContextMonitor = class {
|
|
2138
2482
|
constructor(hooksEngine2, config) {
|
|
2139
2483
|
this.hooksEngine = hooksEngine2;
|
|
2140
2484
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
2485
|
+
if (this.config.thresholdPercent !== void 0 && this.config.notifyPercent === void 0 && this.config.notifyThreshold === void 0) {
|
|
2486
|
+
this.config.notifyPercent = this.config.thresholdPercent;
|
|
2487
|
+
}
|
|
2141
2488
|
}
|
|
2142
2489
|
config;
|
|
2143
2490
|
usageMap = /* @__PURE__ */ new Map();
|
|
2491
|
+
/** Get backend context config, merging user overrides with defaults */
|
|
2492
|
+
getBackendConfig(backendId) {
|
|
2493
|
+
const defaults = DEFAULT_BACKEND_CONTEXT[backendId];
|
|
2494
|
+
const overrides = this.config.backends?.[backendId];
|
|
2495
|
+
return {
|
|
2496
|
+
contextWindow: overrides?.contextWindow ?? defaults.contextWindow,
|
|
2497
|
+
compactThreshold: overrides?.compactThreshold ?? defaults.compactThreshold
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
/** Calculate the notification threshold in tokens for a given backend */
|
|
2501
|
+
getNotifyThreshold(backendId) {
|
|
2502
|
+
if (this.config.notifyThreshold !== void 0) {
|
|
2503
|
+
return this.config.notifyThreshold;
|
|
2504
|
+
}
|
|
2505
|
+
const backendConfig = this.getBackendConfig(backendId);
|
|
2506
|
+
const notifyPercent = this.config.notifyPercent ?? DEFAULT_NOTIFY_PERCENT;
|
|
2507
|
+
return Math.round(backendConfig.contextWindow * notifyPercent / 100);
|
|
2508
|
+
}
|
|
2144
2509
|
/** Update token usage for a session and check threshold */
|
|
2145
|
-
updateUsage(sessionId, backendId, estimatedTokens
|
|
2510
|
+
updateUsage(sessionId, backendId, estimatedTokens) {
|
|
2146
2511
|
if (!this.config.enabled) return;
|
|
2147
|
-
const
|
|
2512
|
+
const backendConfig = this.getBackendConfig(backendId);
|
|
2513
|
+
const contextWindow = backendConfig.contextWindow;
|
|
2514
|
+
const usagePercent = contextWindow > 0 ? Math.round(estimatedTokens / contextWindow * 100) : 0;
|
|
2148
2515
|
const existing = this.usageMap.get(sessionId);
|
|
2149
|
-
|
|
2516
|
+
let wasNotified = existing?.notified ?? false;
|
|
2517
|
+
if (existing && estimatedTokens < existing.estimatedTokens * 0.7) {
|
|
2518
|
+
wasNotified = false;
|
|
2519
|
+
}
|
|
2150
2520
|
this.usageMap.set(sessionId, {
|
|
2151
2521
|
estimatedTokens,
|
|
2152
|
-
|
|
2522
|
+
contextWindow,
|
|
2523
|
+
compactThreshold: backendConfig.compactThreshold,
|
|
2153
2524
|
usagePercent,
|
|
2154
2525
|
backendId,
|
|
2155
2526
|
notified: wasNotified
|
|
2156
2527
|
});
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
this.
|
|
2528
|
+
const notifyAt = this.getNotifyThreshold(backendId);
|
|
2529
|
+
if (estimatedTokens >= notifyAt && !wasNotified) {
|
|
2530
|
+
const entry = this.usageMap.get(sessionId);
|
|
2531
|
+
entry.notified = true;
|
|
2532
|
+
this.notify(
|
|
2533
|
+
sessionId,
|
|
2534
|
+
backendId,
|
|
2535
|
+
usagePercent,
|
|
2536
|
+
estimatedTokens,
|
|
2537
|
+
contextWindow,
|
|
2538
|
+
backendConfig.compactThreshold
|
|
2539
|
+
);
|
|
2160
2540
|
}
|
|
2161
2541
|
}
|
|
2162
2542
|
/** Get usage info for a session */
|
|
@@ -2165,17 +2545,31 @@ var ContextMonitor = class {
|
|
|
2165
2545
|
if (!entry) return null;
|
|
2166
2546
|
return {
|
|
2167
2547
|
usagePercent: entry.usagePercent,
|
|
2168
|
-
isEstimated: true
|
|
2548
|
+
isEstimated: true,
|
|
2549
|
+
backendId: entry.backendId,
|
|
2550
|
+
contextWindow: entry.contextWindow,
|
|
2551
|
+
compactThreshold: entry.compactThreshold,
|
|
2552
|
+
estimatedTokens: entry.estimatedTokens,
|
|
2553
|
+
remainingBeforeCompact: Math.max(
|
|
2554
|
+
0,
|
|
2555
|
+
entry.compactThreshold - entry.estimatedTokens
|
|
2556
|
+
),
|
|
2557
|
+
notifyThreshold: this.getNotifyThreshold(entry.backendId)
|
|
2169
2558
|
};
|
|
2170
2559
|
}
|
|
2171
2560
|
/** Remove usage tracking for a session */
|
|
2172
2561
|
removeSession(sessionId) {
|
|
2173
2562
|
this.usageMap.delete(sessionId);
|
|
2174
2563
|
}
|
|
2175
|
-
notify(sessionId, backendId, usagePercent) {
|
|
2564
|
+
notify(sessionId, backendId, usagePercent, currentTokens, contextWindow, compactThreshold) {
|
|
2565
|
+
const remainingBeforeCompact = Math.max(
|
|
2566
|
+
0,
|
|
2567
|
+
compactThreshold - currentTokens
|
|
2568
|
+
);
|
|
2569
|
+
const warningMessage = `${backendId} session ${sessionId} at ${usagePercent}% (${currentTokens}/${contextWindow} tokens). Compact in ~${remainingBeforeCompact} tokens. Save your work state now.`;
|
|
2176
2570
|
if (this.config.notifyMethod === "stderr") {
|
|
2177
2571
|
process.stderr.write(
|
|
2178
|
-
`[relay] Context
|
|
2572
|
+
`[relay] Context warning: ${warningMessage}
|
|
2179
2573
|
`
|
|
2180
2574
|
);
|
|
2181
2575
|
} else if (this.config.notifyMethod === "hook" && this.hooksEngine) {
|
|
@@ -2186,7 +2580,10 @@ var ContextMonitor = class {
|
|
|
2186
2580
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2187
2581
|
data: {
|
|
2188
2582
|
usagePercent,
|
|
2189
|
-
|
|
2583
|
+
currentTokens,
|
|
2584
|
+
contextWindow,
|
|
2585
|
+
compactThreshold,
|
|
2586
|
+
remainingBeforeCompact
|
|
2190
2587
|
}
|
|
2191
2588
|
};
|
|
2192
2589
|
void this.hooksEngine.emit("on-context-threshold", hookInput);
|
|
@@ -2323,6 +2720,7 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
|
|
|
2323
2720
|
try {
|
|
2324
2721
|
if (flags.prompt) {
|
|
2325
2722
|
logger.debug(`Executing prompt on ${backendId}`);
|
|
2723
|
+
let nativeSessionId;
|
|
2326
2724
|
if (adapter.executeStreaming) {
|
|
2327
2725
|
for await (const event of adapter.executeStreaming(flags)) {
|
|
2328
2726
|
switch (event.type) {
|
|
@@ -2353,18 +2751,17 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
|
|
|
2353
2751
|
break;
|
|
2354
2752
|
case "usage": {
|
|
2355
2753
|
if (contextMonitor2 && relaySessionId) {
|
|
2356
|
-
const maxTokens = backendId === "gemini" ? 128e3 : 2e5;
|
|
2357
2754
|
contextMonitor2.updateUsage(
|
|
2358
2755
|
relaySessionId,
|
|
2359
2756
|
backendId,
|
|
2360
|
-
event.inputTokens + event.outputTokens
|
|
2361
|
-
maxTokens
|
|
2757
|
+
event.inputTokens + event.outputTokens
|
|
2362
2758
|
);
|
|
2363
2759
|
}
|
|
2364
2760
|
break;
|
|
2365
2761
|
}
|
|
2366
2762
|
case "done":
|
|
2367
2763
|
process.exitCode = event.result.exitCode;
|
|
2764
|
+
nativeSessionId = event.nativeSessionId;
|
|
2368
2765
|
if (event.nativeSessionId && sessionManager2 && relaySessionId) {
|
|
2369
2766
|
try {
|
|
2370
2767
|
await sessionManager2.update(relaySessionId, {
|
|
@@ -2381,6 +2778,7 @@ function createBackendCommand(backendId, registry2, sessionManager2, hooksEngine
|
|
|
2381
2778
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
2382
2779
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
2383
2780
|
process.exitCode = result.exitCode;
|
|
2781
|
+
nativeSessionId = result.nativeSessionId;
|
|
2384
2782
|
if (result.nativeSessionId && sessionManager2 && relaySessionId) {
|
|
2385
2783
|
try {
|
|
2386
2784
|
await sessionManager2.update(relaySessionId, {
|
package/package.json
CHANGED