[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:
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user