Files
stack/packages/harness/tests/proxy-bundle.test.mjs
T
jason.woltje 39eadf8ba8 Row 332 R2 (#1830): hermetic pi launches in cli tests, pi bundle default, review F2 and F3
The cli tests that launch pi deleted only the session login names, so pi's
preflight passed or refused by whatever provider keys the host exported. A
shared helper now deletes every credential pi's preflight reads and gives pi
launches one made-up key. The refusal itself is unchanged.

pi bundle() takes a default argument again, so a call with none returns the
non-proxy bundle, as before P3. pi.bundle leaves out a launch.models entry
whose provider has no proxy route instead of failing every pi launch (F2);
malformed proxy entries still refuse. The invalid session proxy context
BundleError gets the code invalid-proxy-context. New tests cover F2, the code,
and the three proxy account branches in the launcher (F3).
2026-11-02 15:22:17 -06:00

147 lines
8.1 KiB
JavaScript

// 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));
}
});