Files
ccs/src/proxy/proxy-daemon-state.ts
T

192 lines
5.2 KiB
TypeScript

import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import {
getOpenAICompatProxyDir,
getLegacyOpenAICompatProxyPidPath,
getLegacyOpenAICompatProxySessionPath,
getOpenAICompatProxyPidPath,
getOpenAICompatProxySessionPath,
} from './proxy-daemon-paths';
export interface OpenAICompatProxySession {
profileName: string;
settingsPath: string;
host: string;
port: number;
baseUrl: string;
authToken: string;
model?: string;
insecure?: boolean;
}
function ensureProxyDir(): void {
const proxyDir = getOpenAICompatProxyDir();
fs.mkdirSync(proxyDir, { recursive: true, mode: 0o700 });
fs.chmodSync(proxyDir, 0o700);
}
function encodeProfileFileKey(profileName: string): string {
return encodeURIComponent(profileName);
}
export function writeOpenAICompatProxyAuthTokenFile(
profileName: string,
authToken: string
): string {
ensureProxyDir();
const tokenPath = path.join(
getOpenAICompatProxyDir(),
`.${encodeProfileFileKey(profileName)}.${crypto.randomBytes(8).toString('hex')}.token`
);
const fd = fs.openSync(tokenPath, 'wx', 0o600);
try {
fs.writeFileSync(fd, authToken, 'utf8');
} finally {
fs.closeSync(fd);
}
fs.chmodSync(tokenPath, 0o600);
return tokenPath;
}
export function removeOpenAICompatProxyAuthTokenFile(tokenPath: string): void {
try {
fs.unlinkSync(tokenPath);
} catch {
// Best-effort cleanup.
}
}
function readPid(pidPath: string): number | null {
try {
const raw = fs.readFileSync(pidPath, 'utf8').trim();
const pid = Number.parseInt(raw, 10);
return Number.isInteger(pid) ? pid : null;
} catch {
return null;
}
}
export function getOpenAICompatProxyPid(profileName: string): number | null {
return readPid(getOpenAICompatProxyPidPath(profileName));
}
export function getLegacyOpenAICompatProxyPid(): number | null {
return readPid(getLegacyOpenAICompatProxyPidPath());
}
export function writeOpenAICompatProxyPid(profileName: string, pid: number): void {
ensureProxyDir();
fs.writeFileSync(getOpenAICompatProxyPidPath(profileName), String(pid), {
encoding: 'utf8',
mode: 0o600,
});
fs.chmodSync(getOpenAICompatProxyPidPath(profileName), 0o600);
}
export function removeOpenAICompatProxyPid(profileName: string): void {
try {
fs.unlinkSync(getOpenAICompatProxyPidPath(profileName));
} catch {
// Best-effort cleanup.
}
}
export function removeLegacyOpenAICompatProxyPid(): void {
try {
fs.unlinkSync(getLegacyOpenAICompatProxyPidPath());
} catch {
// Best-effort cleanup.
}
}
function readSession(sessionPath: string): OpenAICompatProxySession | null {
try {
return JSON.parse(fs.readFileSync(sessionPath, 'utf8')) as OpenAICompatProxySession;
} catch {
return null;
}
}
export function readOpenAICompatProxySession(profileName: string): OpenAICompatProxySession | null {
return readSession(getOpenAICompatProxySessionPath(profileName));
}
export function readLegacyOpenAICompatProxySession(): OpenAICompatProxySession | null {
return readSession(getLegacyOpenAICompatProxySessionPath());
}
export function writeOpenAICompatProxySession(session: OpenAICompatProxySession): void {
ensureProxyDir();
const sessionPath = getOpenAICompatProxySessionPath(session.profileName);
fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n', {
encoding: 'utf8',
mode: 0o600,
});
fs.chmodSync(sessionPath, 0o600);
}
export function removeOpenAICompatProxySession(profileName: string): void {
try {
fs.unlinkSync(getOpenAICompatProxySessionPath(profileName));
} catch {
// Best-effort cleanup.
}
}
export function removeLegacyOpenAICompatProxySession(): void {
try {
fs.unlinkSync(getLegacyOpenAICompatProxySessionPath());
} catch {
// Best-effort cleanup.
}
}
function decodeProfileKey(profileKey: string): string[] {
try {
return [decodeURIComponent(profileKey)];
} catch {
return [];
}
}
function listProfileNamesForSuffix(suffix: string, legacyName?: string): string[] {
try {
const entries = fs.readdirSync(getOpenAICompatProxyDir(), { withFileTypes: true });
const profileKeys = new Set<string>();
for (const entry of entries) {
if (!entry.isFile() || entry.name === legacyName || !entry.name.endsWith(suffix)) {
continue;
}
const profileKey = entry.name.slice(0, -suffix.length);
if (!profileKey) {
continue;
}
profileKeys.add(profileKey);
}
return [...profileKeys].flatMap(decodeProfileKey);
} catch {
return [];
}
}
export function listOpenAICompatProxyProfileNames(): string[] {
return [
...new Set([
...listProfileNamesForSuffix('.session.json', 'session.json'),
...listProfileNamesForSuffix('.daemon.pid', 'daemon.pid'),
]),
];
}
export function resolveOpenAICompatProxyEntrypointCandidates(): string[] {
const jsEntry = path.join(__dirname, 'proxy-daemon-entry.js');
const tsEntry = path.join(__dirname, 'proxy-daemon-entry.ts');
const isBunRuntime = process.execPath.toLowerCase().includes('bun');
const runningFromDist = __filename.endsWith('.js');
if (runningFromDist) {
return [jsEntry];
}
return isBunRuntime ? [tsEntry, jsEntry] : [jsEntry];
}