chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { existsSync, readFileSync, mkdtempSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
let tmpDir: string;
|
||||
let origHome: string | undefined;
|
||||
|
||||
test.before(() => {
|
||||
tmpDir = mkdtempSync(join(tmpdir(), "omniroute-autostart-linux-"));
|
||||
origHome = process.env.HOME;
|
||||
process.env.HOME = tmpDir;
|
||||
});
|
||||
|
||||
test.after(() => {
|
||||
if (origHome === undefined) delete process.env.HOME;
|
||||
else process.env.HOME = origHome;
|
||||
try {
|
||||
rmSync(tmpDir, { recursive: true, force: true });
|
||||
} catch {}
|
||||
});
|
||||
|
||||
test("resolveCliPath finds omniroute.mjs from argv", async () => {
|
||||
const { enable, disable, getAutostartStatus } =
|
||||
await import("../../../bin/cli/tray/autostart.mjs");
|
||||
if (process.platform !== "linux") return;
|
||||
|
||||
const ok = enable();
|
||||
assert.equal(typeof ok, "boolean");
|
||||
|
||||
const unitPath = join(tmpDir, ".config", "systemd", "user", "omniroute.service");
|
||||
const desktopPath = join(tmpDir, ".config", "autostart", "omniroute.desktop");
|
||||
|
||||
const status = getAutostartStatus();
|
||||
assert.equal(typeof status.enabled, "boolean");
|
||||
|
||||
if (existsSync(unitPath)) {
|
||||
const unit = readFileSync(unitPath, "utf8");
|
||||
assert.match(unit, /^\[Unit\]/m);
|
||||
assert.match(unit, /ExecStart=.*omniroute\.mjs.*serve --no-open/m);
|
||||
assert.doesNotMatch(unit, /--tray/);
|
||||
}
|
||||
|
||||
if (existsSync(desktopPath)) {
|
||||
const desktop = readFileSync(desktopPath, "utf8");
|
||||
assert.match(desktop, /Exec=.*serve --no-open/);
|
||||
}
|
||||
|
||||
disable();
|
||||
assert.equal(getAutostartStatus().enabled, false);
|
||||
});
|
||||
|
||||
test("Linux autostart invokes loginctl/systemctl without shell interpolation", () => {
|
||||
const source = readFileSync(join(process.cwd(), "bin/cli/tray/autostart.mjs"), "utf8");
|
||||
|
||||
assert.match(source, /execFileSync\("systemctl", \["--user", \.\.\.args\]/);
|
||||
assert.match(source, /execFileSync\("loginctl", \["enable-linger", user\]/);
|
||||
assert.match(source, /execFileSync\("loginctl", \["show-user", user, "-p", "Linger"\]/);
|
||||
assert.doesNotMatch(source, /ignoreFailure\s*\?\s*false\s*:\s*false/);
|
||||
assert.doesNotMatch(source, /execSync\(`(?:loginctl|systemctl)\b/);
|
||||
});
|
||||
|
||||
test("Linux enable path prefers graphical desktop autostart over systemd", () => {
|
||||
const source = readFileSync(join(process.cwd(), "bin/cli/tray/autostart.mjs"), "utf8");
|
||||
const graphicalBranch = source.indexOf("if (graphicalSession)");
|
||||
const systemdBranch = source.indexOf("} else if (systemdAvailable)");
|
||||
|
||||
assert.ok(graphicalBranch > -1, "expected a graphical-session branch");
|
||||
assert.ok(systemdBranch > -1, "expected a systemd fallback branch");
|
||||
assert.ok(graphicalBranch < systemdBranch, "graphical autostart should be preferred");
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
parseAgentSelfFromLaunchctl,
|
||||
isLaunchdAgentLoaded,
|
||||
} from "../../../bin/cli/tray/autostart.mjs";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// parseAgentSelfFromLaunchctl — pure parse of `launchctl list <label>` output.
|
||||
// True only when the agent's "PID" line matches the current process PID, so the
|
||||
// enable()/disable() macOS paths can skip launchctl unload/load when the running
|
||||
// process IS the autostart agent (the unload would self-SIGTERM the tray).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("parseAgentSelfFromLaunchctl returns true when PID matches", () => {
|
||||
const output = `{
|
||||
\t"StandardOutPath" = "/tmp/omniroute.out.log";
|
||||
\t"PID" = 4242;
|
||||
\t"Label" = "com.omniroute.autostart";
|
||||
}`;
|
||||
assert.equal(parseAgentSelfFromLaunchctl(output, 4242), true);
|
||||
});
|
||||
|
||||
test("parseAgentSelfFromLaunchctl returns false when PID differs", () => {
|
||||
const output = `{
|
||||
\t"PID" = 9999;
|
||||
\t"Label" = "com.omniroute.autostart";
|
||||
}`;
|
||||
assert.equal(parseAgentSelfFromLaunchctl(output, 4242), false);
|
||||
});
|
||||
|
||||
test("parseAgentSelfFromLaunchctl returns false when no PID present", () => {
|
||||
// Loaded-but-not-running agent: launchctl omits the PID key.
|
||||
const output = `{
|
||||
\t"Label" = "com.omniroute.autostart";
|
||||
\t"LastExitStatus" = 0;
|
||||
}`;
|
||||
assert.equal(parseAgentSelfFromLaunchctl(output, 4242), false);
|
||||
});
|
||||
|
||||
test("parseAgentSelfFromLaunchctl tolerates whitespace variations", () => {
|
||||
assert.equal(parseAgentSelfFromLaunchctl('"PID"=4242;', 4242), true);
|
||||
assert.equal(parseAgentSelfFromLaunchctl('"PID" = 4242 ;', 4242), true);
|
||||
});
|
||||
|
||||
test("parseAgentSelfFromLaunchctl returns false on empty/garbage", () => {
|
||||
assert.equal(parseAgentSelfFromLaunchctl("", 4242), false);
|
||||
assert.equal(parseAgentSelfFromLaunchctl("not launchctl output", 4242), false);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// isLaunchdAgentLoaded — true only when `launchctl list <label>` succeeds.
|
||||
// This is what closes the false-"Enabled" gap: a plist on disk that launchd
|
||||
// does not actually recognize must report NOT enabled.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("isLaunchdAgentLoaded returns true when launchctl list succeeds", () => {
|
||||
const ran = isLaunchdAgentLoaded(() => {
|
||||
/* launchctl exited 0 — agent recognized */
|
||||
});
|
||||
assert.equal(ran, true);
|
||||
});
|
||||
|
||||
test("isLaunchdAgentLoaded returns false when launchctl list throws", () => {
|
||||
const ran = isLaunchdAgentLoaded(() => {
|
||||
throw new Error("Could not find service");
|
||||
});
|
||||
assert.equal(ran, false);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Source-level guard: isEnabledMac() must verify with launchctl, not just the
|
||||
// plist file existence. Mirrors the repo's existing Linux source-assertion test
|
||||
// (the darwin branch can't execute on the Linux CI runner).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test("isEnabledMac verifies launchd registration, not just plist existence", () => {
|
||||
const source = readFileSync(join(process.cwd(), "bin/cli/tray/autostart.mjs"), "utf8");
|
||||
// The verification helper is wired into the darwin enabled-check.
|
||||
assert.match(source, /isLaunchdAgentLoaded/);
|
||||
// launchctl list is queried with the app label, no shell interpolation.
|
||||
assert.match(source, /launchctl",\s*\["list",\s*APP_LABEL\]/);
|
||||
});
|
||||
|
||||
test("enable/disable macOS skip launchctl when the current process is the agent", () => {
|
||||
const source = readFileSync(join(process.cwd(), "bin/cli/tray/autostart.mjs"), "utf8");
|
||||
assert.match(source, /isAgentSelfMac/);
|
||||
assert.match(source, /parseAgentSelfFromLaunchctl/);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { isAutoStartEnabled } from "../../../bin/cli/tray/autostart.ts";
|
||||
|
||||
test("isAutoStartEnabled does not throw and returns boolean", async () => {
|
||||
const result = await isAutoStartEnabled();
|
||||
assert.equal(typeof result, "boolean");
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
buildCodexEnv,
|
||||
buildCodexProviderArgs,
|
||||
resolveCodexTarget,
|
||||
} from "../../../bin/cli/commands/launch-codex.mjs";
|
||||
|
||||
test("buildCodexEnv strips stale OpenAI/Codex creds from the child env (defense-in-depth)", () => {
|
||||
const env = buildCodexEnv(
|
||||
{
|
||||
OPENAI_API_KEY: "leak",
|
||||
OPENAI_BASE_URL: "https://api.openai.com/v1",
|
||||
OPENAI_ORG_ID: "org",
|
||||
CODEX_API_KEY: "leak2",
|
||||
PATH: "/bin",
|
||||
},
|
||||
"oma_live_x"
|
||||
);
|
||||
assert.equal(env.OPENAI_API_KEY, undefined);
|
||||
assert.equal(env.OPENAI_BASE_URL, undefined);
|
||||
assert.equal(env.OPENAI_ORG_ID, undefined);
|
||||
assert.equal(env.CODEX_API_KEY, undefined);
|
||||
assert.equal(env.OMNIROUTE_API_KEY, "oma_live_x");
|
||||
assert.equal(env.PATH, "/bin", "unrelated vars preserved");
|
||||
});
|
||||
|
||||
test("buildCodexEnv uses a no-auth sentinel when no token is given", () => {
|
||||
const env = buildCodexEnv({ PATH: "/bin" }, undefined);
|
||||
assert.equal(env.OMNIROUTE_API_KEY, "omniroute-no-auth");
|
||||
});
|
||||
|
||||
test("buildCodexEnv does not mutate the input env", () => {
|
||||
const input = { OPENAI_API_KEY: "leak", PATH: "/bin" };
|
||||
buildCodexEnv(input, "x");
|
||||
assert.equal(input.OPENAI_API_KEY, "leak");
|
||||
});
|
||||
|
||||
test("buildCodexProviderArgs defines the omniroute provider inline (works without config.toml)", () => {
|
||||
const args = buildCodexProviderArgs("http://vps:20128");
|
||||
const joined = args.join(" ");
|
||||
assert.ok(joined.includes('model_provider="omniroute"'));
|
||||
assert.ok(joined.includes('model_providers.omniroute.base_url="http://vps:20128/v1"'));
|
||||
assert.ok(joined.includes('model_providers.omniroute.env_key="OMNIROUTE_API_KEY"'));
|
||||
assert.ok(joined.includes('model_providers.omniroute.wire_api="responses"'));
|
||||
assert.ok(joined.includes("model_providers.omniroute.requires_openai_auth=false"));
|
||||
// each assignment is preceded by a -c flag
|
||||
assert.equal(args.filter((a) => a === "-c").length, 6);
|
||||
});
|
||||
|
||||
test("resolveCodexTarget: --remote wins and /v1 is stripped from the root", () => {
|
||||
const { baseUrl } = resolveCodexTarget({ remote: "http://vps:20128/v1" });
|
||||
assert.equal(baseUrl, "http://vps:20128");
|
||||
});
|
||||
|
||||
test("resolveCodexTarget: explicit --api-key wins", () => {
|
||||
const { authToken } = resolveCodexTarget({ remote: "http://x:20128", apiKey: "tok-explicit" });
|
||||
assert.equal(authToken, "tok-explicit");
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { buildClaudeEnv } from "../../../bin/cli/commands/launch.mjs";
|
||||
|
||||
test("buildClaudeEnv strips ANTHROPIC_* and injects proxy vars", () => {
|
||||
const env = buildClaudeEnv({ ANTHROPIC_API_KEY: "leak", ANTHROPIC_BASE_URL: "old", PATH: "/bin" }, 20128, "secret");
|
||||
assert.equal(env.ANTHROPIC_API_KEY, undefined);
|
||||
assert.equal(env.ANTHROPIC_BASE_URL, "http://localhost:20128");
|
||||
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "secret");
|
||||
assert.equal(env.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY, "1");
|
||||
assert.equal(env.CLAUDE_CODE_AUTO_COMPACT_WINDOW, "190000");
|
||||
assert.equal(env.PATH, "/bin", "non-ANTHROPIC vars are preserved");
|
||||
});
|
||||
|
||||
test("buildClaudeEnv uses a no-auth sentinel when no token is provided (bypasses Claude's login gate)", () => {
|
||||
const env = buildClaudeEnv({ PATH: "/bin" }, 20128, undefined);
|
||||
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "omniroute-no-auth");
|
||||
assert.equal(env.ANTHROPIC_BASE_URL, "http://localhost:20128");
|
||||
});
|
||||
|
||||
test("buildClaudeEnv does not mutate the input env object", () => {
|
||||
const input = { ANTHROPIC_API_KEY: "leak", PATH: "/bin" };
|
||||
buildClaudeEnv(input, 20128, "x");
|
||||
assert.equal(input.ANTHROPIC_API_KEY, "leak");
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveAiderTarget, buildAiderConfig, buildAiderRecipe } from "../../../bin/cli/commands/setup-aider.mjs";
|
||||
|
||||
test("resolveAiderTarget strips /v1 (LiteLLM appends it)", () => {
|
||||
assert.equal(resolveAiderTarget({ remote: "http://vps:20128/v1/" }).apiBase, "http://vps:20128");
|
||||
assert.equal(resolveAiderTarget({ remote: "http://vps:20128" }).apiBase, "http://vps:20128");
|
||||
});
|
||||
test("resolveAiderTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveAiderTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
|
||||
});
|
||||
test("buildAiderConfig sets openai-api-base + openai/<model>, preserves rest", () => {
|
||||
const c = buildAiderConfig({ "auto-commits": false }, { apiBase: "http://vps:20128", model: "glm/glm-5.2" });
|
||||
assert.equal(c["openai-api-base"], "http://vps:20128");
|
||||
assert.equal(c.model, "openai/glm/glm-5.2");
|
||||
assert.equal(c["auto-commits"], false);
|
||||
});
|
||||
test("buildAiderRecipe references the env key + headless command", () => {
|
||||
const r = buildAiderRecipe({ apiBase: "http://vps:20128", model: "glm/glm-5.2" });
|
||||
assert.ok(r.includes("OPENAI_API_BASE=http://vps:20128"));
|
||||
assert.ok(r.includes("OPENAI_API_KEY=$OMNIROUTE_API_KEY"));
|
||||
assert.ok(r.includes("--model openai/glm/glm-5.2"));
|
||||
assert.ok(r.includes("--message") && r.includes("--yes"));
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
import { test, before, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
buildProfileSettings,
|
||||
fallbackClaudeProfile,
|
||||
syncClaudeProfilesFromModels,
|
||||
} from "../../../bin/cli/commands/setup-claude.mjs";
|
||||
import { buildClaudeEnv, resolveLaunchTarget } from "../../../bin/cli/commands/launch.mjs";
|
||||
import { categoriseModel } from "../../../bin/cli/commands/setup-codex.mjs";
|
||||
|
||||
// #5959 — deflake: `node --test` runs each file in a child process that streams
|
||||
// its report back to the parent as V8-serialized frames on fd 1 (stdout). The CLI
|
||||
// helpers under test (`syncClaudeProfilesFromModels`) print progress via
|
||||
// `console.log`, and that stdout output interleaves with the serialized frames,
|
||||
// corrupting the stream — the parent then throws
|
||||
// "Unable to deserialize cloned data due to invalid or unsupported version" at
|
||||
// file teardown ~50% of runs (all subtests pass; only the file errors). No test
|
||||
// here asserts on stdout, so silence the stdout-writing console methods for the
|
||||
// duration of this file. Restored in `after` for good hygiene.
|
||||
const _console = { log: console.log, info: console.info, warn: console.warn };
|
||||
before(() => {
|
||||
console.log = () => {};
|
||||
console.info = () => {};
|
||||
console.warn = () => {};
|
||||
});
|
||||
after(() => {
|
||||
console.log = _console.log;
|
||||
console.info = _console.info;
|
||||
console.warn = _console.warn;
|
||||
});
|
||||
|
||||
// ── setup-claude profile generation ──────────────────────────────────────────
|
||||
|
||||
test("buildProfileSettings pins the model + base URL + gateway discovery", () => {
|
||||
const cfg = categoriseModel("glm/glm-5.2"); // thinking → effort xhigh
|
||||
const json = JSON.parse(buildProfileSettings("glm/glm-5.2", "http://vps:20128", cfg));
|
||||
assert.equal(json.model, "glm/glm-5.2");
|
||||
assert.equal(json.env.ANTHROPIC_BASE_URL, "http://vps:20128");
|
||||
assert.equal(json.env.ANTHROPIC_MODEL, "glm/glm-5.2");
|
||||
assert.equal(json.env.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY, "1");
|
||||
assert.equal(json.effortLevel, "xhigh");
|
||||
});
|
||||
|
||||
test("buildProfileSettings NEVER writes the auth token to disk", () => {
|
||||
const cfg = categoriseModel("kmc/kimi-k2.7");
|
||||
const raw = buildProfileSettings("kmc/kimi-k2.7", "http://vps:20128", cfg);
|
||||
assert.equal(raw.includes("ANTHROPIC_AUTH_TOKEN"), false);
|
||||
assert.equal(raw.includes("ANTHROPIC_API_KEY"), false);
|
||||
});
|
||||
|
||||
test("buildProfileSettings omits effortLevel for the simple tier", () => {
|
||||
const cfg = categoriseModel("ollamacloud/gemma4:31b"); // simple → no effort
|
||||
const json = JSON.parse(buildProfileSettings("ollamacloud/gemma4:31b", "http://x:20128", cfg));
|
||||
assert.equal("effortLevel" in json, false);
|
||||
});
|
||||
|
||||
test("profile names match setup-codex (cross-CLI consistency)", () => {
|
||||
assert.equal(categoriseModel("glm/glm-5.2").name, "glm52");
|
||||
assert.equal(categoriseModel("kmc/kimi-k2.7").name, "kimi-k27");
|
||||
});
|
||||
|
||||
// #regression: setup-codex.mjs has a fallbackCodexProfile() so live-catalog
|
||||
// models unmatched by the hardcoded categoriseModel() pattern list (glm/kimi/
|
||||
// mimo/…) still get a profile. setup-claude.mjs never got the equivalent,
|
||||
// so ANY catalog not matching those old patterns (e.g. a fresh install with
|
||||
// custom-provider-prefixed models) generates 0 Claude Code profiles.
|
||||
test("fallbackClaudeProfile creates a profile for a live-catalog model unmatched by categoriseModel", () => {
|
||||
assert.equal(categoriseModel("new-provider/future-chat-1"), null);
|
||||
const cfg = fallbackClaudeProfile("new-provider/future-chat-1", {
|
||||
id: "new-provider/future-chat-1",
|
||||
output_modalities: ["text"],
|
||||
});
|
||||
assert.deepEqual(cfg, { name: "new-provider-future-chat-1" });
|
||||
});
|
||||
|
||||
test("fallbackClaudeProfile skips media and non-text models", () => {
|
||||
assert.equal(
|
||||
fallbackClaudeProfile("veo-free/seedance", {
|
||||
id: "veo-free/seedance",
|
||||
output_modalities: ["video"],
|
||||
}),
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test("syncClaudeProfilesFromModels falls back to a generic profile for unmatched catalog models", async () => {
|
||||
const claudeHome = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-claude-fallback-"));
|
||||
try {
|
||||
const result = await syncClaudeProfilesFromModels(
|
||||
[{ id: "new-provider/future-chat-1", output_modalities: ["text"] }],
|
||||
{ claudeHome, baseUrl: "http://vps:20128" }
|
||||
);
|
||||
|
||||
assert.equal(result.written, 1);
|
||||
assert.equal(result.skipped, 0);
|
||||
|
||||
const settingsPath = path.join(
|
||||
claudeHome,
|
||||
"profiles",
|
||||
"new-provider-future-chat-1",
|
||||
"settings.json"
|
||||
);
|
||||
const json = JSON.parse(await fs.readFile(settingsPath, "utf8"));
|
||||
assert.equal(json.model, "new-provider/future-chat-1");
|
||||
assert.equal(json.env.ANTHROPIC_BASE_URL, "http://vps:20128");
|
||||
// No effort tier for the generic fallback — effortLevel must be omitted.
|
||||
assert.equal("effortLevel" in json, false);
|
||||
} finally {
|
||||
await fs.rm(claudeHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("syncClaudeProfilesFromModels writes directory-per-profile settings + threads baseUrl, skips non-ids", async () => {
|
||||
const claudeHome = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-claude-profiles-"));
|
||||
try {
|
||||
const result = await syncClaudeProfilesFromModels([{ id: "glm/glm-5.2" }, { id: "" }], {
|
||||
claudeHome,
|
||||
baseUrl: "http://vps:20128",
|
||||
});
|
||||
|
||||
assert.equal(result.written, 1);
|
||||
assert.equal(result.skipped, 1);
|
||||
assert.deepEqual(
|
||||
result.profiles.map((p) => p.name),
|
||||
["glm52"]
|
||||
);
|
||||
|
||||
// Directory-per-profile: <claudeHome>/profiles/<name>/settings.json
|
||||
const settingsPath = path.join(claudeHome, "profiles", "glm52", "settings.json");
|
||||
const json = JSON.parse(await fs.readFile(settingsPath, "utf8"));
|
||||
assert.equal(json.model, "glm/glm-5.2");
|
||||
assert.equal(json.env.ANTHROPIC_BASE_URL, "http://vps:20128");
|
||||
assert.equal(json.env.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY, "1");
|
||||
// The auth token must never be written to disk.
|
||||
assert.equal(JSON.stringify(json).includes("ANTHROPIC_AUTH_TOKEN"), false);
|
||||
} finally {
|
||||
await fs.rm(claudeHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("syncClaudeProfilesFromModels dry-run writes nothing and reports via the injected log (#5959)", async () => {
|
||||
const claudeHome = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-claude-dry-"));
|
||||
// The collector keeps the dry-run's multi-byte "──" heading OFF this child
|
||||
// process's stdout: under the node:test runner that write corrupts the V8
|
||||
// serialization stream ~50% of runs (#5959, "Unable to deserialize cloned
|
||||
// data due to invalid or unsupported version").
|
||||
const lines: string[] = [];
|
||||
try {
|
||||
const result = await syncClaudeProfilesFromModels([{ id: "glm/glm-5.2" }], {
|
||||
claudeHome,
|
||||
baseUrl: "http://vps:20128",
|
||||
dryRun: true,
|
||||
log: (line: string) => lines.push(line),
|
||||
});
|
||||
assert.equal(result.written, 1);
|
||||
// Dry-run reports the would-be file + its content through the log sink…
|
||||
assert.equal(lines.length, 2);
|
||||
const settingsPath = path.join(claudeHome, "profiles", "glm52", "settings.json");
|
||||
assert.ok(lines[0].includes(settingsPath));
|
||||
const printed = JSON.parse(lines[1]);
|
||||
assert.equal(printed.model, "glm/glm-5.2");
|
||||
assert.equal(printed.env.ANTHROPIC_BASE_URL, "http://vps:20128");
|
||||
// …and writes nothing to disk.
|
||||
await assert.rejects(fs.stat(settingsPath), /ENOENT/);
|
||||
} finally {
|
||||
await fs.rm(claudeHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// ── launch env (Claude Code) ─────────────────────────────────────────────────
|
||||
|
||||
test("buildClaudeEnv still accepts a bare port (backward compatible)", () => {
|
||||
const env = buildClaudeEnv({ PATH: "/bin" }, 20128, "secret");
|
||||
assert.equal(env.ANTHROPIC_BASE_URL, "http://localhost:20128");
|
||||
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "secret");
|
||||
assert.equal(env.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY, "1");
|
||||
});
|
||||
|
||||
test("buildClaudeEnv accepts a full base URL and strips /v1", () => {
|
||||
const env = buildClaudeEnv({}, "https://vps.example.com:20128/v1", "t");
|
||||
assert.equal(env.ANTHROPIC_BASE_URL, "https://vps.example.com:20128");
|
||||
});
|
||||
|
||||
test("buildClaudeEnv sets CLAUDE_CONFIG_DIR for a profile", () => {
|
||||
const env = buildClaudeEnv({}, 20128, "t", { configDir: "/home/u/.claude/profiles/glm52" });
|
||||
assert.equal(env.CLAUDE_CONFIG_DIR, "/home/u/.claude/profiles/glm52");
|
||||
});
|
||||
|
||||
test("buildClaudeEnv strips inherited ANTHROPIC_* and does not mutate input", () => {
|
||||
const input = { ANTHROPIC_API_KEY: "leak", PATH: "/bin" };
|
||||
const env = buildClaudeEnv(input, 20128, "x");
|
||||
assert.equal(env.ANTHROPIC_API_KEY, undefined);
|
||||
assert.equal(input.ANTHROPIC_API_KEY, "leak");
|
||||
});
|
||||
|
||||
test("resolveLaunchTarget: explicit --remote wins, strips /v1", () => {
|
||||
const { baseUrl } = resolveLaunchTarget({ remote: "https://vps:20128/v1" });
|
||||
assert.equal(baseUrl, "https://vps:20128");
|
||||
});
|
||||
|
||||
test("resolveLaunchTarget: explicit token wins over everything", () => {
|
||||
const { authToken } = resolveLaunchTarget({ remote: "http://x:20128", token: "tok-explicit" });
|
||||
assert.equal(authToken, "tok-explicit");
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
buildClineGlobalState,
|
||||
buildClineSecrets,
|
||||
resolveClineTarget,
|
||||
} from "../../../bin/cli/commands/setup-cline.mjs";
|
||||
|
||||
test("buildClineGlobalState sets the openai provider for Plan + Act, root base URL, model", () => {
|
||||
const gs = buildClineGlobalState({}, { baseUrl: "http://vps:20128", model: "glm/glm-5.2" });
|
||||
assert.equal(gs.actModeApiProvider, "openai");
|
||||
assert.equal(gs.planModeApiProvider, "openai");
|
||||
assert.equal(gs.openAiBaseUrl, "http://vps:20128");
|
||||
assert.equal(gs.openAiModelId, "glm/glm-5.2");
|
||||
assert.equal(gs.planModeOpenAiModelId, "glm/glm-5.2");
|
||||
});
|
||||
|
||||
test("buildClineGlobalState merges (preserves unrelated existing keys)", () => {
|
||||
const gs = buildClineGlobalState(
|
||||
{ telemetrySetting: "off", taskHistory: [1, 2, 3] },
|
||||
{ baseUrl: "http://x:20128", model: "m" }
|
||||
);
|
||||
assert.equal(gs.telemetrySetting, "off");
|
||||
assert.deepEqual(gs.taskHistory, [1, 2, 3]);
|
||||
assert.equal(gs.openAiBaseUrl, "http://x:20128");
|
||||
});
|
||||
|
||||
test("buildClineSecrets stores the key (separate secrets file), preserving others", () => {
|
||||
const sec = buildClineSecrets({ anthropicApiKey: "keepme" }, { apiKey: "sk-omni" });
|
||||
assert.equal(sec.openAiApiKey, "sk-omni");
|
||||
assert.equal(sec.anthropicApiKey, "keepme");
|
||||
});
|
||||
|
||||
test("buildClineSecrets falls back to a placeholder when no key", () => {
|
||||
assert.equal(buildClineSecrets({}, { apiKey: "" }).openAiApiKey, "sk_omniroute");
|
||||
});
|
||||
|
||||
test("resolveClineTarget strips /v1 from --remote (Cline wants the ROOT url)", () => {
|
||||
const { baseUrl } = resolveClineTarget({ remote: "http://vps:20128/v1/" });
|
||||
assert.equal(baseUrl, "http://vps:20128");
|
||||
});
|
||||
|
||||
test("resolveClineTarget: explicit --api-key wins", () => {
|
||||
const { apiKey } = resolveClineTarget({ remote: "http://x:20128", apiKey: "sk-explicit" });
|
||||
assert.equal(apiKey, "sk-explicit");
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import {
|
||||
fallbackCodexProfile,
|
||||
isCodexCompatibleTextModel,
|
||||
syncCodexProfilesFromModels,
|
||||
} from "../../../bin/cli/commands/setup-codex.mjs";
|
||||
|
||||
test("fallbackCodexProfile creates profiles for compatible live catalog models", () => {
|
||||
const cfg = fallbackCodexProfile("new-provider/future-chat-1", {
|
||||
id: "new-provider/future-chat-1",
|
||||
context_length: 250000,
|
||||
max_output_tokens: 65536,
|
||||
output_modalities: ["text"],
|
||||
});
|
||||
|
||||
assert.deepEqual(cfg, {
|
||||
name: "new-provider-future-chat-1",
|
||||
ctx: 250000,
|
||||
compact: 212500,
|
||||
summary: false,
|
||||
toolLimit: 32768,
|
||||
});
|
||||
});
|
||||
|
||||
test("fallbackCodexProfile skips media and non-text models", () => {
|
||||
assert.equal(
|
||||
isCodexCompatibleTextModel({
|
||||
id: "antigravity/gemini-3.1-flash-image",
|
||||
type: "image",
|
||||
output_modalities: ["image"],
|
||||
}),
|
||||
false
|
||||
);
|
||||
assert.equal(
|
||||
fallbackCodexProfile("veo-free/seedance", {
|
||||
id: "veo-free/seedance",
|
||||
name: "Seedance",
|
||||
context_length: 128000,
|
||||
}),
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test("syncCodexProfilesFromModels writes compatible profiles and skips media", async () => {
|
||||
const codexHome = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-codex-profiles-"));
|
||||
try {
|
||||
const result = await syncCodexProfilesFromModels(
|
||||
[
|
||||
{
|
||||
id: "new-provider/future-chat-1",
|
||||
context_length: 250000,
|
||||
output_modalities: ["text"],
|
||||
},
|
||||
{
|
||||
id: "video-provider/seedance",
|
||||
type: "video",
|
||||
output_modalities: ["video"],
|
||||
},
|
||||
],
|
||||
{ codexHome }
|
||||
);
|
||||
|
||||
assert.equal(result.written, 1);
|
||||
assert.equal(result.skipped, 1);
|
||||
const content = await fs.readFile(
|
||||
path.join(codexHome, "new-provider-future-chat-1.config.toml"),
|
||||
"utf8"
|
||||
);
|
||||
assert.match(content, /model\s+= "new-provider\/future-chat-1"/);
|
||||
await assert.rejects(
|
||||
fs.stat(path.join(codexHome, "video-provider-seedance.config.toml")),
|
||||
/ENOENT/
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(codexHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
buildContinueModels,
|
||||
mergeContinueConfig,
|
||||
resolveContinueTarget,
|
||||
} from "../../../bin/cli/commands/setup-continue.mjs";
|
||||
|
||||
test("buildContinueModels emits provider:openai + apiBase + secret ref + roles", () => {
|
||||
const models = buildContinueModels(["glm/glm-5.2"], "http://vps:20128/v1");
|
||||
assert.equal(models.length, 1);
|
||||
const m = models[0];
|
||||
assert.equal(m.provider, "openai");
|
||||
assert.equal(m.model, "glm/glm-5.2");
|
||||
assert.equal(m.apiBase, "http://vps:20128/v1");
|
||||
assert.equal(m.apiKey, "${{ secrets.OMNIROUTE_API_KEY }}");
|
||||
assert.ok(m.roles.includes("chat") && m.roles.includes("edit") && m.roles.includes("apply"));
|
||||
});
|
||||
|
||||
test("buildContinueModels gives the fast tier an autocomplete role", () => {
|
||||
const fast = buildContinueModels(["glm/glm-5-turbo"], "http://x/v1")[0]; // fast → effort low
|
||||
assert.ok(fast.roles.includes("autocomplete"));
|
||||
});
|
||||
|
||||
test("buildContinueModels skips uncategorised models", () => {
|
||||
assert.equal(buildContinueModels(["some/unknown-model"], "http://x/v1").length, 0);
|
||||
});
|
||||
|
||||
test("mergeContinueConfig replaces prior OmniRoute models, keeps others", () => {
|
||||
const existing = {
|
||||
name: "My Config",
|
||||
models: [
|
||||
{ name: "Local Ollama", provider: "ollama", model: "llama3", apiBase: "http://localhost:11434" },
|
||||
{ name: "old omni", provider: "openai", model: "x", apiBase: "http://vps:20128/v1" },
|
||||
],
|
||||
};
|
||||
const fresh = buildContinueModels(["glm/glm-5.2"], "http://vps:20128/v1");
|
||||
const merged = mergeContinueConfig(existing, fresh, "http://vps:20128/v1");
|
||||
// kept the ollama model; dropped the old omni one (same apiBase); added the new
|
||||
const apiBases = merged.models.map((m) => m.apiBase);
|
||||
assert.ok(merged.models.some((m) => m.provider === "ollama"));
|
||||
assert.equal(merged.models.filter((m) => m.apiBase === "http://vps:20128/v1").length, 1);
|
||||
assert.equal(merged.name, "My Config", "preserves existing top-level keys");
|
||||
});
|
||||
|
||||
test("mergeContinueConfig sets defaults on an empty config", () => {
|
||||
const merged = mergeContinueConfig({}, buildContinueModels(["glm/glm-5.2"], "http://x/v1"), "http://x/v1");
|
||||
assert.equal(merged.schema, "v1");
|
||||
assert.ok(merged.name && merged.version);
|
||||
});
|
||||
|
||||
test("resolveContinueTarget ensures /v1 on apiBase", () => {
|
||||
assert.equal(resolveContinueTarget({ remote: "http://vps:20128" }).apiBase, "http://vps:20128/v1");
|
||||
assert.equal(resolveContinueTarget({ remote: "http://vps:20128/v1/" }).apiBase, "http://vps:20128/v1");
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveCrushTarget, buildCrushProvider, mergeCrushConfig } from "../../../bin/cli/commands/setup-crush.mjs";
|
||||
|
||||
test("resolveCrushTarget ensures /v1", () => {
|
||||
assert.equal(resolveCrushTarget({ remote: "http://vps:20128" }).baseUrl, "http://vps:20128/v1");
|
||||
});
|
||||
test("resolveCrushTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveCrushTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
|
||||
});
|
||||
test("buildCrushProvider emits openai-compat + env-ref key + curated models w/ context_window", () => {
|
||||
const p = buildCrushProvider(["glm/glm-5.2", "some/unknown"], "http://vps:20128/v1");
|
||||
assert.equal(p.type, "openai-compat");
|
||||
assert.equal(p.base_url, "http://vps:20128/v1");
|
||||
assert.equal(p.api_key, "$OMNIROUTE_API_KEY");
|
||||
assert.equal(p.models.length, 1); // unknown skipped
|
||||
assert.equal(p.models[0].id, "glm/glm-5.2");
|
||||
assert.ok(p.models[0].context_window > 0);
|
||||
});
|
||||
test("mergeCrushConfig adds providers.omniroute, preserves the rest", () => {
|
||||
const m = mergeCrushConfig({ options: { tui: true }, providers: { local: {} } }, buildCrushProvider(["glm/glm-5.2"], "http://x/v1"));
|
||||
assert.deepEqual(m.options, { tui: true });
|
||||
assert.ok(m.providers.local);
|
||||
assert.ok(m.providers.omniroute);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveCursorTarget, buildCursorInstructions } from "../../../bin/cli/commands/setup-cursor.mjs";
|
||||
|
||||
test("resolveCursorTarget ensures /v1 (Cursor appends /chat/completions)", () => {
|
||||
assert.equal(resolveCursorTarget({ remote: "http://vps:20128" }).apiBase, "http://vps:20128/v1");
|
||||
assert.equal(resolveCursorTarget({ remote: "http://vps:20128/v1/" }).apiBase, "http://vps:20128/v1");
|
||||
});
|
||||
|
||||
test("resolveCursorTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveCursorTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
|
||||
});
|
||||
|
||||
test("buildCursorInstructions includes the base URL, /v1 note, and model samples", () => {
|
||||
const txt = buildCursorInstructions({ apiBase: "http://vps:20128/v1", models: ["glm/glm-5.2", "kmc/kimi-k2.7"] });
|
||||
assert.ok(txt.includes("http://vps:20128/v1"));
|
||||
assert.ok(txt.includes("Override OpenAI Base URL"));
|
||||
assert.ok(txt.includes("glm/glm-5.2"));
|
||||
assert.ok(/chat panel only/i.test(txt));
|
||||
});
|
||||
|
||||
test("buildCursorInstructions falls back to sample models when none given", () => {
|
||||
const txt = buildCursorInstructions({ apiBase: "http://x/v1", models: [] });
|
||||
assert.ok(txt.includes("glm/glm-5.2"));
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveGooseTarget, buildGooseConfig, buildGooseEnvRecipe } from "../../../bin/cli/commands/setup-goose.mjs";
|
||||
|
||||
test("resolveGooseTarget strips /v1 (Goose appends it)", () => {
|
||||
assert.equal(resolveGooseTarget({ remote: "http://vps:20128/v1/" }).host, "http://vps:20128");
|
||||
assert.equal(resolveGooseTarget({ remote: "http://vps:20128" }).host, "http://vps:20128");
|
||||
});
|
||||
test("resolveGooseTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveGooseTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
|
||||
});
|
||||
test("buildGooseConfig sets GOOSE_PROVIDER/MODEL + OPENAI_HOST (root), preserves rest", () => {
|
||||
const c = buildGooseConfig({ GOOSE_MODE: "auto" }, { host: "http://vps:20128", model: "glm/glm-5.2" });
|
||||
assert.equal(c.GOOSE_PROVIDER, "openai");
|
||||
assert.equal(c.GOOSE_MODEL, "glm/glm-5.2");
|
||||
assert.equal(c.OPENAI_HOST, "http://vps:20128");
|
||||
assert.equal(c.GOOSE_MODE, "auto");
|
||||
});
|
||||
test("buildGooseEnvRecipe references the env key (secret off disk)", () => {
|
||||
const r = buildGooseEnvRecipe({ host: "http://vps:20128", model: "m" });
|
||||
assert.ok(r.includes("OPENAI_HOST=http://vps:20128"));
|
||||
assert.ok(r.includes("OPENAI_API_KEY=$OMNIROUTE_API_KEY"));
|
||||
assert.ok(r.includes("GOOSE_PROVIDER=openai"));
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
buildKiloAuth,
|
||||
buildKiloVscodeSettings,
|
||||
resolveKiloTarget,
|
||||
} from "../../../bin/cli/commands/setup-kilo.mjs";
|
||||
|
||||
test("buildKiloAuth sets the openai-compatible provider (baseUrl WITH /v1, model)", () => {
|
||||
const auth = buildKiloAuth({}, { apiKey: "sk-x", baseUrl: "http://vps:20128/v1", model: "glm/glm-5.2" });
|
||||
assert.equal(auth["openai-compatible"].apiKey, "sk-x");
|
||||
assert.equal(auth["openai-compatible"].baseUrl, "http://vps:20128/v1");
|
||||
assert.equal(auth["openai-compatible"].model, "glm/glm-5.2");
|
||||
});
|
||||
|
||||
test("buildKiloAuth merges (preserves other providers/keys)", () => {
|
||||
const auth = buildKiloAuth({ anthropic: { apiKey: "keep" } }, { apiKey: "k", baseUrl: "http://x/v1", model: "m" });
|
||||
assert.equal(auth.anthropic.apiKey, "keep");
|
||||
assert.equal(auth["openai-compatible"].model, "m");
|
||||
});
|
||||
|
||||
test("buildKiloAuth falls back to a placeholder key", () => {
|
||||
const auth = buildKiloAuth({}, { apiKey: "", baseUrl: "http://x/v1", model: "m" });
|
||||
assert.equal(auth["openai-compatible"].apiKey, "sk_omniroute");
|
||||
});
|
||||
|
||||
test("buildKiloVscodeSettings sets kilocode.customProvider + defaultModel, preserving others", () => {
|
||||
const s = buildKiloVscodeSettings(
|
||||
{ "editor.fontSize": 14 },
|
||||
{ apiKey: "k", baseUrl: "http://vps:20128/v1", model: "glm/glm-5.2" }
|
||||
);
|
||||
assert.equal(s["editor.fontSize"], 14);
|
||||
assert.equal(s["kilocode.customProvider"].name, "OmniRoute");
|
||||
assert.equal(s["kilocode.customProvider"].baseURL, "http://vps:20128/v1");
|
||||
assert.equal(s["kilocode.defaultModel"], "glm/glm-5.2");
|
||||
});
|
||||
|
||||
test("resolveKiloTarget ensures /v1 on the base URL (Kilo wants it)", () => {
|
||||
assert.equal(resolveKiloTarget({ remote: "http://vps:20128" }).baseUrl, "http://vps:20128/v1");
|
||||
assert.equal(resolveKiloTarget({ remote: "http://vps:20128/v1/" }).baseUrl, "http://vps:20128/v1");
|
||||
});
|
||||
|
||||
test("resolveKiloTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveKiloTarget({ remote: "http://x:20128", apiKey: "sk-explicit" }).apiKey, "sk-explicit");
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
postProcessOpencodeConfig,
|
||||
resolveOpencodeTarget,
|
||||
} from "../../../bin/cli/commands/setup-opencode.mjs";
|
||||
|
||||
const RAW = JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
provider: {
|
||||
omniroute: {
|
||||
name: "OmniRoute",
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
options: { baseURL: "http://vps:20128/v1", apiKey: "sk-secret-literal" },
|
||||
models: {
|
||||
"glm/glm-5.2": { name: "glm/glm-5.2", limit: { context: 131072, output: 32768 } },
|
||||
"kmc/kimi-k2.7": { name: "kmc/kimi-k2.7", limit: { context: 131072 } },
|
||||
"openai/gpt-4o": { name: "openai/gpt-4o", limit: { context: 128000 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
test("postProcessOpencodeConfig replaces the literal API key with an env ref (no secret on disk)", () => {
|
||||
const { json } = postProcessOpencodeConfig(RAW);
|
||||
assert.equal(json.includes("sk-secret-literal"), false);
|
||||
const cfg = JSON.parse(json);
|
||||
assert.equal(cfg.provider.omniroute.options.apiKey, "{env:OMNIROUTE_API_KEY}");
|
||||
assert.equal(cfg.provider.omniroute.options.baseURL, "http://vps:20128/v1");
|
||||
});
|
||||
|
||||
test("postProcessOpencodeConfig keeps all models by default", () => {
|
||||
const { modelCount } = postProcessOpencodeConfig(RAW);
|
||||
assert.equal(modelCount, 3);
|
||||
});
|
||||
|
||||
test("postProcessOpencodeConfig --only filters the model map by substring", () => {
|
||||
const { json, modelCount } = postProcessOpencodeConfig(RAW, { only: ["glm", "kimi"] });
|
||||
const cfg = JSON.parse(json);
|
||||
assert.equal(modelCount, 2);
|
||||
assert.ok(cfg.provider.omniroute.models["glm/glm-5.2"]);
|
||||
assert.ok(cfg.provider.omniroute.models["kmc/kimi-k2.7"]);
|
||||
assert.equal("openai/gpt-4o" in cfg.provider.omniroute.models, false);
|
||||
});
|
||||
|
||||
test("postProcessOpencodeConfig preserves $schema, provider name and npm", () => {
|
||||
const { json } = postProcessOpencodeConfig(RAW);
|
||||
const cfg = JSON.parse(json);
|
||||
assert.equal(cfg.$schema, "https://opencode.ai/config.json");
|
||||
assert.equal(cfg.provider.omniroute.npm, "@ai-sdk/openai-compatible");
|
||||
});
|
||||
|
||||
test("resolveOpencodeTarget: --remote wins and trailing slashes are trimmed", () => {
|
||||
const { baseUrl } = resolveOpencodeTarget({ remote: "http://vps:20128/" });
|
||||
assert.equal(baseUrl, "http://vps:20128");
|
||||
});
|
||||
|
||||
test("resolveOpencodeTarget: explicit --api-key wins", () => {
|
||||
const { apiKey } = resolveOpencodeTarget({ remote: "http://x:20128", apiKey: "sk-explicit" });
|
||||
assert.equal(apiKey, "sk-explicit");
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveQwenTarget, buildQwenSettings } from "../../../bin/cli/commands/setup-qwen.mjs";
|
||||
|
||||
test("resolveQwenTarget ensures /v1", () => {
|
||||
assert.equal(resolveQwenTarget({ remote: "http://vps:20128" }).baseUrl, "http://vps:20128/v1");
|
||||
});
|
||||
test("resolveQwenTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveQwenTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
|
||||
});
|
||||
test("buildQwenSettings adds an openai modelProvider (baseUrl /v1, envKey), sets model", () => {
|
||||
const s = buildQwenSettings({}, { baseUrl: "http://vps:20128/v1", model: "glm/glm-5.2" });
|
||||
const p = s.modelProviders.find((x) => x.id === "omniroute");
|
||||
assert.equal(p.authType, "openai");
|
||||
assert.equal(p.baseUrl, "http://vps:20128/v1");
|
||||
assert.equal(p.envKey, "OMNIROUTE_API_KEY");
|
||||
assert.equal(s.model, "glm/glm-5.2");
|
||||
assert.equal(s.selectedProvider, "omniroute");
|
||||
});
|
||||
test("buildQwenSettings de-dupes the omniroute provider + preserves others", () => {
|
||||
const s = buildQwenSettings(
|
||||
{ modelProviders: [{ id: "other" }, { id: "omniroute", baseUrl: "old" }], theme: "dark" },
|
||||
{ baseUrl: "http://x/v1", model: "m" }
|
||||
);
|
||||
assert.equal(s.modelProviders.filter((p) => p.id === "omniroute").length, 1);
|
||||
assert.ok(s.modelProviders.some((p) => p.id === "other"));
|
||||
assert.equal(s.theme, "dark");
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveRooTarget, buildRooImport, buildRooVscodeAutoImport } from "../../../bin/cli/commands/setup-roo.mjs";
|
||||
|
||||
test("resolveRooTarget ensures /v1 on the base URL", () => {
|
||||
assert.equal(resolveRooTarget({ remote: "http://vps:20128" }).baseUrl, "http://vps:20128/v1");
|
||||
});
|
||||
test("resolveRooTarget: explicit --api-key wins", () => {
|
||||
assert.equal(resolveRooTarget({ remote: "http://x:20128", apiKey: "sk-x" }).apiKey, "sk-x");
|
||||
});
|
||||
test("buildRooImport produces an openai-compatible provider profile (baseUrl /v1, model)", () => {
|
||||
const d = buildRooImport({ baseUrl: "http://vps:20128/v1", apiKey: "k", model: "glm/glm-5.2" });
|
||||
const cfg = d.providerProfiles.apiConfigs.OmniRoute;
|
||||
assert.equal(cfg.apiProvider, "openai");
|
||||
assert.equal(cfg.openAiBaseUrl, "http://vps:20128/v1");
|
||||
assert.equal(cfg.openAiModelId, "glm/glm-5.2");
|
||||
assert.equal(d.providerProfiles.currentApiConfigName, "OmniRoute");
|
||||
});
|
||||
test("buildRooImport falls back to a placeholder key", () => {
|
||||
assert.equal(buildRooImport({ baseUrl: "http://x/v1", apiKey: "", model: "m" }).providerProfiles.apiConfigs.OmniRoute.openAiApiKey, "sk_omniroute");
|
||||
});
|
||||
test("buildRooVscodeAutoImport sets the pointer, preserving other settings", () => {
|
||||
const s = buildRooVscodeAutoImport({ "editor.tabSize": 2 }, "/home/u/.omniroute/roo-settings.json");
|
||||
assert.equal(s["editor.tabSize"], 2);
|
||||
assert.equal(s["roo-cline.autoImportSettingsPath"], "/home/u/.omniroute/roo-settings.json");
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { buildMenuItems, isTraySupported } from "../../../bin/cli/tray/tray.ts";
|
||||
|
||||
test("buildMenuItems contains expected entries", () => {
|
||||
const items = buildMenuItems({ port: 20128, autostartEnabled: false });
|
||||
const titles = items.map((i) => i.title);
|
||||
assert.ok(titles.includes("Open OmniRoute Dashboard"), "has Open entry");
|
||||
assert.ok(
|
||||
titles.some((t) => t.startsWith("Port: 20128")),
|
||||
"shows port"
|
||||
);
|
||||
assert.ok(titles.includes("Enable Autostart"), "shows toggle when disabled");
|
||||
assert.ok(titles.includes("Quit OmniRoute"), "has quit");
|
||||
});
|
||||
|
||||
test("buildMenuItems shows Disable Autostart when enabled", () => {
|
||||
const items = buildMenuItems({ port: 3000, autostartEnabled: true });
|
||||
const titles = items.map((i) => i.title);
|
||||
assert.ok(titles.includes("Disable Autostart"));
|
||||
assert.ok(!titles.includes("Enable Autostart"));
|
||||
});
|
||||
|
||||
test("isTraySupported returns false on linux without DISPLAY", () => {
|
||||
if (process.platform !== "linux") return;
|
||||
const originalDisplay = process.env.DISPLAY;
|
||||
delete process.env.DISPLAY;
|
||||
try {
|
||||
assert.equal(isTraySupported(), false);
|
||||
} finally {
|
||||
if (originalDisplay) process.env.DISPLAY = originalDisplay;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user