fix(settings): restore CLI fingerprint toggles (#1863)

Integrated into release/v3.7.8
This commit is contained in:
Raxxoor
2026-05-02 00:24:16 +01:00
committed by GitHub
parent 399a911675
commit a10a4bf491
6 changed files with 150 additions and 47 deletions

64
package-lock.json generated
View File

@@ -33,7 +33,7 @@
"lowdb": "^7.0.1",
"monaco-editor": "^0.55.1",
"next": "^16.2.3",
"next-intl": "^4.8.3",
"next-intl": "^4.11.0",
"node-machine-id": "^1.1.12",
"open": "^11.0.0",
"ora": "^9.1.0",
@@ -1409,18 +1409,18 @@
"license": "MIT"
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.5.tgz",
"integrity": "sha512-ASMon8BNlKHgQQpZx84xI80EXRS90GlsEU4wEulCKCzrMtUdrfEvFc9UEYmRbvEvtFQLZ4qHXnisUy6PuFjwyA==",
"version": "3.5.6",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.6.tgz",
"integrity": "sha512-04ZjRIeQCnR/h32wBP9/S7rkyy1hLAs2fXJcNwc7hseJd//K9TMBqK0ukb4dXqnALKQ9m5ruZeOD2qqEkK9ixg==",
"license": "MIT",
"dependencies": {
"@formatjs/icu-skeleton-parser": "2.1.5"
"@formatjs/icu-skeleton-parser": "2.1.6"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.5.tgz",
"integrity": "sha512-9Kc6tMaAPZKTGevdfcvx5zT3v4BTfamo+djJE29wF6ds1QLhoA09MZNDpWMZaebWzuoOTIXhDvgmqmjSlUOGlw==",
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.6.tgz",
"integrity": "sha512-9f1VQ2kaaLHK0WPU1OrAmiNKCKJwyoDmwNzQXbUa6XtFBOgHZ4YZURE8sSedHmMr0kvpB75OtplB0hMYkfdwfg==",
"license": "MIT"
},
"node_modules/@formatjs/intl-localematcher": {
@@ -8634,9 +8634,9 @@
}
},
"node_modules/icu-minify": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.2.tgz",
"integrity": "sha512-MYt1aKn6Hrag6KO1gns+2XUjguLnL4lUxnHZXCStB2hdml+ooYzMW1VnOkrwKg5aTh5fGjvfZsgJ9DYC21lTmg==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.11.0.tgz",
"integrity": "sha512-XRvblCwLqWXio5ZLcmDqXvJv7alSACK6UjXuuMOdQWB//d25AQX6xlVlI1FEbc3Q6iPLXXo6HaVLn8LcAFhn1Q==",
"funding": [
{
"type": "individual",
@@ -8767,15 +8767,21 @@
}
},
"node_modules/intl-messageformat": {
"version": "11.2.2",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.2.tgz",
"integrity": "sha512-yUfyIkPGqMvvk2onw2xBJeLsjXdiYUYebR8mmZVQYBuZUJsFGVht48Ftm1khgu8EZ0n+izX4rAEj3fLAilkh9g==",
"version": "11.2.3",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.3.tgz",
"integrity": "sha512-kZthTU+3WLcoWoRg5j6LOkN1TeUBtmkX0OIwSAbcHVIfQAEbGVdmANM8u6GL3eUDOqLwheYoXMUshAh1UdeXlQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@formatjs/fast-memoize": "3.1.2",
"@formatjs/icu-messageformat-parser": "3.5.5"
"@formatjs/fast-memoize": "3.1.3",
"@formatjs/icu-messageformat-parser": "3.5.6"
}
},
"node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.3.tgz",
"integrity": "sha512-Ocd1vPuD68rW6BJDuAOtnnc1GPeVepY5kZXML1psGVFQ+1Q8CfkftT3Tnam+Mxx97Pz08jIEDCotl/GV+Naccg==",
"license": "MIT"
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
@@ -11160,9 +11166,9 @@
}
},
"node_modules/next-intl": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.2.tgz",
"integrity": "sha512-AZoMRsVGLZczB2hisq1OTWmNAYAKwk/jaWH4+9pfl5TCG8kbILZZptZHux9zw7DyN1yzh6X7jmaQvoykHs9Y7Q==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.11.0.tgz",
"integrity": "sha512-Chp8rgEVUYOX/bCtYy+PXH6lDX3X+GPT9sR9HScHroL283em/4urP9btfdHEMEHJJXdq2W/5wDaDDtWONPdNSA==",
"funding": [
{
"type": "individual",
@@ -11174,11 +11180,11 @@
"@formatjs/intl-localematcher": "^0.8.1",
"@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2",
"icu-minify": "^4.9.2",
"icu-minify": "^4.11.0",
"negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "^4.9.2",
"next-intl-swc-plugin-extractor": "^4.11.0",
"po-parser": "^2.1.1",
"use-intl": "^4.9.2"
"use-intl": "^4.11.0"
},
"peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -11191,9 +11197,9 @@
}
},
"node_modules/next-intl-swc-plugin-extractor": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.2.tgz",
"integrity": "sha512-VMlEe18tvYgl+2Fl+/hOKckm0Z0EZooBLt3nFjDMva+gopFwZYhNeLBDrc/R8rulWvwBxz8LN+uKPFSQ0fFJ4A==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.11.0.tgz",
"integrity": "sha512-WUGBSxGNd8eQ0rAsJHFmRw2H7+SZAXQIY/HAnYM57JaUsj5D2vx4KOz4zFtXlyKDtsw9awHfgWVvBae2/RDF9A==",
"license": "MIT"
},
"node_modules/next/node_modules/@swc/helpers": {
@@ -14346,9 +14352,9 @@
}
},
"node_modules/use-intl": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.2.tgz",
"integrity": "sha512-/FCW/P/wJCv6oGTUOWLNvv+i2uowOg7r/1HAZSBfwbcNoBe5tzXgh9uZaoEKaRl9t7MnZqrDNgEJoYd+1ko1oQ==",
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.11.0.tgz",
"integrity": "sha512-7ILhTLuo3fnSKhoTGDk5X9591pjtWr6qB4inrlvGkN9OEyKhoiG73GZFoLSs68wz3BsSGtoWa62iWvrYEYU+iA==",
"funding": [
{
"type": "individual",
@@ -14359,7 +14365,7 @@
"dependencies": {
"@formatjs/fast-memoize": "^3.1.0",
"@schummar/icu-type-parser": "1.21.5",
"icu-minify": "^4.9.2",
"icu-minify": "^4.11.0",
"intl-messageformat": "^11.1.0"
},
"peerDependencies": {

View File

@@ -127,7 +127,7 @@
"lowdb": "^7.0.1",
"monaco-editor": "^0.55.1",
"next": "^16.2.3",
"next-intl": "^4.8.3",
"next-intl": "^4.11.0",
"node-machine-id": "^1.1.12",
"open": "^11.0.0",
"ora": "^9.1.0",

View File

@@ -403,8 +403,8 @@ export default function AgentsPage() {
<span className="material-symbols-outlined text-[14px] text-text-muted">fingerprint</span>
<p className="text-xs text-text-muted">
{t("fingerprintSettingsHint")}{" "}
<Link href="/dashboard/settings" className="text-primary hover:underline">
{t("openSettings")}
<Link href="/dashboard/settings?tab=routing" className="text-primary hover:underline">
{t("settingsRoutingLink")}
</Link>
</p>
</div>

View File

@@ -1,9 +1,14 @@
"use client";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Button, Card } from "@/shared/components";
import { useTranslations } from "next-intl";
import FallbackChainsEditor from "./FallbackChainsEditor";
import {
CLI_COMPAT_PROVIDER_DISPLAY,
CLI_COMPAT_TOGGLE_IDS,
normalizeCliCompatProviderId,
} from "@/shared/constants/cliCompatProviders";
export default function RoutingTab() {
const [settings, setSettings] = useState<any>({
@@ -40,6 +45,26 @@ export default function RoutingTab() {
}
};
const cliCompatProviders = useMemo(
() =>
Array.isArray(settings.cliCompatProviders)
? settings.cliCompatProviders.map((providerId: string) => normalizeCliCompatProviderId(providerId))
: [],
[settings.cliCompatProviders]
);
const cliCompatProviderSet = useMemo(() => new Set(cliCompatProviders), [cliCompatProviders]);
const toggleCliCompatProvider = (providerId: string, enabled: boolean) => {
const normalizedProviderId = normalizeCliCompatProviderId(providerId);
const nextProviders = new Set(cliCompatProviders);
if (enabled) {
nextProviders.add(normalizedProviderId);
} else {
nextProviders.delete(normalizedProviderId);
}
updateSetting({ cliCompatProviders: Array.from(nextProviders) });
};
return (
<div className="flex flex-col gap-6">
<Card>
@@ -224,6 +249,62 @@ export default function RoutingTab() {
</div>
</Card>
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-indigo-500/10 text-indigo-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
fingerprint
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("cliFingerprint")}</h3>
<p className="text-sm text-text-muted">{t("cliFingerprintDesc")}</p>
<p className="mt-1 text-xs text-text-muted">
{t("cliFingerprintEnabled", { count: cliCompatProviderSet.size })}
</p>
</div>
</div>
<div className="grid gap-3 md:grid-cols-2">
{CLI_COMPAT_TOGGLE_IDS.map((providerId) => {
const normalizedProviderId = normalizeCliCompatProviderId(providerId);
const providerDisplay = CLI_COMPAT_PROVIDER_DISPLAY[providerId];
const checked = cliCompatProviderSet.has(normalizedProviderId);
const label = providerDisplay?.name || providerId;
const description = providerDisplay?.description || providerId;
return (
<button
key={providerId}
type="button"
onClick={() => toggleCliCompatProvider(providerId, !checked)}
disabled={loading}
aria-pressed={checked}
title={checked ? t("disableFingerprintTitle", { provider: label }) : t("enableFingerprintTitle", { provider: label })}
className={`flex items-start gap-3 rounded-lg border p-3 text-left transition-all ${
checked
? "border-indigo-500/50 bg-indigo-500/5 ring-1 ring-indigo-500/20"
: "border-border/50 hover:border-border hover:bg-surface/30"
} ${loading ? "cursor-not-allowed opacity-60" : ""}`}
>
<span
className={`material-symbols-outlined mt-0.5 text-[18px] ${checked ? "text-indigo-400" : "text-text-muted"}`}
aria-hidden="true"
>
{checked ? "check_circle" : "radio_button_unchecked"}
</span>
<span className="min-w-0 flex-1">
<span className={`block text-sm font-medium ${checked ? "text-indigo-400" : ""}`}>
{label}
</span>
<span className="mt-1 block text-xs text-text-muted">{description}</span>
</span>
</button>
);
})}
</div>
</Card>
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-green-500/10 text-green-500">

View File

@@ -4669,6 +4669,7 @@
"flowDiagramCli": "CLI Agent",
"flowDiagramCliDesc": "Processes with own auth/model",
"fingerprintSettingsHint": "CLI Fingerprint matching (disguise requests as specific CLI tools) can be configured in",
"settingsRoutingLink": "Settings/Routing",
"openSettings": "Settings"
},
"templateNames": {

View File

@@ -1,4 +1,3 @@
import { CLI_TOOLS } from "./cliTools";
import { normalizeCliCompatProviderId } from "../utils/cliCompat";
export { normalizeCliCompatProviderId };
@@ -53,21 +52,37 @@ export const CLI_COMPAT_OMITTED_PROVIDER_IDS = [
* Provider IDs toggled in Settings -> CLI Fingerprint.
*
* Source of truth:
* - derive from visible CLI tools when a provider mapping exists
* - keep legacy-compatible IDs that are still used by existing setups
* - expose only providers with an implemented `CLI_FINGERPRINTS` entry
* - keep legacy-compatible display IDs such as `copilot` while persisting normalized provider IDs
*/
const TOOL_ID_TO_PROVIDER_ID: Record<string, string> = {
kilo: "kilocode",
copilot: "github",
};
const DERIVED_PROVIDER_IDS = Object.values(CLI_TOOLS)
.map((tool: any) => normalizeCliCompatProviderId(TOOL_ID_TO_PROVIDER_ID[tool.id] ?? tool.id))
// "continue" currently has no provider id in AI_PROVIDERS
.filter((providerId) => IMPLEMENTED_CLI_FINGERPRINT_PROVIDER_IDS.includes(providerId as any));
export const CLI_COMPAT_PROVIDER_IDS = Array.from(
new Set([...DERIVED_PROVIDER_IDS, ...IMPLEMENTED_CLI_FINGERPRINT_PROVIDER_IDS])
new Set([...IMPLEMENTED_CLI_FINGERPRINT_PROVIDER_IDS])
);
export const CLI_COMPAT_TOGGLE_IDS = Array.from(new Set(CLI_COMPAT_DISPLAY_PROVIDER_IDS));
export const CLI_COMPAT_PROVIDER_DISPLAY: Record<
(typeof CLI_COMPAT_DISPLAY_PROVIDER_IDS)[number],
{ name: string; description: string }
> = {
claude: {
name: "Claude Code",
description: "Anthropic Claude Code CLI",
},
codex: {
name: "OpenAI Codex CLI",
description: "OpenAI Codex CLI",
},
copilot: {
name: "GitHub Copilot",
description: "GitHub Copilot CLI compatibility",
},
antigravity: {
name: "Antigravity",
description: "Google Antigravity IDE compatibility",
},
qwen: {
name: "Qwen Code / Qoder",
description: "Qwen Code and Qoder CLI compatibility",
},
};