[codex] Add model tester fixed-channel selection (#352)

* feat: add model tester forced channel

* fix: keep codex websocket defaults and route refresh checks

* fix: scope tester-only forced channel overrides

* fix: keep tester hydration and websocket hints internal

* fix: reject gemini on legacy tester chat routes

* fix: harden forced channel selection edges
This commit is contained in:
Cita
2026-04-01 12:56:13 +08:00
committed by GitHub
parent 8c3be1a613
commit bafceb855b
27 changed files with 1331 additions and 288 deletions
@@ -13,6 +13,7 @@ import {
buildGeminiNativeConversationProxyEnvelope,
buildRawProxyRequestEnvelope,
buildSearchRequestEnvelope,
attachForcedChannelToEnvelope,
collectModelTesterModelNames,
countConversationTurns,
createConversationUserMessage,
@@ -74,9 +75,11 @@ describe('modelTesterSession', () => {
stream: false,
jobMode: false,
rawMode: false,
forcedChannelId: 44,
jsonBody: { model: '__search', query: 'hello', max_results: 7 },
},
pendingJobId: 'job-1',
forcedChannelId: 44,
customRequestMode: true,
customRequestBody: '{"model":"gemini-2.5-pro","contents":[]}',
showDebugPanel: true,
@@ -188,6 +191,29 @@ describe('modelTesterSession', () => {
});
});
it('attaches a forced channel id to tester envelopes without mutating the request body', () => {
const base = buildEmbeddingsRequestEnvelope('hello', {
...DEFAULT_INPUTS,
model: 'text-embedding-3-small',
});
const payload = attachForcedChannelToEnvelope(base, 42);
expect(payload).toEqual({
method: 'POST',
path: '/v1/embeddings',
requestKind: 'json',
stream: false,
jobMode: false,
rawMode: false,
forcedChannelId: 42,
jsonBody: {
model: 'text-embedding-3-small',
input: 'hello',
},
});
});
it('builds multipart upload envelopes for /v1/files', () => {
expect(buildFileUploadRequestEnvelope({
name: 'paper.pdf',
+29 -1
View File
@@ -93,6 +93,7 @@ export type TesterProxyEnvelope = {
stream: boolean;
jobMode: boolean;
rawMode: boolean;
forcedChannelId?: number | null;
jsonBody?: unknown;
rawJsonText?: string;
multipartFields?: Record<string, string>;
@@ -146,6 +147,7 @@ export type ModelTesterSessionState = {
conversationFiles: ConversationDraftFile[];
pendingPayload: TesterProxyEnvelope | null;
pendingJobId?: string | null;
forcedChannelId?: number | null;
customRequestMode: boolean;
customRequestBody: string;
showDebugPanel: boolean;
@@ -233,6 +235,13 @@ const toNullableFiniteNumber = (value: unknown): number | null => {
return null;
};
const toPositiveInteger = (value: unknown): number | null => {
const parsed = toNullableFiniteNumber(value);
if (parsed === null) return null;
const normalized = Math.trunc(parsed);
return normalized > 0 ? normalized : null;
};
const sanitizeString = (value: unknown, fallback = ''): string =>
typeof value === 'string' ? value : fallback;
@@ -838,6 +847,8 @@ const parsePendingPayload = (
if ('jsonBody' in value) pending.jsonBody = value.jsonBody;
if (typeof value.rawJsonText === 'string') pending.rawJsonText = value.rawJsonText;
const forcedChannelId = toPositiveInteger(value.forcedChannelId);
if (forcedChannelId !== null) pending.forcedChannelId = forcedChannelId;
if (isRecord(value.multipartFields)) {
pending.multipartFields = Object.fromEntries(
Object.entries(value.multipartFields)
@@ -1152,6 +1163,7 @@ export const parseModelTesterSession = (raw: string | null): ModelTesterSessionS
pendingPayload: parsePendingPayload(parsed.pendingPayload, inputs, parameterEnabled),
customRequestMode: toBoolean(parsed.customRequestMode, false),
customRequestBody: typeof parsed.customRequestBody === 'string' ? parsed.customRequestBody : '',
forcedChannelId: toPositiveInteger(parsed.forcedChannelId),
showDebugPanel: toBoolean(parsed.showDebugPanel, false),
activeDebugTab: typeof parsed.activeDebugTab === 'string' && VALID_DEBUG_TABS.has(parsed.activeDebugTab)
? parsed.activeDebugTab as DebugTab
@@ -1341,6 +1353,23 @@ export const buildApiPayload = (
): TesterProxyEnvelope =>
buildConversationRequestEnvelope(messages, inputs, parameterEnabled);
export const attachForcedChannelToEnvelope = (
envelope: TesterProxyEnvelope,
forcedChannelId?: number | null,
): TesterProxyEnvelope => {
const normalizedForcedChannelId = toPositiveInteger(forcedChannelId);
if (normalizedForcedChannelId === null) {
if (!('forcedChannelId' in envelope)) return envelope;
const { forcedChannelId: _forcedChannelId, ...rest } = envelope;
return rest;
}
return {
...envelope,
forcedChannelId: normalizedForcedChannelId,
};
};
export const parseCustomRequestBody = (raw: string): Record<string, unknown> | null => {
if (!raw.trim()) return null;
try {
@@ -1496,4 +1525,3 @@ export const findLastLoadingAssistantIndex = (messages: ChatMessage[]): number =
export const countConversationTurns = (messages: ChatMessage[]): number =>
messages.reduce((turns, message) => turns + (message.role === 'user' ? 1 : 0), 0);