mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-03 20:13:02 +00:00
feat(cliproxy): persist scoped routing rules
This commit is contained in:
1 parent
a77ee1b91a
commit
9fb2016f90
7 files changed
+425
-17
No files matched your search
@@ -17,6 +17,17 @@ import { CLIPROXY_DEFAULT_PORT } from './port-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
import { getActiveDockerLegacyApiKeys } from '../../docker/docker-key-rotation';
|
||||
import { isValidCliproxyRetryValue } from '../../config/schemas/cliproxy';
|
||||
import type { CLIProxyOAuthModelAliasConfig } from '../../config/schemas/cliproxy';
|
||||
import {
|
||||
mergeOAuthModelAliases,
|
||||
parseOAuthModelAliasSection,
|
||||
serializeOAuthModelAliasBody,
|
||||
} from './oauth-model-alias-config';
|
||||
import {
|
||||
mergePayloadConfig,
|
||||
parsePayloadSection,
|
||||
serializePayloadSection,
|
||||
} from './payload-rule-config';
|
||||
|
||||
/** Internal API key for CCS-managed requests */
|
||||
export const CCS_INTERNAL_API_KEY = 'ccs-internal-managed';
|
||||
@@ -46,8 +57,9 @@ export const CCS_CONTROL_PANEL_SECRET = 'ccs';
|
||||
* v18: Persist routing.session-affinity and routing.session-affinity-ttl from CCS unified config
|
||||
* v19: Persist backend-aware management panel repository from CCS unified config
|
||||
* v20: Pool-gated cooling/routing/retry-cap block; disable-cooling flips to false for pool users
|
||||
* v21: Persist user-defined OAuth aliases and scoped payload override rules
|
||||
*/
|
||||
export const CLIPROXY_CONFIG_VERSION = 20;
|
||||
export const CLIPROXY_CONFIG_VERSION = 21;
|
||||
|
||||
export const ORIGINAL_MANAGEMENT_PANEL_REPOSITORY =
|
||||
'https://github.com/router-for-me/Cli-Proxy-API-Management-Center';
|
||||
@@ -296,7 +308,7 @@ function addAliasEntry(
|
||||
if (getDeniedModelIdReasonForProvider(normalized.name, 'agy')) return;
|
||||
if (getDeniedModelIdReasonForProvider(normalized.alias, 'agy')) return;
|
||||
|
||||
const key = `${normalized.name}\u0000${normalized.alias}`;
|
||||
const key = normalized.alias;
|
||||
const existingIndex = indexByKey.get(key);
|
||||
if (existingIndex !== undefined) {
|
||||
if (normalized.fork) entries[existingIndex].fork = true;
|
||||
@@ -307,6 +319,33 @@ function addAliasEntry(
|
||||
entries.push(normalized);
|
||||
}
|
||||
|
||||
function upsertConfiguredAliasEntry(
|
||||
entries: OAuthModelAliasEntry[],
|
||||
indexByKey: Map<string, number>,
|
||||
entry: OAuthModelAliasEntry
|
||||
): void {
|
||||
const normalized: OAuthModelAliasEntry = {
|
||||
name: sanitizeYamlScalar(entry.name),
|
||||
alias: normalizeAntigravityAlias(entry.alias),
|
||||
fork: entry.fork || undefined,
|
||||
};
|
||||
if (!normalized.name || !normalized.alias) return;
|
||||
if (getDeniedModelIdReasonForProvider(normalized.name, 'agy')) return;
|
||||
if (getDeniedModelIdReasonForProvider(normalized.alias, 'agy')) return;
|
||||
|
||||
const existingIndex = entries.findIndex((candidate) => candidate.alias === normalized.alias);
|
||||
if (existingIndex === -1) {
|
||||
addAliasEntry(entries, indexByKey, normalized);
|
||||
return;
|
||||
}
|
||||
|
||||
entries[existingIndex] = normalized;
|
||||
indexByKey.clear();
|
||||
entries.forEach((candidate, index) => {
|
||||
indexByKey.set(candidate.alias, index);
|
||||
});
|
||||
}
|
||||
|
||||
function buildAntigravityAliasKey(entry: Pick<OAuthModelAliasEntry, 'name' | 'alias'>): string {
|
||||
return `${sanitizeYamlScalar(entry.name)}\u0000${normalizeAntigravityAlias(entry.alias)}`;
|
||||
}
|
||||
@@ -622,9 +661,13 @@ function writeLegacyGeminiAliasCleanupBackup(configPath: string, existingContent
|
||||
* Generate oauth-model-alias YAML section.
|
||||
* Merges default Antigravity aliases with any user-added custom aliases.
|
||||
*/
|
||||
function generateOAuthModelAliasSection(existingAliases?: string): string {
|
||||
function generateOAuthModelAliasSection(
|
||||
existingAliases?: string,
|
||||
configuredAliases?: CLIProxyOAuthModelAliasConfig
|
||||
): string {
|
||||
const aliasEntries: OAuthModelAliasEntry[] = [];
|
||||
const aliasIndexByKey = new Map<string, number>();
|
||||
const normalizedConfiguredAliases = mergeOAuthModelAliases({}, configuredAliases);
|
||||
|
||||
// Start with default aliases.
|
||||
for (const alias of DEFAULT_ANTIGRAVITY_ALIASES) {
|
||||
@@ -639,6 +682,11 @@ function generateOAuthModelAliasSection(existingAliases?: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// CCS-owned structured aliases replace the same client alias in place.
|
||||
for (const alias of normalizedConfiguredAliases.antigravity ?? []) {
|
||||
upsertConfiguredAliasEntry(aliasEntries, aliasIndexByKey, alias);
|
||||
}
|
||||
|
||||
// Expand lightweight compatibility aliases (dot/hyphen + customtools toggle).
|
||||
for (const alias of getCompatibilityAliases(aliasEntries)) {
|
||||
addAliasEntry(aliasEntries, aliasIndexByKey, alias);
|
||||
@@ -652,7 +700,15 @@ function generateOAuthModelAliasSection(existingAliases?: string): string {
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return `oauth-model-alias:\n antigravity:\n${entries}`;
|
||||
const existingAliasConfig = parseOAuthModelAliasSection(existingAliases ?? '');
|
||||
const mergedAliasConfig = mergeOAuthModelAliases(
|
||||
existingAliasConfig,
|
||||
normalizedConfiguredAliases
|
||||
);
|
||||
delete mergedAliasConfig.antigravity;
|
||||
const otherProviders = serializeOAuthModelAliasBody(mergedAliasConfig);
|
||||
|
||||
return `oauth-model-alias:\n antigravity:\n${entries}${otherProviders ? `\n${otherProviders}` : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -663,11 +719,13 @@ function generateOAuthModelAliasSection(existingAliases?: string): string {
|
||||
* @param port - Server port (default: 8317)
|
||||
* @param userApiKeys - User-added API keys to preserve (default: [])
|
||||
* @param existingAliases - Existing oauth-model-alias content to merge with defaults
|
||||
* @param existingPayload - Existing payload content to merge with structured CCS rules
|
||||
*/
|
||||
function generateUnifiedConfigContent(
|
||||
port: number = CLIPROXY_DEFAULT_PORT,
|
||||
userApiKeys: string[] = [],
|
||||
existingAliases?: string
|
||||
existingAliases?: string,
|
||||
existingPayload?: string
|
||||
): string {
|
||||
const authDir = getAuthDir(); // Base auth dir - CLIProxyAPI scans subdirectories
|
||||
// Convert Windows backslashes to forward slashes for YAML compatibility
|
||||
@@ -682,6 +740,10 @@ function generateUnifiedConfigContent(
|
||||
const requestRetry = getRequestRetry();
|
||||
const maxRetryInterval = getMaxRetryInterval();
|
||||
const managementPanelRepository = getManagementPanelRepository();
|
||||
const userRoutingConfig = loadOrCreateUnifiedConfig().cliproxy;
|
||||
const payloadSection = serializePayloadSection(
|
||||
mergePayloadConfig(parsePayloadSection(existingPayload ?? ''), userRoutingConfig.payload)
|
||||
);
|
||||
|
||||
// Get effective auth tokens (respects user customization)
|
||||
const effectiveApiKey = getEffectiveApiKey();
|
||||
@@ -795,7 +857,8 @@ ${apiKeysYaml}
|
||||
|
||||
# OAuth tokens directory (auto-discovered by CLIProxyAPI)
|
||||
auth-dir: "${authDirNormalized}"
|
||||
${generateOAuthModelAliasSection(existingAliases)}
|
||||
${generateOAuthModelAliasSection(existingAliases, userRoutingConfig.oauth_model_alias)}
|
||||
${payloadSection ? `${payloadSection}\n` : ''}
|
||||
`;
|
||||
|
||||
return config;
|
||||
@@ -917,6 +980,7 @@ export function regenerateConfig(
|
||||
let effectivePort = port;
|
||||
let userApiKeys: string[] = [];
|
||||
let existingAliases = '';
|
||||
let existingPayload = '';
|
||||
const preservedSections: PreservedYamlSection[] = [];
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
@@ -945,15 +1009,21 @@ export function regenerateConfig(
|
||||
|
||||
// Preserve user customizations while pruning legacy generated Gemini preview noise.
|
||||
const existingConfigVersion = getConfigVersionFromContent(content);
|
||||
const preservedAliases = extractPreservedAntigravityAliases(
|
||||
extractYamlSection(content, 'oauth-model-alias'),
|
||||
{
|
||||
enableLegacyGeminiStaleCleanup:
|
||||
existingConfigVersion === null ||
|
||||
existingConfigVersion < LEGACY_GEMINI_STALE_ALIAS_MIGRATION_VERSION,
|
||||
}
|
||||
);
|
||||
existingAliases = preservedAliases.yaml;
|
||||
const existingAliasBody = extractYamlSection(content, 'oauth-model-alias');
|
||||
const preservedAliases = extractPreservedAntigravityAliases(existingAliasBody, {
|
||||
enableLegacyGeminiStaleCleanup:
|
||||
existingConfigVersion === null ||
|
||||
existingConfigVersion < LEGACY_GEMINI_STALE_ALIAS_MIGRATION_VERSION,
|
||||
});
|
||||
const preservedAliasConfig = parseOAuthModelAliasSection(existingAliasBody);
|
||||
const preservedAntigravityConfig = parseOAuthModelAliasSection(preservedAliases.yaml);
|
||||
if (preservedAntigravityConfig.antigravity) {
|
||||
preservedAliasConfig.antigravity = preservedAntigravityConfig.antigravity;
|
||||
} else {
|
||||
delete preservedAliasConfig.antigravity;
|
||||
}
|
||||
existingAliases = serializeOAuthModelAliasBody(preservedAliasConfig);
|
||||
existingPayload = extractYamlSection(content, 'payload');
|
||||
if (preservedAliases.prunedLegacyAliasCount > 0) {
|
||||
writeLegacyGeminiAliasCleanupBackup(configPath, content);
|
||||
}
|
||||
@@ -969,7 +1039,12 @@ export function regenerateConfig(
|
||||
fs.mkdirSync(authDir, { recursive: true, mode: 0o700 });
|
||||
|
||||
// Generate fresh config with preserved user API keys and aliases
|
||||
let configContent = generateUnifiedConfigContent(effectivePort, userApiKeys, existingAliases);
|
||||
let configContent = generateUnifiedConfigContent(
|
||||
effectivePort,
|
||||
userApiKeys,
|
||||
existingAliases,
|
||||
existingPayload
|
||||
);
|
||||
|
||||
// Re-append managed top-level sections that are not part of the generated defaults.
|
||||
for (const section of preservedSections) {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/** Parse, merge, and serialize user-owned CLIProxy OAuth model aliases. */
|
||||
|
||||
import * as yaml from 'js-yaml';
|
||||
import type {
|
||||
CLIProxyOAuthModelAliasConfig,
|
||||
CLIProxyOAuthModelAliasEntry,
|
||||
} from '../../config/schemas/cliproxy';
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeAliasEntry(value: unknown): CLIProxyOAuthModelAliasEntry | null {
|
||||
if (!isRecord(value) || typeof value.name !== 'string' || typeof value.alias !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name = value.name.trim();
|
||||
const alias = value.alias.trim();
|
||||
if (!name || !alias) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
alias,
|
||||
...(value.fork === true ? { fork: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function aliasKey(entry: CLIProxyOAuthModelAliasEntry): string {
|
||||
return entry.alias;
|
||||
}
|
||||
|
||||
export function parseOAuthModelAliasSection(body: string): CLIProxyOAuthModelAliasConfig {
|
||||
if (!body.trim()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = yaml.load(`oauth-model-alias:\n${body}`);
|
||||
if (!isRecord(parsed) || !isRecord(parsed['oauth-model-alias'])) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result = Object.create(null) as CLIProxyOAuthModelAliasConfig;
|
||||
for (const [provider, entries] of Object.entries(parsed['oauth-model-alias'])) {
|
||||
if (!provider.trim() || !Array.isArray(entries)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalized = entries
|
||||
.map(normalizeAliasEntry)
|
||||
.filter((entry): entry is CLIProxyOAuthModelAliasEntry => entry !== null);
|
||||
if (normalized.length > 0) {
|
||||
result[provider] = normalized;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeOAuthModelAliases(
|
||||
existing: CLIProxyOAuthModelAliasConfig,
|
||||
configured?: CLIProxyOAuthModelAliasConfig
|
||||
): CLIProxyOAuthModelAliasConfig {
|
||||
const merged = Object.create(null) as CLIProxyOAuthModelAliasConfig;
|
||||
|
||||
for (const [sourceIndex, source] of [existing, configured].entries()) {
|
||||
if (!isRecord(source)) {
|
||||
continue;
|
||||
}
|
||||
const replaceMatching = sourceIndex === 1;
|
||||
for (const [provider, rawEntries] of Object.entries(source)) {
|
||||
if (!provider.trim() || !Array.isArray(rawEntries)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entries = merged[provider] ?? [];
|
||||
const indexByKey = new Map(entries.map((entry, index) => [aliasKey(entry), index]));
|
||||
for (const rawEntry of rawEntries) {
|
||||
const entry = normalizeAliasEntry(rawEntry);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = aliasKey(entry);
|
||||
const existingIndex = indexByKey.get(key);
|
||||
if (existingIndex !== undefined) {
|
||||
if (replaceMatching) {
|
||||
entries[existingIndex] = entry;
|
||||
} else if (entry.fork) {
|
||||
entries[existingIndex] = { ...entries[existingIndex], fork: true };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
indexByKey.set(key, entries.length);
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
if (entries.length > 0) {
|
||||
merged[provider] = entries;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function serializeOAuthModelAliasBody(config: CLIProxyOAuthModelAliasConfig): string {
|
||||
if (Object.keys(config).length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return yaml
|
||||
.dump(config, {
|
||||
indent: 2,
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
noCompatMode: true,
|
||||
quotingType: '"',
|
||||
})
|
||||
.trimEnd()
|
||||
.split('\n')
|
||||
.map((line) => ` ${line}`)
|
||||
.join('\n');
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/** Parse, merge, and serialize user-owned CLIProxy payload rules. */
|
||||
|
||||
import * as yaml from 'js-yaml';
|
||||
import type {
|
||||
CLIProxyPayloadConfig,
|
||||
CLIProxyPayloadModelSelector,
|
||||
CLIProxyPayloadOverrideRule,
|
||||
} from '../../config/schemas/cliproxy';
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isValidModelSelector(value: unknown): value is CLIProxyPayloadModelSelector {
|
||||
if (!isRecord(value) || typeof value.name !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.name.trim().length > 0;
|
||||
}
|
||||
|
||||
function cloneOverrideRule(value: unknown): CLIProxyPayloadOverrideRule | null {
|
||||
if (!isRecord(value) || !Array.isArray(value.models) || !isRecord(value.params)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.models.length === 0 || !value.models.every(isValidModelSelector)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return structuredClone(value) as CLIProxyPayloadOverrideRule;
|
||||
}
|
||||
|
||||
function canonicalizeForKey(value: unknown): unknown {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(canonicalizeForKey);
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.keys(value)
|
||||
.sort()
|
||||
.map((key) => [key, canonicalizeForKey(value[key])])
|
||||
);
|
||||
}
|
||||
|
||||
function predicateKey(rule: CLIProxyPayloadOverrideRule): string {
|
||||
const predicate = Object.fromEntries(Object.entries(rule).filter(([key]) => key !== 'params'));
|
||||
return JSON.stringify(canonicalizeForKey(predicate));
|
||||
}
|
||||
|
||||
export function parsePayloadSection(body: string): CLIProxyPayloadConfig | undefined {
|
||||
if (!body.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = yaml.load(`payload:\n${body}`);
|
||||
if (!isRecord(parsed) || !isRecord(parsed.payload)) {
|
||||
return undefined;
|
||||
}
|
||||
return parsed.payload as CLIProxyPayloadConfig;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function mergePayloadConfig(
|
||||
existing?: CLIProxyPayloadConfig,
|
||||
configured?: CLIProxyPayloadConfig
|
||||
): CLIProxyPayloadConfig | undefined {
|
||||
const existingConfig = isRecord(existing) ? (existing as CLIProxyPayloadConfig) : undefined;
|
||||
const configuredConfig = isRecord(configured) ? (configured as CLIProxyPayloadConfig) : undefined;
|
||||
if (!existingConfig && !configuredConfig) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const merged: CLIProxyPayloadConfig = {
|
||||
...(existingConfig ?? {}),
|
||||
...(configuredConfig ?? {}),
|
||||
};
|
||||
const rules: CLIProxyPayloadOverrideRule[] = [];
|
||||
const indexByPredicate = new Map<string, number>();
|
||||
|
||||
const addRules = (rawRules: unknown, replaceMatching: boolean) => {
|
||||
if (!Array.isArray(rawRules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const rawRule of rawRules) {
|
||||
const rule = cloneOverrideRule(rawRule);
|
||||
if (!rule) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = predicateKey(rule);
|
||||
const existingIndex = indexByPredicate.get(key);
|
||||
if (existingIndex !== undefined) {
|
||||
if (replaceMatching) {
|
||||
rules[existingIndex] = rule;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
indexByPredicate.set(key, rules.length);
|
||||
rules.push(rule);
|
||||
}
|
||||
};
|
||||
|
||||
addRules(existingConfig?.override, false);
|
||||
addRules(configuredConfig?.override, true);
|
||||
|
||||
if (rules.length > 0) {
|
||||
merged.override = rules;
|
||||
} else {
|
||||
delete merged.override;
|
||||
}
|
||||
|
||||
return Object.keys(merged).length > 0 ? merged : undefined;
|
||||
}
|
||||
|
||||
export function serializePayloadSection(config?: CLIProxyPayloadConfig): string {
|
||||
if (!config || Object.keys(config).length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return yaml
|
||||
.dump(
|
||||
{ payload: config },
|
||||
{
|
||||
indent: 2,
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
noCompatMode: true,
|
||||
quotingType: '"',
|
||||
}
|
||||
)
|
||||
.trimEnd();
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
*/
|
||||
|
||||
import type { CompositeTierConfig } from '../../config/unified-config-types';
|
||||
import type {
|
||||
CLIProxyOAuthModelAliasConfig,
|
||||
CLIProxyPayloadConfig,
|
||||
} from '../../config/schemas/cliproxy';
|
||||
import type { CliproxyRoutingStrategy } from './provider-types';
|
||||
|
||||
/** CLIProxy config.yaml structure (minimal) */
|
||||
@@ -11,6 +15,8 @@ export interface CLIProxyConfig {
|
||||
'api-keys': string[];
|
||||
'auth-dir': string;
|
||||
debug: boolean;
|
||||
'oauth-model-alias'?: CLIProxyOAuthModelAliasConfig;
|
||||
payload?: CLIProxyPayloadConfig;
|
||||
routing?: {
|
||||
strategy?: CliproxyRoutingStrategy;
|
||||
'session-affinity'?: boolean;
|
||||
|
||||
@@ -135,6 +135,50 @@ export interface CLIProxyRetryConfig {
|
||||
max_retry_interval?: number;
|
||||
}
|
||||
|
||||
/** One OAuth model alias exposed by CLIProxy to compatible clients. */
|
||||
export interface CLIProxyOAuthModelAliasEntry {
|
||||
/** Upstream model ID used after alias resolution. */
|
||||
name: string;
|
||||
/** Client-visible model ID accepted by CLIProxy. */
|
||||
alias: string;
|
||||
/** Keep both the upstream name and alias visible when supported upstream. */
|
||||
fork?: boolean;
|
||||
}
|
||||
|
||||
/** Provider/protocol keyed OAuth model aliases (for example `codex`). */
|
||||
export type CLIProxyOAuthModelAliasConfig = Record<string, CLIProxyOAuthModelAliasEntry[]>;
|
||||
|
||||
/** Model selector for a CLIProxy payload rule. */
|
||||
export interface CLIProxyPayloadModelSelector {
|
||||
/** Client-visible model ID to match before OAuth alias resolution. */
|
||||
name: string;
|
||||
/** Optional protocol boundary, such as `codex`. */
|
||||
protocol?: string;
|
||||
/** Additional CLIProxy selector constraints retained across regeneration. */
|
||||
[constraint: string]: unknown;
|
||||
}
|
||||
|
||||
/** A scoped CLIProxy payload override rule. */
|
||||
export interface CLIProxyPayloadOverrideRule {
|
||||
/** All client model/protocol selectors covered by this rule. */
|
||||
models: CLIProxyPayloadModelSelector[];
|
||||
/** Upstream request parameters added when a selector matches. */
|
||||
params: Record<string, unknown>;
|
||||
/** Additional CLIProxy match constraints retained across regeneration. */
|
||||
[constraint: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* User-owned CLIProxy payload configuration.
|
||||
*
|
||||
* Unknown supported payload subsections are retained so CCS regeneration does
|
||||
* not erase options introduced by newer CLIProxy releases.
|
||||
*/
|
||||
export interface CLIProxyPayloadConfig {
|
||||
override?: CLIProxyPayloadOverrideRule[];
|
||||
[section: string]: unknown;
|
||||
}
|
||||
|
||||
/** Lower bound (inclusive) accepted for CLIProxy retry config fields. */
|
||||
export const CLIPROXY_RETRY_MIN_VALUE = 0;
|
||||
|
||||
@@ -217,4 +261,8 @@ export interface CLIProxyConfig {
|
||||
pool_routing?: CLIProxyPoolRoutingConfig;
|
||||
/** Request-retry behavior for transient upstream errors (opt-in, default: disabled) */
|
||||
retry?: CLIProxyRetryConfig;
|
||||
/** User-defined OAuth model aliases emitted into managed CLIProxy config. */
|
||||
oauth_model_alias?: CLIProxyOAuthModelAliasConfig;
|
||||
/** Scoped payload rules emitted into managed CLIProxy config. */
|
||||
payload?: CLIProxyPayloadConfig;
|
||||
}
|
||||
@@ -29,6 +29,13 @@ export type {
|
||||
CLIProxyLoggingConfig,
|
||||
CLIProxySafetyConfig,
|
||||
CLIProxyRoutingConfig,
|
||||
CLIProxyRetryConfig,
|
||||
CLIProxyPoolRoutingConfig,
|
||||
CLIProxyOAuthModelAliasEntry,
|
||||
CLIProxyOAuthModelAliasConfig,
|
||||
CLIProxyPayloadModelSelector,
|
||||
CLIProxyPayloadOverrideRule,
|
||||
CLIProxyPayloadConfig,
|
||||
CLIProxyConfig,
|
||||
} from './cliproxy';
|
||||
|
||||
|
||||
@@ -19,5 +19,6 @@
|
||||
* Version 11 = Discord Channels runtime auto-enable preferences
|
||||
* Version 12 = Official Channels multi-provider support (Telegram, Discord, iMessage)
|
||||
* Version 13 = Browser automation defaults to safe manual/off exposure
|
||||
* Version 14 = User-defined CLIProxy OAuth aliases and scoped payload rules
|
||||
*/
|
||||
export const UNIFIED_CONFIG_VERSION = 13;
|
||||
export const UNIFIED_CONFIG_VERSION = 14;
|
||||
Reference in new issue
Block a user