visual-journal / src /lib /channel-request-mode.ts
gpt-image-playground deploy
Deploy 17eadcc to Docker Space
96bdf6c
Raw
History Blame Contribute Delete
6.81 kB
import {
DEFAULT_CHANNEL_REQUEST_MODE_PRIORITY as SHARED_DEFAULT_CHANNEL_REQUEST_MODE_PRIORITY,
CHANNEL_REQUEST_MODES as SHARED_CHANNEL_REQUEST_MODES,
CHANNEL_REQUEST_MODE_ADMIN_CONTROL as SHARED_CHANNEL_REQUEST_MODE_ADMIN_CONTROL
} from './channel-request-mode-values.mjs';
import { RequestValidationError } from './image-request-utils';
export const CHANNEL_REQUEST_MODES = SHARED_CHANNEL_REQUEST_MODES as readonly [
'images-non-stream',
'images-sse',
'responses-non-stream',
'responses-sse'
];
export type ChannelRequestMode = (typeof CHANNEL_REQUEST_MODES)[number];
export type ChannelRequestModeBackend = 'images-api' | 'responses-image-generation';
export type ChannelRequestModeDecision = {
requested_backend: ChannelRequestModeBackend;
candidate_channel_request_modes?: readonly ChannelRequestMode[];
request_mode_priority?: readonly ChannelRequestMode[];
preferred_channel_request_mode?: ChannelRequestMode;
fallback_channel_request_mode?: ChannelRequestMode;
selected_channel_request_mode?: ChannelRequestMode;
fallback_applied: boolean;
selected_channel_id?: string;
upstream_host?: string;
no_channel_reason?: string;
};
export type ChannelRequestModeHealthSummary = {
configuredRequestModes: readonly ChannelRequestMode[];
effectiveRequestModes: readonly ChannelRequestMode[];
defaultRequestModePriority: readonly ChannelRequestMode[];
modes: Array<{
mode: ChannelRequestMode;
configuredCredentialCount: number;
healthyCredentialCount: number;
configuredChannelCount: number;
healthyChannelCount: number;
}>;
effectiveRequestModesByChannel: Array<{
channelId: string;
requestModes: readonly ChannelRequestMode[];
requestModePriority: readonly ChannelRequestMode[];
}>;
};
export const DEFAULT_CHANNEL_REQUEST_MODES: readonly ChannelRequestMode[] = ['images-non-stream'];
export const DEFAULT_CHANNEL_REQUEST_MODE_PRIORITY = SHARED_DEFAULT_CHANNEL_REQUEST_MODE_PRIORITY as readonly [
'images-non-stream',
'images-sse',
'responses-non-stream',
'responses-sse'
];
export const CHANNEL_REQUEST_MODE_ADMIN_CONTROL = SHARED_CHANNEL_REQUEST_MODE_ADMIN_CONTROL as {
readonly source: 'admin_env_whitelist';
readonly globalEnv: 'OPENAI_UPSTREAM_REQUEST_MODES';
readonly channelEnvPattern: 'OPENAI_CHANNEL_N_REQUEST_MODES';
readonly globalPriorityEnv: 'OPENAI_UPSTREAM_REQUEST_MODE_PRIORITY';
readonly channelPriorityEnvPattern: 'OPENAI_CHANNEL_N_REQUEST_MODE_PRIORITY';
readonly defaultPriority: readonly ChannelRequestMode[];
readonly defaultPriorityPolicy: 'lowest_cost_first';
readonly mutableAtRuntime: false;
readonly finalGateCommand: string;
readonly smokeGateCommands: Record<ChannelRequestMode, readonly string[]>;
};
const CHANNEL_REQUEST_MODE_SET = new Set<string>(CHANNEL_REQUEST_MODES);
const CHANNEL_REQUEST_MODE_ALIASES: Record<string, ChannelRequestMode> = {
'images-api': 'images-non-stream',
'images-api-non-stream': 'images-non-stream',
'images-api-json': 'images-non-stream',
'images-json': 'images-non-stream',
'images-nonstream': 'images-non-stream',
'images-api-sse': 'images-sse',
'images-stream': 'images-sse',
'images-api-stream': 'images-sse',
responses: 'responses-non-stream',
'responses-json': 'responses-non-stream',
'responses-nonstream': 'responses-non-stream',
'responses-image-generation': 'responses-non-stream',
'responses-image-generation-non-stream': 'responses-non-stream',
'responses-image-generation-sse': 'responses-sse',
'responses-stream': 'responses-sse'
};
export function parseChannelRequestModes(
value: string | undefined,
fieldName: string
): ChannelRequestMode[] | undefined {
if (!value?.trim()) return undefined;
const modes: ChannelRequestMode[] = [];
for (const rawPart of value.split(/[,\s]+/)) {
const normalized = normalizeChannelRequestMode(rawPart);
if (!normalized) continue;
if (!modes.includes(normalized)) {
modes.push(normalized);
}
}
if (modes.length === 0) {
throw new RequestValidationError(`${fieldName} 至少需要包含一个请求方式。`, 500);
}
return modes;
}
export function parseChannelRequestModePriority(
value: string | undefined,
fieldName: string
): ChannelRequestMode[] | undefined {
return parseChannelRequestModes(value, fieldName);
}
export function getEffectiveChannelRequestModes(input: {
requestModes?: readonly ChannelRequestMode[];
}): readonly ChannelRequestMode[] {
return input.requestModes?.length ? input.requestModes : DEFAULT_CHANNEL_REQUEST_MODES;
}
export function orderChannelRequestModesByPriority(input: {
requestModes: readonly ChannelRequestMode[];
requestModePriority?: readonly ChannelRequestMode[];
}): ChannelRequestMode[] {
const allowed = new Set(input.requestModes);
const ordered: ChannelRequestMode[] = [];
appendAllowedRequestModes(ordered, allowed, input.requestModePriority ?? []);
appendAllowedRequestModes(ordered, allowed, DEFAULT_CHANNEL_REQUEST_MODE_PRIORITY);
appendAllowedRequestModes(ordered, allowed, input.requestModes);
return ordered;
}
function appendAllowedRequestModes(
ordered: ChannelRequestMode[],
allowed: ReadonlySet<ChannelRequestMode>,
modes: readonly ChannelRequestMode[]
): void {
for (const mode of modes) {
if (allowed.has(mode) && !ordered.includes(mode)) ordered.push(mode);
}
}
export function channelSupportsRequestMode(
input: { requestModes?: readonly ChannelRequestMode[] },
mode: ChannelRequestMode
): boolean {
return getEffectiveChannelRequestModes(input).includes(mode);
}
export function resolveChannelRequestMode(input: {
imageBackend: ChannelRequestModeBackend;
streamEnabled: boolean;
}): ChannelRequestMode {
if (input.imageBackend === 'responses-image-generation') {
return input.streamEnabled ? 'responses-sse' : 'responses-non-stream';
}
return input.streamEnabled ? 'images-sse' : 'images-non-stream';
}
export function isStreamingChannelRequestMode(mode: ChannelRequestMode): boolean {
return mode.endsWith('-sse');
}
function normalizeChannelRequestMode(value: string): ChannelRequestMode | undefined {
const normalized = value.trim().toLowerCase().replace(/_/g, '-');
if (!normalized) return undefined;
if (CHANNEL_REQUEST_MODE_SET.has(normalized)) return normalized as ChannelRequestMode;
const aliased = CHANNEL_REQUEST_MODE_ALIASES[normalized];
if (aliased) return aliased;
throw new RequestValidationError(`请求方式 ${value} 无效,必须是 ${CHANNEL_REQUEST_MODES.join(', ')} 之一。`, 500);
}