llm-cli-gateway 1.4.0 → 1.5.13
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 +135 -1
- package/README.md +358 -15
- package/dist/approval-manager.d.ts +1 -1
- package/dist/async-job-manager.d.ts +32 -2
- package/dist/async-job-manager.js +101 -16
- package/dist/auth.d.ts +15 -0
- package/dist/auth.js +46 -0
- package/dist/cli-updater.d.ts +19 -2
- package/dist/cli-updater.js +110 -7
- package/dist/codex-json-parser.d.ts +34 -0
- package/dist/codex-json-parser.js +105 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +167 -0
- package/dist/doctor.d.ts +110 -0
- package/dist/doctor.js +280 -0
- package/dist/endpoint-exposure.d.ts +22 -0
- package/dist/endpoint-exposure.js +231 -0
- package/dist/entrypoint-url.d.ts +1 -0
- package/dist/entrypoint-url.js +5 -0
- package/dist/executor.d.ts +9 -1
- package/dist/executor.js +52 -17
- package/dist/flight-recorder.d.ts +3 -1
- package/dist/flight-recorder.js +31 -2
- package/dist/gateway-server.d.ts +2 -0
- package/dist/gateway-server.js +1 -0
- package/dist/gemini-json-parser.d.ts +21 -0
- package/dist/gemini-json-parser.js +47 -0
- package/dist/health.d.ts +7 -0
- package/dist/health.js +22 -0
- package/dist/http-transport.d.ts +22 -0
- package/dist/http-transport.js +164 -0
- package/dist/index.d.ts +186 -2
- package/dist/index.js +2761 -1454
- package/dist/job-store.d.ts +118 -2
- package/dist/job-store.js +176 -5
- package/dist/logger.d.ts +9 -0
- package/dist/logger.js +14 -0
- package/dist/model-registry.js +40 -6
- package/dist/provider-login-guidance.d.ts +21 -0
- package/dist/provider-login-guidance.js +98 -0
- package/dist/provider-status.d.ts +41 -0
- package/dist/provider-status.js +203 -0
- package/dist/request-helpers.d.ts +484 -4
- package/dist/request-helpers.js +613 -0
- package/dist/resources.js +44 -0
- package/dist/session-manager-pg.js +1 -0
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.js +2 -1
- package/dist/upstream-contracts.d.ts +62 -0
- package/dist/upstream-contracts.js +620 -0
- package/dist/validation-normalizer.d.ts +23 -0
- package/dist/validation-normalizer.js +79 -0
- package/dist/validation-orchestrator.d.ts +47 -0
- package/dist/validation-orchestrator.js +145 -0
- package/dist/validation-prompts.d.ts +15 -0
- package/dist/validation-prompts.js +52 -0
- package/dist/validation-report.d.ts +57 -0
- package/dist/validation-report.js +129 -0
- package/dist/validation-tools.d.ts +7 -0
- package/dist/validation-tools.js +198 -0
- package/package.json +25 -10
- package/setup/status.schema.json +271 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { getProviderLoginGuidance } from "./provider-login-guidance.js";
|
|
6
|
+
const PROVIDERS = ["claude", "codex", "gemini", "grok", "mistral"];
|
|
7
|
+
const VERSION_ARGS = {
|
|
8
|
+
claude: ["--version"],
|
|
9
|
+
codex: ["--version"],
|
|
10
|
+
gemini: ["--version"],
|
|
11
|
+
grok: ["--version"],
|
|
12
|
+
mistral: ["--version"],
|
|
13
|
+
};
|
|
14
|
+
// Mistral Vibe ships as the `vibe` binary (PyPI package vibe-cli); the gateway
|
|
15
|
+
// uses `mistral` as the provider key but invokes `vibe` on the shell.
|
|
16
|
+
export const PROVIDER_COMMANDS = {
|
|
17
|
+
claude: "claude",
|
|
18
|
+
codex: "codex",
|
|
19
|
+
gemini: "gemini",
|
|
20
|
+
grok: "grok",
|
|
21
|
+
mistral: "vibe",
|
|
22
|
+
};
|
|
23
|
+
const LOGIN_CHECKS = {
|
|
24
|
+
claude: ["auth", "status", "--json"],
|
|
25
|
+
codex: ["login", "status"],
|
|
26
|
+
grok: ["inspect", "--json"],
|
|
27
|
+
mistral: ["auth", "status"],
|
|
28
|
+
};
|
|
29
|
+
export function listProviderRuntimeStatuses() {
|
|
30
|
+
return Object.fromEntries(PROVIDERS.map(provider => [provider, getProviderRuntimeStatus(provider)]));
|
|
31
|
+
}
|
|
32
|
+
export function getProviderRuntimeStatus(provider) {
|
|
33
|
+
const guidance = getProviderLoginGuidance(provider);
|
|
34
|
+
const command = PROVIDER_COMMANDS[provider];
|
|
35
|
+
const version = runCommand(command, VERSION_ARGS[provider], 5_000);
|
|
36
|
+
const installed = version.exitCode === 0 || Boolean(version.output);
|
|
37
|
+
const versionText = installed ? firstLine(version.output) : null;
|
|
38
|
+
const base = {
|
|
39
|
+
provider,
|
|
40
|
+
displayName: guidance.displayName,
|
|
41
|
+
command,
|
|
42
|
+
installed,
|
|
43
|
+
version: versionText,
|
|
44
|
+
versionCommand: [command, ...VERSION_ARGS[provider]],
|
|
45
|
+
loginStatus: installed ? "unknown" : "not_checked",
|
|
46
|
+
loginCheck: {
|
|
47
|
+
method: installed ? "not_checked" : "not_checked",
|
|
48
|
+
command: null,
|
|
49
|
+
credentialStore: "not_checked",
|
|
50
|
+
detail: installed
|
|
51
|
+
? "No safe non-interactive login check is available."
|
|
52
|
+
: "Runtime is not installed.",
|
|
53
|
+
},
|
|
54
|
+
guidance,
|
|
55
|
+
};
|
|
56
|
+
if (!installed)
|
|
57
|
+
return base;
|
|
58
|
+
if (provider === "gemini") {
|
|
59
|
+
const auth = geminiAuthStatus();
|
|
60
|
+
const store = auth.status;
|
|
61
|
+
const matchedMethods = Object.entries(auth.methods)
|
|
62
|
+
.filter(([, v]) => v)
|
|
63
|
+
.map(([k]) => k);
|
|
64
|
+
return {
|
|
65
|
+
...base,
|
|
66
|
+
loginStatus: store === "present" ? "authenticated" : "unknown",
|
|
67
|
+
loginCheck: {
|
|
68
|
+
method: "credential_store",
|
|
69
|
+
command: null,
|
|
70
|
+
credentialStore: store,
|
|
71
|
+
detail: store === "present"
|
|
72
|
+
? `Gemini auth detected via: ${matchedMethods.join(", ")}; contents were not inspected.`
|
|
73
|
+
: "Gemini CLI is installed, but no credential store or auth env vars were found (oauth_creds.json, GEMINI_API_KEY, GOOGLE_API_KEY, or GOOGLE_CLOUD_PROJECT+GOOGLE_GENAI_USE_VERTEXAI).",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const args = LOGIN_CHECKS[provider];
|
|
78
|
+
if (!args)
|
|
79
|
+
return base;
|
|
80
|
+
const login = runCommand(command, args, 5_000);
|
|
81
|
+
const status = inferLoginStatus(provider, login.exitCode, login.output);
|
|
82
|
+
const credentialStore = provider === "grok"
|
|
83
|
+
? grokCredentialStoreStatus()
|
|
84
|
+
: provider === "mistral"
|
|
85
|
+
? mistralCredentialStoreStatus()
|
|
86
|
+
: "not_checked";
|
|
87
|
+
return {
|
|
88
|
+
...base,
|
|
89
|
+
loginStatus: status,
|
|
90
|
+
loginCheck: {
|
|
91
|
+
method: "cli",
|
|
92
|
+
command: [command, ...args],
|
|
93
|
+
credentialStore,
|
|
94
|
+
detail: loginCheckDetail(provider, status, login.exitCode),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function runCommand(command, args, timeoutMs) {
|
|
99
|
+
const result = spawnSync(command, args, {
|
|
100
|
+
encoding: "utf8",
|
|
101
|
+
input: "",
|
|
102
|
+
timeout: timeoutMs,
|
|
103
|
+
windowsHide: true,
|
|
104
|
+
});
|
|
105
|
+
const output = sanitizeOutput(`${result.stdout || ""}\n${result.stderr || ""}`);
|
|
106
|
+
return {
|
|
107
|
+
exitCode: typeof result.status === "number" ? result.status : null,
|
|
108
|
+
output,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function firstLine(text) {
|
|
112
|
+
return (text
|
|
113
|
+
.split(/\r?\n/)
|
|
114
|
+
.map(line => line.trim())
|
|
115
|
+
.find(Boolean) || null);
|
|
116
|
+
}
|
|
117
|
+
function inferLoginStatus(provider, exitCode, output) {
|
|
118
|
+
if (provider === "claude") {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(output);
|
|
121
|
+
if (parsed.loggedIn === true)
|
|
122
|
+
return "authenticated";
|
|
123
|
+
if (parsed.loggedIn === false)
|
|
124
|
+
return "not_authenticated";
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Fall through to text heuristics.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (/not\s+(logged|signed|authenticated)\s*in|unauthenticated|login required|not authorized/i.test(output)) {
|
|
131
|
+
return "not_authenticated";
|
|
132
|
+
}
|
|
133
|
+
if (/logged\s*in|signed\s*in|authenticated|authorized|using chatgpt|auth store/i.test(output)) {
|
|
134
|
+
return "authenticated";
|
|
135
|
+
}
|
|
136
|
+
if (provider === "grok" && grokCredentialStoreStatus() === "present") {
|
|
137
|
+
return "authenticated";
|
|
138
|
+
}
|
|
139
|
+
if (provider === "mistral" && mistralCredentialStoreStatus() === "present") {
|
|
140
|
+
return "authenticated";
|
|
141
|
+
}
|
|
142
|
+
if (exitCode && exitCode !== 0)
|
|
143
|
+
return "unknown";
|
|
144
|
+
return "unknown";
|
|
145
|
+
}
|
|
146
|
+
function loginCheckDetail(provider, status, exitCode) {
|
|
147
|
+
if (status === "authenticated")
|
|
148
|
+
return `${provider} login check indicates an authenticated local runtime.`;
|
|
149
|
+
if (status === "not_authenticated")
|
|
150
|
+
return `${provider} login check indicates the provider is not authenticated.`;
|
|
151
|
+
if (exitCode && exitCode !== 0)
|
|
152
|
+
return `${provider} login check exited non-zero without exposing credential material.`;
|
|
153
|
+
return `${provider} login check completed, but the output did not clearly indicate login state.`;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* U27: Detect Gemini auth across all supported methods.
|
|
157
|
+
* Returns "present" if ANY of:
|
|
158
|
+
* - OAuth credential file present (~/.gemini/oauth_creds.json, etc.)
|
|
159
|
+
* - GEMINI_API_KEY env var set and non-empty
|
|
160
|
+
* - GOOGLE_API_KEY env var set and non-empty
|
|
161
|
+
* - GOOGLE_CLOUD_PROJECT set AND GOOGLE_GENAI_USE_VERTEXAI=true
|
|
162
|
+
*/
|
|
163
|
+
export function geminiAuthStatus(env = process.env, home = homedir()) {
|
|
164
|
+
const candidates = [
|
|
165
|
+
join(home, ".gemini", "oauth_creds.json"),
|
|
166
|
+
join(home, ".gemini", "google_accounts.json"),
|
|
167
|
+
join(home, ".config", "gemini", "oauth_creds.json"),
|
|
168
|
+
];
|
|
169
|
+
const oauth = candidates.some(p => existsSync(p));
|
|
170
|
+
const geminiApiKey = Boolean(env.GEMINI_API_KEY && env.GEMINI_API_KEY.length > 0);
|
|
171
|
+
const googleApiKey = Boolean(env.GOOGLE_API_KEY && env.GOOGLE_API_KEY.length > 0);
|
|
172
|
+
const vertexAi = Boolean(env.GOOGLE_CLOUD_PROJECT &&
|
|
173
|
+
env.GOOGLE_CLOUD_PROJECT.length > 0 &&
|
|
174
|
+
env.GOOGLE_GENAI_USE_VERTEXAI === "true");
|
|
175
|
+
const methods = { oauth, geminiApiKey, googleApiKey, vertexAi };
|
|
176
|
+
const status = oauth || geminiApiKey || googleApiKey || vertexAi ? "present" : "not_found";
|
|
177
|
+
return { status, methods };
|
|
178
|
+
}
|
|
179
|
+
/** Back-compat shim retained for callers that only need the binary store status. */
|
|
180
|
+
function geminiCredentialStoreStatus() {
|
|
181
|
+
return geminiAuthStatus().status;
|
|
182
|
+
}
|
|
183
|
+
function grokCredentialStoreStatus() {
|
|
184
|
+
const home = homedir();
|
|
185
|
+
const candidates = [join(home, ".grok", "auth.json"), join(home, ".config", "grok", "auth.json")];
|
|
186
|
+
return candidates.some(path => existsSync(path)) ? "present" : "not_found";
|
|
187
|
+
}
|
|
188
|
+
function mistralCredentialStoreStatus() {
|
|
189
|
+
const home = homedir();
|
|
190
|
+
const candidates = [
|
|
191
|
+
join(home, ".vibe", "credentials.json"),
|
|
192
|
+
join(home, ".vibe", "auth.json"),
|
|
193
|
+
join(home, ".config", "vibe", "credentials.json"),
|
|
194
|
+
];
|
|
195
|
+
return candidates.some(path => existsSync(path)) ? "present" : "not_found";
|
|
196
|
+
}
|
|
197
|
+
function sanitizeOutput(output) {
|
|
198
|
+
return output
|
|
199
|
+
.replace(/([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})/gi, "<redacted-email>")
|
|
200
|
+
.replace(/\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b/gi, "<redacted-id>")
|
|
201
|
+
.replace(/((?:token|secret|credential|password|authorization|api[_-]?key|access[_-]?key)[=:]\s*)\S+/gi, "$1<redacted>")
|
|
202
|
+
.trim();
|
|
203
|
+
}
|