Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
177 lines
6.2 KiB
TypeScript
177 lines
6.2 KiB
TypeScript
import type { ProviderAccount, ProviderVendorInfo, ProviderWithKeyInfo } from '@/lib/providers';
|
|
import {
|
|
getKnownModelCapabilityKind,
|
|
getModelCapabilityLabel,
|
|
type ModelCapabilityKind,
|
|
} from '../../shared/model-capabilities';
|
|
import {
|
|
NIANCODE_USER_MODEL_ACCOUNT_ID,
|
|
normalizeImportedUserModelId,
|
|
selectUserModelRuntimeAccounts,
|
|
} from '../../shared/user-model-config';
|
|
|
|
export interface ConfiguredModelOption {
|
|
modelRef: string;
|
|
label: string;
|
|
runtimeProviderKey: string;
|
|
accountId: string;
|
|
capability: ModelCapabilityKind;
|
|
}
|
|
|
|
export interface RuntimeProviderOption {
|
|
runtimeProviderKey: string;
|
|
accountId: string;
|
|
label: string;
|
|
modelIdPlaceholder?: string;
|
|
configuredModelId?: string;
|
|
}
|
|
|
|
export function resolveRuntimeProviderKey(account: ProviderAccount): string {
|
|
return account.id;
|
|
}
|
|
|
|
export function splitModelRef(modelRef: string | null | undefined): { providerKey: string; modelId: string } | null {
|
|
const value = (modelRef || '').trim();
|
|
if (!value) return null;
|
|
const separatorIndex = value.indexOf('/');
|
|
if (separatorIndex <= 0 || separatorIndex >= value.length - 1) return null;
|
|
return {
|
|
providerKey: value.slice(0, separatorIndex),
|
|
modelId: value.slice(separatorIndex + 1),
|
|
};
|
|
}
|
|
|
|
export function formatModelRefLabel(modelRef: string | null | undefined): string {
|
|
const parsed = splitModelRef(modelRef);
|
|
return parsed?.modelId || (modelRef || '').trim() || 'Model';
|
|
}
|
|
|
|
export function toModelOptionTestId(label: string): string {
|
|
return label.replace(/[^a-zA-Z0-9_-]+/g, '-');
|
|
}
|
|
|
|
export function getConfiguredModelCapability(
|
|
option: ConfiguredModelOption,
|
|
): ModelCapabilityKind {
|
|
return option.capability;
|
|
}
|
|
|
|
export function getConfiguredModelCapabilityLabel(
|
|
option: ConfiguredModelOption,
|
|
): string {
|
|
return getModelCapabilityLabel(getConfiguredModelCapability(option));
|
|
}
|
|
|
|
export function hasConfiguredProviderCredentials(
|
|
account: ProviderAccount,
|
|
statusById: Map<string, ProviderWithKeyInfo>,
|
|
): boolean {
|
|
if (account.authMode === 'oauth_device' || account.authMode === 'oauth_browser' || account.authMode === 'local') {
|
|
return true;
|
|
}
|
|
return statusById.get(account.id)?.hasKey ?? false;
|
|
}
|
|
|
|
function normalizeModelIdForRuntimeKey(
|
|
rawModel: string | undefined,
|
|
runtimeProviderKey: string,
|
|
account: ProviderAccount,
|
|
): string | null {
|
|
const trimmed = rawModel?.trim();
|
|
if (!trimmed) return null;
|
|
const modelId = trimmed.startsWith(`${runtimeProviderKey}/`)
|
|
? trimmed.slice(runtimeProviderKey.length + 1)
|
|
: trimmed;
|
|
return account.id === NIANCODE_USER_MODEL_ACCOUNT_ID
|
|
? normalizeImportedUserModelId(modelId)
|
|
: modelId;
|
|
}
|
|
|
|
function modelIdsForAccount(account: ProviderAccount, runtimeProviderKey: string): string[] {
|
|
const seen = new Set<string>();
|
|
const modelIds: string[] = [];
|
|
for (const rawModel of [account.model, ...(account.fallbackModels ?? [])]) {
|
|
const modelId = normalizeModelIdForRuntimeKey(rawModel, runtimeProviderKey, account);
|
|
if (!modelId || seen.has(modelId)) continue;
|
|
seen.add(modelId);
|
|
modelIds.push(modelId);
|
|
}
|
|
return modelIds;
|
|
}
|
|
|
|
export function buildRuntimeProviderOptions(
|
|
providerAccounts: ProviderAccount[],
|
|
providerStatuses: ProviderWithKeyInfo[],
|
|
providerVendors: ProviderVendorInfo[],
|
|
providerDefaultAccountId: string | null,
|
|
): RuntimeProviderOption[] {
|
|
const safeAccounts = Array.isArray(providerAccounts) ? providerAccounts : [];
|
|
const safeStatuses = Array.isArray(providerStatuses) ? providerStatuses : [];
|
|
const safeVendors = Array.isArray(providerVendors) ? providerVendors : [];
|
|
const vendorMap = new Map<string, ProviderVendorInfo>(safeVendors.map((vendor) => [vendor.id, vendor]));
|
|
const statusById = new Map<string, ProviderWithKeyInfo>(safeStatuses.map((status) => [status.id, status]));
|
|
const entries = selectUserModelRuntimeAccounts(safeAccounts)
|
|
.filter((account) => account.enabled && hasConfiguredProviderCredentials(account, statusById))
|
|
.sort((left, right) => {
|
|
if (left.id === providerDefaultAccountId) return -1;
|
|
if (right.id === providerDefaultAccountId) return 1;
|
|
return right.updatedAt.localeCompare(left.updatedAt);
|
|
});
|
|
|
|
const deduped = new Map<string, RuntimeProviderOption>();
|
|
for (const account of entries) {
|
|
const runtimeProviderKey = resolveRuntimeProviderKey(account);
|
|
if (!runtimeProviderKey || deduped.has(runtimeProviderKey)) continue;
|
|
const vendor = vendorMap.get(account.vendorId);
|
|
const label = `${account.label} (${vendor?.name || account.vendorId})`;
|
|
const configuredModelId = account.model
|
|
? normalizeModelIdForRuntimeKey(account.model, runtimeProviderKey, account) ?? undefined
|
|
: undefined;
|
|
|
|
deduped.set(runtimeProviderKey, {
|
|
runtimeProviderKey,
|
|
accountId: account.id,
|
|
label,
|
|
modelIdPlaceholder: vendor?.modelIdPlaceholder,
|
|
configuredModelId,
|
|
});
|
|
}
|
|
|
|
return [...deduped.values()];
|
|
}
|
|
|
|
export function buildConfiguredModelOptions(
|
|
providerAccounts: ProviderAccount[],
|
|
providerStatuses: ProviderWithKeyInfo[],
|
|
providerDefaultAccountId: string | null,
|
|
): ConfiguredModelOption[] {
|
|
const safeAccounts = Array.isArray(providerAccounts) ? providerAccounts : [];
|
|
const safeStatuses = Array.isArray(providerStatuses) ? providerStatuses : [];
|
|
const statusById = new Map<string, ProviderWithKeyInfo>(safeStatuses.map((status) => [status.id, status]));
|
|
const entries = selectUserModelRuntimeAccounts(safeAccounts)
|
|
.filter((account) => account.enabled && hasConfiguredProviderCredentials(account, statusById))
|
|
.sort((left, right) => {
|
|
if (left.id === providerDefaultAccountId) return -1;
|
|
if (right.id === providerDefaultAccountId) return 1;
|
|
return right.updatedAt.localeCompare(left.updatedAt);
|
|
});
|
|
|
|
const deduped = new Map<string, ConfiguredModelOption>();
|
|
for (const account of entries) {
|
|
const runtimeProviderKey = resolveRuntimeProviderKey(account);
|
|
for (const modelId of modelIdsForAccount(account, runtimeProviderKey)) {
|
|
const modelRef = `${runtimeProviderKey}/${modelId}`;
|
|
if (deduped.has(modelRef)) continue;
|
|
deduped.set(modelRef, {
|
|
modelRef,
|
|
label: modelId,
|
|
runtimeProviderKey,
|
|
accountId: account.id,
|
|
capability: getKnownModelCapabilityKind(modelId),
|
|
});
|
|
}
|
|
}
|
|
|
|
return [...deduped.values()];
|
|
}
|