文件
mynode/packages/lib/services/ai/classification.ts
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

69 行
2.3 KiB
TypeScript

import { ProviderClassification, ProviderType } from './types';
// The split is "does my data leave my private network", so loopback, LAN and
// internal-only hostnames all count as local. `.local` is excluded on purpose:
// mDNS can resolve it to any machine on an untrusted network.
const privateHosts = new Set(['localhost', '::1', '0.0.0.0', '::']);
const privateSuffixes = ['.localhost', '.internal', '.home.arpa'];
// Dotted quad is enough because new URL() has already canonicalised octal, hex,
// decimal and short forms - `http://2130706433/` arrives here as "127.0.0.1".
const isPrivateIpV4 = (host: string) => {
const parts = host.split('.');
if (parts.length !== 4) return false;
const bytes = parts.map(p => (/^\d{1,3}$/.test(p) ? Number(p) : -1));
if (bytes.some(b => b < 0 || b > 255)) return false;
const [a, b] = bytes;
if (a === 10) return true;
if (a === 127) return true;
if (a === 172 && b >= 16 && b <= 31) return true;
if (a === 192 && b === 168) return true;
if (a === 169 && b === 254) return true;
return false;
};
const isPrivateIpV6 = (host: string) => {
// URL.hostname keeps the brackets around IPv6 literals.
const address = host.replace(/^\[/, '').replace(/\]$/, '');
if (privateHosts.has(address)) return true;
// Unique local (fc00::/7) then link-local (fe80::/10).
if (/^f[cd][0-9a-f]{2}:/.test(address)) return true;
if (/^fe[89ab][0-9a-f]:/.test(address)) return true;
return false;
};
const isPrivateHost = (host: string) => {
if (!host) return false;
if (privateHosts.has(host)) return true;
if (privateSuffixes.some(suffix => host === suffix.substring(1) || host.endsWith(suffix))) return true;
if (isPrivateIpV4(host)) return true;
if (isPrivateIpV6(host)) return true;
return false;
};
const hostFromBaseUrl = (baseUrl: string): string => {
if (!baseUrl) return '';
try {
return new URL(baseUrl).hostname.toLowerCase();
} catch {
return '';
}
};
const deriveClassification = (
providerType: ProviderType,
baseUrl: string,
): ProviderClassification => {
if (providerType === 'anthropic') return 'remote';
if (providerType === 'joplin-cloud') return 'remote';
if (providerType === 'openai-compatible') {
return isPrivateHost(hostFromBaseUrl(baseUrl)) ? 'local' : 'remote';
}
return 'remote';
};
export default deriveClassification;