llm-cli-gateway 2.12.1 → 2.13.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/CHANGELOG.md +46 -0
- package/README.md +177 -19
- package/dist/acp/provider-registry.js +13 -0
- package/dist/api-provider.d.ts +1 -0
- package/dist/api-provider.js +13 -0
- package/dist/approval-manager.d.ts +1 -1
- package/dist/async-job-manager.d.ts +35 -3
- package/dist/async-job-manager.js +344 -46
- package/dist/cli-updater.d.ts +1 -1
- package/dist/cli-updater.js +17 -9
- package/dist/config.d.ts +34 -0
- package/dist/config.js +93 -8
- package/dist/doctor.d.ts +32 -4
- package/dist/doctor.js +80 -13
- package/dist/endpoint-exposure.js +15 -3
- package/dist/executor.js +2 -0
- package/dist/http-transport.d.ts +3 -0
- package/dist/http-transport.js +138 -8
- package/dist/index.d.ts +62 -5
- package/dist/index.js +774 -83
- package/dist/model-registry.d.ts +1 -1
- package/dist/model-registry.js +12 -16
- package/dist/provider-login-guidance.d.ts +12 -0
- package/dist/provider-login-guidance.js +46 -0
- package/dist/provider-status.d.ts +13 -1
- package/dist/provider-status.js +22 -13
- package/dist/provider-tool-capabilities.d.ts +4 -1
- package/dist/provider-tool-capabilities.js +310 -11
- package/dist/provider-types.d.ts +4 -0
- package/dist/provider-types.js +10 -0
- package/dist/resources.d.ts +6 -2
- package/dist/resources.js +72 -8
- package/dist/session-manager.d.ts +3 -5
- package/dist/session-manager.js +4 -2
- package/dist/upstream-contracts.js +169 -0
- package/dist/validation-normalizer.js +5 -4
- package/dist/validation-orchestrator.js +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/setup/status.schema.json +68 -3
package/dist/config.d.ts
CHANGED
|
@@ -36,6 +36,38 @@ export interface PersistenceConfigSources {
|
|
|
36
36
|
}
|
|
37
37
|
export declare function defaultGatewayConfigPath(): string;
|
|
38
38
|
export declare function loadPersistenceConfig(logger?: Logger): PersistenceConfig;
|
|
39
|
+
export declare const DEFAULT_HTTP_MAX_SESSIONS = 100;
|
|
40
|
+
export declare const DEFAULT_HTTP_SESSION_IDLE_TTL_MS: number;
|
|
41
|
+
export declare const DEFAULT_HTTP_SESSION_REAPER_INTERVAL_MS: number;
|
|
42
|
+
export declare const DEFAULT_MAX_RUNNING_JOBS = 32;
|
|
43
|
+
export declare const DEFAULT_MAX_RUNNING_JOBS_PER_PROVIDER = 16;
|
|
44
|
+
export declare const DEFAULT_MAX_QUEUED_JOBS = 128;
|
|
45
|
+
export declare const DEFAULT_QUEUE_TIMEOUT_MS = 120000;
|
|
46
|
+
export declare const DEFAULT_COMPLETED_JOB_MEMORY_TTL_MS: number;
|
|
47
|
+
export declare const DEFAULT_MAX_JOB_OUTPUT_BYTES: number;
|
|
48
|
+
export interface HttpSessionLimitsConfig {
|
|
49
|
+
maxSessions: number;
|
|
50
|
+
sessionIdleTtlMs: number;
|
|
51
|
+
sessionReaperIntervalMs: number;
|
|
52
|
+
}
|
|
53
|
+
export interface JobLimitsConfig {
|
|
54
|
+
maxRunningJobs: number;
|
|
55
|
+
maxRunningJobsPerProvider: number;
|
|
56
|
+
maxQueuedJobs: number;
|
|
57
|
+
queueTimeoutMs: number;
|
|
58
|
+
completedJobMemoryTtlMs: number;
|
|
59
|
+
maxJobOutputBytes: number;
|
|
60
|
+
}
|
|
61
|
+
export interface GatewayLimitsConfig {
|
|
62
|
+
http: HttpSessionLimitsConfig;
|
|
63
|
+
jobs: JobLimitsConfig;
|
|
64
|
+
sources: {
|
|
65
|
+
configFile: string | null;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export declare const DEFAULT_HTTP_SESSION_LIMITS: HttpSessionLimitsConfig;
|
|
69
|
+
export declare const DEFAULT_JOB_LIMITS: JobLimitsConfig;
|
|
70
|
+
export declare function loadLimitsConfig(logger?: Logger): GatewayLimitsConfig;
|
|
39
71
|
export declare const ANTHROPIC_TTL_SECONDS_VALUES: readonly [300, 3600];
|
|
40
72
|
export type AnthropicTtlSeconds = (typeof ANTHROPIC_TTL_SECONDS_VALUES)[number];
|
|
41
73
|
export declare const DEFAULT_MIN_STABLE_TOKENS_FOR_CACHE_CONTROL: {
|
|
@@ -81,6 +113,7 @@ export interface ApiProviderConfig {
|
|
|
81
113
|
export interface ApiProviderRuntime {
|
|
82
114
|
name: string;
|
|
83
115
|
kind: ApiProviderKind;
|
|
116
|
+
apiKeyEnv: string | null;
|
|
84
117
|
baseUrl: string;
|
|
85
118
|
defaultModel: string;
|
|
86
119
|
models?: string[];
|
|
@@ -96,6 +129,7 @@ export interface ProvidersConfig {
|
|
|
96
129
|
}
|
|
97
130
|
export declare function loadProvidersConfig(logger?: Logger): ProvidersConfig;
|
|
98
131
|
export declare function isApiProviderEnabled(provider: ApiProviderConfig, env?: NodeJS.ProcessEnv): boolean;
|
|
132
|
+
export declare function apiProviderKeyPresent(provider: ApiProviderConfig, env?: NodeJS.ProcessEnv): boolean;
|
|
99
133
|
export declare function enabledApiProviders(config: ProvidersConfig, env?: NodeJS.ProcessEnv): ApiProviderRuntime[];
|
|
100
134
|
export declare function isXaiProviderEnabled(config: ProvidersConfig, env?: NodeJS.ProcessEnv): boolean;
|
|
101
135
|
export declare const ACP_TRANSPORTS: readonly ["cli", "acp"];
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@ import { z } from "zod/v3";
|
|
|
6
6
|
import { logWarn, noopLogger } from "./logger.js";
|
|
7
7
|
import { hashSecret, isSecretHash } from "./oauth.js";
|
|
8
8
|
import { isHttpsOrLoopbackUrl, isLoopbackUrl } from "./api-http.js";
|
|
9
|
+
import { CLI_TYPES } from "./provider-types.js";
|
|
9
10
|
const DatabaseUrlSchema = z
|
|
10
11
|
.string()
|
|
11
12
|
.url()
|
|
@@ -185,6 +186,93 @@ export function loadPersistenceConfig(logger = noopLogger) {
|
|
|
185
186
|
sources,
|
|
186
187
|
};
|
|
187
188
|
}
|
|
189
|
+
export const DEFAULT_HTTP_MAX_SESSIONS = 100;
|
|
190
|
+
export const DEFAULT_HTTP_SESSION_IDLE_TTL_MS = 30 * 60 * 1000;
|
|
191
|
+
export const DEFAULT_HTTP_SESSION_REAPER_INTERVAL_MS = 60 * 1000;
|
|
192
|
+
export const DEFAULT_MAX_RUNNING_JOBS = 32;
|
|
193
|
+
export const DEFAULT_MAX_RUNNING_JOBS_PER_PROVIDER = 16;
|
|
194
|
+
export const DEFAULT_MAX_QUEUED_JOBS = 128;
|
|
195
|
+
export const DEFAULT_QUEUE_TIMEOUT_MS = 120000;
|
|
196
|
+
export const DEFAULT_COMPLETED_JOB_MEMORY_TTL_MS = 60 * 60 * 1000;
|
|
197
|
+
export const DEFAULT_MAX_JOB_OUTPUT_BYTES = 50 * 1024 * 1024;
|
|
198
|
+
const HttpSessionLimitsSchema = z
|
|
199
|
+
.object({
|
|
200
|
+
max_sessions: z.number().int().positive().default(DEFAULT_HTTP_MAX_SESSIONS),
|
|
201
|
+
session_idle_ttl_ms: z.number().int().positive().default(DEFAULT_HTTP_SESSION_IDLE_TTL_MS),
|
|
202
|
+
session_reaper_interval_ms: z
|
|
203
|
+
.number()
|
|
204
|
+
.int()
|
|
205
|
+
.positive()
|
|
206
|
+
.default(DEFAULT_HTTP_SESSION_REAPER_INTERVAL_MS),
|
|
207
|
+
})
|
|
208
|
+
.passthrough();
|
|
209
|
+
const JobLimitsSchema = z
|
|
210
|
+
.object({
|
|
211
|
+
max_running_jobs: z.number().int().positive().default(DEFAULT_MAX_RUNNING_JOBS),
|
|
212
|
+
max_running_jobs_per_provider: z
|
|
213
|
+
.number()
|
|
214
|
+
.int()
|
|
215
|
+
.positive()
|
|
216
|
+
.default(DEFAULT_MAX_RUNNING_JOBS_PER_PROVIDER),
|
|
217
|
+
max_queued_jobs: z.number().int().positive().default(DEFAULT_MAX_QUEUED_JOBS),
|
|
218
|
+
queue_timeout_ms: z.number().int().positive().default(DEFAULT_QUEUE_TIMEOUT_MS),
|
|
219
|
+
completed_job_memory_ttl_ms: z
|
|
220
|
+
.number()
|
|
221
|
+
.int()
|
|
222
|
+
.positive()
|
|
223
|
+
.default(DEFAULT_COMPLETED_JOB_MEMORY_TTL_MS),
|
|
224
|
+
max_job_output_bytes: z.number().int().positive().default(DEFAULT_MAX_JOB_OUTPUT_BYTES),
|
|
225
|
+
})
|
|
226
|
+
.strict();
|
|
227
|
+
export const DEFAULT_HTTP_SESSION_LIMITS = {
|
|
228
|
+
maxSessions: DEFAULT_HTTP_MAX_SESSIONS,
|
|
229
|
+
sessionIdleTtlMs: DEFAULT_HTTP_SESSION_IDLE_TTL_MS,
|
|
230
|
+
sessionReaperIntervalMs: DEFAULT_HTTP_SESSION_REAPER_INTERVAL_MS,
|
|
231
|
+
};
|
|
232
|
+
export const DEFAULT_JOB_LIMITS = {
|
|
233
|
+
maxRunningJobs: DEFAULT_MAX_RUNNING_JOBS,
|
|
234
|
+
maxRunningJobsPerProvider: DEFAULT_MAX_RUNNING_JOBS_PER_PROVIDER,
|
|
235
|
+
maxQueuedJobs: DEFAULT_MAX_QUEUED_JOBS,
|
|
236
|
+
queueTimeoutMs: DEFAULT_QUEUE_TIMEOUT_MS,
|
|
237
|
+
completedJobMemoryTtlMs: DEFAULT_COMPLETED_JOB_MEMORY_TTL_MS,
|
|
238
|
+
maxJobOutputBytes: DEFAULT_MAX_JOB_OUTPUT_BYTES,
|
|
239
|
+
};
|
|
240
|
+
export function loadLimitsConfig(logger = noopLogger) {
|
|
241
|
+
const configPath = defaultGatewayConfigPath();
|
|
242
|
+
const { parsed, sourcePath } = readGatewayTomlFile(configPath, logger, "limits");
|
|
243
|
+
const rawHttp = parsed?.http ?? {};
|
|
244
|
+
const rawLimits = parsed?.limits ?? {};
|
|
245
|
+
let httpParsed;
|
|
246
|
+
try {
|
|
247
|
+
httpParsed = HttpSessionLimitsSchema.parse(rawHttp);
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
throw new Error(`Invalid [http] session-limit config: ${err instanceof Error ? err.message : String(err)}`);
|
|
251
|
+
}
|
|
252
|
+
let limitsParsed;
|
|
253
|
+
try {
|
|
254
|
+
limitsParsed = JobLimitsSchema.parse(rawLimits);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
throw new Error(`Invalid [limits] config: ${err instanceof Error ? err.message : String(err)}`);
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
http: {
|
|
261
|
+
maxSessions: httpParsed.max_sessions,
|
|
262
|
+
sessionIdleTtlMs: httpParsed.session_idle_ttl_ms,
|
|
263
|
+
sessionReaperIntervalMs: httpParsed.session_reaper_interval_ms,
|
|
264
|
+
},
|
|
265
|
+
jobs: {
|
|
266
|
+
maxRunningJobs: limitsParsed.max_running_jobs,
|
|
267
|
+
maxRunningJobsPerProvider: limitsParsed.max_running_jobs_per_provider,
|
|
268
|
+
maxQueuedJobs: limitsParsed.max_queued_jobs,
|
|
269
|
+
queueTimeoutMs: limitsParsed.queue_timeout_ms,
|
|
270
|
+
completedJobMemoryTtlMs: limitsParsed.completed_job_memory_ttl_ms,
|
|
271
|
+
maxJobOutputBytes: limitsParsed.max_job_output_bytes,
|
|
272
|
+
},
|
|
273
|
+
sources: { configFile: sourcePath },
|
|
274
|
+
};
|
|
275
|
+
}
|
|
188
276
|
export const ANTHROPIC_TTL_SECONDS_VALUES = [300, 3600];
|
|
189
277
|
export const DEFAULT_MIN_STABLE_TOKENS_FOR_CACHE_CONTROL = {
|
|
190
278
|
sonnet: 1024,
|
|
@@ -268,14 +356,7 @@ export function minStableTokensForModel(config, modelName) {
|
|
|
268
356
|
return table.haiku;
|
|
269
357
|
return table.default;
|
|
270
358
|
}
|
|
271
|
-
const RESERVED_CLI_PROVIDER_NAMES =
|
|
272
|
-
"claude",
|
|
273
|
-
"codex",
|
|
274
|
-
"gemini",
|
|
275
|
-
"grok",
|
|
276
|
-
"mistral",
|
|
277
|
-
"devin",
|
|
278
|
-
];
|
|
359
|
+
const RESERVED_CLI_PROVIDER_NAMES = CLI_TYPES;
|
|
279
360
|
export const DEFAULT_XAI_API_KEY_ENV = "XAI_API_KEY";
|
|
280
361
|
export const DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1";
|
|
281
362
|
export const DEFAULT_XAI_MODEL = "grok-build-0.1";
|
|
@@ -386,6 +467,9 @@ export function isApiProviderEnabled(provider, env = process.env) {
|
|
|
386
467
|
return true;
|
|
387
468
|
return provider.kind === "openai-compatible" && isLoopbackUrl(provider.baseUrl);
|
|
388
469
|
}
|
|
470
|
+
export function apiProviderKeyPresent(provider, env = process.env) {
|
|
471
|
+
return resolveProviderKey(provider.apiKeyEnv, env) !== null;
|
|
472
|
+
}
|
|
389
473
|
export function enabledApiProviders(config, env = process.env) {
|
|
390
474
|
const runtimes = [];
|
|
391
475
|
for (const provider of Object.values(config.providers ?? {})) {
|
|
@@ -394,6 +478,7 @@ export function enabledApiProviders(config, env = process.env) {
|
|
|
394
478
|
runtimes.push({
|
|
395
479
|
name: provider.name,
|
|
396
480
|
kind: provider.kind,
|
|
481
|
+
apiKeyEnv: provider.apiKeyEnv,
|
|
397
482
|
baseUrl: provider.baseUrl,
|
|
398
483
|
defaultModel: provider.defaultModel,
|
|
399
484
|
models: provider.models,
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type EndpointExposureReport } from "./endpoint-exposure.js";
|
|
2
2
|
import { type ProviderLoginStatus } from "./provider-status.js";
|
|
3
|
+
import { type ApiProviderLoginGuidance } from "./provider-login-guidance.js";
|
|
3
4
|
import type { FlightRecorderQuery } from "./flight-recorder.js";
|
|
4
|
-
import { type CacheAwarenessConfig } from "./config.js";
|
|
5
|
+
import { type CacheAwarenessConfig, type ProvidersConfig } from "./config.js";
|
|
5
6
|
import { type ProviderCapabilityId, type ProviderKind } from "./provider-tool-capabilities.js";
|
|
6
|
-
|
|
7
|
+
import { type CliType } from "./session-manager.js";
|
|
7
8
|
export interface CacheAwarenessReport {
|
|
8
9
|
enabled_features: Array<"anthropic_cache_control" | "ttl_warnings">;
|
|
9
10
|
last_24h: {
|
|
@@ -38,6 +39,22 @@ export interface ProviderCapabilitySummaryReport {
|
|
|
38
39
|
warnings: string[];
|
|
39
40
|
}>;
|
|
40
41
|
}
|
|
42
|
+
export interface ApiProviderHealthEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
kind: "openai-compatible" | "anthropic" | "xai-responses";
|
|
45
|
+
base_url: string;
|
|
46
|
+
default_model: string;
|
|
47
|
+
models: string[] | null;
|
|
48
|
+
api_key_env: string | null;
|
|
49
|
+
api_key_present: boolean;
|
|
50
|
+
reachable: boolean | null;
|
|
51
|
+
reachability_error?: string;
|
|
52
|
+
login_guidance: ApiProviderLoginGuidance;
|
|
53
|
+
}
|
|
54
|
+
export interface ApiProviderHealthReport {
|
|
55
|
+
enabled_count: number;
|
|
56
|
+
providers: Record<string, ApiProviderHealthEntry>;
|
|
57
|
+
}
|
|
41
58
|
export interface VibeSessionLoggingStatus {
|
|
42
59
|
config_path: string;
|
|
43
60
|
config_present: boolean;
|
|
@@ -106,7 +123,7 @@ export interface DoctorReport {
|
|
|
106
123
|
allowed_root_count: number;
|
|
107
124
|
gateway_app_dir_is_workspace: boolean;
|
|
108
125
|
};
|
|
109
|
-
providers: Record<
|
|
126
|
+
providers: Record<CliType, {
|
|
110
127
|
cli_available: boolean;
|
|
111
128
|
version: string | null;
|
|
112
129
|
login_status: ProviderLoginStatus;
|
|
@@ -138,6 +155,7 @@ export interface DoctorReport {
|
|
|
138
155
|
};
|
|
139
156
|
cache_awareness: CacheAwarenessReport;
|
|
140
157
|
provider_capabilities: ProviderCapabilitySummaryReport;
|
|
158
|
+
api_providers?: ApiProviderHealthReport;
|
|
141
159
|
upstream: {
|
|
142
160
|
note: string;
|
|
143
161
|
recommendation: string;
|
|
@@ -154,8 +172,18 @@ export interface CreateDoctorReportOptions {
|
|
|
154
172
|
flightRecorder?: FlightRecorderQuery;
|
|
155
173
|
cacheAwareness?: CacheAwarenessConfig;
|
|
156
174
|
probeUpstream?: boolean;
|
|
175
|
+
providersConfig?: ProvidersConfig;
|
|
176
|
+
apiReachability?: Record<string, {
|
|
177
|
+
reachable: boolean;
|
|
178
|
+
error?: string;
|
|
179
|
+
}>;
|
|
157
180
|
}
|
|
158
181
|
export declare function createDoctorReport(envOrOptions?: NodeJS.ProcessEnv | CreateDoctorReportOptions): DoctorReport;
|
|
159
182
|
export declare function printDoctorJson(opts?: {
|
|
160
183
|
probeUpstream?: boolean;
|
|
161
|
-
|
|
184
|
+
probeApiProviders?: boolean;
|
|
185
|
+
}): Promise<void>;
|
|
186
|
+
export declare function probeApiProviderReachability(baseUrl: string, timeoutMs?: number): Promise<{
|
|
187
|
+
reachable: boolean;
|
|
188
|
+
error?: string;
|
|
189
|
+
}>;
|
package/dist/doctor.js
CHANGED
|
@@ -4,14 +4,16 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { loadAuthConfig } from "./auth.js";
|
|
6
6
|
import { createEndpointExposureReport, redactDiagnosticUrl, } from "./endpoint-exposure.js";
|
|
7
|
-
import { listProviderRuntimeStatuses, } from "./provider-status.js";
|
|
7
|
+
import { getApiProviderStatus, listProviderRuntimeStatuses, } from "./provider-status.js";
|
|
8
|
+
import { getApiProviderLoginGuidance, } from "./provider-login-guidance.js";
|
|
8
9
|
import { CLAUDE_MCP_SERVER_NAMES } from "./claude-mcp-config.js";
|
|
9
|
-
import { loadCacheAwarenessConfig, loadRemoteOAuthConfig, } from "./config.js";
|
|
10
|
+
import { enabledApiProviders, loadCacheAwarenessConfig, loadProvidersConfig, loadRemoteOAuthConfig, } from "./config.js";
|
|
10
11
|
import { loadWorkspaceRegistry } from "./workspace-registry.js";
|
|
11
12
|
import { computeGlobalCacheStats } from "./cache-stats.js";
|
|
12
13
|
import { FlightRecorder, resolveFlightRecorderDbPath } from "./flight-recorder.js";
|
|
13
14
|
import { buildUpstreamContractReport } from "./upstream-contracts.js";
|
|
14
|
-
import { getProviderToolCapabilities,
|
|
15
|
+
import { getProviderToolCapabilities, knownProviderCapabilityIds, } from "./provider-tool-capabilities.js";
|
|
16
|
+
import { CLI_TYPES } from "./session-manager.js";
|
|
15
17
|
export function checkVibeSessionLogging(home = homedir()) {
|
|
16
18
|
const configPath = join(home, ".vibe", "config.toml");
|
|
17
19
|
if (!existsSync(configPath)) {
|
|
@@ -172,6 +174,34 @@ function chatGPTConnectorUrl(env, rawPublicUrl) {
|
|
|
172
174
|
return null;
|
|
173
175
|
return "<redacted>";
|
|
174
176
|
}
|
|
177
|
+
function buildApiProviderHealthReport(opts, env) {
|
|
178
|
+
const config = opts.providersConfig;
|
|
179
|
+
if (!config)
|
|
180
|
+
return undefined;
|
|
181
|
+
const providers = {};
|
|
182
|
+
for (const providerConfig of Object.values(config.providers)) {
|
|
183
|
+
const status = getApiProviderStatus(providerConfig, env);
|
|
184
|
+
if (!status.enabled)
|
|
185
|
+
continue;
|
|
186
|
+
const probe = opts.apiReachability?.[status.provider];
|
|
187
|
+
providers[status.provider] = {
|
|
188
|
+
name: status.provider,
|
|
189
|
+
kind: status.kind,
|
|
190
|
+
base_url: status.baseUrl,
|
|
191
|
+
default_model: status.defaultModel,
|
|
192
|
+
models: status.models,
|
|
193
|
+
api_key_env: status.apiKeyEnv,
|
|
194
|
+
api_key_present: status.apiKeyPresent,
|
|
195
|
+
reachable: probe ? probe.reachable : null,
|
|
196
|
+
...(probe?.error ? { reachability_error: probe.error } : {}),
|
|
197
|
+
login_guidance: getApiProviderLoginGuidance(providerConfig),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const names = Object.keys(providers);
|
|
201
|
+
if (names.length === 0)
|
|
202
|
+
return undefined;
|
|
203
|
+
return { enabled_count: names.length, providers };
|
|
204
|
+
}
|
|
175
205
|
function buildCacheAwarenessReport(opts) {
|
|
176
206
|
const enabled = [];
|
|
177
207
|
if (opts.cacheAwareness?.emitAnthropicCacheControl) {
|
|
@@ -234,7 +264,7 @@ function buildProviderCapabilitySummary(providerStatuses) {
|
|
|
234
264
|
includeUnsupported: true,
|
|
235
265
|
includePaths: false,
|
|
236
266
|
});
|
|
237
|
-
const providers = Object.fromEntries(
|
|
267
|
+
const providers = Object.fromEntries(knownProviderCapabilityIds().map(provider => {
|
|
238
268
|
const capability = capabilities[provider];
|
|
239
269
|
if (!capability) {
|
|
240
270
|
throw new Error(`Missing provider capability record for ${provider}`);
|
|
@@ -264,7 +294,7 @@ function buildProviderCapabilitySummary(providerStatuses) {
|
|
|
264
294
|
tool: "provider_tool_capabilities",
|
|
265
295
|
resources: {
|
|
266
296
|
catalog: "provider-tools://catalog",
|
|
267
|
-
providers: Object.fromEntries(
|
|
297
|
+
providers: Object.fromEntries(knownProviderCapabilityIds().map(provider => [provider, `provider-tools://${provider}`])),
|
|
268
298
|
},
|
|
269
299
|
cache_ttl_ms: 60_000,
|
|
270
300
|
providers,
|
|
@@ -350,13 +380,7 @@ export function createDoctorReport(envOrOptions = process.env) {
|
|
|
350
380
|
allowed_root_count: workspaceRegistry.allowedRoots.length,
|
|
351
381
|
gateway_app_dir_is_workspace: workspaceRegistry.repos.some(repo => repo.path === join(homedir(), ".llm-cli-gateway")),
|
|
352
382
|
},
|
|
353
|
-
providers:
|
|
354
|
-
claude: doctorProviderStatus(providerStatuses.claude),
|
|
355
|
-
codex: doctorProviderStatus(providerStatuses.codex),
|
|
356
|
-
gemini: doctorProviderStatus(providerStatuses.gemini),
|
|
357
|
-
grok: doctorProviderStatus(providerStatuses.grok),
|
|
358
|
-
mistral: doctorProviderStatus(providerStatuses.mistral),
|
|
359
|
-
},
|
|
383
|
+
providers: Object.fromEntries(CLI_TYPES.map(provider => [provider, doctorProviderStatus(providerStatuses[provider])])),
|
|
360
384
|
endpoint_exposure: endpointExposure,
|
|
361
385
|
client_config: clientConfigStatus(),
|
|
362
386
|
cache_awareness: buildCacheAwarenessReport(opts),
|
|
@@ -364,6 +388,10 @@ export function createDoctorReport(envOrOptions = process.env) {
|
|
|
364
388
|
upstream,
|
|
365
389
|
next_actions: [],
|
|
366
390
|
};
|
|
391
|
+
const apiProviders = buildApiProviderHealthReport(opts, env);
|
|
392
|
+
if (apiProviders) {
|
|
393
|
+
report.api_providers = apiProviders;
|
|
394
|
+
}
|
|
367
395
|
if (transport === "http" && auth.required && !auth.tokenConfigured) {
|
|
368
396
|
report.ok = false;
|
|
369
397
|
report.next_actions.push("Set LLM_GATEWAY_AUTH_TOKEN before starting HTTP transport.");
|
|
@@ -402,9 +430,10 @@ export function createDoctorReport(envOrOptions = process.env) {
|
|
|
402
430
|
}
|
|
403
431
|
return report;
|
|
404
432
|
}
|
|
405
|
-
export function printDoctorJson(opts = {}) {
|
|
433
|
+
export async function printDoctorJson(opts = {}) {
|
|
406
434
|
let cacheAwareness;
|
|
407
435
|
let flightRecorder;
|
|
436
|
+
let providersConfig;
|
|
408
437
|
try {
|
|
409
438
|
cacheAwareness = loadCacheAwarenessConfig();
|
|
410
439
|
}
|
|
@@ -417,11 +446,25 @@ export function printDoctorJson(opts = {}) {
|
|
|
417
446
|
}
|
|
418
447
|
catch {
|
|
419
448
|
}
|
|
449
|
+
try {
|
|
450
|
+
providersConfig = loadProvidersConfig();
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
}
|
|
454
|
+
let apiReachability;
|
|
455
|
+
if (opts.probeApiProviders && providersConfig) {
|
|
456
|
+
apiReachability = {};
|
|
457
|
+
for (const runtime of enabledApiProviders(providersConfig)) {
|
|
458
|
+
apiReachability[runtime.name] = await probeApiProviderReachability(runtime.baseUrl);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
420
461
|
const report = createDoctorReport({
|
|
421
462
|
env: process.env,
|
|
422
463
|
cacheAwareness,
|
|
423
464
|
flightRecorder,
|
|
424
465
|
probeUpstream: opts.probeUpstream,
|
|
466
|
+
providersConfig,
|
|
467
|
+
apiReachability,
|
|
425
468
|
});
|
|
426
469
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
427
470
|
if (flightRecorder) {
|
|
@@ -432,6 +475,30 @@ export function printDoctorJson(opts = {}) {
|
|
|
432
475
|
}
|
|
433
476
|
}
|
|
434
477
|
}
|
|
478
|
+
export async function probeApiProviderReachability(baseUrl, timeoutMs = 5_000) {
|
|
479
|
+
let url;
|
|
480
|
+
try {
|
|
481
|
+
url = new URL(baseUrl);
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
return { reachable: false, error: "invalid base_url" };
|
|
485
|
+
}
|
|
486
|
+
const transport = url.protocol === "http:" ? await import("node:http") : await import("node:https");
|
|
487
|
+
return new Promise(resolve => {
|
|
488
|
+
const request = transport.request(url, { method: "GET", timeout: timeoutMs }, response => {
|
|
489
|
+
response.resume();
|
|
490
|
+
resolve({ reachable: true });
|
|
491
|
+
});
|
|
492
|
+
request.on("timeout", () => {
|
|
493
|
+
request.destroy();
|
|
494
|
+
resolve({ reachable: false, error: `timed out after ${timeoutMs}ms` });
|
|
495
|
+
});
|
|
496
|
+
request.on("error", err => {
|
|
497
|
+
resolve({ reachable: false, error: err instanceof Error ? err.message : String(err) });
|
|
498
|
+
});
|
|
499
|
+
request.end();
|
|
500
|
+
});
|
|
501
|
+
}
|
|
435
502
|
function isCreateDoctorReportOptions(value) {
|
|
436
503
|
if (value === null || typeof value !== "object")
|
|
437
504
|
return false;
|
|
@@ -68,16 +68,28 @@ export function redactDiagnosticUrl(rawUrl) {
|
|
|
68
68
|
const redactSensitivePairs = (value) => value.replace(new RegExp(`((${sensitiveKeyPattern.source})=)[^&\\s#]+`, "gi"), "$1<redacted>");
|
|
69
69
|
try {
|
|
70
70
|
const url = new URL(rawUrl);
|
|
71
|
-
|
|
71
|
+
let redacted = false;
|
|
72
|
+
if (url.username) {
|
|
72
73
|
url.username = "<redacted>";
|
|
73
|
-
|
|
74
|
+
redacted = true;
|
|
75
|
+
}
|
|
76
|
+
if (url.password) {
|
|
74
77
|
url.password = "<redacted>";
|
|
78
|
+
redacted = true;
|
|
79
|
+
}
|
|
75
80
|
for (const key of Array.from(url.searchParams.keys())) {
|
|
76
81
|
if (sensitiveKeyPattern.test(key)) {
|
|
77
82
|
url.searchParams.set(key, "<redacted>");
|
|
83
|
+
redacted = true;
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
|
-
|
|
86
|
+
const redactedHash = redactSensitivePairs(url.hash);
|
|
87
|
+
if (redactedHash !== url.hash) {
|
|
88
|
+
url.hash = redactedHash;
|
|
89
|
+
redacted = true;
|
|
90
|
+
}
|
|
91
|
+
if (!redacted)
|
|
92
|
+
return rawUrl;
|
|
81
93
|
return url.toString().replace(/%3Credacted%3E/gi, "<redacted>");
|
|
82
94
|
}
|
|
83
95
|
catch {
|
package/dist/executor.js
CHANGED
package/dist/http-transport.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type Server } from "node:http";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import type { GatewayServerDeps } from "./index.js";
|
|
4
|
+
import { type HttpSessionLimitsConfig } from "./config.js";
|
|
4
5
|
export interface HttpTransportOptions {
|
|
5
6
|
host?: string;
|
|
6
7
|
port?: number;
|
|
7
8
|
path?: string;
|
|
8
9
|
deps?: GatewayServerDeps;
|
|
9
10
|
createGatewayServer: (deps?: GatewayServerDeps) => McpServer;
|
|
11
|
+
httpLimits?: HttpSessionLimitsConfig;
|
|
10
12
|
logger?: {
|
|
11
13
|
info: (...args: any[]) => void;
|
|
12
14
|
error: (...args: any[]) => void;
|
|
@@ -18,5 +20,6 @@ export interface HttpGatewayHandle {
|
|
|
18
20
|
url: string;
|
|
19
21
|
close: () => Promise<void>;
|
|
20
22
|
sessionCount: () => number;
|
|
23
|
+
sessionHealth: () => Record<string, unknown>;
|
|
21
24
|
}
|
|
22
25
|
export declare function startHttpGateway(options: HttpTransportOptions): Promise<HttpGatewayHandle>;
|