// P3: generate real harness config through buildBundle; credentials never enter disk artifacts. import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdirSync, readFileSync, statSync } from 'node:fs'; import { join } from 'node:path'; import { buildBundle } from '../src/bundle.mjs'; import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from './helpers.mjs'; const models = { a: { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' }, z: { provider: 'proxy', model: 'glm-fixture', route: 'glm' }, g: { provider: 'proxy', model: 'gpt-fixture', route: 'codex' }, }; function build(t, selection, proxy, declared) { const root = scratch(t, 'p332-'), runDir = join(root, 'run'); mkdirSync(runDir, { mode: 0o700 }); const valid = resolvedFor(t, 'pm'); assert.ok(Object.hasOwn(declared, selection), 'fixture selection must exist in declared launch.models'); const resolved = { ...valid, vars: { ...valid.vars, harness: 'pi', model: selection } }; const bundle = buildBundle({ dir: join(runDir, 'bundle'), resolved, models: declared, contract: '# Fixture\n', harness: HARNESS.pi, harnessVersion: 'fixture', skillsDir: REPO_SKILLS, proxy, session: { run: 'r332', launchedBy: 'chief', workspace: join(root, 'ws'), toolSocket: join(runDir, 'tools.sock'), turnMarker: join(runDir, 'turn.done') }, }); const file = join(runDir, 'home', '.pi', 'agent', 'models.json'); return { ...bundle, file, runDir, text: readFileSync(file, 'utf8') }; } function assertSafe(value) { if (typeof value === 'string') assert.ok(!value.startsWith('!'), value); else if (value && typeof value === 'object') for (const item of Object.values(value)) assertSafe(item); } test('P3 Anthropic route uses messages at proxy root and the declared model id', t => { const b = build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { a: models.a }); const { anthropic } = JSON.parse(b.text).providers; assert.equal(anthropic.api, 'anthropic-messages'); assert.equal(anthropic.baseUrl, 'http://127.0.0.1:12345'); assert.deepEqual(anthropic.models.map(m => m.id), ['claude-fixture']); assert.equal(b.env.PI_PROVIDER, 'anthropic'); assert.equal(b.env.PI_MODEL, 'claude-fixture'); }); test('P3 omitted route refuses proxy-route-missing before model config generation', t => { assert.throws(() => build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { a: { provider: 'proxy', model: 'claude-fixture' }, }), { code: 'proxy-route-missing' }); }); test('P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility', t => { const b = build(t, 'z', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { z: models.z }); const glm = JSON.parse(b.text).providers['mosaic-proxy-glm']; assert.equal(glm.api, 'openai-completions'); assert.equal(glm.baseUrl, 'http://127.0.0.1:12345/v1'); assert.deepEqual(glm.models.map(m => m.id), ['glm-fixture']); assert.equal(glm.compat.thinkingFormat, 'zai'); assert.equal(glm.compat.zaiToolStream, true); assert.equal(b.env.PI_PROVIDER, 'mosaic-proxy-glm'); }); test('P3 GPT route selects a stack-owned provider instead of pi Codex OAuth', t => { const b = build(t, 'g', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { g: models.g }); 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'); }); 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, }); 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 => { const key = 'fx332-agent-a'; const b = build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key }, { a: models.a, z: models.z }); const document = JSON.parse(b.text); assert.deepEqual(Object.keys(document.providers).sort(), ['anthropic', 'mosaic-proxy-glm']); for (const provider of Object.values(document.providers)) assert.equal(provider.apiKey, '$ANTHROPIC_API_KEY'); assertSafe(document); assert.ok(!b.text.includes(key)); assert.equal(b.env.ANTHROPIC_API_KEY, key); assert.equal(statSync(b.file).mode & 0o777, 0o600); for (const dir of ['home', 'home/.pi', 'home/.pi/agent']) assert.equal(statSync(join(b.runDir, dir)).mode & 0o777, 0o700); for (const file of Object.values(b.files)) assert.ok(!readFileSync(file, 'utf8').includes(key), file); assert.ok(!JSON.stringify(b.manifest).includes(key)); }); test('P3 every launch regenerates proxy port, declared models and own key without caching', t => { const first = build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { a: models.a, z: models.z }); const second = build(t, 'a', { baseUrl: 'http://127.0.0.1:23456', key: 'fx332-agent-b' }, { a: { provider: 'proxy', model: 'changed-model-id', route: 'anthropic' }, z: { provider: 'proxy', model: 'changed-glm-id', route: 'glm' }, }); assert.equal(second.env.ANTHROPIC_API_KEY, 'fx332-agent-b'); assert.ok(!JSON.stringify(second).includes('fx332-agent-a')); const { providers } = JSON.parse(second.text); assert.deepEqual(Object.keys(providers), ['anthropic', 'mosaic-proxy-glm']); assert.equal(providers.anthropic.baseUrl, 'http://127.0.0.1:23456'); assert.deepEqual(providers.anthropic.models.map(m => m.id), ['changed-model-id']); assert.equal(providers['mosaic-proxy-glm'].baseUrl, 'http://127.0.0.1:23456/v1'); assert.deepEqual(providers['mosaic-proxy-glm'].models.map(m => m.id), ['changed-glm-id']); assert.equal(JSON.parse(first.text).providers.anthropic.baseUrl, 'http://127.0.0.1:12345'); }); // Row 332 R2 (#1830, review F2): launch.models is open table data. An entry whose provider has no // proxy route is one this session can't use, so pi leaves it out of models.json instead of failing // the launch; the selected entry was routed by sessionModel first. A malformed proxy entry still refuses. test('row 332 R2 an unroutable model table entry is left out of models.json and the launch goes on', t => { const declared = { opus: { provider: 'anthropic', model: 'claude-fixture' }, glm: { provider: 'zai', model: 'glm-fixture' }, router: { provider: 'openrouter', model: 'x' }, }; const b = build(t, 'opus', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, declared); const { providers } = JSON.parse(b.text); assert.deepEqual(Object.keys(providers).sort(), ['anthropic', 'mosaic-proxy-glm']); assert.deepEqual(providers.anthropic.models.map(m => m.id), ['claude-fixture']); assert.deepEqual(providers['mosaic-proxy-glm'].models.map(m => m.id), ['glm-fixture']); assert.ok(!Object.values(providers).some(p => p.models.some(m => m.id === 'x')), 'the openrouter entry is not written'); assert.equal(b.env.PI_MODEL, 'claude-fixture'); assert.throws(() => build(t, 'opus', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { ...declared, broken: { provider: 'proxy', model: 'claude-fixture' }, }), { code: 'proxy-route-missing' }); }); // Row 332 R2 (#1830, review F3): a proxy context of the wrong shape refuses with its own code. test('row 332 R2 a session proxy context without a key or loopback URL refuses invalid-proxy-context', t => { for (const proxy of [ { baseUrl: 'http://127.0.0.1:12345', key: '' }, { baseUrl: 'http://localhost:12345', key: 'fx332-agent-a' }, { baseUrl: 'http://127.0.0.1:12345' }, ]) { assert.throws(() => build(t, 'a', proxy, { a: models.a }), { code: 'invalid-proxy-context', message: 'invalid session proxy context' }, JSON.stringify(proxy)); } }); test('GLM 5.3 flash has documented output and existing context limits; other model ids are unchanged', t => { const declared = { flash: { provider:'zai', model:'glm-5.3-flash' }, other: { provider:'zai', model:'glm-fixture' }, }; const b = build(t,'flash',{baseUrl:'http://127.0.0.1:12345',key:'fx332-agent-a'},declared); const registered = JSON.parse(b.text).providers['mosaic-proxy-glm'].models; assert.deepEqual(registered.find(m => m.id === 'glm-5.3-flash'),{id:'glm-5.3-flash',reasoning:true,maxTokens:65536,contextWindow:128000}); assert.deepEqual(registered.find(m => m.id === 'glm-fixture'),{id:'glm-fixture',reasoning:true}); });