mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 08:02:14 +03:00
Adds a Grok Build card to the CLI Code dashboard: configures the main model and per-subagent model overrides, surgically upserts only the [model.omniroute] section of config.toml (preserving unrelated user sections), resolves config.toml via GROK_HOME or the CLI config home, and omits API keys from settings responses. Closes #10829. Validated in an isolated worktree boarded onto origin/release/v3.8.50 (0 conflicts, 14 files): - 6/48 focused node:test cases in tests/integration/cli-settings-grok-build.test.ts initially failed with 422 instead of 200/409 — traced to this route being the first CLI-tool settings route to actually pass a targetPath into the shared guardCliConfigWrite/ensureCliConfigWriteAllowed container-ephemeral-write guard (every other cli-tools/*-settings route calls it with no path, which always no-ops). This devbox genuinely runs inside Docker with no bind mount for the test's tmpdir fixtures, so the guard correctly refused — a real environment-dependent gap in the PR's own test setup (missing OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=1, the pattern already used by tests/unit/cli-tools-apply-container-422.test.ts for this exact guard). Fixed by setting that flag in the test file's setup, pushed fix-in-place. - 61/61 tests pass after the fix (grok-build-config, check-tool-config-status, all-statuses-route, cli-settings-grok-build, plus 13/13 UI tests via vitest for GrokBuildToolCard/ToolDetailClient). - Verified the "omit API keys from settings responses" claim: GET applies omitApiKeys() to both config/settings before responding. - check-file-size, check-changelog-integrity: OK. - typecheck:core: clean. - check-complexity / check-cognitive-complexity: OK, both under baseline. Co-authored-by: tuandinh0801 <tuandinh0801@users.noreply.github.com>
122 lines
4.2 KiB
TypeScript
122 lines
4.2 KiB
TypeScript
// DRY: shared between /api/cli-tools/status and /api/cli-tools/all-statuses (plan 14 F2)
|
|
|
|
import fs from "fs/promises";
|
|
import { getCliConfigHome, getCliPrimaryConfigPath } from "@/shared/services/cliRuntime";
|
|
import { hasOmniRouteQwenCodeConfig } from "@/shared/services/qwenCodeConfig";
|
|
import {
|
|
parseGrokBuildConfig,
|
|
resolveGrokBuildConfigPath,
|
|
} from "@/shared/services/grokBuildConfig";
|
|
import { getRuntimePorts } from "@/lib/runtime/ports";
|
|
|
|
const { apiPort } = getRuntimePorts();
|
|
|
|
/**
|
|
* Check if a tool has OmniRoute configured by reading its config file directly.
|
|
* This replaces the expensive self-referential HTTP calls to /api/cli-tools/*-settings.
|
|
*
|
|
* @param toolId - CLI tool identifier (e.g. "claude", "codex", "cline")
|
|
* @param _configPathOverride - optional path override (used in tests for DI)
|
|
*
|
|
* Returns: "configured" | "not_configured" | "not_installed" | "unknown" | "other"
|
|
*/
|
|
export async function checkToolConfigStatus(
|
|
toolId: string,
|
|
_configPathOverride?: string
|
|
): Promise<"configured" | "not_configured" | "not_installed" | "unknown" | "other"> {
|
|
try {
|
|
const configPath =
|
|
_configPathOverride ??
|
|
(toolId === "grok-build"
|
|
? resolveGrokBuildConfigPath(process.env, getCliConfigHome())
|
|
: getCliPrimaryConfigPath(toolId));
|
|
if (!configPath) return "unknown";
|
|
|
|
const content = await fs.readFile(configPath, "utf-8");
|
|
|
|
if (toolId === "grok-build") {
|
|
const settings = parseGrokBuildConfig(content);
|
|
return settings.default === "omniroute" &&
|
|
settings.model?.base_url &&
|
|
settings.model.api_backend === "chat_completions"
|
|
? "configured"
|
|
: "not_configured";
|
|
}
|
|
|
|
// Codex uses TOML config — parse as raw text, not JSON
|
|
if (toolId === "codex") {
|
|
const lower = content.toLowerCase();
|
|
const hasOmniRoute =
|
|
lower.includes("omniroute") ||
|
|
lower.includes(`localhost:${apiPort}`) ||
|
|
lower.includes(`127.0.0.1:${apiPort}`);
|
|
if (!hasOmniRoute) return "not_configured";
|
|
|
|
// Also verify auth.json has an API key (not masked/empty)
|
|
try {
|
|
const authPath = configPath.replace(/config\.toml$/, "auth.json");
|
|
const authContent = await fs.readFile(authPath, "utf-8");
|
|
const auth = JSON.parse(authContent) as Record<string, unknown>;
|
|
const apiKey = (auth?.OPENAI_API_KEY as string) || "";
|
|
if (!apiKey || apiKey.includes("****") || apiKey.length < 20) {
|
|
return "not_configured";
|
|
}
|
|
} catch {
|
|
return "not_configured";
|
|
}
|
|
|
|
return "configured";
|
|
}
|
|
|
|
if (toolId === "hermes") {
|
|
const lower = content.toLowerCase();
|
|
const hasOmniRoute =
|
|
lower.includes("omniroute") ||
|
|
lower.includes(`localhost:${apiPort}`) ||
|
|
lower.includes(`127.0.0.1:${apiPort}`);
|
|
return hasOmniRoute ? "configured" : "not_configured";
|
|
}
|
|
|
|
const config = JSON.parse(content) as Record<string, unknown>;
|
|
|
|
// Each tool stores OmniRoute config differently
|
|
switch (toolId) {
|
|
case "claude":
|
|
return (config?.env as Record<string, unknown>)?.ANTHROPIC_BASE_URL
|
|
? "configured"
|
|
: "not_configured";
|
|
case "qwen":
|
|
return hasOmniRouteQwenCodeConfig(config) ? "configured" : "not_configured";
|
|
case "droid":
|
|
case "openclaw":
|
|
case "cline":
|
|
case "kilo": {
|
|
// Generic check: look for OmniRoute-specific markers in the config
|
|
const configStr = JSON.stringify(config).toLowerCase();
|
|
if (
|
|
configStr.includes("omniroute") ||
|
|
configStr.includes("sk_omniroute") ||
|
|
configStr.includes(`localhost:${apiPort}`) ||
|
|
configStr.includes(`127.0.0.1:${apiPort}`)
|
|
) {
|
|
return "configured";
|
|
}
|
|
// Also accept openai-compatible provider with any non-empty baseUrl
|
|
// (user may configure an external domain instead of localhost)
|
|
if (
|
|
toolId === "cline" &&
|
|
(config.actModeApiProvider === "openai" || config.planModeApiProvider === "openai") &&
|
|
((config.openAiBaseUrl as string) || "").trim().length > 0
|
|
) {
|
|
return "configured";
|
|
}
|
|
return "not_configured";
|
|
}
|
|
default:
|
|
return "unknown";
|
|
}
|
|
} catch {
|
|
return "not_configured";
|
|
}
|
|
}
|