mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-17 02:22:46 +00:00
fix: complete codex tool sanitization coverage
This commit is contained in:
@@ -54,15 +54,32 @@ const GEMINI_UNSUPPORTED_TOOL_FIELDS = new Set([
|
|||||||
'defer_loading',
|
'defer_loading',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const CODEX_UNSUPPORTED_TOOL_FIELDS = new Set(['cache_control', 'defer_loading']);
|
const CODEX_UNSUPPORTED_TOOL_FIELDS = new Set(['cache_control']);
|
||||||
|
const EXTENDED_CONTEXT_SUFFIX_REGEX = /\[1m\]$/i;
|
||||||
|
const LEGACY_CODEX_MODEL_ID_REGEX = /^gpt-5(?:\.\d+)?-codex(?:-(?:mini|max))?$/i;
|
||||||
|
|
||||||
|
function canonicalizeCodexModelId(model: string | undefined): string | null {
|
||||||
|
const normalizedModel = model?.trim().toLowerCase();
|
||||||
|
if (!normalizedModel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withoutExtendedContext = normalizedModel.replace(EXTENDED_CONTEXT_SUFFIX_REGEX, '').trim();
|
||||||
|
return stripCodexEffortSuffix(withoutExtendedContext);
|
||||||
|
}
|
||||||
|
|
||||||
function isKnownCodexModelId(model: string | undefined): boolean {
|
function isKnownCodexModelId(model: string | undefined): boolean {
|
||||||
const normalizedModel = model?.trim().toLowerCase();
|
const normalizedModel = canonicalizeCodexModelId(model);
|
||||||
if (!normalizedModel) {
|
if (!normalizedModel) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getModelMaxLevel('codex', stripCodexEffortSuffix(normalizedModel)) !== undefined;
|
// Root-routed requests can carry Codex model IDs that CCS uses outside the
|
||||||
|
// small interactive catalog (for example image analysis and Cursor defaults).
|
||||||
|
return (
|
||||||
|
LEGACY_CODEX_MODEL_ID_REGEX.test(normalizedModel) ||
|
||||||
|
getModelMaxLevel('codex', normalizedModel) !== undefined
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUnsupportedToolFields(
|
function getUnsupportedToolFields(
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ describe('ToolSanitizationProxy Integration', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('strips Codex-unsupported top-level tool fields before forwarding', async () => {
|
it('strips only Codex-unsupported top-level tool fields before forwarding', async () => {
|
||||||
const proxy = new ToolSanitizationProxy({
|
const proxy = new ToolSanitizationProxy({
|
||||||
upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`,
|
upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`,
|
||||||
});
|
});
|
||||||
@@ -422,7 +422,7 @@ describe('ToolSanitizationProxy Integration', () => {
|
|||||||
expect(sentTools[0].name).toBe('codex_tool');
|
expect(sentTools[0].name).toBe('codex_tool');
|
||||||
expect(sentTools[0].description).toBe('Codex test');
|
expect(sentTools[0].description).toBe('Codex test');
|
||||||
expect(sentTools[0].cache_control).toBeUndefined();
|
expect(sentTools[0].cache_control).toBeUndefined();
|
||||||
expect(sentTools[0].defer_loading).toBeUndefined();
|
expect(sentTools[0].defer_loading).toBe(true);
|
||||||
expect(sentTools[0].input_schema).toEqual({
|
expect(sentTools[0].input_schema).toEqual({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
@@ -436,57 +436,64 @@ describe('ToolSanitizationProxy Integration', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('strips Codex-unsupported top-level tool fields on root model-routed requests', async () => {
|
for (const model of [
|
||||||
const proxy = new ToolSanitizationProxy({
|
'gpt-5.3-codex-xhigh',
|
||||||
upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`,
|
'gpt-5.1-codex-mini',
|
||||||
});
|
'gpt-5.1-codex',
|
||||||
const port = await proxy.start();
|
'gpt-5-codex',
|
||||||
|
]) {
|
||||||
|
it(`strips only Codex-unsupported top-level tool fields on root model-routed request (${model})`, async () => {
|
||||||
|
const proxy = new ToolSanitizationProxy({
|
||||||
|
upstreamBaseUrl: `http://127.0.0.1:${mockUpstreamPort}`,
|
||||||
|
});
|
||||||
|
const port = await proxy.start();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(`http://127.0.0.1:${port}/v1/messages`, {
|
await fetch(`http://127.0.0.1:${port}/v1/messages`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: 'gpt-5.3-codex-xhigh',
|
model,
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
name: 'root_codex_tool',
|
name: 'root_codex_tool',
|
||||||
description: 'Root-routed Codex test',
|
description: 'Root-routed Codex test',
|
||||||
cache_control: { type: 'ephemeral' },
|
cache_control: { type: 'ephemeral' },
|
||||||
defer_loading: true,
|
defer_loading: true,
|
||||||
input_schema: {
|
input_schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
examples: ['fix the failing test'],
|
examples: ['fix the failing test'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
}),
|
||||||
}),
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const sentTools = (lastRequest!.body as Record<string, unknown>).tools as Array<
|
const sentTools = (lastRequest!.body as Record<string, unknown>).tools as Array<
|
||||||
Record<string, unknown>
|
Record<string, unknown>
|
||||||
>;
|
>;
|
||||||
expect(sentTools[0].name).toBe('root_codex_tool');
|
expect(sentTools[0].name).toBe('root_codex_tool');
|
||||||
expect(sentTools[0].description).toBe('Root-routed Codex test');
|
expect(sentTools[0].description).toBe('Root-routed Codex test');
|
||||||
expect(sentTools[0].cache_control).toBeUndefined();
|
expect(sentTools[0].cache_control).toBeUndefined();
|
||||||
expect(sentTools[0].defer_loading).toBeUndefined();
|
expect(sentTools[0].defer_loading).toBe(true);
|
||||||
expect(sentTools[0].input_schema).toEqual({
|
expect(sentTools[0].input_schema).toEqual({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
prompt: {
|
prompt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
} finally {
|
||||||
} finally {
|
proxy.stop();
|
||||||
proxy.stop();
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
it('preserves top-level tool fields for non-target root routes', async () => {
|
it('preserves top-level tool fields for non-target root routes', async () => {
|
||||||
const proxy = new ToolSanitizationProxy({
|
const proxy = new ToolSanitizationProxy({
|
||||||
|
|||||||
Reference in New Issue
Block a user