From ef0939e351f860f82a8802627ea00f3f668ede35 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sat, 24 Oct 2026 19:14:10 -0500 Subject: [PATCH] PROXY_CODEX_API openai-responses per sage-4331, untested --- agents/fethry/work/queue-332/BUILD.md | 8 +++++++ .../fethry/work/queue-332/proxy-evidence.mjs | 21 ++++++++++++------- packages/harness/tests/proxy-bundle.test.mjs | 12 +++++++---- packages/plugins/README.md | 7 ++++--- packages/plugins/harness/pi/index.mjs | 7 +++---- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/agents/fethry/work/queue-332/BUILD.md b/agents/fethry/work/queue-332/BUILD.md index bf8fd6fa3..c942b32d5 100644 --- a/agents/fethry/work/queue-332/BUILD.md +++ b/agents/fethry/work/queue-332/BUILD.md @@ -274,3 +274,11 @@ GLM compatibility finding: thinkingFormat=zai-generated thinking.type=enabled/cl Both GPT shapes pass on the stand-in openai-compatibility credential route. This does not distinguish them for the Codex-login route; no Codex OAuth/auth file exists in this experiment. Evidence is INCONCLUSIVE for the production GPT choice. PROXY_CODEX_API remains null/PENDING; no source or API selection changed. Sage rules or takes to Mos per plan item5; first real Codex-auth turn stays row209 phaseA step7. Niceness: attempt2 entry/emitted turn values19, explicit setPriority at entry/child start and100ms restore over owned descendants. Host policy temporarily changed processes to16 eight times; corrections restored19, disclosed here. Initial fetch end16 and failed inspection guard are retained separately, not represented as clean19 throughout. Full safe request/response receipts in evidence/offline-attempt2.txt; no auth/header value recorded. Process cleanup reports remainingManagedChildren0. Sweep over worktree and owned evidence root found same six pre-existing tool services only, no proxy/pi/timeout/chromium test leftovers. Lane B free. No mutants or other test files run. + +## API choice and active-tool follow-up — sage-4331 + +Sage's ruling: PROXY_CODEX_API=openai-responses. The evidence does not separate the two shapes: both pass through pinned8.0.42. This is a tie broken on reasoning; it is NOT an evidence-decided pick. Sage's reason: Codex upstream speaks Responses natively and Responses carries pi's reasoning summary field, while completions drops that field; expected fewer translations/lost fields on the real route. This reasoning is a lead decision, not a claim of an authenticated Codex experiment. Row209 phaseA step7 verifies first real Codex turn; change the single constant if it fails there, with no runtime fallback/env switch. Earlier PENDING logs/notes remain historical evidence. + +Set constant/remove pending refusal; GPT route test asserts openai-responses on mosaic-proxy-codex. Replaced temporary pending-refusal test with generated single-provider/responses/key-reference assertions per ruling. No other production change. Active plug-in docs updated. + +Evidence script adds glm-tool mode: one GLM turn only, same pinned verified binary and isolated setup, distinct attempt3-tool root, active built-in read tool but stand-in still returns text only. Capture tool_stream and tool names before/after proxy, never header/auth values. No actual tool execution is demanded by stand-in, so this checks wire-field survival, not arbitrary tool-call semantics. Rehash before launch, no fetch,30s catalog readiness with counts,50s timeout-k10 for one turn,own descendants restored nice19. Commit untested before cleared run; then proxy-bundle and proxy-harness each whole once. No other runs/mutants. diff --git a/agents/fethry/work/queue-332/proxy-evidence.mjs b/agents/fethry/work/queue-332/proxy-evidence.mjs index 41214055e..d742f742d 100644 --- a/agents/fethry/work/queue-332/proxy-evidence.mjs +++ b/agents/fethry/work/queue-332/proxy-evidence.mjs @@ -31,12 +31,13 @@ if (mode === 'fetch') { writeFileSync(join(root, 'installed.json'), JSON.stringify(installed) + '\n', { mode: 0o600, flag: 'wx' }); emit({ phase: 'fetch', version: PIN.version, binarySha256: PIN.binary.sha256, panelVersion: PIN.panel.version, panelSha256: PIN.panel.sha256, enteredNice, nice: getPriority(0), verified: true }); -} else if (mode === 'offline') { +} else if (mode === 'offline' || mode === 'glm-tool') { + const withTool = mode === 'glm-tool'; if (readlinkSync('/proc/self/ns/net') === process.env.P3_HOST_NET_NS) throw Error('network namespace was not isolated'); if (Object.keys(networkInterfaces()).some(name => name !== 'lo')) throw Error('loopback-only namespace required'); // Re-hash the already fetched assets; this function performs no network I/O. const installed = verifyInstall(join(root, 'data'), PIN); - const attemptRoot = join(root, 'attempt2'); + const attemptRoot = join(root, withTool ? 'attempt3-tool' : 'attempt2'); mkdirSync(attemptRoot, { mode: 0o700 }); setPriority(0, 19); emit({ phase: 'isolation', interfaces: Object.keys(networkInterfaces()), enteredNice, nice: getPriority(0), version: PIN.version, @@ -60,6 +61,8 @@ if (mode === 'fetch') { ...(Object.hasOwn(body, 'thinking') ? { thinking: body.thinking } : {}), ...(Object.hasOwn(body, 'reasoning_effort') ? { reasoning_effort: body.reasoning_effort } : {}), ...(Object.hasOwn(body, 'enable_thinking') ? { enable_thinking: body.enable_thinking } : {}), + ...(Object.hasOwn(body, 'tool_stream') ? { tool_stream: body.tool_stream } : {}), + ...(Array.isArray(body.tools) ? { toolNames: body.tools.map(tool => tool.function?.name) } : {}), ...(Object.hasOwn(body, 'stream_options') ? { stream_options: body.stream_options } : {}), ...(Object.hasOwn(body, 'max_tokens') ? { max_tokens: body.max_tokens } : {}), ...(Object.hasOwn(body, 'max_completion_tokens') ? { max_completion_tokens: body.max_completion_tokens } : {}) }); @@ -163,11 +166,12 @@ if (mode === 'fetch') { }); const tapPort = await listen(tap); if (tapPort === 8317) throw Error('reserved host-proxy port excluded'); - for (const [name, api, model, compat] of [ + const scenarios = [ ['gpt-responses', 'openai-responses', 'gpt-fixture', null], ['gpt-completions', 'openai-completions', 'gpt-fixture', null], ['glm-completions', 'openai-completions', 'glm-fixture', { thinkingFormat: 'zai', zaiToolStream: true }], - ]) { + ]; + for (const [name, api, model, compat] of (withTool ? scenarios.filter(([name]) => name === 'glm-completions') : scenarios)) { const turnHome = join(attemptRoot, name, 'home'); const agentDir = join(turnHome, '.pi', 'agent'); mkdirSync(agentDir, { recursive: true, mode: 0o700 }); @@ -177,18 +181,19 @@ if (mode === 'fetch') { writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({ retry: { enabled: false, maxRetries: 0, provider: { maxRetries: 0 } } }) + '\n', { mode: 0o600, flag: 'wx' }); const reqStart = requests.length, upstreamStart = upstreamRequests.length, responseStart = responses.length; const pi = managed(['50', '/usr/bin/node', join(repo, 'node_modules/@earendil-works/pi-coding-agent/dist/cli.js'), - '--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes', '--no-context-files', '--no-session', '--no-tools', + '--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes', '--no-context-files', '--no-session', + ...(withTool ? ['--tools', 'read'] : ['--no-tools']), '--provider', 'p332-probe', '--model', model, '--thinking', 'low', '-p', 'Reply P332_OFFLINE_OK only.'], { env: { HOME: turnHome, PI_CODING_AGENT_DIR: agentDir, ANTHROPIC_API_KEY: fakeKeys[0] } }); let stdout = '', stderr = ''; pi.stdout.on('data', bytes => { stdout += bytes; }); pi.stderr.on('data', bytes => { stderr += bytes; }); const [exit, signal] = await once(pi, 'close'); - emit({ phase: 'turn', name, api, exit, signal, marker: stdout.includes(marker), + emit({ phase: 'turn', name: withTool ? `${name}-tool` : name, api, exit, signal, marker: stdout.includes(marker), piRequests: requests.slice(reqStart), proxyResponses: responses.slice(responseStart), upstreamRequests: upstreamRequests.slice(upstreamStart), ...(exit !== 0 ? { stderr: redact(stderr).slice(0, 1200), stdout: redact(stdout).slice(0, 1200) } : {}), nice: getPriority(0) }); } - emit({ phase: 'conclusion', limitation: 'Stand-in openai-compatibility credential only; no Codex OAuth credential or real-auth turn. API value remains PENDING.' }); + emit({ phase: 'conclusion', limitation: 'Stand-in openai-compatibility credential only; no Codex OAuth credential or real-auth turn. This script never selects the production API.' }); emit({ phase: 'nice-corrections', restoredTo: 19, observedBeforeRestore: corrections }); } catch (error) { emit({ phase: 'error', message: error.message, proxyDiagnostic: redact(proxyText).slice(-3000) }); @@ -203,4 +208,4 @@ if (mode === 'fetch') { for (const server of [tap, upstream].filter(Boolean)) { server.closeAllConnections(); await new Promise(ok => server.close(ok)); } emit({ phase: 'cleanup', remainingManagedChildren: children.size }); } -} else { throw Error('fetch or offline phase required'); } +} else { throw Error('fetch, offline or glm-tool phase required'); } diff --git a/packages/harness/tests/proxy-bundle.test.mjs b/packages/harness/tests/proxy-bundle.test.mjs index bdaad7c9d..d2a3a3e79 100644 --- a/packages/harness/tests/proxy-bundle.test.mjs +++ b/packages/harness/tests/proxy-bundle.test.mjs @@ -64,16 +64,20 @@ test('P3 GPT route selects a stack-owned provider instead of pi Codex OAuth', t const providers = JSON.parse(b.text).providers; assert.equal(Object.hasOwn(providers, 'openai-codex'), false); const gpt = providers['mosaic-proxy-codex']; + assert.equal(gpt.api, 'openai-responses'); assert.equal(gpt.baseUrl, 'http://127.0.0.1:12345/v1'); assert.deepEqual(gpt.models.map(m => m.id), ['gpt-fixture']); assert.equal(b.env.PI_PROVIDER, 'mosaic-proxy-codex'); - // Assert the one chosen API after the authorized pinned-binary probe, before claiming GREEN. }); -test('P3 a declared Codex route refuses generation while the build-time API is PENDING', t => { - assert.throws(() => build(t, 'g', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { +test('P3 a declared Codex route generates only the single build-time responses provider', t => { + const b = build(t, 'g', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { g: models.g, - }), /proxy GPT API is PENDING build-time evidence/); + }); + const providers = JSON.parse(b.text).providers; + assert.deepEqual(Object.keys(providers), ['mosaic-proxy-codex']); + assert.equal(providers['mosaic-proxy-codex'].api, 'openai-responses'); + assert.equal(providers['mosaic-proxy-codex'].apiKey, '$ANTHROPIC_API_KEY'); }); test('P3 models file is private, references the one session key, and stores no executable values or keys', t => { diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 39539e865..fb37d9359 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -352,6 +352,7 @@ Pi writes private, fresh run-local `home/.pi/agent/models.json`, with `$ANTHROPIC_API_KEY` references. Anthropic uses messages at the proxy root; GLM uses completions at `/v1` with z.ai compatibility fields. GPT uses the stack-owned `mosaic-proxy-codex` provider, never pi's Codex OAuth provider. Its build-time API -value is currently PENDING: declarations of that route refuse generation until -the separately approved offline probe supplies evidence. There is no runtime -API selection or fallback. +value is `openai-responses` per Sage's sage-4331 ruling: both shapes passed the +pinned offline probe, so this is a reasoning-based tie-break rather than an +evidence-decided pick. Row209 phase A step7 verifies the first real Codex turn. +There is no runtime API selection or fallback. diff --git a/packages/plugins/harness/pi/index.mjs b/packages/plugins/harness/pi/index.mjs index e44acafcb..3dcdbb683 100644 --- a/packages/plugins/harness/pi/index.mjs +++ b/packages/plugins/harness/pi/index.mjs @@ -13,9 +13,9 @@ export const interfaceVersion = 1; const REPO = fileURLToPath(new URL("../../../../", import.meta.url)); const PACKAGE = "@earendil-works/pi-coding-agent"; const BIN = join(REPO, "node_modules", ".bin", "pi"); -// PENDING: choose exactly once from the separately cleared, pinned-proxy probe. -// Neither an environment override nor a runtime fallback may pick this value. -const PROXY_CODEX_API = null; +// Sage sage-4331: reasoning-based tie-break; both pinned-proxy shapes passed. +// First real Codex verification remains row209 phase A step7. No runtime switch. +const PROXY_CODEX_API = "openai-responses"; const PROXY_PROVIDERS = Object.freeze({ anthropic: "anthropic", glm: "mosaic-proxy-glm", codex: "mosaic-proxy-codex" }); const EXTENSION = fileURLToPath(new URL("../../../harness/src/pi-extension.mjs", import.meta.url)); // Row 66 (#1559): the unslop check, loaded as a second extension. It logs to MOSAIC_UNSLOP_LOG, @@ -75,7 +75,6 @@ export function bundle({ dir, models, proxy = null }) { const route = entry.route; if (!Object.hasOwn(PROXY_PROVIDERS, route)) throw new Error("proxy model has no supported route"); const provider = PROXY_PROVIDERS[route]; - if (route === "codex" && PROXY_CODEX_API === null) throw new Error("proxy GPT API is PENDING build-time evidence"); if (typeof entry.model !== "string" || !entry.model || entry.model.startsWith("!")) throw new Error("unsafe proxy model id"); const api = route === "anthropic" ? "anthropic-messages" : route === "glm" ? "openai-completions" : PROXY_CODEX_API;