Files
stack/packages/harness/tests/proxy-bundle.test.mjs
jason.woltje 03072cc08b fix(plugins): set documented output limit for glm-5.3-flash (#1852)
Register the documented 65536-token default output limit and retain the existing 128000-token context window for glm-5.3-flash. Other model registrations are unchanged.

Pi's isRecoverableLength (pi-ai dist/utils/overflow.js:163), used by agent-session.js:1659, now treats a length stop below 65536 output tokens as recoverable, including context-clamped requests or a lower provider cap. Pi then compacts and retries. It allows one recovery in a row: the flag resets on a new user message or a successful assistant message that is not a length stop, so tool rounds can re-arm it within one prompt.

The recovery happens inside one runner attempt. The turn marker reflects the final agent_end, and the failed and retried messages still count in that message's usage delta, so three runner attempts need not mean three provider calls.

The 128000 context and 16384 reserve are unchanged, so threshold compaction still starts above 111616 tokens. Output clamping now starts above about 58368 estimated input tokens instead of 107520. No new local refusal.

This commit can be dropped without affecting Part A.
2026-11-06 16:49:31 -06:00

158 lines
8.7 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));
}
});
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});
});