From 0711ddab0b839326b38bf9c5902af42df0895d97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:36:55 +0000 Subject: [PATCH 1/2] Initial plan From e98dd3c8ee6ed365e16c67e919af5de8a19105d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:42:34 +0000 Subject: [PATCH 2/2] fix: accept Codex standalone search input and output limits Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/api-proxy/codex-hosted-web.js | 17 +++- containers/api-proxy/codex-hosted-web.test.js | 87 +++++++++++++++---- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/containers/api-proxy/codex-hosted-web.js b/containers/api-proxy/codex-hosted-web.js index ee84b808e..12130554b 100644 --- a/containers/api-proxy/codex-hosted-web.js +++ b/containers/api-proxy/codex-hosted-web.js @@ -13,7 +13,7 @@ const SEARCH_PATHS = new Set(['/v1/alpha/search', '/alpha/search']); const SEARCH_COMMANDS = new Set(['search_query', 'image_query', 'open', 'click', 'find', 'screenshot']); const URL_COMMANDS = new Set(['open', 'find', 'screenshot']); const FILTER_FIELDS = new Set(['allowed_domains', 'blocked_domains']); -const STANDALONE_FIELDS = new Set(['id', 'model', 'settings', 'commands']); +const STANDALONE_FIELDS = new Set(['id', 'model', 'settings', 'commands', 'max_output_tokens', 'input']); const TOOL_FIELDS = new Set([ 'type', 'external_web_access', 'indexed_web_access', 'filters', 'user_location', 'search_context_size', 'search_content_types', 'image_settings', 'max_uses', @@ -271,6 +271,21 @@ function enforceStandalone(body, policy) { 'codex_hosted_web_shape_invalid', 'Codex standalone hosted search body contains an unrecognized field.', ); + if (hasField(body, 'max_output_tokens') && + (!Number.isInteger(body.max_output_tokens) || body.max_output_tokens < 0)) { + throw new CodexHostedWebPolicyError( + 'codex_hosted_web_shape_invalid', + 'Codex standalone hosted search "max_output_tokens" must be a non-negative integer.', + 400, + ); + } + if (hasField(body, 'input') && typeof body.input !== 'string' && !Array.isArray(body.input)) { + throw new CodexHostedWebPolicyError( + 'codex_hosted_web_shape_invalid', + 'Codex standalone hosted search "input" must be a string or an array of items.', + 400, + ); + } const settings = body.settings === undefined ? {} : body.settings; if (!settings || typeof settings !== 'object' || Array.isArray(settings)) { throw new CodexHostedWebPolicyError( diff --git a/containers/api-proxy/codex-hosted-web.test.js b/containers/api-proxy/codex-hosted-web.test.js index df799503c..b49a4260c 100644 --- a/containers/api-proxy/codex-hosted-web.test.js +++ b/containers/api-proxy/codex-hosted-web.test.js @@ -103,6 +103,49 @@ describe('Codex hosted-web policy', () => { .toEqual([['docs.github.com'], ['docs.github.com']]); }); + it.each([ + 'Find the latest documentation', + [{ role: 'user', content: [{ type: 'input_text', text: 'Find the latest documentation' }] }], + [], + ])('preserves standalone conversation input and output limits while applying filters: %j', input => { + const body = { + id: 'search-request', + model: 'gpt-5.6-terra', + max_output_tokens: 2048, + input, + settings: { filters: { allowed_domains: ['github.com'] } }, + commands: { search_query: [{ q: 'documentation', domains: ['github.com'] }] }, + }; + expect(enforceStandalone(body, { ...allow, maxUses: undefined })).toEqual({ + ...body, + settings: { filters: { allowed_domains: ['docs.github.com'] } }, + commands: { search_query: [{ q: 'documentation', domains: ['docs.github.com'] }] }, + }); + expect(body.settings.filters.allowed_domains).toEqual(['github.com']); + expect(body.commands.search_query[0].domains).toEqual(['github.com']); + }); + + it('accepts a zero standalone output limit', () => { + expect(enforceStandalone({ max_output_tokens: 0 }, { ...allow, maxUses: undefined }) + .max_output_tokens).toBe(0); + }); + + it.each([-1, 1.5, '2048', null, true, {}, []])( + 'rejects invalid standalone max_output_tokens: %j', + maxOutputTokens => { + expect(() => enforceStandalone({ + max_output_tokens: maxOutputTokens, + }, { ...allow, maxUses: undefined })).toThrow(expect.objectContaining({ + code: 'codex_hosted_web_shape_invalid', statusCode: 400, + })); + }, + ); + + it.each([null, 42, true, {}])('rejects invalid standalone input: %j', input => { + expect(() => enforceStandalone({ input }, { ...allow, maxUses: undefined })) + .toThrow(expect.objectContaining({ code: 'codex_hosted_web_shape_invalid', statusCode: 400 })); + }); + it('unions standalone blocklists and prevents query scopes from removing blocks', () => { const result = enforceStandalone({ settings: { filters: { blocked_domains: ['ads.example'] } }, @@ -159,22 +202,30 @@ describe('Codex hosted-web policy', () => { .toThrow(expect.objectContaining({ code: 'codex_hosted_web_max_uses_unsupported' })); }); - it('uses the request path to select the standalone body shape', () => { - const transform = makeCodexHostedWebTransform({ ...allow, maxUses: undefined }); - const transformed = transform( - Buffer.from(JSON.stringify({ commands: {} })), - { url: '/v1/alpha/search' }, - ); - expect(JSON.parse(transformed)).toEqual({ - commands: {}, - settings: { filters: { allowed_domains: ['docs.github.com'] } }, - }); - expect(JSON.parse(transform( - Buffer.from(JSON.stringify({ commands: {} })), - { url: '/v1/alpha/search/' }, - ))).toEqual({ - commands: {}, - settings: { filters: { allowed_domains: ['docs.github.com'] } }, - }); - }); + it.each(['/alpha/search', '/v1/alpha/search'])( + 'uses the request path to select the standalone body shape: %s', + pathname => { + const transform = makeCodexHostedWebTransform({ ...allow, maxUses: undefined }); + const body = { + commands: { search_query: [{ q: 'documentation' }] }, + input: [{ role: 'user', content: 'Find documentation' }], + max_output_tokens: 2048, + }; + const transformed = transform( + Buffer.from(JSON.stringify(body)), + { url: pathname }, + ); + expect(JSON.parse(transformed)).toEqual({ + ...body, + settings: { filters: { allowed_domains: ['docs.github.com'] } }, + }); + expect(JSON.parse(transform( + Buffer.from(JSON.stringify(body)), + { url: `${pathname}/` }, + ))).toEqual({ + ...body, + settings: { filters: { allowed_domains: ['docs.github.com'] } }, + }); + }, + ); });