llm-cli-gateway 1.5.24 → 1.5.26

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to the llm-cli-gateway project.
4
4
 
5
+ ## [1.5.26] - 2026-05-25
6
+
7
+ ### Fixed
8
+
9
+ - Make `upstream_contracts --probe-installed` use the same extended provider PATH and Windows shim resolver as request execution and `doctor --json`, avoiding false `ENOENT` diagnostics for npm-installed CLIs such as Gemini.
10
+
11
+ ## [1.5.25] - 2026-05-25
12
+
13
+ ### Fixed
14
+
15
+ - Stop passing unsupported Gemini `--session-id` arguments for fresh or `createNewSession` requests. The gateway now lets Gemini CLI create fresh sessions with its own default behavior and only emits `--resume` for explicit resume requests, fixing Gemini CLI 0.43 exit-code-1 failures misreported as spawn errors.
16
+
5
17
  ## [1.5.24] - 2026-05-25
6
18
 
7
19
  ### Fixed
package/dist/index.js CHANGED
@@ -1253,9 +1253,8 @@ export async function handleGeminiRequest(deps, params) {
1253
1253
  }, runtime);
1254
1254
  deps.logger.info(`[${corrId}] gemini_request invoked with model=${prep.resolvedModel || "default"}, approvalMode=${params.approvalMode}, prompt length=${params.prompt.length}`);
1255
1255
  try {
1256
- // U27: Session arg planning. For fresh sessions, emit `--session-id <uuid>`
1257
- // so the gateway and Gemini agree on the session identifier from turn 1.
1258
- // For resume flows, fall back to `--resume <id>` (existing behavior).
1256
+ // Gemini CLI 0.43 supports `--resume`, but not a supported fresh
1257
+ // `--session-id` flag. Fresh sessions emit no session flag.
1259
1258
  const sessionPlan = resolveGeminiSessionPlan({
1260
1259
  sessionId: params.sessionId,
1261
1260
  resumeLatest: params.resumeLatest,
@@ -1263,7 +1262,7 @@ export async function handleGeminiRequest(deps, params) {
1263
1262
  });
1264
1263
  args.push(...sessionPlan.args);
1265
1264
  const userProvidedSession = sessionPlan.resumed;
1266
- const effectiveSessionIdHint = sessionPlan.emittedSessionId ?? params.sessionId;
1265
+ const effectiveSessionIdHint = sessionPlan.resumed ? params.sessionId : undefined;
1267
1266
  const result = await awaitJobOrDefer("gemini", args, corrId, resolveIdleTimeout("gemini", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime);
1268
1267
  // Deferred — job still running, return async reference
1269
1268
  if (isDeferredResponse(result)) {
@@ -1286,9 +1285,9 @@ export async function handleGeminiRequest(deps, params) {
1286
1285
  return createErrorResponse("gemini", code, stderr, corrId);
1287
1286
  }
1288
1287
  wasSuccessful = true;
1289
- // U27 Post-success session I/O. Mirror the gateway store 1:1 to whatever
1290
- // session id Gemini is using (either the user-supplied resume id or the
1291
- // deterministic --session-id we emitted).
1288
+ // Post-success session I/O for explicit resume flows. Fresh Gemini sessions
1289
+ // are owned by the CLI because the current CLI has no supported fresh
1290
+ // session-id flag the gateway can inject.
1292
1291
  let effectiveSessionId = effectiveSessionIdHint;
1293
1292
  if (effectiveSessionId) {
1294
1293
  const existing = await deps.sessionManager.getSession(effectiveSessionId);
@@ -1368,7 +1367,7 @@ export async function handleGeminiRequestAsync(deps, params) {
1368
1367
  return prep;
1369
1368
  const { corrId, args, requestedMcpServers, approvalDecision } = prep;
1370
1369
  try {
1371
- // U27: Session arg planning with deterministic --session-id for fresh sessions.
1370
+ // Gemini CLI 0.43 supports `--resume`, but fresh sessions emit no session flag.
1372
1371
  const sessionPlan = resolveGeminiSessionPlan({
1373
1372
  sessionId: params.sessionId,
1374
1373
  resumeLatest: params.resumeLatest,
@@ -1376,7 +1375,7 @@ export async function handleGeminiRequestAsync(deps, params) {
1376
1375
  });
1377
1376
  args.push(...sessionPlan.args);
1378
1377
  // Pre-start session I/O (async handlers: prevent orphaned jobs)
1379
- let effectiveSessionId = sessionPlan.emittedSessionId ?? params.sessionId;
1378
+ let effectiveSessionId = sessionPlan.resumed ? params.sessionId : undefined;
1380
1379
  if (effectiveSessionId) {
1381
1380
  const existing = await deps.sessionManager.getSession(effectiveSessionId);
1382
1381
  if (!existing) {
@@ -457,14 +457,6 @@ export interface CodexForkRequestInput {
457
457
  export declare function prepareCodexForkRequest(input: CodexForkRequestInput): {
458
458
  args: string[];
459
459
  };
460
- /**
461
- * Strict UUID v4 regex. Gemini's CLI is reportedly stricter about session id
462
- * shape than the gateway's internal handles, so caller-supplied IDs (and IDs
463
- * generated by `crypto.randomUUID()`) are validated against this regex before
464
- * being emitted as `--session-id <uuid>`.
465
- */
466
- export declare const GEMINI_SESSION_ID_REGEX: RegExp;
467
- export declare function isValidGeminiSessionId(id: string): boolean;
468
460
  /**
469
461
  * Prepend `@<abs-path>` tokens to a Gemini prompt so the CLI's attachment
470
462
  * resolver picks them up. Each path MUST be absolute and exist on disk.
@@ -520,34 +512,21 @@ export interface GeminiHighImpactFlagsResult {
520
512
  */
521
513
  export declare function prepareGeminiHighImpactFlags(input: GeminiHighImpactFlagsInput): GeminiHighImpactFlagsResult;
522
514
  /**
523
- * Result of resolving Gemini's session-id emission strategy.
524
- *
525
- * U27 introduces deterministic `--session-id <uuid>` emission for fresh
526
- * sessions, mapping the gateway-side session ID 1:1 to Gemini's authoritative
527
- * store. The existing `--resume <id>` flow is preserved for user-supplied
528
- * session IDs.
515
+ * Result of resolving Gemini's session strategy.
529
516
  */
530
517
  export interface GeminiSessionPlan {
531
- /** Flag pair to inject into argv (one of `["--session-id", uuid]`, `["--resume", id]`, `["--resume", "latest"]`, or `[]`). */
518
+ /** Flag pair to inject into argv (one of `["--resume", id]`, `["--resume", "latest"]`, or `[]`). */
532
519
  args: string[];
533
- /** The UUID emitted via `--session-id`, if any. Gateway should persist this. */
534
- emittedSessionId?: string;
535
520
  /** True iff `--resume <id>` was emitted with a user-supplied id. */
536
521
  resumed: boolean;
537
522
  }
538
523
  /**
539
- * Resolve Gemini session-id args. When a fresh session is being established
540
- * (either `createNewSession: true`, or no sessionId/resumeLatest set), emit
541
- * `--session-id <uuid>` so the gateway and Gemini agree on the session
542
- * identifier from the first turn.
543
- *
544
- * Falls back to `--resume <id>` when the caller supplies a sessionId, and
545
- * `--resume latest` for `resumeLatest` (existing behavior preserved).
524
+ * Resolve Gemini session args. Gemini CLI 0.43 exposes `--resume` but not a
525
+ * supported `--session-id` flag for fresh sessions, so new-session requests
526
+ * intentionally emit no session flag and let the CLI create its own session.
546
527
  */
547
528
  export declare function resolveGeminiSessionPlan(opts: {
548
529
  sessionId?: string;
549
530
  resumeLatest?: boolean;
550
531
  createNewSession?: boolean;
551
- /** Override generator for deterministic tests. Must produce a v4 UUID. */
552
- generateId?: () => string;
553
532
  }): GeminiSessionPlan;
@@ -580,16 +580,6 @@ export function prepareCodexForkRequest(input) {
580
580
  //──────────────────────────────────────────────────────────────────────────────
581
581
  // U27: Gemini high-impact features
582
582
  //──────────────────────────────────────────────────────────────────────────────
583
- /**
584
- * Strict UUID v4 regex. Gemini's CLI is reportedly stricter about session id
585
- * shape than the gateway's internal handles, so caller-supplied IDs (and IDs
586
- * generated by `crypto.randomUUID()`) are validated against this regex before
587
- * being emitted as `--session-id <uuid>`.
588
- */
589
- export const GEMINI_SESSION_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
590
- export function isValidGeminiSessionId(id) {
591
- return GEMINI_SESSION_ID_REGEX.test(id);
592
- }
593
583
  /**
594
584
  * Prepend `@<abs-path>` tokens to a Gemini prompt so the CLI's attachment
595
585
  * resolver picks them up. Each path MUST be absolute and exist on disk.
@@ -674,16 +664,11 @@ export function prepareGeminiHighImpactFlags(input) {
674
664
  return { args, missingPolicyPath: null, missingPolicyField: null };
675
665
  }
676
666
  /**
677
- * Resolve Gemini session-id args. When a fresh session is being established
678
- * (either `createNewSession: true`, or no sessionId/resumeLatest set), emit
679
- * `--session-id <uuid>` so the gateway and Gemini agree on the session
680
- * identifier from the first turn.
681
- *
682
- * Falls back to `--resume <id>` when the caller supplies a sessionId, and
683
- * `--resume latest` for `resumeLatest` (existing behavior preserved).
667
+ * Resolve Gemini session args. Gemini CLI 0.43 exposes `--resume` but not a
668
+ * supported `--session-id` flag for fresh sessions, so new-session requests
669
+ * intentionally emit no session flag and let the CLI create its own session.
684
670
  */
685
671
  export function resolveGeminiSessionPlan(opts) {
686
- const gen = opts.generateId ?? randomUUID;
687
672
  if (opts.sessionId && !opts.createNewSession) {
688
673
  validateSessionId(opts.sessionId);
689
674
  return {
@@ -694,14 +679,5 @@ export function resolveGeminiSessionPlan(opts) {
694
679
  if (opts.resumeLatest && !opts.createNewSession) {
695
680
  return { args: ["--resume", "latest"], resumed: false };
696
681
  }
697
- // Fresh session emit deterministic --session-id
698
- const candidate = gen();
699
- if (!isValidGeminiSessionId(candidate)) {
700
- throw new Error(`Generated session id "${candidate}" does not match Gemini's UUID v4 format`);
701
- }
702
- return {
703
- args: ["--session-id", candidate],
704
- emittedSessionId: candidate,
705
- resumed: false,
706
- };
682
+ return { args: [], resumed: false };
707
683
  }
@@ -50,6 +50,8 @@ export declare function assertUpstreamCliEnv(cli: CliType, env: Record<string, s
50
50
  export interface InstalledCliContractProbe {
51
51
  cli: CliType;
52
52
  executable: string;
53
+ resolvedCommand?: string;
54
+ resolvedArgs?: string[];
53
55
  available: boolean;
54
56
  checkedHelpCommands: string[][];
55
57
  missingFlags: string[];
@@ -1,4 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { envWithExtendedPath, getExtendedPath, resolveCommandForSpawn } from "./executor.js";
2
3
  const PERMISSION_MODES = [
3
4
  "default",
4
5
  "acceptEdits",
@@ -234,7 +235,6 @@ export const UPSTREAM_CLI_CONTRACTS = {
234
235
  "--policy": { arity: "one", description: "Policy file path" },
235
236
  "--admin-policy": { arity: "one", description: "Admin policy file path" },
236
237
  "-o": { arity: "one", values: ["json"], description: "Output format" },
237
- "--session-id": { arity: "one", description: "Fresh session UUID" },
238
238
  "--resume": { arity: "one", description: "Resume session" },
239
239
  },
240
240
  env: {},
@@ -535,16 +535,29 @@ export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
535
535
  const contract = UPSTREAM_CLI_CONTRACTS[cli];
536
536
  const outputs = [];
537
537
  const warnings = [];
538
+ let resolvedCommand;
539
+ let resolvedArgs;
538
540
  for (const helpArgs of contract.helpArgs) {
539
- const result = spawnSync(contract.executable, helpArgs, {
541
+ const extendedPath = getExtendedPath();
542
+ const env = envWithExtendedPath(process.env, extendedPath);
543
+ const resolved = resolveCommandForSpawn(contract.executable, helpArgs, {
544
+ envPath: extendedPath,
545
+ });
546
+ resolvedCommand ??= resolved.command;
547
+ resolvedArgs ??= resolved.args;
548
+ const result = spawnSync(resolved.command, resolved.args, {
540
549
  encoding: "utf8",
541
550
  timeout: timeoutMs,
542
551
  maxBuffer: 1024 * 1024,
552
+ env,
553
+ windowsHide: true,
543
554
  });
544
555
  if (result.error) {
545
556
  return {
546
557
  cli,
547
558
  executable: contract.executable,
559
+ resolvedCommand: resolved.command,
560
+ resolvedArgs: resolved.args,
548
561
  available: false,
549
562
  checkedHelpCommands: contract.helpArgs,
550
563
  missingFlags: [],
@@ -561,6 +574,8 @@ export function probeInstalledCliContract(cli, timeoutMs = 5_000) {
561
574
  return {
562
575
  cli,
563
576
  executable: contract.executable,
577
+ resolvedCommand,
578
+ resolvedArgs,
564
579
  available: true,
565
580
  checkedHelpCommands: contract.helpArgs,
566
581
  missingFlags,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "1.5.24",
3
+ "version": "1.5.26",
4
4
  "mcpName": "io.github.verivus-oss/llm-cli-gateway",
5
5
  "description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, and Mistral Vibe CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
6
6
  "license": "MIT",