diff --git a/packages/ui/src/pages/home/shared/external.tsx b/packages/ui/src/pages/home/shared/external.tsx
index 54f9d89..4aa2f32 100644
--- a/packages/ui/src/pages/home/shared/external.tsx
+++ b/packages/ui/src/pages/home/shared/external.tsx
@@ -1,4 +1,4 @@
-import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, type DragEvent as ReactDragEvent, type HTMLAttributes, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
+import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, type HTMLAttributes, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
import {
closestCenter,
DndContext,
@@ -571,7 +571,7 @@ export {
providerIdentitySafetyIssue, getProviderPresets, setProviderPresets, standardProviderAccountConfig, normalizeProviderBaseUrl, providerUrlWithDefaultScheme
};
export type {
- HTMLAttributes, ReactDragEvent, ReactPointerEvent, ReactNode, CollisionDetection, DragEndEvent, DragOverEvent, DragStartEvent,
+ HTMLAttributes, ReactPointerEvent, ReactNode, CollisionDetection, DragEndEvent, DragOverEvent, DragStartEvent,
LucideIcon, AgentAnalysisFilter, AgentAnalysisSessionSelection, AgentAnalysisSnapshot, AgentAnalysisTrace, AgentAnalysisTracePayloadFullResult, AgentAnalysisTracePayloadRequest, AgentAnalysisTraceRun, AgentAnalysisTraceRunKind, AgentKind, AppConfig, AppInfo, AppSaveConfigOptions, AppUpdateStatus, ApiKeyConfig,
ApiKeyLimitConfig, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginCancelResult, BotGatewayQrLoginStartRequest, BotGatewayQrLoginStartResult, BotGatewayQrLoginWaitRequest, BotGatewayQrLoginWaitResult, BotGatewayQrWindowOpenResult, BotGatewayRuntimeConfig, BotGatewaySavedConfig, BotHandoffScanTarget, GatewayProviderConfig, GatewayProviderCapability, GatewayPluginAppConfig, GatewayProviderProbeResult, GatewayProviderProtocol, GatewayMcpServerConfig,
GatewayMcpServerTransport, GatewayMcpStdioMessageMode, GatewayMcpToolInfo, GatewayStatus, OverviewMetricKind, OverviewWidgetConfig, OverviewWidgetSize, OverviewWidgetType,
diff --git a/packages/ui/src/pages/home/shared/i18n.tsx b/packages/ui/src/pages/home/shared/i18n.tsx
index 91fea4a..216526e 100644
--- a/packages/ui/src/pages/home/shared/i18n.tsx
+++ b/packages/ui/src/pages/home/shared/i18n.tsx
@@ -278,7 +278,6 @@ export const appCopy: Record
= {
"Ready": "Ready",
"Select at least one protocol.": "Select at least one protocol.",
"Service": "Service",
- "Service failed to start": "Service failed to start",
"Scanning local agent logins": "Scanning local agent logins",
"Start": "Start",
"Start check": "Start check",
@@ -430,9 +429,6 @@ export const appCopy: Record = {
"Arguments": "Arguments",
"Working directory": "Working directory",
"API key env": "API key env",
- "CLAUDE_APP_PATH": "CLAUDE_APP_PATH",
- "CHATGPT_APP_PATH": "CHATGPT_APP_PATH",
- "Drop the app here or paste the executable path": "Drop the app here or paste the executable path",
"Tool name": "Tool name",
"Third-party tool environment": "Third-party tool environment",
"Environment variables": "Environment variables",
@@ -587,6 +583,7 @@ export const appCopy: Record = {
"API key created": "API 密钥已创建",
"API keys database": "API 密钥数据库",
"Copy this key now. It may not be shown again.": "请现在复制保存这个密钥,之后可能不会再次完整显示。",
+ "Only enter an API key issued for this endpoint. Official provider keys must only be used with official endpoints.": "只输入由当前端点签发的 API 密钥。官方供应商密钥只能用于官方端点。",
"Add": "添加",
"Add env variable": "添加环境变量",
"Add header": "添加请求头",
@@ -1314,9 +1311,6 @@ export const appCopy: Record = {
"Arguments": "参数",
"Working directory": "工作目录",
"API key env": "API Key 环境变量",
- "CLAUDE_APP_PATH": "CLAUDE_APP_PATH",
- "CHATGPT_APP_PATH": "CHATGPT_APP_PATH",
- "Drop the app here or paste the executable path": "拖动应用到这里,或粘贴可执行文件路径",
"Third-party tool environment": "三方工具环境变量",
"Environment variable keys are required when values are set.": "设置变量值时必须填写变量名。",
"MCP tool discovery is available in the Electron app.": "MCP 工具发现仅在 Electron App 中可用。",
@@ -1656,7 +1650,6 @@ export const appCopy: Record = {
"Select a scanned target": "选择扫描到的目标",
"Selected": "已选择",
"Service": "服务",
- "Service failed to start": "服务启动失败",
"Service control is available in the Electron app.": "服务控制仅在 Electron App 中可用。",
"Scanning targets": "正在扫描目标",
"Service status": "服务状态",
diff --git a/packages/ui/src/pages/home/shared/options.ts b/packages/ui/src/pages/home/shared/options.ts
index 3f47852..18ad4c8 100644
--- a/packages/ui/src/pages/home/shared/options.ts
+++ b/packages/ui/src/pages/home/shared/options.ts
@@ -46,13 +46,11 @@ import bailianProviderIconUrl from "@/assets/provider-icons/bailian.ico";
import claudeapiProviderIconUrl from "@/assets/provider-icons/claudeapi.png";
import code0ProviderIconUrl from "@/assets/provider-icons/code0.png";
import deepseekProviderIconUrl from "@/assets/provider-icons/deepseek.ico";
-import fennoProviderIconUrl from "@/assets/provider-icons/fenno.jpg";
import geminiProviderIconUrl from "@/assets/provider-icons/gemini.svg";
import mistralProviderIconUrl from "@/assets/provider-icons/mistral.webp";
import moonshotProviderIconUrl from "@/assets/provider-icons/moonshot.ico";
import openaiProviderIconUrl from "@/assets/provider-icons/openai.png";
import openrouterProviderIconUrl from "@/assets/provider-icons/openrouter.ico";
-import qiniuAiProviderIconUrl from "@/assets/provider-icons/qiniu-ai.png";
import runapiProviderIconUrl from "@/assets/provider-icons/runapi.jpg";
import siliconflowProviderIconUrl from "@/assets/provider-icons/siliconflow.png";
import teamorouterProviderIconUrl from "@/assets/provider-icons/teamorouter.png";
@@ -326,7 +324,6 @@ export const providerPresetIconUrls: Record = {
claudeapi: claudeapiProviderIconUrl,
code0: code0ProviderIconUrl,
deepseek: deepseekProviderIconUrl,
- fenno: fennoProviderIconUrl,
gemini: geminiProviderIconUrl,
"kimi-coding": moonshotProviderIconUrl,
mistral: mistralProviderIconUrl,
@@ -334,7 +331,6 @@ export const providerPresetIconUrls: Record = {
"moonshot-global": moonshotProviderIconUrl,
openai: openaiProviderIconUrl,
openrouter: openrouterProviderIconUrl,
- "qiniu-ai": qiniuAiProviderIconUrl,
runapi: runapiProviderIconUrl,
siliconflow: siliconflowProviderIconUrl,
teamorouter: teamorouterProviderIconUrl,
diff --git a/packages/ui/src/pages/home/shared/profiles.ts b/packages/ui/src/pages/home/shared/profiles.ts
index cb593e5..285b464 100644
--- a/packages/ui/src/pages/home/shared/profiles.ts
+++ b/packages/ui/src/pages/home/shared/profiles.ts
@@ -754,7 +754,6 @@ export function createProfileDraft(agent: ProfileConfig["agent"] = "claude-code"
const surface = agent === "zcode" ? "app" : "cli";
return {
agent,
- appPath: "",
...createBotGatewayDraft(),
configFile: defaultCodexConfigFile(agent),
envRows: agent === "claude-code" ? keyValueRowsFromRecord(claudeCodeProfileEnv()) : [],
@@ -779,7 +778,6 @@ export function createProfileDraftFromProfile(profile: ProfileConfig, botConfigs
return {
...createProfileDraft("claude-code", profile.name),
...botDraft,
- appPath: profile.appPath ?? "",
botConfigId,
botEnabled: surface !== "cli" && Boolean(selectedBot || profile.botGateway?.enabled),
envRows: keyValueRowsFromRecord(claudeCodeProfileEnv(profile.env ?? {})),
@@ -794,7 +792,6 @@ export function createProfileDraftFromProfile(profile: ProfileConfig, botConfigs
return {
...createProfileDraft(profile.agent, profile.name),
...botDraft,
- appPath: profile.appPath ?? "",
botConfigId,
botEnabled: surface !== "cli" && Boolean(selectedBot || profile.botGateway?.enabled),
configFile: profile.configFile ?? defaultCodexConfigFile(profile.agent),
@@ -866,7 +863,6 @@ export function profileConfigFromDraft(
: {};
return normalizeProfileItem({
agent: draft.agent,
- appPath: draft.appPath,
...botGateway,
configFile: draft.configFile,
enabled: existingProfile?.enabled ?? true,
@@ -1366,10 +1362,6 @@ export function profileSummaryItems(
const envSummaryItems = envCount > 0
? [{ label: t("Environment variables"), value: String(envCount) }]
: [];
- const appPath = profile.appPath?.trim() || "";
- const appPathSummaryItems = appPath && surface !== "cli" && profile.agent !== "zcode"
- ? [{ label: t(profile.agent === "claude-code" ? "CLAUDE_APP_PATH" : "CHATGPT_APP_PATH"), value: appPath }]
- : [];
const savedBot = profile.botConfigId
? config.botConfigs.find((item) => item.id === profile.botConfigId)
: undefined;
@@ -1408,7 +1400,6 @@ export function profileSummaryItems(
: t("Keep Claude Code default")
},
...botSummaryItems,
- ...appPathSummaryItems,
...envSummaryItems
];
}
@@ -1417,7 +1408,6 @@ export function profileSummaryItems(
{ label: t("Model"), value: modelValue },
{ label: t("Provider ID"), value: profile.providerId ?? "claude-code-router" },
...(profile.agent === "zcode" ? [] : [{ label: t("Show all sessions"), value: profile.showAllSessions ? t("Enabled") : t("Disabled") }]),
- ...appPathSummaryItems,
...botSummaryItems,
...envSummaryItems
];
@@ -1433,10 +1423,8 @@ export function normalizeProfileItem(profile: ProfileConfig, index: number): Pro
const botGateway = surface !== "cli" ? normalizeBotGatewayRuntimeConfig(profile.botGateway) : undefined;
const botConfigId = surface !== "cli" ? stringValue(profile.botConfigId) : "";
if (agent === "claude-code") {
- const appPath = profile.appPath?.trim() || "";
return {
agent: "claude-code",
- ...(surface !== "cli" && appPath ? { appPath } : {}),
...(botConfigId ? { botConfigId } : {}),
...(botGateway ? { botGateway } : {}),
enabled: profile.enabled,
@@ -1452,7 +1440,6 @@ export function normalizeProfileItem(profile: ProfileConfig, index: number): Pro
}
return {
agent: normalizeCodexCompatibleAgent(agent),
- ...(surface !== "cli" && agent !== "zcode" && profile.appPath?.trim() ? { appPath: profile.appPath.trim() } : {}),
...(botConfigId ? { botConfigId } : {}),
...(botGateway ? { botGateway } : {}),
cliMiddleware: true,
@@ -1531,27 +1518,6 @@ export function normalizeUnknownProfileItem(value: Record, inde
}
return normalizeProfileItem({
agent,
- appPath: typeof value.appPath === "string"
- ? value.appPath
- : typeof value.app_path === "string"
- ? value.app_path
- : typeof value.appExecutablePath === "string"
- ? value.appExecutablePath
- : typeof value.app_executable_path === "string"
- ? value.app_executable_path
- : agent === "claude-code" && typeof value.claudeAppPath === "string"
- ? value.claudeAppPath
- : agent === "claude-code" && typeof value.claude_app_path === "string"
- ? value.claude_app_path
- : agent === "codex" && typeof value.chatgptAppPath === "string"
- ? value.chatgptAppPath
- : agent === "codex" && typeof value.chatgpt_app_path === "string"
- ? value.chatgpt_app_path
- : agent === "codex" && typeof value.codexAppPath === "string"
- ? value.codexAppPath
- : agent === "codex" && typeof value.codex_app_path === "string"
- ? value.codex_app_path
- : undefined,
botConfigId: typeof value.botConfigId === "string" ? value.botConfigId : typeof value.bot_config_id === "string" ? value.bot_config_id : undefined,
botGateway: normalizeBotGatewayRuntimeConfig(value.botGateway ?? value.bot_gateway ?? value.bot),
cliMiddleware: typeof value.cliMiddleware === "boolean" ? value.cliMiddleware : undefined,
diff --git a/packages/ui/src/pages/home/shared/providers.ts b/packages/ui/src/pages/home/shared/providers.ts
index ac1d6b9..afa0b24 100644
--- a/packages/ui/src/pages/home/shared/providers.ts
+++ b/packages/ui/src/pages/home/shared/providers.ts
@@ -190,7 +190,6 @@ import type {
ProviderCredentialConfig,
ProviderDeepLinkPayload,
ProviderDeepLinkRequest,
- ProviderModelMetadata,
ProfileConfig,
ProfileOpenSurface,
CodexProfileConfigFormat,
@@ -773,19 +772,8 @@ export function providerDeepLinkDisplayIcon(payload: ProviderDeepLinkPayload): s
return presetIcon || payload.icon?.trim() || "";
}
-export function providerDisplayIcon(provider: GatewayProviderConfig): string {
- const icon = provider.icon?.trim();
- if (icon) {
- return icon;
- }
-
- const preset = findProviderPresetByBaseUrl(providerBaseUrl(provider));
- return preset ? providerPresetIconUrls[preset.id] ?? "" : "";
-}
-
export type ProviderDeepLinkCatalogModelsResolution = {
modelDisplayNames?: Record;
- modelMetadata?: Record;
models: string[];
};
@@ -806,7 +794,6 @@ export async function resolveProviderDeepLinkCatalogModels(payload: ProviderDeep
});
return {
modelDisplayNames: mergeModelDisplayNames(result.modelDisplayNames),
- modelMetadata: modelMetadataForModels(result.modelMetadata, result.models),
models: mergeProviderModelLists(result.models)
};
} catch {
@@ -875,7 +862,6 @@ export function createProviderDraftFromDeepLinkPayload(
mergeModelDisplayNames(providerPresetModelDisplayNames(preset), payload.modelDisplayNames),
models
),
- modelMetadata: modelMetadataForModels(payload.modelMetadata, models),
modelSearch: "",
modelsText: models.join("\n"),
name: uniqueProviderName(providers, baseName),
@@ -892,8 +878,8 @@ export function createProviderConfigFromDeepLink(
providers: GatewayProviderConfig[],
probe: GatewayProviderProbeResult | undefined
): GatewayProviderConfig {
- const protocol = payload.protocol ?? probe?.detectedProtocol ?? "openai_chat_completions";
- const baseUrl = providerGlobalBaseUrlForProbe(payload.baseUrl, probe, [protocol]);
+ const protocol = probe?.detectedProtocol ?? payload.protocol ?? "openai_chat_completions";
+ const baseUrl = probe?.normalizedBaseUrl || payload.baseUrl;
const apiKey = payload.apiKey?.trim() || "";
const models = apiKey && probe?.models.length
? mergeProviderModelLists(probe.models)
@@ -924,17 +910,19 @@ export function createProviderConfigFromDeepLink(
throw new Error(accountKeySafetyIssue.message);
}
- const capabilities = providerCapabilitiesForProtocols(payload.baseUrl, [protocol], probe);
+ const capabilities = mergeProviderCapabilities(
+ probe?.capabilities ?? [],
+ protocol && baseUrl ? [{ baseUrl, source: probe?.detectedProtocol ? "detected" : "preset", type: protocol }] : []
+ );
return {
account: cloneProviderAccountConfig(account),
- api_base_url: normalizeProviderBaseUrl(baseUrl),
+ api_base_url: normalizeProviderBaseUrl(baseUrl, protocol),
api_key: apiKey,
capabilities: capabilities.length > 0 ? capabilities : undefined,
icon: payload.icon?.trim() || undefined,
modelDescriptions: modelDescriptionsForModels(payload.modelDescriptions, models),
modelDisplayNames: modelDisplayNamesForModels(mergeModelDisplayNames(payload.modelDisplayNames, probe?.modelDisplayNames), models),
- modelMetadata: modelMetadataForModels(mergeModelMetadata(payload.modelMetadata, probe?.modelMetadata), models),
models,
name,
type: protocol
@@ -961,7 +949,6 @@ export function createProviderDraft(providers: GatewayProviderConfig[]): AddProv
icon: "",
modelDescriptions: undefined,
modelDisplayNames: undefined,
- modelMetadata: undefined,
modelSearch: "",
modelsText: "",
name: uniqueProviderName(providers),
@@ -989,7 +976,6 @@ export function createProviderDraftFromProvider(provider: GatewayProviderConfig)
mergeModelDisplayNames(providerPresetModelDisplayNames(preset), provider.modelDisplayNames),
provider.models
),
- modelMetadata: modelMetadataForModels(provider.modelMetadata, provider.models),
modelSearch: "",
modelsText: provider.models.join("\n"),
name: provider.name,
@@ -1503,13 +1489,8 @@ export function providerUsageFieldPatch(target: ProviderUsageFieldTarget, path:
export function createProviderInstallLinkFromDraft(draft: AddProviderDraft, probe: GatewayProviderProbeResult | undefined): string {
const providerName = draft.name.trim();
- const selectedProtocols = uniqueProviderProtocols(draft.selectedProtocols);
- const protocol = selectedProtocols.includes(draft.protocol)
- ? draft.protocol
- : selectedProtocols.length === 1
- ? selectedProtocols[0]
- : probe?.detectedProtocol ?? draft.protocol;
- const baseUrl = providerGlobalBaseUrlForProbe(draft.baseUrl, probe, selectedProtocols.length > 0 ? selectedProtocols : [protocol]);
+ const baseUrl = (probe?.normalizedBaseUrl || draft.baseUrl).trim();
+ const protocol = probe?.detectedProtocol ?? draft.protocol;
const models = mergeProviderModelLists(draft.selectedModels, splitLines(draft.modelsText));
if (!providerName || !baseUrl) {
return "Provider name and Base URL are required.";
@@ -1551,14 +1532,12 @@ export function createProviderInstallLinkFromDraft(draft: AddProviderDraft, prob
const modelDescriptions = modelDescriptionsForModels(draft.modelDescriptions, models);
const modelDisplayNames = modelDisplayNamesForModels(draft.modelDisplayNames, models);
- const modelMetadata = modelMetadataForModels(draft.modelMetadata, models);
const payload: ProviderDeepLinkPayload = {
...(account ? { account } : {}),
baseUrl,
...(draft.icon.trim() ? { icon: draft.icon.trim() } : {}),
...(modelDescriptions ? { modelDescriptions } : {}),
...(modelDisplayNames ? { modelDisplayNames } : {}),
- ...(modelMetadata ? { modelMetadata } : {}),
models,
name: providerName,
protocol
@@ -1716,11 +1695,9 @@ export function providerProbeCandidates(draft: AddProviderDraft): ProviderProbeC
const preset = findProviderPreset(draft.presetId);
const protocols = providerProtocolOptions.map((option) => option.value);
if (preset) {
- const probeAllProtocols = preset.endpoints.length === 1;
return preset.endpoints.map((endpoint) => ({
...endpoint,
- declaredProtocols: endpoint.protocols,
- protocols: probeAllProtocols ? protocols : endpoint.protocols,
+ protocols,
source: "preset"
}));
}
@@ -1809,10 +1786,7 @@ export function providerSelectableProtocolsFromProbe(probe: GatewayProviderProbe
return [];
}
- return uniqueProviderProtocols([
- ...probe.protocols.filter((item) => item.supported).map((item) => item.protocol),
- ...(probe.capabilities ?? []).map((capability) => capability.type)
- ]);
+ return uniqueProviderProtocols(probe.protocols.filter((item) => item.supported).map((item) => item.protocol));
}
export function selectedProviderProtocolsFromCapabilities(
@@ -1903,83 +1877,18 @@ export function mergeProviderCapabilities(...groups: GatewayProviderCapability[]
return capabilities;
}
-export function providerGlobalBaseUrlForProbe(
- inputBaseUrl: string,
- _probe: GatewayProviderProbeResult | undefined,
- _protocols: GatewayProviderProtocol[] = []
-): string {
- return normalizeProviderBaseUrl(inputBaseUrl) || inputBaseUrl.trim();
-}
-
-export function providerCapabilityBaseUrlForProtocol(
- inputBaseUrl: string,
- protocol: GatewayProviderProtocol,
- probe: GatewayProviderProbeResult | undefined
-): string {
- const detectedCapability = (probe?.capabilities ?? []).find((capability) =>
- capability.type === protocol && capability.baseUrl.trim()
- );
- if (detectedCapability) {
- return detectedCapability.baseUrl.trim();
- }
-
- if (probe?.detectedProtocol === protocol && probe.normalizedBaseUrl.trim()) {
- return probe.normalizedBaseUrl.trim();
- }
-
- return normalizeProviderBaseUrl(inputBaseUrl, protocol) || normalizeProviderBaseUrl(inputBaseUrl) || inputBaseUrl.trim();
-}
-
-export function providerCapabilitiesForProtocols(
- inputBaseUrl: string,
- protocols: GatewayProviderProtocol[],
- probe: GatewayProviderProbeResult | undefined,
- presetCapabilities: GatewayProviderCapability[] = []
-): GatewayProviderCapability[] {
- const detectedCapabilities = probe?.capabilities ?? [];
- const selectedCapabilities = uniqueProviderProtocols(protocols)
- .map((type) => {
- const capability =
- detectedCapabilities.find((item) => item.type === type && item.baseUrl.trim()) ??
- presetCapabilities.find((item) => item.type === type && item.baseUrl.trim());
- if (capability) {
- return capability;
- }
-
- const baseUrl = providerCapabilityBaseUrlForProtocol(inputBaseUrl, type, probe);
- return baseUrl
- ? {
- baseUrl,
- source: probe?.detectedProtocol ? ("detected" as const) : ("preset" as const),
- type
- }
- : undefined;
- })
- .filter((item): item is GatewayProviderCapability => Boolean(item));
-
- return mergeProviderCapabilities(selectedCapabilities);
-}
-
export function applyProviderProbeResult(draft: AddProviderDraft, probe: GatewayProviderProbeResult): AddProviderDraft {
- const detectedProtocol = probe.detectedProtocol ?? draft.protocol;
- const selectedProtocols = selectedProviderProtocolsForProbe(draft.selectedProtocols, probe, detectedProtocol, draft.presetId);
- const protocol = selectedProtocols.includes(draft.protocol)
- ? draft.protocol
- : selectedProtocols.includes(detectedProtocol)
- ? detectedProtocol
- : selectedProtocols[0] ?? detectedProtocol;
+ const protocol = probe.detectedProtocol ?? draft.protocol;
+ const selectedProtocols = selectedProviderProtocolsForProbe(draft.selectedProtocols, probe, protocol, draft.presetId);
const modelDisplayNames = mergeModelDisplayNames(draft.modelDisplayNames, probe.modelDisplayNames);
- const modelMetadata = mergeModelMetadata(draft.modelMetadata, probe.modelMetadata);
const accountDraft = providerProbeAccountDraftPatch(draft, probe.account);
- const baseUrl = providerGlobalBaseUrlForProbe(draft.baseUrl, probe, selectedProtocols);
if (probe.models.length === 0) {
return {
...draft,
...accountDraft,
- baseUrl,
+ baseUrl: probe.normalizedBaseUrl || draft.baseUrl,
modelDisplayNames,
- modelMetadata,
protocol,
selectedModels: mergeProviderModelLists(draft.selectedModels),
selectedProtocols
@@ -2001,9 +1910,8 @@ export function applyProviderProbeResult(draft: AddProviderDraft, probe: Gateway
return {
...draft,
...accountDraft,
- baseUrl,
+ baseUrl: probe.normalizedBaseUrl || draft.baseUrl,
modelDisplayNames,
- modelMetadata,
protocol,
modelsText: customModels.join("\n"),
selectedModels: nextSelectedModels,
@@ -2038,33 +1946,6 @@ export function mergeModelDisplayNames(
return Object.keys(merged).length > 0 ? merged : undefined;
}
-export function mergeModelMetadata(
- ...groups: Array | undefined>
-): Record | undefined {
- const merged: Record = {};
- for (const group of groups) {
- for (const [rawModel, metadata] of Object.entries(group ?? {})) {
- const model = rawModel.trim();
- if (!model || !metadata || typeof metadata !== "object") {
- continue;
- }
- merged[model] = metadata;
- }
- }
- return Object.keys(merged).length > 0 ? merged : undefined;
-}
-
-export function modelMetadataForModels(
- value: Record | undefined,
- models: string[]
-): Record | undefined {
- const modelIds = new Set(models);
- const entries = Object.entries(value ?? {})
- .map(([rawModel, metadata]) => [rawModel.trim(), metadata] as const)
- .filter(([model, metadata]) => model && modelIds.has(model) && metadata && typeof metadata === "object");
- return entries.length > 0 ? Object.fromEntries(entries) : undefined;
-}
-
export function modelDisplayNamesForModels(
value: Record | undefined,
models: string[]
diff --git a/packages/ui/src/pages/home/shared/types.ts b/packages/ui/src/pages/home/shared/types.ts
index 08b5803..52f1f4c 100644
--- a/packages/ui/src/pages/home/shared/types.ts
+++ b/packages/ui/src/pages/home/shared/types.ts
@@ -188,7 +188,6 @@ import type {
ProviderCredentialConfig,
ProviderDeepLinkPayload,
ProviderDeepLinkRequest,
- ProviderModelMetadata,
ProfileConfig,
ProfileOpenSurface,
CodexProfileConfigFormat,
@@ -425,7 +424,6 @@ export type AddProviderDraft = {
icon: string;
modelDescriptions?: Record;
modelDisplayNames?: Record;
- modelMetadata?: Record;
modelSearch: string;
modelsText: string;
name: string;
@@ -485,7 +483,6 @@ export type AddApiKeyDraft = {
export type AddProfileDraft = {
agent: ProfileConfig["agent"];
- appPath: string;
botConfigId: string;
botAuthFields: Record;
botAuthType: string;
diff --git a/packages/ui/src/pages/home/shared/usage.ts b/packages/ui/src/pages/home/shared/usage.ts
index ed29c9b..040988d 100644
--- a/packages/ui/src/pages/home/shared/usage.ts
+++ b/packages/ui/src/pages/home/shared/usage.ts
@@ -155,7 +155,7 @@ export function formatUsdCost(value: number | undefined): string {
return new Intl.NumberFormat(undefined, {
currency: "USD",
maximumFractionDigits: normalized >= 100 ? 0 : 2,
- minimumFractionDigits: normalized >= 100 ? 0 : 2,
+ minimumFractionDigits: 2,
style: "currency"
}).format(normalized);
}
diff --git a/packages/ui/src/pages/tray/shared.tsx b/packages/ui/src/pages/tray/shared.tsx
index ef2cac4..1976d25 100644
--- a/packages/ui/src/pages/tray/shared.tsx
+++ b/packages/ui/src/pages/tray/shared.tsx
@@ -609,7 +609,7 @@ export function formatUsdCost(value: number | undefined): string {
return new Intl.NumberFormat(undefined, {
currency: "USD",
maximumFractionDigits: normalized >= 100 ? 0 : 2,
- minimumFractionDigits: normalized >= 100 ? 0 : 2,
+ minimumFractionDigits: 2,
style: "currency"
}).format(normalized);
}
diff --git a/packages/ui/src/types/electron.d.ts b/packages/ui/src/types/electron.d.ts
index 006682e..97af330 100644
--- a/packages/ui/src/types/electron.d.ts
+++ b/packages/ui/src/types/electron.d.ts
@@ -40,8 +40,6 @@ import type {
LocalAgentProviderCandidate,
LocalAgentProviderImportRequest,
LocalAgentProviderImportResult,
- LocalAgentProviderProbeRequest,
- LocalAgentProviderProbeResult,
PluginDirectorySelection,
PluginMarketplaceEntry,
ProfileOpenCommandResult,
@@ -95,7 +93,6 @@ declare global {
getAgentTracePayload: (request: AgentAnalysisTracePayloadRequest) => Promise;
getAppInfo: () => Promise;
getConfig: () => Promise;
- getFilePath?: (file: File) => string;
getGatewayStatus: () => Promise;
getLocalAgentProviderCandidates: () => Promise;
getOnboardingFinished: () => Promise;
@@ -121,7 +118,6 @@ declare global {
openExternal: (url: string) => Promise;
openProfile: (request: ProfileOpenRequest) => Promise;
prepareImageExportTarget?: (request: AppImageExportTargetRequest) => Promise;
- probeLocalAgentProvider?: (request: LocalAgentProviderProbeRequest) => Promise;
probeProviderCandidates: (request: GatewayProviderProbeCandidatesRequest) => Promise;
probeProvider: (request: GatewayProviderProbeRequest) => Promise;
quitApp: () => Promise;
diff --git a/packages/ui/src/web-client-bridge.ts b/packages/ui/src/web-client-bridge.ts
index af5eef1..5e85df3 100644
--- a/packages/ui/src/web-client-bridge.ts
+++ b/packages/ui/src/web-client-bridge.ts
@@ -148,7 +148,6 @@ const webClientBridge: CcrApi = {
window.open(normalizeExternalHttpUrl(url), "_blank", "noopener,noreferrer");
},
openProfile: (request) => rpc("openProfile", [request]) as ReturnType,
- probeLocalAgentProvider: (request) => rpc("probeLocalAgentProvider", [request]) as ReturnType>,
probeProvider: (request) => rpc("probeProvider", [request]) as ReturnType,
probeProviderCandidates: (request) => rpc("probeProviderCandidates", [request]) as ReturnType,
quitApp: () => rpc("quitApp") as ReturnType,
diff --git a/tests/main/claude-app-launch.test.mjs b/tests/main/claude-app-launch.test.mjs
index 92c39da..47b9a0d 100644
--- a/tests/main/claude-app-launch.test.mjs
+++ b/tests/main/claude-app-launch.test.mjs
@@ -3,7 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
-import { claudeAppLaunchCommand, findInstalledClaudeAppExecutable, normalizeClaudeAppCandidate } from "../../packages/core/src/agents/claude-app/launch.ts";
+import { claudeAppLaunchCommand } from "../../packages/core/src/agents/claude-app/launch.ts";
test("claudeAppLaunchCommand opens macOS app bundles through LaunchServices", (t) => {
const tempDir = mkdtempForTest();
@@ -49,88 +49,6 @@ test("claudeAppLaunchCommand opens macOS app bundles through LaunchServices", (t
assert.ok(launch.args.includes(`--user-data-dir=${path.join(tempDir, "profile data")}`));
});
-test("Claude App discovery rejects generic Claude Code CLI shims", (t) => {
- const tempDir = mkdtempForTest();
- t.after(() => rmSync(tempDir, { force: true, recursive: true }));
-
- const windowsCliShim = path.join(tempDir, ".local", "bin", "claude.exe");
- mkdirSync(path.dirname(windowsCliShim), { recursive: true });
- writeFileSync(windowsCliShim, "");
- withPlatform("win32", () => {
- assert.equal(normalizeClaudeAppCandidate(windowsCliShim), undefined);
- });
-
- const linuxCliShim = path.join(tempDir, "usr", "bin", "claude");
- mkdirSync(path.dirname(linuxCliShim), { recursive: true });
- writeFileSync(linuxCliShim, "");
- withPlatform("linux", () => {
- assert.equal(normalizeClaudeAppCandidate(linuxCliShim), undefined);
- });
-});
-
-test("Claude App discovery accepts generic Electron desktop app executables", (t) => {
- const tempDir = mkdtempForTest();
- t.after(() => rmSync(tempDir, { force: true, recursive: true }));
-
- const windowsApp = path.join(tempDir, "Programs", "Claude", "Claude.exe");
- mkdirSync(path.join(path.dirname(windowsApp), "resources", "app"), { recursive: true });
- writeFileSync(windowsApp, "");
- writeFileSync(path.join(path.dirname(windowsApp), "resources", "app", "package.json"), "{}");
- withPlatform("win32", () => {
- assert.equal(normalizeClaudeAppCandidate(windowsApp), windowsApp);
- });
-
- const linuxApp = path.join(tempDir, "opt", "Claude", "claude");
- mkdirSync(path.join(path.dirname(linuxApp), "resources", "app"), { recursive: true });
- writeFileSync(linuxApp, "");
- writeFileSync(path.join(path.dirname(linuxApp), "resources", "app", "package.json"), "{}");
- withPlatform("linux", () => {
- assert.equal(normalizeClaudeAppCandidate(linuxApp), linuxApp);
- });
-});
-
-test("Claude App profile appPath overrides process env discovery", (t) => {
- const tempDir = mkdtempForTest();
- const previous = process.env.CLAUDE_APP_PATH;
- t.after(() => {
- if (previous === undefined) {
- delete process.env.CLAUDE_APP_PATH;
- } else {
- process.env.CLAUDE_APP_PATH = previous;
- }
- rmSync(tempDir, { force: true, recursive: true });
- });
-
- const envCliShim = path.join(tempDir, ".local", "bin", "claude");
- mkdirSync(path.dirname(envCliShim), { recursive: true });
- writeFileSync(envCliShim, "");
- process.env.CLAUDE_APP_PATH = envCliShim;
-
- const profileApp = path.join(tempDir, "opt", "Claude", "claude");
- mkdirSync(path.join(path.dirname(profileApp), "resources", "app"), { recursive: true });
- writeFileSync(profileApp, "");
- writeFileSync(path.join(path.dirname(profileApp), "resources", "app", "package.json"), "{}");
-
- withPlatform("linux", () => {
- const result = findInstalledClaudeAppExecutable(profileApp);
- assert.equal(result.executable, profileApp);
- assert.equal(result.checked[0], profileApp);
- });
-});
-
function mkdtempForTest() {
return mkdtempSync(path.join(os.tmpdir(), "ccr-claude-app-launch-"));
}
-
-function withPlatform(platform, callback) {
- const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
- Object.defineProperty(process, "platform", {
- configurable: true,
- value: platform
- });
- try {
- return callback();
- } finally {
- Object.defineProperty(process, "platform", descriptor);
- }
-}
diff --git a/tests/main/codex-app-model-catalog.test.mjs b/tests/main/codex-app-model-catalog.test.mjs
index 2f4a08b..7992a89 100644
--- a/tests/main/codex-app-model-catalog.test.mjs
+++ b/tests/main/codex-app-model-catalog.test.mjs
@@ -1,16 +1,11 @@
import assert from "node:assert/strict";
-import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
-import {
- codexDesktopAppName,
- findInstalledCodexAppExecutable,
- removeLegacyCodexVirtualAuthMarker,
- writeCodexCompatibleAppModelCatalog
-} from "../../packages/core/src/agents/codex/app-launch.ts";
+import { writeCodexCompatibleAppModelCatalog } from "../../packages/core/src/agents/codex/app-launch.ts";
-test("ChatGPT model catalog write includes patch bridge capabilities", () => {
+test("Codex App model catalog write includes patch bridge capabilities", () => {
const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-codex-app-catalog-"));
try {
const config = {
@@ -57,106 +52,3 @@ test("ChatGPT model catalog write includes patch bridge capabilities", () => {
rmSync(configDir, { force: true, recursive: true });
}
});
-
-test("ChatGPT desktop app path override discovers the renamed executable", () => {
- const root = mkdtempSync(path.join(os.tmpdir(), "ccr-chatgpt-app-"));
- const previous = process.env.CHATGPT_APP_PATH;
- try {
- let configuredPath;
- let expectedExecutable;
- if (process.platform === "darwin") {
- configuredPath = path.join(root, "ChatGPT.app");
- const macosDir = path.join(configuredPath, "Contents", "MacOS");
- mkdirSync(macosDir, { recursive: true });
- expectedExecutable = path.join(macosDir, "ChatGPT");
- writeFileSync(expectedExecutable, "");
- writeFileSync(
- path.join(configuredPath, "Contents", "Info.plist"),
- "CFBundleExecutableChatGPT"
- );
- } else {
- expectedExecutable = path.join(root, process.platform === "win32" ? "ChatGPT.exe" : "chatgpt");
- configuredPath = expectedExecutable;
- writeFileSync(expectedExecutable, "");
- }
-
- process.env.CHATGPT_APP_PATH = configuredPath;
- const result = findInstalledCodexAppExecutable();
- assert.equal(codexDesktopAppName, "ChatGPT");
- assert.equal(result.executable, expectedExecutable);
- assert.equal(result.checked[0], configuredPath);
- } finally {
- if (previous === undefined) {
- delete process.env.CHATGPT_APP_PATH;
- } else {
- process.env.CHATGPT_APP_PATH = previous;
- }
- rmSync(root, { force: true, recursive: true });
- }
-});
-
-test("ChatGPT profile appPath overrides process env discovery", () => {
- const root = mkdtempSync(path.join(os.tmpdir(), "ccr-chatgpt-profile-app-"));
- const previous = process.env.CHATGPT_APP_PATH;
- try {
- const envExecutable = path.join(root, "env", "ChatGPT");
- mkdirSync(path.dirname(envExecutable), { recursive: true });
- writeFileSync(envExecutable, "");
- process.env.CHATGPT_APP_PATH = envExecutable;
-
- const profileExecutable = path.join(root, "profile", "ChatGPT");
- mkdirSync(path.dirname(profileExecutable), { recursive: true });
- writeFileSync(profileExecutable, "");
-
- withPlatform("linux", () => {
- const result = findInstalledCodexAppExecutable(profileExecutable);
- assert.equal(result.executable, profileExecutable);
- assert.equal(result.checked[0], profileExecutable);
- });
- } finally {
- if (previous === undefined) {
- delete process.env.CHATGPT_APP_PATH;
- } else {
- process.env.CHATGPT_APP_PATH = previous;
- }
- rmSync(root, { force: true, recursive: true });
- }
-});
-
-function withPlatform(platform, callback) {
- const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
- Object.defineProperty(process, "platform", {
- configurable: true,
- value: platform
- });
- try {
- return callback();
- } finally {
- Object.defineProperty(process, "platform", descriptor);
- }
-}
-
-test("ChatGPT migration removes only the exact legacy CCR auth marker", () => {
- const root = mkdtempSync(path.join(os.tmpdir(), "ccr-chatgpt-auth-migration-"));
- const authFile = path.join(root, "auth.json");
- try {
- writeFileSync(authFile, JSON.stringify({
- auth_mode: "apikey",
- OPENAI_API_KEY: "ccr-local-profile"
- }));
- assert.equal(removeLegacyCodexVirtualAuthMarker(root), true);
- assert.equal(existsSync(authFile), false);
-
- const realAuth = { auth_mode: "chatgpt", tokens: { access_token: "preserve-me" } };
- writeFileSync(authFile, JSON.stringify(realAuth));
- assert.equal(removeLegacyCodexVirtualAuthMarker(root), false);
- assert.deepEqual(JSON.parse(readFileSync(authFile, "utf8")), realAuth);
-
- const customApiKey = { auth_mode: "apikey", OPENAI_API_KEY: "user-key" };
- writeFileSync(authFile, JSON.stringify(customApiKey));
- assert.equal(removeLegacyCodexVirtualAuthMarker(root), false);
- assert.deepEqual(JSON.parse(readFileSync(authFile, "utf8")), customApiKey);
- } finally {
- rmSync(root, { force: true, recursive: true });
- }
-});
diff --git a/tests/main/codex-cli-middleware-runtime.test.mjs b/tests/main/codex-cli-middleware-runtime.test.mjs
index cc4675b..25704f8 100644
--- a/tests/main/codex-cli-middleware-runtime.test.mjs
+++ b/tests/main/codex-cli-middleware-runtime.test.mjs
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
-import { execFileSync, spawnSync } from "node:child_process";
-import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
+import { execFileSync } from "node:child_process";
+import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
@@ -13,211 +13,6 @@ test("generated Codex CLI middleware runtime is valid JavaScript", () => {
execFileSync(process.execPath, ["--check", file], { stdio: "pipe" });
});
-test("Codex app-server exposes a ChatGPT-shaped workspace identity without credentials", { skip: process.platform === "win32" }, () => {
- const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-virtual-auth-"));
- const runtimeFile = writeRuntimeScript(dir);
- const fakeCodex = path.join(dir, "fake-codex");
- const codexHome = path.join(dir, "codex-home");
- mkdirSync(codexHome, { recursive: true });
- writeFileSync(fakeCodex, [
- "#!/usr/bin/env node",
- "const fs = require('node:fs');",
- "const path = require('node:path');",
- "const readline = require('node:readline');",
- "const sawBootstrap = fs.existsSync(path.join(process.env.CODEX_HOME, 'auth.json'));",
- "const input = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });",
- "input.on('line', (line) => {",
- " const request = JSON.parse(line);",
- " const result = request.method === 'probe/auth-bootstrap'",
- " ? { sawBootstrap }",
- " : request.method === 'account/read'",
- " ? { account: { type: 'chatgpt', email: 'real@example.com', planType: 'pro' }, requiresOpenaiAuth: true }",
- " : { authMethod: 'chatgpt', authToken: 'real-chatgpt-token', requiresOpenaiAuth: true };",
- " process.stdout.write(JSON.stringify({ id: request.id, result }) + '\\n');",
- "});",
- ""
- ].join("\n"));
- chmodSync(fakeCodex, 0o700);
-
- const result = spawnSync(process.execPath, [runtimeFile, "app-server"], {
- encoding: "utf8",
- env: {
- ...process.env,
- CCR_CODEX_REMOTE_FRONTEND_MODE: "app",
- CCR_PROFILE_SCOPE: "ccr",
- CCR_REAL_CODEX_CLI_PATH: fakeCodex,
- CODEX_HOME: codexHome,
- CODEXL_CODEX_WORKSPACE_NAME: "CCR Workspace"
- },
- input: [
- JSON.stringify({ id: 0, method: "probe/auth-bootstrap", params: {} }),
- JSON.stringify({ id: 1, method: "getAuthStatus", params: { includeToken: true, refreshToken: false } }),
- JSON.stringify({ id: 2, method: "getAuthStatus", params: { includeToken: false, refreshToken: false } }),
- JSON.stringify({ id: 3, method: "account/read", params: {} }),
- ""
- ].join("\n")
- });
-
- assert.equal(result.status, 0, result.stderr);
- const responses = result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line));
- assert.deepEqual(responses[0].result, { sawBootstrap: true });
- assert.deepEqual(responses[1].result, {
- authMethod: "chatgpt",
- authToken: null,
- requiresOpenaiAuth: true
- });
- assert.deepEqual(responses[2].result, {
- authMethod: "chatgpt",
- requiresOpenaiAuth: true
- });
- assert.deepEqual(responses[3].result, {
- account: { type: "chatgpt", email: "CCR Workspace", planType: "unknown" },
- requiresOpenaiAuth: true
- });
- assert.equal(existsSync(path.join(codexHome, "auth.json")), false);
-});
-
-test("Codex app-server reads but never overwrites an existing ChatGPT auth file", { skip: process.platform === "win32" }, () => {
- const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-preserve-auth-"));
- const runtimeFile = writeRuntimeScript(dir);
- const fakeCodex = path.join(dir, "fake-codex");
- const codexHome = path.join(dir, "codex-home");
- const authFile = path.join(codexHome, "auth.json");
- const token = "header.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL3Byb2ZpbGUiOnsiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIn0sImh0dHBzOi8vYXBpLm9wZW5haS5jb20vYXV0aCI6eyJjaGF0Z3B0X3BsYW5fdHlwZSI6InBsdXMifX0.signature";
- const existingAuth = {
- auth_mode: "chatgpt",
- tokens: { access_token: token, id_token: token, refresh_token: "preserve-me" }
- };
- mkdirSync(codexHome, { recursive: true });
- writeFileSync(authFile, JSON.stringify(existingAuth));
- writeFileSync(fakeCodex, [
- "#!/usr/bin/env node",
- "const readline = require('node:readline');",
- "const input = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });",
- "input.on('line', (line) => {",
- " const request = JSON.parse(line);",
- " const result = request.method === 'account/read'",
- " ? { account: null, requiresOpenaiAuth: false }",
- " : { authMethod: null, authToken: null, requiresOpenaiAuth: false };",
- " process.stdout.write(JSON.stringify({ id: request.id, result }) + '\\n');",
- "});",
- ""
- ].join("\n"));
- chmodSync(fakeCodex, 0o700);
-
- const result = spawnSync(process.execPath, [runtimeFile, "app-server"], {
- encoding: "utf8",
- env: {
- ...process.env,
- CCR_CODEX_REMOTE_FRONTEND_MODE: "app",
- CCR_PROFILE_SCOPE: "ccr",
- CCR_REAL_CODEX_CLI_PATH: fakeCodex,
- CODEX_HOME: codexHome
- },
- input: [
- JSON.stringify({ id: 1, method: "getAuthStatus", params: { includeToken: true, refreshToken: false } }),
- JSON.stringify({ id: 2, method: "account/read", params: {} }),
- ""
- ].join("\n")
- });
-
- assert.equal(result.status, 0, result.stderr);
- const responses = result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line));
- assert.deepEqual(responses[0].result, {
- authMethod: "chatgpt",
- authToken: token,
- requiresOpenaiAuth: true
- });
- assert.deepEqual(responses[1].result, {
- account: { type: "chatgpt", email: "user@example.com", planType: "plus" },
- requiresOpenaiAuth: true
- });
- assert.deepEqual(JSON.parse(readFileSync(authFile, "utf8")), existingAuth);
-});
-
-test("Codex app-server delegates public Git marketplaces and leaves account-private marketplaces empty", { skip: process.platform === "win32" }, () => {
- const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-official-plugins-"));
- const runtimeFile = writeRuntimeScript(dir);
- const fakeCodex = path.join(dir, "fake-codex");
- const codexHome = path.join(dir, "codex-home");
- mkdirSync(codexHome, { recursive: true });
- writeFileSync(fakeCodex, [
- "#!/usr/bin/env node",
- "const readline = require('node:readline');",
- "const input = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });",
- "input.on('line', (line) => {",
- " const request = JSON.parse(line);",
- " const kind = request.params.marketplaceKinds[0];",
- " process.stdout.write(JSON.stringify({ id: request.id, result: { marketplaces: [{ name: kind, path: '/native/' + kind }], marketplaceLoadErrors: [], featuredPluginIds: [] } }) + '\\n');",
- "});",
- ""
- ].join("\n"));
- chmodSync(fakeCodex, 0o700);
-
- const result = spawnSync(process.execPath, [runtimeFile, "app-server"], {
- encoding: "utf8",
- env: {
- ...process.env,
- CCR_CODEX_REMOTE_FRONTEND_MODE: "app",
- CCR_PROFILE_SCOPE: "ccr",
- CCR_REAL_CODEX_CLI_PATH: fakeCodex,
- CODEX_HOME: codexHome
- },
- input: [
- JSON.stringify({ id: 1, method: "plugin/list", params: { marketplaceKinds: ["local", "vertical"] } }),
- JSON.stringify({ id: 2, method: "plugin/list", params: { marketplaceKinds: ["created-by-me-remote"] } }),
- ""
- ].join("\n")
- });
-
- assert.equal(result.status, 0, result.stderr);
- const responses = new Map(result.stdout.trim().split(/\r?\n/).map((line) => JSON.parse(line)).map((response) => [response.id, response]));
- assert.equal(responses.get(1).result.marketplaces[0].name, "local");
- assert.equal(responses.get(1).result.marketplaces[0].path, "/native/local");
- assert.deepEqual(responses.get(2).result, {
- marketplaces: [],
- marketplaceLoadErrors: [],
- featuredPluginIds: []
- });
-});
-
-test("Codex app-server delegates the native model catalog unchanged", { skip: process.platform === "win32" }, () => {
- const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-native-models-"));
- const runtimeFile = writeRuntimeScript(dir);
- const fakeCodex = path.join(dir, "fake-codex");
- const codexHome = path.join(dir, "codex-home");
- mkdirSync(codexHome, { recursive: true });
- writeFileSync(fakeCodex, [
- "#!/usr/bin/env node",
- "const readline = require('node:readline');",
- "const input = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });",
- "input.on('line', (line) => {",
- " const request = JSON.parse(line);",
- " process.stdout.write(JSON.stringify({ id: request.id, result: { data: [{ id: 'native-model', hidden: true }], nextCursor: null } }) + '\\n');",
- "});",
- ""
- ].join("\n"));
- chmodSync(fakeCodex, 0o700);
-
- const result = spawnSync(process.execPath, [runtimeFile, "app-server"], {
- encoding: "utf8",
- env: {
- ...process.env,
- CCR_CODEX_MODEL_CATALOG: JSON.stringify({ models: [{ slug: "must-not-be-merged" }] }),
- CCR_CODEX_REMOTE_FRONTEND_MODE: "app",
- CCR_REAL_CODEX_CLI_PATH: fakeCodex,
- CODEX_HOME: codexHome
- },
- input: JSON.stringify({ id: 1, method: "model/list", params: {} }) + "\n"
- });
-
- assert.equal(result.status, 0, result.stderr);
- assert.deepEqual(JSON.parse(result.stdout.trim()).result, {
- data: [{ id: "native-model", hidden: true }],
- nextCursor: null
- });
-});
-
test("Claude Code wrapper injects the scoped profile model into real CLI args", { skip: process.platform === "win32" }, () => {
const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-wrapper-"));
const runtimeFile = writeRuntimeScript(dir);
diff --git a/tests/main/codex-model-catalog.test.mjs b/tests/main/codex-model-catalog.test.mjs
index 680c543..1e68471 100644
--- a/tests/main/codex-model-catalog.test.mjs
+++ b/tests/main/codex-model-catalog.test.mjs
@@ -1,7 +1,4 @@
import assert from "node:assert/strict";
-import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
-import os from "node:os";
-import path from "node:path";
import test from "node:test";
import { buildCodexModelCatalog } from "../../packages/core/src/agents/codex/model-catalog.ts";
@@ -64,97 +61,6 @@ test("codex catalog enables multimodal reasoning and search when provider protoc
assert.equal(model.apply_patch_tool_type, "freeform");
});
-test("codex catalog uses provider model metadata for reasoning effort and speed tiers", () => {
- const model = catalogModelFor({
- Providers: [
- {
- modelMetadata: {
- "gpt-5-codex": {
- additionalSpeedTiers: [{ id: "fast", label: "Fast" }],
- defaultReasoningLevel: "high",
- defaultReasoningSummary: "auto",
- serviceTiers: [{ id: "auto" }],
- supportedReasoningLevels: [
- { description: "Low", effort: "low" },
- { description: "High", effort: "high" }
- ],
- supportsReasoningSummaries: true
- }
- },
- models: ["gpt-5-codex"],
- name: "Codex API",
- type: "openai_responses"
- }
- ]
- }, "Codex API/gpt-5-codex");
-
- assert.deepEqual(model.additional_speed_tiers, [{ id: "fast", label: "Fast" }]);
- assert.equal(model.default_reasoning_level, "high");
- assert.equal(model.default_reasoning_summary, "auto");
- assert.deepEqual(model.service_tiers, [{ id: "auto" }]);
- assert.deepEqual(model.supported_reasoning_levels, [
- { description: "Low", effort: "low" },
- { description: "High", effort: "high" }
- ]);
- assert.equal(model.supports_reasoning_summaries, true);
-});
-
-test("codex catalog falls back to local Codex model cache metadata", () => {
- const previousCcrHome = process.env.CCR_INTERNAL_HOME_DIR;
- const previousHome = process.env.HOME;
- const home = mkdtempSync(path.join(os.tmpdir(), "ccr-codex-model-catalog-"));
- try {
- process.env.CCR_INTERNAL_HOME_DIR = home;
- process.env.HOME = home;
- const codexHome = path.join(home, ".codex");
- mkdirSync(codexHome, { recursive: true });
- writeFileSync(path.join(codexHome, "models_cache.json"), JSON.stringify({
- models: [
- {
- additional_speed_tiers: [{ id: "fast", label: "Fast" }],
- default_reasoning_level: "high",
- service_tiers: [{ id: "auto" }],
- slug: "gpt-5-codex",
- supported_reasoning_levels: [
- { description: "Low", effort: "low" },
- { description: "High", effort: "high" }
- ],
- supports_reasoning_summaries: true
- }
- ]
- }));
-
- const model = catalogModelFor({
- Providers: [
- {
- api_base_url: "https://chatgpt.com/backend-api/codex",
- api_key: "ccr-local-agent-login",
- models: ["gpt-5-codex"],
- name: "Codex API",
- type: "openai_responses"
- }
- ]
- }, "Codex API/gpt-5-codex");
-
- assert.deepEqual(model.additional_speed_tiers, [{ id: "fast", label: "Fast" }]);
- assert.equal(model.default_reasoning_level, "high");
- assert.deepEqual(model.service_tiers, [{ id: "auto" }]);
- assert.deepEqual(model.supported_reasoning_levels.map((level) => level.effort), ["low", "high"]);
- } finally {
- if (previousCcrHome === undefined) {
- delete process.env.CCR_INTERNAL_HOME_DIR;
- } else {
- process.env.CCR_INTERNAL_HOME_DIR = previousCcrHome;
- }
- if (previousHome === undefined) {
- delete process.env.HOME;
- } else {
- process.env.HOME = previousHome;
- }
- rmSync(home, { force: true, recursive: true });
- }
-});
-
test("codex catalog enables native search for Gemini Interactions providers", () => {
const model = catalogModelFor({
Providers: [
diff --git a/tests/main/gateway-client-disconnect.test.mjs b/tests/main/gateway-client-disconnect.test.mjs
deleted file mode 100644
index bdad5b9..0000000
--- a/tests/main/gateway-client-disconnect.test.mjs
+++ /dev/null
@@ -1,155 +0,0 @@
-import assert from "node:assert/strict";
-import { createServer } from "node:http";
-import { mkdtempSync, rmSync } from "node:fs";
-import { tmpdir } from "node:os";
-import path from "node:path";
-import test from "node:test";
-import { createDefaultAppConfig } from "../../packages/core/src/config/default-config.ts";
-import { gatewayService } from "../../packages/core/src/gateway/service.ts";
-
-test("gateway treats downstream client aborts as expected stream cleanup", async () => {
- const dir = mkdtempSync(path.join(tmpdir(), "ccr-gateway-client-abort-test-"));
- const uncaughtErrors = [];
- const unhandledRejections = [];
- const onUncaughtException = (error) => uncaughtErrors.push(error);
- const onUnhandledRejection = (error) => unhandledRejections.push(error);
- process.prependListener("uncaughtException", onUncaughtException);
- process.prependListener("unhandledRejection", onUnhandledRejection);
- let upstreamResponseClosed = false;
- const patch = "*** Begin Patch\n*** Add File: foo.txt\n+hi\n*** End Patch\n";
-
- const upstream = createServer((request, response) => {
- request.resume();
- response.writeHead(200, { "content-type": "text/event-stream" });
- response.write("event: response.output_text.delta\n");
- response.write(`data: ${JSON.stringify({
- item: {
- arguments: JSON.stringify({ patch }),
- call_id: "call_patch",
- name: "virtual_apply_patch",
- type: "function_call"
- },
- type: "response.output_item.done"
- })}\n\n`);
- const interval = setInterval(() => {
- response.write("event: response.output_text.delta\n");
- response.write(`data: ${JSON.stringify({ delta: "tick", type: "response.output_text.delta" })}\n\n`);
- }, 20);
- response.on("close", () => {
- upstreamResponseClosed = true;
- clearInterval(interval);
- });
- });
- const gateway = createServer((request, response) => {
- const requestPath = new URL(request.url ?? "/", "http://127.0.0.1").pathname;
- void gatewayService.proxyRequest(request, response, requestPath).catch((error) => {
- if (!response.headersSent) {
- response.writeHead(502, { "content-type": "application/json" });
- }
- if (!response.writableEnded) {
- response.end(`${JSON.stringify({ error: { message: String(error?.message ?? error) } })}\n`);
- }
- });
- });
-
- try {
- await listen(upstream);
- const upstreamPort = serverPort(upstream);
- const config = createDefaultAppConfig({ generatedConfigFile: path.join(dir, "gateway.config.json") });
- config.APIKEY = "test-api-key";
- config.gateway.coreHost = "127.0.0.1";
- config.gateway.corePort = upstreamPort;
- config.gateway.host = "127.0.0.1";
- config.gateway.port = 0;
- gatewayService.updateConfig(config);
- gatewayService.coreAuthToken = "test-core-auth-token";
-
- await listen(gateway);
- const gatewayUrl = `http://127.0.0.1:${serverPort(gateway)}/v1/responses`;
- const controller = new AbortController();
- const response = await fetch(gatewayUrl, {
- body: JSON.stringify({
- input: "hello",
- model: "provider-deepseek::openai_chat_completions/deepseek-v4-flash",
- stream: true,
- tools: [{ type: "custom", name: "apply_patch", format: { type: "grammar", syntax: "lark", definition: "start: begin_patch" } }]
- }),
- headers: {
- authorization: "Bearer test-api-key",
- "content-type": "application/json",
- "user-agent": "codex-test"
- },
- method: "POST",
- signal: controller.signal
- });
-
- const errorText = response.status === 200 ? "" : await response.text();
- assert.equal(response.status, 200, errorText);
- const reader = response.body.getReader();
- const firstChunk = await reader.read();
- assert.equal(firstChunk.done, false);
- const firstChunkText = new TextDecoder().decode(firstChunk.value);
- assert.match(firstChunkText, /"type":"custom_tool_call"/, "expected the response to use the Codex apply_patch response transform");
- assert.match(firstChunkText, /"name":"apply_patch"/, "expected virtual_apply_patch to be rewritten back to apply_patch");
-
- await reader.cancel("test downstream disconnect");
- controller.abort();
- await waitFor(() => upstreamResponseClosed, 1000);
-
- assert.deepEqual(uncaughtErrors.map((error) => error?.message ?? String(error)), []);
- assert.deepEqual(unhandledRejections.map((error) => error?.message ?? String(error)), []);
- } finally {
- process.off("uncaughtException", onUncaughtException);
- process.off("unhandledRejection", onUnhandledRejection);
- await closeServer(gateway);
- await closeServer(upstream);
- await gatewayService.stop();
- rmSync(dir, { force: true, recursive: true });
- }
-});
-
-function listen(server) {
- return new Promise((resolve, reject) => {
- server.once("error", reject);
- server.listen(0, "127.0.0.1", () => {
- server.off("error", reject);
- resolve();
- });
- });
-}
-
-function serverPort(server) {
- const address = server.address();
- assert.equal(typeof address, "object");
- assert.ok(address);
- return address.port;
-}
-
-function closeServer(server) {
- return new Promise((resolve, reject) => {
- if (!server.listening) {
- resolve();
- return;
- }
- const timeout = setTimeout(() => server.closeAllConnections?.(), 1000);
- server.close((error) => {
- clearTimeout(timeout);
- error ? reject(error) : resolve();
- });
- });
-}
-
-function sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
-}
-
-async function waitFor(predicate, timeoutMs) {
- const deadline = Date.now() + timeoutMs;
- while (Date.now() < deadline) {
- if (predicate()) {
- return;
- }
- await sleep(10);
- }
- assert.equal(predicate(), true);
-}
diff --git a/tests/main/gateway-status.test.mjs b/tests/main/gateway-status.test.mjs
deleted file mode 100644
index 9d60b27..0000000
--- a/tests/main/gateway-status.test.mjs
+++ /dev/null
@@ -1,20 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import { createDefaultAppConfig } from "../../packages/core/src/config/default-config.ts";
-import { gatewayService } from "../../packages/core/src/gateway/service.ts";
-
-test("gateway start persists preflight validation failures for status polling", async () => {
- await gatewayService.stop();
-
- const config = createDefaultAppConfig({ generatedConfigFile: "/tmp/ccr-gateway.config.json" });
- config.gateway.coreHost = "0.0.0.0";
-
- const startStatus = await gatewayService.start(config);
- const polledStatus = gatewayService.getStatus();
-
- assert.equal(startStatus.state, "error");
- assert.equal(polledStatus.state, "error");
- assert.equal(polledStatus.lastError, "Core gateway host must be 127.0.0.1 or ::1.");
-
- await gatewayService.stop();
-});
diff --git a/tests/main/local-agent-provider-codex.test.mjs b/tests/main/local-agent-provider-codex.test.mjs
index f57cbf8..dd687fe 100644
--- a/tests/main/local-agent-provider-codex.test.mjs
+++ b/tests/main/local-agent-provider-codex.test.mjs
@@ -3,7 +3,6 @@ import test from "node:test";
import {
attachCodexRateLimitResetCreditDetails,
codexDefaultBaseUrl,
- codexModelCatalogFromPayloadForTest,
codexProviderAccountConfig,
codexRateLimitResetCreditDetails,
normalizeCodexProviderAccountConfig
@@ -205,44 +204,3 @@ test("Codex local provider account config keeps custom connectors", () => {
assert.equal(provider.account, account);
});
-
-test("Codex model catalog parser accepts live model endpoint shapes", () => {
- const catalog = codexModelCatalogFromPayloadForTest({
- data: [
- {
- additional_speed_tiers: [{ id: "fast", label: "Fast" }],
- default_reasoning_level: "high",
- display_name: "GPT-5 Codex",
- service_tiers: [{ id: "auto" }],
- slug: "gpt-5-codex",
- supported_reasoning_levels: [
- { description: "Low", effort: "low" },
- { description: "High", effort: "high" }
- ],
- supports_reasoning_summaries: true
- },
- { displayName: "GPT-5.1 Codex", id: "gpt-5.1-codex" }
- ],
- models: [
- "gpt-5-codex",
- { label: "GPT-5.2 Codex", name: "gpt-5.2-codex" }
- ]
- });
-
- assert.deepEqual(catalog.models, ["gpt-5-codex", "gpt-5.1-codex", "gpt-5.2-codex"]);
- assert.deepEqual(catalog.modelDisplayNames, {
- "gpt-5-codex": "GPT-5 Codex",
- "gpt-5.1-codex": "GPT-5.1 Codex",
- "gpt-5.2-codex": "GPT-5.2 Codex"
- });
- assert.deepEqual(catalog.modelMetadata["gpt-5-codex"], {
- additionalSpeedTiers: [{ id: "fast", label: "Fast" }],
- defaultReasoningLevel: "high",
- serviceTiers: [{ id: "auto" }],
- supportedReasoningLevels: [
- { description: "Low", effort: "low" },
- { description: "High", effort: "high" }
- ],
- supportsReasoningSummaries: true
- });
-});
diff --git a/tests/main/plugin-service.test.mjs b/tests/main/plugin-service.test.mjs
deleted file mode 100644
index 04a4978..0000000
--- a/tests/main/plugin-service.test.mjs
+++ /dev/null
@@ -1,84 +0,0 @@
-import assert from "node:assert/strict";
-import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
-import { tmpdir } from "node:os";
-import path from "node:path";
-import test from "node:test";
-import { createDefaultAppConfig } from "../../packages/core/src/config/default-config.ts";
-import { pluginService } from "../../packages/core/src/plugins/service.ts";
-
-test("plugin service skips failed plugins and rolls back their registrations", async () => {
- const dir = mkdtempSync(path.join(tmpdir(), "ccr-plugin-service-test-"));
- const warnings = [];
- const originalWarn = console.warn;
- console.warn = (...args) => warnings.push(args.map(String).join(" "));
-
- try {
- const brokenPlugin = path.join(dir, "broken-plugin.cjs");
- const goodPlugin = path.join(dir, "good-plugin.cjs");
- writeFileSync(brokenPlugin, `
-module.exports = async function brokenPlugin(context) {
- context.registerApp({ id: "broken-context-app", name: "Broken context", url: "http://broken-context.local" });
- context.registerGatewayRoute({ id: "broken-context-route", path: "/broken-context", handler(_request, response) { response.end("broken"); } });
- context.registerCoreGatewayProviderPlugin({ key: "broken-context-provider" });
- context.registerCoreGatewayVirtualModelProfile({ id: "broken-context-vm" });
- context.registerProviderAccountConnector({ id: "broken-account", resolve() { return []; } });
- throw new Error("broken plugin setup failed");
-};
-`);
- writeFileSync(goodPlugin, `
-module.exports = {
- setup(context) {
- context.registerCoreGatewayProviderPlugin({ key: "good-context-provider" });
- context.registerCoreGatewayVirtualModelProfile({ id: "good-context-vm" });
- return {
- apps: [{ id: "good-app", name: "Good app", url: "http://good.local" }],
- coreGateway: {
- config: { agent: { mcpServers: [{ name: "good-mcp", command: "good" }] } },
- providerPlugins: [{ key: "good-provider" }],
- virtualModelProfiles: [{ id: "good-vm" }]
- },
- gatewayRoutes: [{ id: "good-route", path: "/good", handler(_request, response) { response.end("good"); } }],
- providerAccountConnectors: [{ id: "good-account", resolve() { return []; } }],
- proxyRoutes: [{ host: "good.local", upstream: "http://127.0.0.1" }]
- };
- }
-};
-`);
-
- const config = createDefaultAppConfig({ generatedConfigFile: path.join(dir, "gateway.json") });
- config.plugins = [
- {
- apps: [{ id: "broken-config-app", name: "Broken config", url: "http://broken-config.local" }],
- coreGateway: {
- config: { agent: { mcpServers: [{ name: "broken-config-mcp", command: "broken" }] } },
- providerPlugins: [{ key: "broken-config-provider" }],
- virtualModelProfiles: [{ id: "broken-config-vm" }]
- },
- id: "broken",
- module: brokenPlugin,
- proxy: { routes: [{ host: "broken.local", upstream: "http://127.0.0.1" }] }
- },
- {
- id: "good",
- module: goodPlugin
- }
- ];
-
- await pluginService.start(config);
-
- assert.match(warnings.join("\n"), /plugin:broken.*Disabled after startup failure.*broken plugin setup failed/);
- assert.deepEqual(pluginService.getApps().map((app) => app.id), ["good-app"]);
- assert.equal(pluginService.matchGatewayRoute("GET", "/broken-context"), undefined);
- assert.equal(pluginService.matchGatewayRoute("GET", "/good")?.id, "good-route");
- assert.deepEqual(pluginService.getProxyRouteTargets(), [{ host: "good.local", paths: undefined }]);
- assert.deepEqual(pluginService.getCoreProviderPlugins().map((plugin) => plugin.key), ["good-context-provider", "good-provider"]);
- assert.deepEqual(pluginService.getVirtualModelProfiles().map((profile) => profile.id), ["good-context-vm", "good-vm"]);
- assert.deepEqual(pluginService.getCoreGatewayConfig(), { agent: { mcpServers: [{ name: "good-mcp", command: "good" }] } });
- assert.equal(pluginService.getProviderAccountConnector("broken", "broken-account"), undefined);
- assert.equal(typeof pluginService.getProviderAccountConnector("good", "good-account")?.resolve, "function");
- } finally {
- console.warn = originalWarn;
- await pluginService.stop();
- rmSync(dir, { force: true, recursive: true });
- }
-});
diff --git a/tests/main/provider-preset-utils.test.mjs b/tests/main/provider-preset-utils.test.mjs
index 062f72e..a131d2e 100644
--- a/tests/main/provider-preset-utils.test.mjs
+++ b/tests/main/provider-preset-utils.test.mjs
@@ -8,16 +8,6 @@ import {
providerIdentitySafetyIssueInList,
providerPresetMatchesBaseUrl
} from "../../packages/core/src/providers/presets/utils.ts";
-import {
- fennoProviderPreset
-} from "../../packages/core/src/providers/presets/fenno/index.ts";
-import {
- moonshotChinaProviderPreset,
- moonshotGlobalProviderPreset
-} from "../../packages/core/src/providers/presets/moonshot/index.ts";
-import {
- qiniuAiProviderPreset
-} from "../../packages/core/src/providers/presets/qiniu-ai/index.ts";
const openAiPreset = {
aliases: ["OpenAI", "ChatGPT"],
@@ -36,7 +26,6 @@ const anthropicPreset = {
};
const presets = [openAiPreset, anthropicPreset];
-const moonshotPresets = [moonshotChinaProviderPreset, moonshotGlobalProviderPreset];
test("provider preset matching accepts endpoint subpaths but rejects different hosts", () => {
const openRouterPreset = {
@@ -58,36 +47,7 @@ test("provider identity lookup normalizes aliases and punctuation", () => {
assert.equal(findProviderPresetByIdentityInList(presets, "Claude Provider")?.id, "anthropic");
});
-test("provider identity lookup prefers exact Kimi regional names over shared aliases", () => {
- assert.equal(findProviderPresetByIdentityInList(moonshotPresets, "Kimi API (Global)")?.id, "moonshot-global");
- assert.equal(findProviderPresetByIdentityInList(moonshotPresets, "Kimi API (China)")?.id, "moonshot");
-});
-
-test("sponsor provider presets expose requested endpoints and protocols", () => {
- assert.equal(fennoProviderPreset.websiteUrl, "https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=9HHHAB5QLAES");
- assert.deepEqual(fennoProviderPreset.endpoints[0]?.protocols, [
- "openai_chat_completions",
- "openai_responses",
- "anthropic_messages"
- ]);
-
- assert.equal(qiniuAiProviderPreset.websiteUrl, "https://s.qiniu.com/AVjMVf");
- assert.equal(providerPresetMatchesBaseUrl(qiniuAiProviderPreset, "https://api.qnaigc.com"), true);
- assert.equal(providerPresetMatchesBaseUrl(qiniuAiProviderPreset, "https://api.modelink.ai/v1/models"), false);
- assert.equal(providerPresetMatchesBaseUrl(qiniuAiProviderPreset, "https://api.qnaigc.com/bypass/openai/v1/responses"), true);
- assert.equal(providerPresetMatchesBaseUrl(qiniuAiProviderPreset, "https://api.qnaigc.com/bypass/vertex/v1/models/gemini-pro:generateContent"), true);
- assert.deepEqual(qiniuAiProviderPreset.endpoints.map((endpoint) => [endpoint.label, endpoint.baseUrl, endpoint.protocols]), [
- ["China mainland OpenAI", "https://api.qnaigc.com", ["openai_chat_completions"]],
- ["China mainland OpenAI Responses", "https://api.qnaigc.com/bypass/openai/v1", ["openai_responses"]],
- ["China mainland Anthropic", "https://api.qnaigc.com", ["anthropic_messages"]],
- ["China mainland Gemini Generate", "https://api.qnaigc.com/bypass/vertex/v1", ["gemini_generate_content"]]
- ]);
- assert.deepEqual(qiniuAiProviderPreset.endpoints[0]?.protocols, [
- "openai_chat_completions"
- ]);
-});
-
-test("provider identity safety does not block branded third-party endpoints", () => {
+test("provider identity safety allows loopback but warns on branded third-party endpoints", () => {
assert.equal(
providerIdentitySafetyIssueInList(presets, {
baseUrl: "http://127.0.0.1:3456/v1",
@@ -95,55 +55,23 @@ test("provider identity safety does not block branded third-party endpoints", ()
}),
undefined
);
- assert.equal(
+ assert.match(
providerIdentitySafetyIssueInList(presets, {
baseUrl: "https://proxy.example.com/v1",
name: "OpenAI proxy"
- }),
- undefined
+ })?.message ?? "",
+ /Provider identity looks like OpenAI/
);
});
-test("provider identity safety does not block shared Kimi aliases", () => {
- assert.equal(
- providerIdentitySafetyIssueInList(moonshotPresets, {
- baseUrl: "https://api.moonshot.ai/anthropic",
- name: "Kimi API (Global)"
- }),
- undefined
- );
- assert.equal(
- providerIdentitySafetyIssueInList(moonshotPresets, {
- baseUrl: "https://api.moonshot.ai/v1",
- name: "Kimi API (China)"
- }),
- undefined
- );
- assert.equal(
- providerEndpointCanReceiveProviderApiKeyInList(moonshotPresets, {
- apiKey: "manifest-provider-api-key",
- endpoint: "https://api.moonshot.ai/v1/users/me/balance",
- providerName: "Kimi API (China)"
- }),
- undefined
- );
- assert.equal(
- providerIdentitySafetyIssueInList(moonshotPresets, {
- baseUrl: "https://proxy.example.com/v1",
- name: "Kimi API (Global)"
- }),
- undefined
- );
-});
-
-test("provider API key safety does not block official-looking keys on third-party endpoints", () => {
- assert.equal(
+test("provider API key safety blocks official-looking keys on untrusted endpoints", () => {
+ assert.match(
providerApiKeySafetyIssueInList(presets, {
apiKey: "sk-openai-test",
baseUrl: "https://proxy.example.com/v1",
name: "neutral proxy"
- }),
- undefined
+ })?.message ?? "",
+ /official OpenAI key/
);
assert.equal(
providerApiKeySafetyIssueInList(presets, {
@@ -152,12 +80,12 @@ test("provider API key safety does not block official-looking keys on third-part
}),
undefined
);
- assert.equal(
+ assert.match(
providerEndpointCanReceiveProviderApiKeyInList(presets, {
apiKey: "sk-ant-test",
endpoint: "https://proxy.example.com/anthropic",
providerName: "Anthropic"
- }),
- undefined
+ })?.message ?? "",
+ /official Anthropic key/
);
});
diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs
index 4505486..9a650ee 100644
--- a/tests/main/provider-probe.test.mjs
+++ b/tests/main/provider-probe.test.mjs
@@ -43,7 +43,7 @@ test("protocol support probe keeps auth-only fallback for unhinted endpoints", (
);
});
-test("protocol support probe treats HTTP 400 validation as protocol support", () => {
+test("protocol support probe treats Gemini contents validation as Gemini support only", () => {
const message = "HTTP 400: * GenerateContentRequest.contents: contents is not specified";
assert.equal(
@@ -52,11 +52,11 @@ test("protocol support probe treats HTTP 400 validation as protocol support", ()
);
assert.equal(
isProviderProtocolEndpointSupportedForProbe(400, message, "openai_chat_completions", ["openai_chat_completions"]),
- true
+ false
);
});
-test("protocol support probe treats HTTP 400 input validation as protocol support", () => {
+test("protocol support probe treats Gemini Interactions input validation as Interactions support", () => {
const message = "HTTP 400: Gemini Interactions request requires input.";
assert.equal(
@@ -65,15 +65,6 @@ test("protocol support probe treats HTTP 400 input validation as protocol suppor
);
assert.equal(
isProviderProtocolEndpointSupportedForProbe(400, message, "gemini_generate_content", ["gemini_generate_content"]),
- true
- );
-});
-
-test("protocol support probe still rejects HTTP 400 route misses", () => {
- const message = "HTTP 400: unknown route";
-
- assert.equal(
- isProviderProtocolEndpointSupportedForProbe(400, message, "openai_chat_completions", ["openai_chat_completions"]),
false
);
});
diff --git a/tests/main/provider-url.test.mjs b/tests/main/provider-url.test.mjs
index e9bf374..6270779 100644
--- a/tests/main/provider-url.test.mjs
+++ b/tests/main/provider-url.test.mjs
@@ -27,18 +27,6 @@ test("provider URL parsing handles local and Gemini endpoint variants", () => {
assert.equal(parsed.geminiBaseUrl, "http://localhost:8787");
});
-test("provider URL parsing preserves versioned Vertex bypass bases for Gemini", () => {
- const parsed = parseProviderBaseUrl("https://api.qnaigc.com/bypass/vertex/v1/models/gemini-pro:generateContent");
-
- assert.equal(parsed.normalizedInputBaseUrl, "https://api.qnaigc.com/bypass/vertex/v1");
- assert.equal(parsed.rootBaseUrl, "https://api.qnaigc.com/bypass/vertex");
- assert.equal(parsed.geminiBaseUrl, "https://api.qnaigc.com/bypass/vertex/v1");
- assert.equal(
- normalizeProviderBaseUrl("https://api.qnaigc.com/bypass/vertex/v1", "gemini_generate_content"),
- "https://api.qnaigc.com/bypass/vertex/v1"
- );
-});
-
test("provider URL parsing handles Gemini Interactions endpoint variants", () => {
const parsed = parseProviderBaseUrl("localhost:8787/v1/interactions/interaction-123/cancel");
diff --git a/tests/main/router-builtins.test.mjs b/tests/main/router-builtins.test.mjs
index b461f52..1928fc5 100644
--- a/tests/main/router-builtins.test.mjs
+++ b/tests/main/router-builtins.test.mjs
@@ -1,11 +1,7 @@
import assert from "node:assert/strict";
import test from "node:test";
import { ClaudeCodeRouterPlugin } from "../../packages/core/src/gateway/claude-code-router-plugin.ts";
-import {
- fallbackRetryDelayAfterNetworkErrorForTest,
- fallbackRetryDelayAfterStatusForTest,
- prepareGatewayUpstreamAttemptForTest
-} from "../../packages/core/src/gateway/service.ts";
+import { prepareGatewayUpstreamAttemptForTest } from "../../packages/core/src/gateway/service.ts";
function createRouterPlugin(options = {}) {
const agent = options.agent ?? "claude-code";
@@ -46,18 +42,6 @@ function createRouterPlugin(options = {}) {
});
}
-test("fallback retry delay backs off retryable HTTP statuses", () => {
- assert.equal(fallbackRetryDelayAfterStatusForTest({ statusCode: 503 }), 1000);
- assert.equal(fallbackRetryDelayAfterStatusForTest({ failedAttemptIndex: 1, statusCode: 408 }), 2000);
- assert.equal(fallbackRetryDelayAfterStatusForTest({ retryAfter: "3", statusCode: 429 }), 3000);
- assert.equal(fallbackRetryDelayAfterStatusForTest({ retryAfter: "0", statusCode: 429 }), 1000);
-});
-
-test("fallback retry delay backs off network errors", () => {
- assert.equal(fallbackRetryDelayAfterNetworkErrorForTest(), 1000);
- assert.equal(fallbackRetryDelayAfterNetworkErrorForTest(2), 4000);
-});
-
function createIssue1480UserConfig() {
return {
APIKEY: "gateway-key",
diff --git a/tests/renderer/layout.test.tsx b/tests/renderer/layout.test.tsx
deleted file mode 100644
index 9c55c68..0000000
--- a/tests/renderer/layout.test.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import assert from "node:assert/strict";
-import test from "node:test";
-import * as React from "react";
-import { renderToStaticMarkup } from "react-dom/server";
-import { GatewayStartupErrorBanner } from "../../packages/ui/src/pages/home/components/layout.tsx";
-import { AppI18nContext, appCopy } from "../../packages/ui/src/pages/home/shared/i18n.tsx";
-
-test("GatewayStartupErrorBanner renders startup failure details", () => {
- const html = renderToStaticMarkup(
-
- undefined} />
-
- );
-
- assert.match(html, /role="alert"/);
- assert.match(html, /aria-live="assertive"/);
- assert.match(html, /服务启动失败/);
- assert.match(html, /没有可用模型。请先配置供应商。/);
- assert.match(html, />服务<\/button>/);
-});
-
-test("GatewayStartupErrorBanner stays hidden without a failure message", () => {
- const html = renderToStaticMarkup();
-
- assert.equal(html, "");
-});
diff --git a/tests/renderer/providers.test.ts b/tests/renderer/providers.test.ts
index 6f0656c..8931dcf 100644
--- a/tests/renderer/providers.test.ts
+++ b/tests/renderer/providers.test.ts
@@ -1,31 +1,15 @@
import assert from "node:assert/strict";
import test from "node:test";
-import * as React from "react";
-import { renderToStaticMarkup } from "react-dom/server";
import { newApiKeyUsageAccountConfig } from "../../packages/core/src/providers/new-api.ts";
import { geminiProviderPreset } from "../../packages/core/src/providers/presets/gemini/index.ts";
-import { moonshotGlobalProviderPreset } from "../../packages/core/src/providers/presets/moonshot/index.ts";
-import { qiniuAiProviderPreset } from "../../packages/core/src/providers/presets/qiniu-ai/index.ts";
-import { AddProviderDialog, ProvidersView } from "../../packages/ui/src/pages/home/components/providers.tsx";
import {
applyProviderProbeResult,
- createProviderConfigFromDeepLink,
createProviderDraft,
- createProviderInstallLinkFromDraft,
- providerCapabilitiesForProtocols,
- providerCapabilityBaseUrlForProtocol,
- providerDisplayIcon,
providerAccountConnectorsTextWithNewApiUserBalanceTemplate,
- providerGlobalBaseUrlForProbe,
- providerPresetIconUrls,
providerProtocolOptions,
providerProbeCandidates,
- providerSelectableProtocolsFromProbe,
setProviderPresets
} from "../../packages/ui/src/pages/home/shared/index.tsx";
-import { installBrowserGlobals } from "./fixtures.ts";
-
-installBrowserGlobals();
test("Gemini preset keeps full protocol probing candidates", () => {
setProviderPresets([geminiProviderPreset]);
@@ -38,23 +22,6 @@ test("Gemini preset keeps full protocol probing candidates", () => {
assert.equal(candidates.length, 1);
assert.deepEqual(candidates[0].protocols, providerProtocolOptions.map((option) => option.value));
- assert.deepEqual(candidates[0].declaredProtocols, ["gemini_generate_content", "gemini_interactions"]);
-});
-
-test("multi-endpoint presets probe only each endpoint's declared protocols", () => {
- setProviderPresets([qiniuAiProviderPreset]);
- const draft = {
- ...createProviderDraft([]),
- presetId: "qiniu-ai"
- };
-
- const candidates = providerProbeCandidates(draft);
-
- assert.equal(candidates.length, qiniuAiProviderPreset.endpoints.length);
- assert.deepEqual(
- candidates.map((candidate) => [candidate.baseUrl, candidate.protocols]),
- qiniuAiProviderPreset.endpoints.map((endpoint) => [endpoint.baseUrl, endpoint.protocols])
- );
});
test("provider probe result drops unavailable selected protocols", () => {
@@ -107,63 +74,6 @@ test("provider probe result keeps only supported selected protocols", () => {
assert.deepEqual(next.selectedProtocols, ["gemini_generate_content"]);
});
-test("provider protocol details keep failed endpoint rows unavailable", () => {
- const draft = {
- ...createProviderDraft([]),
- baseUrl: "https://api.example.com/v1",
- name: "Example",
- protocol: "openai_chat_completions" as const,
- selectedProtocols: ["openai_chat_completions" as const]
- };
- const html = renderToStaticMarkup(
- React.createElement(AddProviderDialog, {
- canSubmit: true,
- draft,
- error: "",
- mode: "edit",
- onChange: () => undefined,
- onClose: () => undefined,
- onSubmit: async () => true,
- probe: {
- capabilities: [
- {
- baseUrl: "https://api.example.com/v1",
- endpoint: "https://api.example.com/v1/chat/completions",
- source: "detected" as const,
- type: "openai_chat_completions" as const
- }
- ],
- detectedProtocol: "openai_chat_completions" as const,
- models: [],
- normalizedBaseUrl: "https://api.example.com/v1",
- protocols: [
- {
- baseUrl: "https://api.example.com",
- endpoint: "https://api.example.com/chat/completions",
- message: "HTTP 404",
- protocol: "openai_chat_completions" as const,
- status: 404,
- supported: false
- },
- {
- baseUrl: "https://api.example.com/v1",
- endpoint: "https://api.example.com/v1/chat/completions",
- message: "HTTP 400: model is required",
- protocol: "openai_chat_completions" as const,
- status: 400,
- supported: true
- }
- ]
- },
- probeLoading: false,
- providers: []
- })
- );
-
- assert.match(html, /Unavailable/);
- assert.match(html, /Available/);
-});
-
test("provider probe result applies detected New API key quota account connector", () => {
const draft = {
...createProviderDraft([]),
@@ -202,301 +112,6 @@ test("provider probe result applies detected New API key quota account connector
assert.equal(connectors[0].mapping.meters[0].remaining, "$.data.total_available");
});
-test("provider probe keeps anthropic prefix on the protocol capability", () => {
- const draft = {
- ...createProviderDraft([]),
- baseUrl: "https://gateway.example",
- protocol: "anthropic_messages",
- selectedProtocols: ["anthropic_messages"]
- };
- const anthropicCapability = {
- baseUrl: "https://gateway.example/anthropic",
- endpoint: "https://gateway.example/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- };
- const probe = {
- capabilities: [anthropicCapability],
- detectedProtocol: "anthropic_messages" as const,
- models: [],
- normalizedBaseUrl: "https://gateway.example/anthropic",
- protocols: [
- {
- ...anthropicCapability,
- message: "HTTP 400: model is required",
- status: 400,
- supported: true
- }
- ]
- };
-
- const next = applyProviderProbeResult(draft, probe);
-
- assert.equal(next.baseUrl, "https://gateway.example");
- assert.equal(providerGlobalBaseUrlForProbe(draft.baseUrl, probe, ["anthropic_messages"]), "https://gateway.example");
- assert.equal(
- providerCapabilityBaseUrlForProtocol(draft.baseUrl, "anthropic_messages", probe),
- "https://gateway.example/anthropic"
- );
- assert.deepEqual(providerCapabilitiesForProtocols(draft.baseUrl, ["anthropic_messages"], probe), [anthropicCapability]);
-});
-
-test("provider capabilities prefer detected anthropic endpoint over preset root", () => {
- const probe = {
- capabilities: [
- {
- baseUrl: "https://gateway.example/anthropic",
- endpoint: "https://gateway.example/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- }
- ],
- detectedProtocol: "anthropic_messages" as const,
- models: [],
- normalizedBaseUrl: "https://gateway.example/anthropic",
- protocols: []
- };
-
- const capabilities = providerCapabilitiesForProtocols(
- "https://gateway.example",
- ["anthropic_messages", "openai_chat_completions"],
- probe,
- [
- { baseUrl: "https://gateway.example", source: "preset" as const, type: "anthropic_messages" as const },
- { baseUrl: "https://gateway.example/v1", source: "preset" as const, type: "openai_chat_completions" as const }
- ]
- );
-
- assert.deepEqual(capabilities.map((capability) => [capability.type, capability.baseUrl]), [
- ["openai_chat_completions", "https://gateway.example/v1"],
- ["anthropic_messages", "https://gateway.example/anthropic"]
- ]);
- assert.equal(
- providerGlobalBaseUrlForProbe("https://gateway.example", probe, ["anthropic_messages", "openai_chat_completions"]),
- "https://gateway.example"
- );
-});
-
-test("provider global URL keeps Kimi Global OpenAI v1 when anthropic probe is detected", () => {
- const probe = {
- capabilities: [
- {
- baseUrl: "https://api.moonshot.ai/anthropic",
- endpoint: "https://api.moonshot.ai/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- }
- ],
- detectedProtocol: "anthropic_messages" as const,
- models: [],
- normalizedBaseUrl: "https://api.moonshot.ai/anthropic",
- protocols: []
- };
-
- assert.equal(
- providerGlobalBaseUrlForProbe("https://api.moonshot.ai/v1", probe, ["openai_chat_completions"]),
- "https://api.moonshot.ai/v1"
- );
- assert.deepEqual(
- providerCapabilitiesForProtocols("https://api.moonshot.ai/v1", ["openai_chat_completions"], probe)
- .map((capability) => [capability.type, capability.baseUrl]),
- [["openai_chat_completions", "https://api.moonshot.ai/v1"]]
- );
-});
-
-test("provider global URL always follows the typed endpoint instead of probe endpoint", () => {
- const probe = {
- capabilities: [
- {
- baseUrl: "https://gateway.example/v1",
- endpoint: "https://gateway.example/v1/chat/completions",
- source: "detected" as const,
- type: "openai_chat_completions" as const
- },
- {
- baseUrl: "https://gateway.example/anthropic",
- endpoint: "https://gateway.example/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- }
- ],
- detectedProtocol: "openai_chat_completions" as const,
- models: [],
- normalizedBaseUrl: "https://gateway.example/v1",
- protocols: []
- };
-
- assert.equal(
- providerGlobalBaseUrlForProbe("https://gateway.example", probe, ["openai_chat_completions", "anthropic_messages"]),
- "https://gateway.example"
- );
-});
-
-test("Kimi Global preset keeps OpenAI Chat when probe detects anthropic fallback", () => {
- setProviderPresets([moonshotGlobalProviderPreset]);
- const draft = {
- ...createProviderDraft([]),
- baseUrl: "https://api.moonshot.ai/v1",
- presetId: "moonshot-global",
- protocol: "openai_chat_completions",
- selectedProtocols: ["openai_chat_completions"]
- };
- const probe = {
- capabilities: [
- {
- baseUrl: "https://api.moonshot.ai/anthropic",
- endpoint: "https://api.moonshot.ai/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- },
- {
- baseUrl: "https://api.moonshot.ai/v1",
- source: "preset" as const,
- type: "openai_chat_completions" as const
- }
- ],
- detectedProtocol: "anthropic_messages" as const,
- models: [],
- normalizedBaseUrl: "https://api.moonshot.ai/anthropic",
- protocols: [
- {
- endpoint: "https://api.moonshot.ai/anthropic/v1/messages",
- message: "HTTP 400: model is required",
- protocol: "anthropic_messages" as const,
- status: 400,
- supported: true
- }
- ]
- };
-
- const next = applyProviderProbeResult(draft, probe);
-
- assert.equal(next.baseUrl, "https://api.moonshot.ai/v1");
- assert.equal(next.protocol, "openai_chat_completions");
- assert.deepEqual(next.selectedProtocols, ["openai_chat_completions", "anthropic_messages"]);
- assert.deepEqual(providerSelectableProtocolsFromProbe(probe), ["openai_chat_completions", "anthropic_messages"]);
-
- const installLink = createProviderInstallLinkFromDraft({
- ...next,
- modelsText: "kimi-k2.7-code",
- name: "Kimi API (Global)"
- }, probe);
- const payload = providerInstallLinkPayload(installLink);
- assert.equal(payload.protocol, "openai_chat_completions");
- assert.equal(payload.baseUrl, "https://api.moonshot.ai/v1");
-});
-
-test("Kimi Global deep link explicit OpenAI protocol wins over anthropic probe", () => {
- const probe = {
- capabilities: [
- {
- baseUrl: "https://api.moonshot.ai/anthropic",
- endpoint: "https://api.moonshot.ai/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- }
- ],
- detectedProtocol: "anthropic_messages" as const,
- models: [],
- normalizedBaseUrl: "https://api.moonshot.ai/anthropic",
- protocols: []
- };
-
- const provider = createProviderConfigFromDeepLink({
- baseUrl: "https://api.moonshot.ai/v1",
- models: ["kimi-k2.7-code"],
- name: "Kimi API (Global)",
- protocol: "openai_chat_completions"
- }, [], probe);
-
- assert.equal(provider.type, "openai_chat_completions");
- assert.equal(provider.api_base_url, "https://api.moonshot.ai/v1");
- assert.deepEqual(provider.capabilities?.map((capability) => [capability.type, capability.baseUrl]), [
- ["openai_chat_completions", "https://api.moonshot.ai/v1"]
- ]);
-});
-
-test("provider deep link config saves anthropic probe prefix as capability URL", () => {
- const probe = {
- capabilities: [
- {
- baseUrl: "https://gateway.example/anthropic",
- endpoint: "https://gateway.example/anthropic/v1/messages",
- source: "detected" as const,
- type: "anthropic_messages" as const
- }
- ],
- detectedProtocol: "anthropic_messages" as const,
- models: [],
- normalizedBaseUrl: "https://gateway.example/anthropic",
- protocols: []
- };
-
- const provider = createProviderConfigFromDeepLink({
- baseUrl: "https://gateway.example",
- models: ["claude-test"],
- name: "Gateway",
- protocol: "anthropic_messages"
- }, [], probe);
-
- assert.equal(provider.api_base_url, "https://gateway.example");
- assert.deepEqual(provider.capabilities?.map((capability) => [capability.type, capability.baseUrl]), [
- ["anthropic_messages", "https://gateway.example/anthropic"]
- ]);
-});
-
-test("provider display icon prefers custom icons and falls back to preset icons", () => {
- setProviderPresets([geminiProviderPreset]);
-
- assert.equal(
- providerDisplayIcon({
- api_base_url: "https://custom.example/v1",
- icon: "https://custom.example/icon.png",
- models: [],
- name: "Custom Provider",
- type: "openai_chat_completions"
- }),
- "https://custom.example/icon.png"
- );
- assert.equal(
- providerDisplayIcon({
- api_base_url: "https://generativelanguage.googleapis.com",
- models: [],
- name: "Google Gemini",
- type: "gemini_generate_content"
- }),
- providerPresetIconUrls.gemini
- );
-});
-
-test("ProvidersView renders configured provider icons in the list", () => {
- const iconUrl = "https://custom.example/icon.png";
- const html = renderToStaticMarkup(
- React.createElement(ProvidersView, {
- accountSnapshots: [],
- addProvider: () => undefined,
- editProvider: () => undefined,
- notify: () => undefined,
- providers: [
- {
- index: 0,
- provider: {
- api_base_url: "https://custom.example/v1",
- icon: iconUrl,
- models: ["custom-model"],
- name: "Custom Provider",
- type: "openai_chat_completions"
- }
- }
- ],
- removeProvider: () => undefined
- })
- );
-
- assert.match(html, /Custom Provider/);
- assert.match(html, /src="https:\/\/custom\.example\/icon\.png"/);
-});
-
test("New API user balance template adds configurable user self connector", () => {
const account = newApiKeyUsageAccountConfig("https://gateway.example/v1");
const text = providerAccountConnectorsTextWithNewApiUserBalanceTemplate(
@@ -514,12 +129,3 @@ test("New API user balance template adds configurable user self connector", () =
assert.equal(connectors[1].headers["New-Api-User"], "42");
assert.equal(connectors[1].mapping.meters[0].id, "new_api_user_balance");
});
-
-function providerInstallLinkPayload(link) {
- const url = new URL(link);
- const payload = url.searchParams.get("payload");
- assert.ok(payload);
- const padded = payload.padEnd(Math.ceil(payload.length / 4) * 4, "=").replace(/-/g, "+").replace(/_/g, "/");
- const bytes = Uint8Array.from(atob(padded), (char) => char.charCodeAt(0));
- return JSON.parse(new TextDecoder().decode(bytes));
-}
diff --git a/tests/renderer/usage-format.test.ts b/tests/renderer/usage-format.test.ts
index a6df6da..43eca8f 100644
--- a/tests/renderer/usage-format.test.ts
+++ b/tests/renderer/usage-format.test.ts
@@ -1,8 +1,7 @@
import assert from "node:assert/strict";
import test from "node:test";
import { formatLogTokenSummary } from "../../packages/ui/src/pages/home/shared/logs.ts";
-import { formatCompactNumber, formatUsdCost as formatHomeUsdCost } from "../../packages/ui/src/pages/home/shared/usage.ts";
-import { formatUsdCost as formatTrayUsdCost } from "../../packages/ui/src/pages/tray/shared.tsx";
+import { formatCompactNumber } from "../../packages/ui/src/pages/home/shared/usage.ts";
import type { RequestLogEntry } from "../../packages/core/src/contracts/app.ts";
test("formatCompactNumber can be bound to the UI language locale", () => {
@@ -10,13 +9,6 @@ test("formatCompactNumber can be bound to the UI language locale", () => {
assert.equal(formatCompactNumber(123456, "zh-CN"), "12.3万");
});
-test("formatUsdCost formats large values without conflicting fraction digits", () => {
- assert.doesNotThrow(() => formatHomeUsdCost(100));
- assert.doesNotThrow(() => formatTrayUsdCost(100));
- assert.doesNotMatch(formatHomeUsdCost(123.45), /[.,]45/);
- assert.doesNotMatch(formatTrayUsdCost(123.45), /[.,]45/);
-});
-
test("formatLogTokenSummary uses the provided locale for token counts", () => {
const entry: RequestLogEntry = {
cacheReadTokens: 0,