llm-cli-gateway 2.12.1 → 2.12.2

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.
@@ -1,4 +1,5 @@
1
1
  import type { CliType } from "./session-manager.js";
2
+ import type { ApiProviderConfig } from "./config.js";
2
3
  export interface ProviderLoginGuidance {
3
4
  provider: CliType;
4
5
  displayName: string;
@@ -19,3 +20,14 @@ export interface ProviderLoginGuidance {
19
20
  }
20
21
  export declare function getProviderLoginGuidance(provider: CliType): ProviderLoginGuidance;
21
22
  export declare function getAllProviderLoginGuidance(): Record<CliType, ProviderLoginGuidance>;
23
+ export interface ApiProviderLoginGuidance {
24
+ provider: string;
25
+ displayName: string;
26
+ kind: ApiProviderConfig["kind"];
27
+ baseUrl: string;
28
+ apiKeyEnv: string | null;
29
+ summary: string;
30
+ steps: string[];
31
+ credentialHandling: string;
32
+ }
33
+ export declare function getApiProviderLoginGuidance(provider: ApiProviderConfig): ApiProviderLoginGuidance;
@@ -1,3 +1,4 @@
1
+ import { redactDiagnosticUrl } from "./endpoint-exposure.js";
1
2
  const GUIDANCE = {
2
3
  claude: {
3
4
  provider: "claude",
@@ -122,3 +123,30 @@ export function getProviderLoginGuidance(provider) {
122
123
  export function getAllProviderLoginGuidance() {
123
124
  return { ...GUIDANCE };
124
125
  }
126
+ export function getApiProviderLoginGuidance(provider) {
127
+ const keyless = provider.apiKeyEnv === null;
128
+ const baseUrl = redactDiagnosticUrl(provider.baseUrl) ?? provider.baseUrl;
129
+ const summary = keyless
130
+ ? `Keyless-local API provider "${provider.name}" (kind: ${provider.kind}); no credential is required for its loopback endpoint.`
131
+ : `API provider "${provider.name}" (kind: ${provider.kind}) authenticates with a key read from the ${provider.apiKeyEnv} environment variable.`;
132
+ const steps = keyless
133
+ ? [
134
+ `Ensure the local endpoint at ${baseUrl} is running (e.g. Ollama or llama.cpp).`,
135
+ `No API key is needed; the provider is enabled as soon as the loopback endpoint is reachable.`,
136
+ ]
137
+ : [
138
+ `Obtain an API key from the provider that serves ${baseUrl}.`,
139
+ `Export it as ${provider.apiKeyEnv} in the gateway's environment (the value is read only at request time).`,
140
+ `Confirm [providers.${provider.name}] in the gateway config points at the intended base_url and default_model.`,
141
+ ];
142
+ return {
143
+ provider: provider.name,
144
+ displayName: provider.name,
145
+ kind: provider.kind,
146
+ baseUrl,
147
+ apiKeyEnv: provider.apiKeyEnv,
148
+ summary,
149
+ steps,
150
+ credentialHandling: "Set the API key only via the named environment variable. Do not paste the key into the gateway config, prompts, or a remote chat.",
151
+ };
152
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CliType } from "./session-manager.js";
2
2
  import { type ProviderLoginGuidance } from "./provider-login-guidance.js";
3
+ import { type ApiProviderConfig } from "./config.js";
3
4
  export type ProviderLoginStatus = "authenticated" | "not_authenticated" | "unknown" | "not_checked";
4
5
  export interface ProviderRuntimeStatus {
5
6
  provider: CliType;
@@ -17,6 +18,17 @@ export interface ProviderRuntimeStatus {
17
18
  };
18
19
  guidance: ProviderLoginGuidance;
19
20
  }
21
+ export interface ApiProviderRuntimeStatus {
22
+ provider: string;
23
+ kind: ApiProviderConfig["kind"];
24
+ baseUrl: string;
25
+ defaultModel: string;
26
+ models: string[] | null;
27
+ apiKeyEnv: string | null;
28
+ apiKeyPresent: boolean;
29
+ enabled: boolean;
30
+ }
31
+ export declare function getApiProviderStatus(provider: ApiProviderConfig, env?: NodeJS.ProcessEnv): ApiProviderRuntimeStatus;
20
32
  export declare const PROVIDER_COMMANDS: Record<CliType, string>;
21
33
  export declare function listProviderRuntimeStatuses(): Record<CliType, ProviderRuntimeStatus>;
22
34
  export declare function getProviderRuntimeStatus(provider: CliType): ProviderRuntimeStatus;
@@ -3,7 +3,21 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { getProviderLoginGuidance } from "./provider-login-guidance.js";
6
+ import { apiProviderKeyPresent, isApiProviderEnabled } from "./config.js";
7
+ import { redactDiagnosticUrl } from "./endpoint-exposure.js";
6
8
  import { envWithExtendedPath, getExtendedPath, providerCommandName, resolveCommandForSpawn, } from "./executor.js";
9
+ export function getApiProviderStatus(provider, env = process.env) {
10
+ return {
11
+ provider: provider.name,
12
+ kind: provider.kind,
13
+ baseUrl: redactDiagnosticUrl(provider.baseUrl) ?? provider.baseUrl,
14
+ defaultModel: provider.defaultModel,
15
+ models: provider.models ?? null,
16
+ apiKeyEnv: provider.apiKeyEnv,
17
+ apiKeyPresent: apiProviderKeyPresent(provider, env),
18
+ enabled: isApiProviderEnabled(provider, env),
19
+ };
20
+ }
7
21
  const PROVIDERS = ["claude", "codex", "gemini", "grok", "mistral", "devin"];
8
22
  const VERSION_ARGS = {
9
23
  claude: ["--version"],
@@ -1,5 +1,6 @@
1
1
  import { type CliInfo } from "./model-registry.js";
2
2
  import { type CliType } from "./session-manager.js";
3
+ import { type ProvidersConfig } from "./config.js";
3
4
  export interface ProviderToolControl {
4
5
  name?: string;
5
6
  supported: boolean;
@@ -128,6 +129,7 @@ export interface ProviderCapabilityQuery {
128
129
  includeUnsupported?: boolean;
129
130
  includePaths?: boolean;
130
131
  refresh?: boolean;
132
+ providersConfig?: ProvidersConfig;
131
133
  }
132
134
  export type ProviderToolCapabilitiesMap = Partial<Record<ProviderCapabilityId, ProviderToolCapabilities>>;
133
135
  export declare function getProviderToolCapabilities(queryOrCli?: ProviderCapabilityQuery | ProviderCapabilityId): ProviderToolCapabilitiesMap;
@@ -137,4 +139,5 @@ export declare function _getCapabilityCacheForTest(): Map<string, {
137
139
  loadedAt: number;
138
140
  value: ProviderToolCapabilities;
139
141
  }>;
140
- export declare function providerCapabilityIds(): readonly KnownProviderCapabilityId[];
142
+ export declare function providerCapabilityIds(providersConfig?: ProvidersConfig): readonly ProviderCapabilityId[];
143
+ export declare function knownProviderCapabilityIds(): readonly KnownProviderCapabilityId[];
@@ -5,7 +5,8 @@ import { parse as parseToml } from "smol-toml";
5
5
  import { CLAUDE_MCP_SERVER_NAMES } from "./claude-mcp-config.js";
6
6
  import { getAvailableCliInfo } from "./model-registry.js";
7
7
  import { CLI_TYPES } from "./session-manager.js";
8
- import { isXaiProviderEnabled, loadProvidersConfig } from "./config.js";
8
+ import { enabledApiProviders, isXaiProviderEnabled, loadProvidersConfig, } from "./config.js";
9
+ import { apiContinuityForKind } from "./api-provider.js";
9
10
  const MAX_SKILLS_PER_DIR = 100;
10
11
  const MAX_SKILL_BYTES = 64 * 1024;
11
12
  const MAX_CONFIG_BYTES = 128 * 1024;
@@ -809,7 +810,7 @@ const TOOL_CONTROLS = {
809
810
  const CAPABILITY_CACHE = new Map();
810
811
  export function getProviderToolCapabilities(queryOrCli = {}) {
811
812
  const query = normalizeQuery(queryOrCli);
812
- const providers = query.cli ? [query.cli] : PROVIDER_CAPABILITY_IDS;
813
+ const providers = query.cli ? [query.cli] : allProviderCapabilityIds(query.providersConfig);
813
814
  const entries = providers.map(provider => [
814
815
  provider,
815
816
  getOneProviderToolCapabilities(provider, query),
@@ -833,14 +834,35 @@ export function clearProviderToolCapabilitiesCache() {
833
834
  export function _getCapabilityCacheForTest() {
834
835
  return CAPABILITY_CACHE;
835
836
  }
836
- export function providerCapabilityIds() {
837
+ function providersConfigForQuery(query) {
838
+ return query.providersConfig ?? loadProvidersConfig();
839
+ }
840
+ function enabledApiCapabilityIds(providersConfig) {
841
+ return enabledApiProviders(providersConfig ?? loadProvidersConfig()).map(provider => provider.name);
842
+ }
843
+ function allProviderCapabilityIds(providersConfig) {
844
+ return [
845
+ ...new Set([
846
+ ...PROVIDER_CAPABILITY_IDS,
847
+ ...enabledApiCapabilityIds(providersConfig),
848
+ ]),
849
+ ];
850
+ }
851
+ export function providerCapabilityIds(providersConfig) {
852
+ return allProviderCapabilityIds(providersConfig);
853
+ }
854
+ export function knownProviderCapabilityIds() {
837
855
  return PROVIDER_CAPABILITY_IDS;
838
856
  }
839
857
  function buildOneProviderToolCapabilities(cli, query) {
840
858
  const warnings = [];
841
859
  if (!isKnownProviderCapabilityId(cli)) {
842
- throw new Error(`No tool-capability metadata for provider "${cli}". ` +
843
- `Known providers: ${PROVIDER_CAPABILITY_IDS.join(", ")}.`);
860
+ const runtime = enabledApiProviders(providersConfigForQuery(query)).find(provider => provider.name === cli);
861
+ if (!runtime) {
862
+ throw new Error(`No tool-capability metadata for provider "${cli}". ` +
863
+ `Known providers: ${allProviderCapabilityIds(query.providersConfig).join(", ")}.`);
864
+ }
865
+ return buildApiProviderToolCapabilities(runtime, query);
844
866
  }
845
867
  const definition = TOOL_CONTROLS[cli];
846
868
  const discoveredSkills = query.includeSkills && cli !== "grok_api" ? discoverSkills(cli, warnings, query) : [];
@@ -848,7 +870,7 @@ function buildOneProviderToolCapabilities(cli, query) {
848
870
  ? extractProviderTools(cli, discoveredSkills)
849
871
  : [];
850
872
  const features = { ...definition.features };
851
- const gatewayRequestTools = cli === "grok_api" && !isXaiProviderEnabled(loadProvidersConfig())
873
+ const gatewayRequestTools = cli === "grok_api" && !isXaiProviderEnabled(providersConfigForQuery(query))
852
874
  ? []
853
875
  : [...definition.gatewayRequestTools];
854
876
  if (cli === "grok") {
@@ -864,7 +886,7 @@ function buildOneProviderToolCapabilities(cli, query) {
864
886
  providerKind: definition.providerKind,
865
887
  gatewayRequestTools,
866
888
  gatewayRequestTool: gatewayRequestTools[0] ?? definition.gatewayRequestTools[0],
867
- modelInfo: getModelInfo(cli, query.refresh),
889
+ modelInfo: getModelInfo(cli, query),
868
890
  summary: definition.summary,
869
891
  acpContract: { ...ACP_CONTRACT.providers[cli] },
870
892
  acp: cloneAcpCapability(ACP_CAPABILITIES[cli]),
@@ -883,6 +905,172 @@ function buildOneProviderToolCapabilities(cli, query) {
883
905
  },
884
906
  };
885
907
  }
908
+ function apiProviderCapabilityDefinition(runtime) {
909
+ const { name, kind } = runtime;
910
+ const forwardsReasoning = kind === "xai-responses";
911
+ const continuity = apiContinuityForKind(kind);
912
+ const continuityTracked = continuity !== "none";
913
+ return {
914
+ providerKind: "api",
915
+ gatewayRequestTools: [`api_${name}_request`],
916
+ summary: `Generic ${kind} API provider "${name}" configured through [providers.${name}]. ` +
917
+ "HTTP request tool only: no local CLI tools, skills, MCP servers, or workspaces.",
918
+ controls: {
919
+ allowlist: {
920
+ supported: false,
921
+ behavior: `api_${name}_request has no CLI tool allow-list input.`,
922
+ },
923
+ denylist: {
924
+ supported: false,
925
+ behavior: `api_${name}_request has no CLI tool deny-list input.`,
926
+ },
927
+ mcpServers: {
928
+ supported: false,
929
+ behavior: "API requests do not configure or expose MCP servers.",
930
+ },
931
+ nativeSkills: {
932
+ supported: false,
933
+ behavior: "API requests do not read local provider skills.",
934
+ },
935
+ reasoningEffort: forwardsReasoning
936
+ ? {
937
+ supported: true,
938
+ requestField: "reasoningEffort",
939
+ behavior: "Passed to the xAI Responses API reasoning.effort field.",
940
+ }
941
+ : {
942
+ supported: false,
943
+ requestField: "reasoningEffort",
944
+ behavior: `Accepted by the schema but ignored by the ${kind} adapter.`,
945
+ },
946
+ maxOutputTokens: {
947
+ supported: true,
948
+ requestField: "maxOutputTokens",
949
+ behavior: "Bounds the provider API max output tokens.",
950
+ },
951
+ sampling: {
952
+ supported: true,
953
+ requestField: "temperature/topP",
954
+ behavior: "Sampling controls are passed through to the provider API.",
955
+ },
956
+ timeout: {
957
+ supported: true,
958
+ requestField: "timeoutMs",
959
+ behavior: "Bounds the API HTTP request timeout.",
960
+ },
961
+ session: continuityTracked
962
+ ? {
963
+ supported: true,
964
+ requestField: "sessionId/createNewSession",
965
+ behavior: continuity === "server-side-id"
966
+ ? "Gateway stores the provider continuation handle (previous_response_id) in session metadata."
967
+ : "Gateway tracks the session (active/owner); stateless adapters resend prior context caller-side without storing conversation content.",
968
+ }
969
+ : {
970
+ supported: false,
971
+ requestField: "sessionId/createNewSession",
972
+ behavior: "This provider kind does not support multi-turn continuity.",
973
+ },
974
+ },
975
+ features: baseFeatures({
976
+ apiProvider: true,
977
+ structuredTextResponses: true,
978
+ sessionContinuity: continuityTracked,
979
+ }),
980
+ unsupportedInputs: [
981
+ {
982
+ input: "localSkills",
983
+ behavior: "not_supported",
984
+ details: `api_${name}_request does not inspect local CLI skills.`,
985
+ },
986
+ {
987
+ input: "allowedTools/disallowedTools",
988
+ behavior: "not_supported",
989
+ details: "Tool allow/deny controls are CLI-only and are not routed to the API.",
990
+ },
991
+ {
992
+ input: "workspace/worktree",
993
+ behavior: "not_supported",
994
+ details: "API providers have no local workspace or worktree controls.",
995
+ },
996
+ ],
997
+ };
998
+ }
999
+ function apiProviderModelInfo(runtime) {
1000
+ const list = runtime.models && runtime.models.length > 0 ? runtime.models : [runtime.defaultModel];
1001
+ const models = {};
1002
+ for (const model of list) {
1003
+ models[model] =
1004
+ model === runtime.defaultModel ? "Configured default model" : "Configured allowlisted model";
1005
+ }
1006
+ if (!(runtime.defaultModel in models)) {
1007
+ models[runtime.defaultModel] = "Configured default model (always permitted)";
1008
+ }
1009
+ return {
1010
+ description: `Generic ${runtime.kind} API provider configured through [providers.${runtime.name}].`,
1011
+ models,
1012
+ defaultModel: runtime.defaultModel,
1013
+ defaultModelSource: `[providers.${runtime.name}].default_model`,
1014
+ };
1015
+ }
1016
+ function apiProviderConfigSurfaces(runtime) {
1017
+ return [
1018
+ {
1019
+ name: `providers.${runtime.name}`,
1020
+ kind: "gateway",
1021
+ present: true,
1022
+ details: `Generic ${runtime.kind} API provider; secret key material is read only from the named environment variable at request time.`,
1023
+ },
1024
+ {
1025
+ name: "api_key_env",
1026
+ kind: "env",
1027
+ present: runtime.apiKey.length > 0,
1028
+ entries: runtime.apiKeyEnv ? [runtime.apiKeyEnv] : [],
1029
+ details: "Reports only the configured environment variable name and whether a key is resolved (keyless-local providers report false); never the value.",
1030
+ },
1031
+ ];
1032
+ }
1033
+ function buildApiProviderToolCapabilities(runtime, query) {
1034
+ const definition = apiProviderCapabilityDefinition(runtime);
1035
+ return {
1036
+ schemaVersion: "provider-tool-capabilities.v2",
1037
+ generatedAt: new Date().toISOString(),
1038
+ cli: runtime.name,
1039
+ providerKind: "api",
1040
+ gatewayRequestTools: [...definition.gatewayRequestTools],
1041
+ gatewayRequestTool: definition.gatewayRequestTools[0],
1042
+ modelInfo: apiProviderModelInfo(runtime),
1043
+ summary: definition.summary,
1044
+ acpContract: {
1045
+ classification: "absent_watchlist",
1046
+ summary: `${runtime.name} is an HTTP API provider with no ACP process transport; watchlist item only.`,
1047
+ },
1048
+ acp: {
1049
+ status: "not_applicable",
1050
+ mediation: "none",
1051
+ targetVersion: `${runtime.kind} API`,
1052
+ entrypoint: null,
1053
+ runtimeEnabled: false,
1054
+ smokeSupported: false,
1055
+ smokeStatus: "unsupported",
1056
+ caveats: ["ACP is a CLI-stdio transport; the HTTP API provider has no ACP surface."],
1057
+ docs: ACP_DOCS_REFERENCE,
1058
+ },
1059
+ controls: cloneControls(definition.controls),
1060
+ features: { ...definition.features },
1061
+ discoveredSkills: [],
1062
+ discoveredProviderTools: [],
1063
+ configSurfaces: apiProviderConfigSurfaces(runtime),
1064
+ unsupportedInputs: query.includeUnsupported ? [...definition.unsupportedInputs] : [],
1065
+ warnings: [],
1066
+ metadata: {
1067
+ deprecatedFields: {
1068
+ gatewayRequestTool: "Use gatewayRequestTools instead.",
1069
+ },
1070
+ cacheTtlMs: CAPABILITY_CACHE_TTL_MS,
1071
+ },
1072
+ };
1073
+ }
886
1074
  function discoverSkills(cli, warnings, query) {
887
1075
  const skills = [];
888
1076
  for (const root of skillRoots(cli)) {
@@ -960,6 +1148,7 @@ function normalizeQuery(queryOrCli) {
960
1148
  includeUnsupported: query.includeUnsupported ?? true,
961
1149
  includePaths: query.includePaths ?? false,
962
1150
  refresh: query.refresh ?? false,
1151
+ providersConfig: query.providersConfig,
963
1152
  };
964
1153
  }
965
1154
  function capabilityCacheKey(cli, query) {
@@ -969,8 +1158,33 @@ function capabilityCacheKey(cli, query) {
969
1158
  includeProviderTools: query.includeProviderTools,
970
1159
  includeUnsupported: query.includeUnsupported,
971
1160
  includePaths: query.includePaths,
1161
+ providersConfig: query.providersConfig ? providerConfigCacheKey(query.providersConfig) : null,
972
1162
  });
973
1163
  }
1164
+ function providerConfigCacheKey(config) {
1165
+ return {
1166
+ xai: config.xai
1167
+ ? {
1168
+ apiKeyEnv: config.xai.apiKeyEnv,
1169
+ baseUrl: config.xai.baseUrl,
1170
+ defaultModel: config.xai.defaultModel,
1171
+ }
1172
+ : null,
1173
+ providers: Object.fromEntries(Object.entries(config.providers ?? {})
1174
+ .sort(([a], [b]) => a.localeCompare(b))
1175
+ .map(([name, provider]) => [
1176
+ name,
1177
+ {
1178
+ kind: provider.kind,
1179
+ apiKeyEnv: provider.apiKeyEnv,
1180
+ baseUrl: provider.baseUrl,
1181
+ defaultModel: provider.defaultModel,
1182
+ models: provider.models ?? null,
1183
+ usageInclude: provider.usageInclude ?? null,
1184
+ },
1185
+ ])),
1186
+ };
1187
+ }
974
1188
  function baseFeatures(overrides) {
975
1189
  const names = [
976
1190
  "gatewayRequestTools",
@@ -1012,11 +1226,11 @@ function baseFeatures(overrides) {
1012
1226
  function cloneControls(controls) {
1013
1227
  return Object.fromEntries(Object.entries(controls).map(([name, control]) => [name, { name, ...control }]));
1014
1228
  }
1015
- function getModelInfo(cli, refresh) {
1229
+ function getModelInfo(cli, query) {
1016
1230
  if (cli !== "grok_api") {
1017
- return getAvailableCliInfo(refresh)[cli];
1231
+ return getAvailableCliInfo(query.refresh)[cli];
1018
1232
  }
1019
- const providers = loadProvidersConfig();
1233
+ const providers = providersConfigForQuery(query);
1020
1234
  const enabled = isXaiProviderEnabled(providers);
1021
1235
  const defaultModel = providers.xai?.defaultModel;
1022
1236
  return {
@@ -1031,7 +1245,7 @@ function getModelInfo(cli, refresh) {
1031
1245
  }
1032
1246
  function discoverConfigSurfaces(cli, query, discoveredSkills) {
1033
1247
  if (cli === "grok_api") {
1034
- const providers = loadProvidersConfig();
1248
+ const providers = providersConfigForQuery(query);
1035
1249
  return [
1036
1250
  {
1037
1251
  name: "providers.xai",
@@ -2,7 +2,7 @@ import { ISessionManager } from "./session-manager.js";
2
2
  import { PerformanceMetrics } from "./metrics.js";
3
3
  import { FlightRecorderQuery } from "./flight-recorder.js";
4
4
  import { type GlobalCacheStats, type PrefixCacheStats, type SessionCacheStats } from "./cache-stats.js";
5
- import type { CacheAwarenessConfig } from "./config.js";
5
+ import { type CacheAwarenessConfig, type ProvidersConfig } from "./config.js";
6
6
  export interface ResourceDefinition {
7
7
  uri: string;
8
8
  name: string;
@@ -25,8 +25,12 @@ export declare class ResourceProvider {
25
25
  private performanceMetrics;
26
26
  private flightRecorder;
27
27
  private cacheAwareness;
28
- constructor(sessionManager: ISessionManager, performanceMetrics: PerformanceMetrics, flightRecorder?: FlightRecorderQuery, cacheAwareness?: CacheAwarenessConfig | null);
28
+ private providers;
29
+ constructor(sessionManager: ISessionManager, performanceMetrics: PerformanceMetrics, flightRecorder?: FlightRecorderQuery, cacheAwareness?: CacheAwarenessConfig | null, providers?: ProvidersConfig | null);
29
30
  getFlightRecorderQuery(): FlightRecorderQuery;
31
+ private apiRuntimes;
32
+ private continuityTrackedApiRuntimes;
33
+ private providerCapabilityIds;
30
34
  readCacheStateGlobal(opts?: {
31
35
  lastNHours?: number;
32
36
  }): GlobalCacheStats;
package/dist/resources.js CHANGED
@@ -2,22 +2,36 @@ import { CLI_TYPES, PROVIDER_TYPES } from "./session-manager.js";
2
2
  import { getRequestContext, principalCanAccess, resolveOwnerPrincipal } from "./request-context.js";
3
3
  import { getAvailableCliInfo } from "./model-registry.js";
4
4
  import { computeGlobalCacheStats, computePrefixCacheStats, computeSessionCacheStats, computeTtlRemaining, } from "./cache-stats.js";
5
+ import { enabledApiProviders, } from "./config.js";
6
+ import { apiContinuityForKind } from "./api-provider.js";
7
+ import { apiProviderCatalogEntry } from "./api-request.js";
5
8
  import { buildProviderSubcommandsCompactCatalog, getCliSubcommandContract, serializeCliSubcommandContract, } from "./upstream-contracts.js";
6
- import { getOneProviderToolCapabilities, getProviderToolCapabilities, providerCapabilityIds, } from "./provider-tool-capabilities.js";
9
+ import { getOneProviderToolCapabilities, getProviderToolCapabilities, knownProviderCapabilityIds, providerCapabilityIds, } from "./provider-tool-capabilities.js";
7
10
  export class ResourceProvider {
8
11
  sessionManager;
9
12
  performanceMetrics;
10
13
  flightRecorder;
11
14
  cacheAwareness;
12
- constructor(sessionManager, performanceMetrics, flightRecorder = { queryRequests: () => [] }, cacheAwareness = null) {
15
+ providers;
16
+ constructor(sessionManager, performanceMetrics, flightRecorder = { queryRequests: () => [] }, cacheAwareness = null, providers = null) {
13
17
  this.sessionManager = sessionManager;
14
18
  this.performanceMetrics = performanceMetrics;
15
19
  this.flightRecorder = flightRecorder;
16
20
  this.cacheAwareness = cacheAwareness;
21
+ this.providers = providers;
17
22
  }
18
23
  getFlightRecorderQuery() {
19
24
  return this.flightRecorder;
20
25
  }
26
+ apiRuntimes() {
27
+ return this.providers ? enabledApiProviders(this.providers) : [];
28
+ }
29
+ continuityTrackedApiRuntimes() {
30
+ return this.apiRuntimes().filter(rt => apiContinuityForKind(rt.kind) !== "none");
31
+ }
32
+ providerCapabilityIds() {
33
+ return this.providers ? providerCapabilityIds(this.providers) : knownProviderCapabilityIds();
34
+ }
21
35
  readCacheStateGlobal(opts = {}) {
22
36
  return computeGlobalCacheStats(this.flightRecorder, opts);
23
37
  }
@@ -101,6 +115,17 @@ export class ResourceProvider {
101
115
  priority: 0.6,
102
116
  },
103
117
  },
118
+ ...this.continuityTrackedApiRuntimes().map(rt => ({
119
+ uri: `sessions://${rt.name}`,
120
+ name: `${rt.name} Sessions`,
121
+ title: `${rt.name} Sessions`,
122
+ description: `List of ${rt.name} API provider conversation sessions`,
123
+ mimeType: "application/json",
124
+ annotations: {
125
+ audience: ["user", "assistant"],
126
+ priority: 0.6,
127
+ },
128
+ })),
104
129
  {
105
130
  uri: "models://claude",
106
131
  name: "Claude Models",
@@ -156,6 +181,17 @@ export class ResourceProvider {
156
181
  priority: 0.8,
157
182
  },
158
183
  },
184
+ ...this.apiRuntimes().map(rt => ({
185
+ uri: `models://${rt.name}`,
186
+ name: `${rt.name} Models`,
187
+ title: `${rt.name} Models & Capabilities`,
188
+ description: `Configured ${rt.name} API provider model catalog`,
189
+ mimeType: "application/json",
190
+ annotations: {
191
+ audience: ["user", "assistant"],
192
+ priority: 0.8,
193
+ },
194
+ })),
159
195
  {
160
196
  uri: "metrics://performance",
161
197
  name: "Performance Metrics",
@@ -189,7 +225,7 @@ export class ResourceProvider {
189
225
  priority: 0.8,
190
226
  },
191
227
  },
192
- ...providerCapabilityIds().map(cli => ({
228
+ ...this.providerCapabilityIds().map(cli => ({
193
229
  uri: `provider-tools://${cli}`,
194
230
  name: `${cli} Tool Capabilities`,
195
231
  title: `${cli} Tool Capabilities`,
@@ -338,6 +374,32 @@ export class ResourceProvider {
338
374
  text: JSON.stringify(cliInfo.mistral, null, 2),
339
375
  };
340
376
  }
377
+ if (uri.startsWith("models://")) {
378
+ const runtime = this.apiRuntimes().find(rt => `models://${rt.name}` === uri);
379
+ if (runtime) {
380
+ return {
381
+ uri,
382
+ mimeType: "application/json",
383
+ text: JSON.stringify(apiProviderCatalogEntry(runtime), null, 2),
384
+ };
385
+ }
386
+ }
387
+ if (uri.startsWith("sessions://")) {
388
+ const runtime = this.continuityTrackedApiRuntimes().find(rt => `sessions://${rt.name}` === uri);
389
+ if (runtime) {
390
+ const sessions = this.ownedSessions(await this.sessionManager.listSessions(runtime.name));
391
+ return {
392
+ uri,
393
+ mimeType: "application/json",
394
+ text: JSON.stringify({
395
+ cli: runtime.name,
396
+ total: sessions.length,
397
+ sessions,
398
+ activeSession: await this.ownedActiveId(runtime.name),
399
+ }, null, 2),
400
+ };
401
+ }
402
+ }
341
403
  if (uri === "metrics://performance") {
342
404
  return {
343
405
  uri,
@@ -356,15 +418,17 @@ export class ResourceProvider {
356
418
  return {
357
419
  uri,
358
420
  mimeType: "application/json",
359
- text: JSON.stringify(getProviderToolCapabilities(), null, 2),
421
+ text: JSON.stringify(getProviderToolCapabilities({ providersConfig: this.providers ?? undefined }), null, 2),
360
422
  };
361
423
  }
362
- const providerToolsResource = parseProviderToolsUri(uri);
424
+ const providerToolsResource = parseProviderToolsUri(uri, this.providerCapabilityIds());
363
425
  if (providerToolsResource) {
364
426
  return {
365
427
  uri,
366
428
  mimeType: "application/json",
367
- text: JSON.stringify(getOneProviderToolCapabilities(providerToolsResource.provider), null, 2),
429
+ text: JSON.stringify(getOneProviderToolCapabilities(providerToolsResource.provider, {
430
+ providersConfig: this.providers ?? undefined,
431
+ }), null, 2),
368
432
  };
369
433
  }
370
434
  const subcommandResource = parseProviderSubcommandUri(uri);
@@ -401,7 +465,7 @@ function parseProviderSubcommandUri(uri) {
401
465
  commandPath: pathParts.map(part => decodeURIComponent(part)).filter(Boolean),
402
466
  };
403
467
  }
404
- function parseProviderToolsUri(uri) {
468
+ function parseProviderToolsUri(uri, providerIds) {
405
469
  const prefix = uri.startsWith("provider-tools://")
406
470
  ? "provider-tools://"
407
471
  : uri.startsWith("provider_tools://")
@@ -410,7 +474,7 @@ function parseProviderToolsUri(uri) {
410
474
  if (!prefix || uri === `${prefix}catalog`)
411
475
  return null;
412
476
  const provider = uri.slice(prefix.length);
413
- if (!providerCapabilityIds().includes(provider))
477
+ if (!providerIds.includes(provider))
414
478
  return null;
415
479
  return { provider: provider };
416
480
  }
@@ -1,10 +1,11 @@
1
1
  export function normalizeStartedJob(provider, model, snapshot, warning) {
2
+ const inProgress = snapshot.status === "running" || snapshot.status === "queued";
2
3
  return {
3
4
  provider,
4
5
  model,
5
- status: snapshot.status,
6
- verdict: snapshot.status === "running" ? "pending" : null,
7
- rationale: snapshot.status === "running" ? "Provider job is running asynchronously." : null,
6
+ status: snapshot.status === "queued" ? "running" : snapshot.status,
7
+ verdict: inProgress ? "pending" : null,
8
+ rationale: inProgress ? "Provider job is running asynchronously." : null,
8
9
  risks: [],
9
10
  rawJobReference: {
10
11
  jobId: snapshot.id,
@@ -34,7 +35,7 @@ export function normalizeJobResult(provider, model, result) {
34
35
  return {
35
36
  provider,
36
37
  model,
37
- status: result.status,
38
+ status: result.status === "queued" ? "running" : result.status,
38
39
  verdict: inferVerdict(output, result.status),
39
40
  rationale: output ? excerpt(output, 1800) : error,
40
41
  risks: extractRisks(output, error),
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "2.12.1",
3
+ "version": "2.12.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "llm-cli-gateway",
9
- "version": "2.12.1",
9
+ "version": "2.12.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@modelcontextprotocol/sdk": "^1.29.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "2.12.1",
3
+ "version": "2.12.2",
4
4
  "mcpName": "io.github.verivus-oss/llm-cli-gateway",
5
5
  "description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, Mistral Vibe, and Devin CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
6
6
  "license": "MIT",