codexmobile-relay / server /provider-api.js
Codex
deploy: CodexMobile Relay
90f0300
Raw
History Blame Contribute Delete
3.37 kB
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { CODEX_CONFIG_PATH } from './codex-config.js';
export const DEFAULT_OPENAI_COMPATIBLE_BASE_URL = 'http://127.0.0.1:8317/v1';
function stripQuotes(value) {
const trimmed = String(value || '').trim();
if (
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
(trimmed.startsWith("'") && trimmed.endsWith("'"))
) {
return trimmed.slice(1, -1);
}
return trimmed;
}
export function normalizeBaseUrl(value, fallback = DEFAULT_OPENAI_COMPATIBLE_BASE_URL) {
return String(value || fallback).replace(/\/+$/, '');
}
export async function readCodexProviderBaseUrl() {
let raw = '';
try {
raw = await fs.readFile(CODEX_CONFIG_PATH, 'utf8');
} catch {
return null;
}
let provider = 'cliproxyapi';
let currentProvider = null;
const baseUrls = new Map();
for (const rawLine of raw.split(/\r?\n/)) {
const line = rawLine.trim();
if (!line || line.startsWith('#')) {
continue;
}
const providerMatch = line.match(/^\[model_providers\.(?:'([^']+)'|"([^"]+)"|([^\]]+))\]$/);
if (providerMatch) {
currentProvider = stripQuotes(providerMatch[1] || providerMatch[2] || providerMatch[3]);
continue;
}
if (line.startsWith('[')) {
currentProvider = null;
continue;
}
const assignment = line.match(/^([A-Za-z0-9_]+)\s*=\s*(.+)$/);
if (!assignment) {
continue;
}
const key = assignment[1];
const value = stripQuotes(assignment[2]);
if (!currentProvider && key === 'model_provider') {
provider = value;
} else if (currentProvider && key === 'base_url') {
baseUrls.set(currentProvider, value);
}
}
return baseUrls.get(provider) || null;
}
export async function readCliProxyApiKeys(extraKeys = []) {
const keys = [
...extraKeys,
process.env.CLIPROXYAPI_API_KEY,
process.env.CLI_PROXY_API_KEY
].filter(Boolean);
const candidates = [
process.env.CLIPROXYAPI_CONFIG,
process.platform === 'win32' ? 'D:\\CLIProxyAPI\\config.yaml' : '',
path.join(os.homedir(), '.cli-proxy-api', 'config.yaml')
].filter(Boolean);
for (const configPath of candidates) {
let raw = '';
try {
raw = await fs.readFile(configPath, 'utf8');
} catch {
continue;
}
let inApiKeys = false;
for (const rawLine of raw.split(/\r?\n/)) {
const line = rawLine.trimEnd();
if (/^api-keys\s*:\s*$/.test(line.trim())) {
inApiKeys = true;
continue;
}
if (inApiKeys && /^\S/.test(line)) {
break;
}
const match = inApiKeys ? line.match(/^\s*-\s*(.+?)\s*(?:#.*)?$/) : null;
if (match) {
const key = stripQuotes(match[1]);
if (key && !keys.includes(key)) {
keys.push(key);
}
}
}
}
if (process.env.OPENAI_API_KEY && !keys.includes(process.env.OPENAI_API_KEY)) {
keys.push(process.env.OPENAI_API_KEY);
}
return keys;
}
export async function openAICompatibleConfig({
baseUrl,
defaultBaseUrl = DEFAULT_OPENAI_COMPATIBLE_BASE_URL,
apiKeys = []
} = {}) {
const resolvedBaseUrl = baseUrl || (await readCodexProviderBaseUrl()) || defaultBaseUrl;
return {
baseUrl: normalizeBaseUrl(resolvedBaseUrl, defaultBaseUrl),
apiKeys: await readCliProxyApiKeys(apiKeys)
};
}