blob: 5d7baeb1a64bc0dec02e44d49921f882f7865d88 [file]
import {
generalizedErrorMessage,
generalizedErrorMessageChinese,
redactSecrets,
type ConnectionTestResult,
type CreateConnectionInput,
type LlmConnection,
type ModelDiscoveryResult,
type RequestHeaderUpdate,
type SavedRequestHeaders,
type ProviderCategory,
type ProviderType,
type UiLocale,
type UpdateConnectionInput,
} from '@maka/core';
import { getProviderSettingsCopy } from '../locales/settings-provider-copy.js';
import { cleanErrorMessage } from '../model-connection-errors.js';
export interface ConnectionsBridge {
list(): Promise<LlmConnection[]>;
getDefault(): Promise<string | null>;
setDefault(slug: string | null): Promise<void>;
create(input: CreateConnectionInput): Promise<LlmConnection>;
update(slug: string, patch: UpdateConnectionInput): Promise<LlmConnection>;
delete(slug: string): Promise<void>;
test(slug: string, opts?: { model?: string }): Promise<ConnectionTestResult>;
fetchModels(slug: string): Promise<ModelDiscoveryResult>;
hasSecret(slug: string): Promise<boolean>;
getRequestHeaders(slug: string): Promise<SavedRequestHeaders>;
setRequestHeaders(
slug: string,
headers: readonly RequestHeaderUpdate[],
): Promise<SavedRequestHeaders>;
subscribeEvents?(handler: () => void): () => void;
}
export type CredentialPresenceStatus = boolean | 'loading' | 'error';
export function providerPanelActionErrorMessage(error: unknown, locale: UiLocale = 'zh'): string {
const shared = getProviderSettingsCopy(locale).shared;
// Electron wraps ipcMain.handle rejections as "Error invoking remote method
// '<channel>': Error: <message>". Classify the original message, not the
// wrapper — channel names like 'connections:fetchModels' contain "fetch",
// which the keyword classifier reads as a network error.
const cleaned = redactSecrets(cleanErrorMessage(error)).trim();
const known = (shared.lastTest as Readonly<Record<string, string>>)[cleaned.toLowerCase()];
if (known) return known;
// Main-process handlers throw display-ready Chinese copy; keep it instead
// of flattening it into a coarser classification or the generic fallback.
if (/[\u3400-\u9fff]/.test(cleaned)) return cleaned;
const classified = locale === 'zh'
? generalizedErrorMessageChinese(new Error(cleaned), '')
: generalizedErrorMessage(new Error(cleaned), '');
return classified || shared.actionFallback;
}
export interface ConnectionTestTroubleshootingCopy {
/** Auth-class failure copy (errorClass 'auth' or HTTP 401/403). */
auth: string;
/** Final fallback copy when no failure class matched. */
recheck: string;
}
// Shared connection-test failure classification. The Models connection
// sheet and the Account page used to each hand-copy this table; only the
// surface-specific troubleshooting copy differs, so callers inject it.
export function connectionTestFailureFallback(
result: ConnectionTestResult,
copy: ConnectionTestTroubleshootingCopy,
locale: UiLocale = 'zh',
): string {
const shared = getProviderSettingsCopy(locale).shared;
if (result.statusCode === 429) return shared.rateLimit;
if (result.errorClass === 'timeout') return shared.timeout;
if (result.errorClass === 'auth' || result.statusCode === 401 || result.statusCode === 403) {
return copy.auth;
}
if (result.errorClass === 'provider_unavailable' || (result.statusCode !== undefined && result.statusCode >= 500)) {
return shared.unavailable;
}
if (result.errorClass === 'network') return shared.network;
return copy.recheck;
}
export function connectionTestFailureMessage(
result: ConnectionTestResult,
copy: ConnectionTestTroubleshootingCopy,
locale: UiLocale = 'zh',
): string {
const fallback = connectionTestFailureFallback(result, copy, locale);
if (!result.errorMessage) return fallback;
return locale === 'zh'
? generalizedErrorMessageChinese(new Error(result.errorMessage), fallback)
: generalizedErrorMessage(new Error(result.errorMessage), fallback);
}
export function connectionLastTestMessageDisplay(message: string | undefined, locale: UiLocale = 'zh'): string | undefined {
if (!message) return undefined;
const trimmed = message.trim();
if (!trimmed) return undefined;
const normalized = trimmed.toLowerCase();
const copy = getProviderSettingsCopy(locale).shared;
const known = (copy.lastTest as Readonly<Record<string, string>>)[normalized];
if (known) return known;
const classified = locale === 'zh'
? generalizedErrorMessageChinese(new Error(trimmed), '')
: generalizedErrorMessage(new Error(trimmed), '');
return classified || copy.statusUnavailable;
}
export function categoryLabel(category: ProviderCategory, locale: UiLocale = 'zh'): string {
return getProviderSettingsCopy(locale).shared.categories[category];
}