Skip to content

Commit 8930f82

Browse files
V48 Gate 3 (implementation-only): the SynthesizeAssetPacks test sweep — ~150 tests pinning primitives, agents, stores, prompts, pipeline, and deposit UI
Nine-domain review + test authoring across the SDIVF surface. New coverage: - Thinkings-generation primitives (Reason→Judge→StructuredOutput order, threading, soft-parse enforcement, error propagation, usage stores) - Failsafe primitives (sequence order + envelope, ChunkThenSum chunked vs single paths, stitch truncation bound, PrepareConciseContext snapshot) - Execution machinery (first execution-generics jest setup: store/child/ findUp/getRoot semantics, stream adapter, tool tracking, LLM registry) - SDIVF pipeline (executor loop, per-mode phase rosters, setup punts, Finish upload-for-review, deposit preprocess) - Deposit agents (store contracts, synthesis/validation/upload/measure) - Prompt system (per-agent prompt contracts, promptpart index exports) - Deposit UI (activity row contract, stall indicator, SSE reconnect, option cards, dispatch->completion error paths) All green: agent-generics 19/87, execution-generics 2/23, pipelines- generics 9/51, asset-pack 56/311, prompts 2/11, pipeline-hosts 6/34, uapi telemetry/deposit batch 6/44; typechecks clean. The sweep's confirmed product bugs (route options read on the wrong execution node, sibling-isolated cross-phase stores, unconditional DIV loop, danger-wall short-circuit swallowed, chunking defeated by full-input threading, reconnect cursor semantics) are documented findings for the follow-up correctness commits — tests pin current-correct behavior only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5c46bd3 commit 8930f82

37 files changed

Lines changed: 6231 additions & 8 deletions
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// @ts-nocheck
2+
/**
3+
* AgentExecution default-LLM env resolution + AgentLLMsRegistry call tracking
4+
* (V48 Gate 3 — tools/executions domain).
5+
*
6+
* Inference is NON-configurable (F26-A): determinism comes from the boundary
7+
* LLM seam, so @bitcode/generic-llms is mocked here with recording providers
8+
* while the REAL env-resolution defaults (resolveDefaultLLMConfig) stay live.
9+
*
10+
* Pins:
11+
* - AgentExecution honors BITCODE_LLM_PROVIDER + BITCODE_LLM_MODEL: the '*'
12+
* global config carries the env model and the env provider is the default.
13+
* - Provider fallback: no BITCODE_LLM_PROVIDER + OPENAI_API_KEY → openai with
14+
* its default model.
15+
* - getDefaultLLM's tracking wrapper stores the llm-namespace telemetry keys
16+
* (content-bearing keys are withheld downstream by sourceSafeStreamEvent —
17+
* pinned in pipelines-generics' streaming tests).
18+
* - BITCODE_LLM_CALL_TIMEOUT_MS bounds every call (reject + failed status);
19+
* 0 disables the bound.
20+
*/
21+
const mockLLMCalls: Array<{ provider: string; config: any }> = [];
22+
23+
jest.mock('@bitcode/generic-llms', () => {
24+
const { LLMRegistry } = jest.requireActual('@bitcode/llm-generics');
25+
const defaults = jest.requireActual('@bitcode/generic-llms/defaults');
26+
return {
27+
resolveDefaultLLMConfig: defaults.resolveDefaultLLMConfig,
28+
resolveDefaultLLMModel: defaults.resolveDefaultLLMModel,
29+
resolveDefaultLLMProvider: defaults.resolveDefaultLLMProvider,
30+
factoryLLMRegistryWithProviders: () => {
31+
const registry = new LLMRegistry();
32+
for (const name of ['openai', 'anthropic', 'google']) {
33+
registry.registerProvider({
34+
name,
35+
createLLM: (config: any) => async () => {
36+
mockLLMCalls.push({ provider: name, config });
37+
return {
38+
content: `stubbed:${name}:${config.model ?? 'unconfigured'}`,
39+
usage: { inputTokens: 3, outputTokens: 4, totalTokens: 7 },
40+
};
41+
},
42+
});
43+
}
44+
return registry;
45+
},
46+
};
47+
});
48+
49+
import { Execution } from '@bitcode/execution-generics';
50+
import { AgentExecution } from '../execution/AgentExecution';
51+
import { AgentLLMsRegistry } from '../execution/AgentLLMsRegistry';
52+
53+
const ENV_KEYS = [
54+
'BITCODE_LLM_PROVIDER',
55+
'BITCODE_LLM_MODEL',
56+
'BITCODE_LLM_CALL_TIMEOUT_MS',
57+
'OPENAI_API_KEY',
58+
];
59+
let envSnapshot: Record<string, string | undefined>;
60+
61+
beforeEach(() => {
62+
envSnapshot = {};
63+
for (const key of ENV_KEYS) envSnapshot[key] = process.env[key];
64+
mockLLMCalls.length = 0;
65+
});
66+
67+
afterEach(() => {
68+
for (const key of ENV_KEYS) {
69+
if (envSnapshot[key] === undefined) delete process.env[key];
70+
else process.env[key] = envSnapshot[key];
71+
}
72+
});
73+
74+
function findLlmChild(execution: Execution, suffix: string): Execution | undefined {
75+
return Array.from(execution.children.values()).find((child) => child.id.endsWith(suffix));
76+
}
77+
78+
describe('AgentExecution — default-LLM env resolution (BITCODE_LLM_*)', () => {
79+
it('honors BITCODE_LLM_PROVIDER + BITCODE_LLM_MODEL for getDefaultLLM', async () => {
80+
process.env.BITCODE_LLM_PROVIDER = 'anthropic';
81+
process.env.BITCODE_LLM_MODEL = 'claude-env-pin';
82+
delete process.env.BITCODE_LLM_CALL_TIMEOUT_MS;
83+
84+
const agentExec = new AgentExecution('agent:env-honored');
85+
const llm = agentExec.llms.getDefaultLLM();
86+
const output = await llm({ messages: [{ role: 'user', content: 'hi' }] });
87+
88+
expect(mockLLMCalls).toHaveLength(1);
89+
expect(mockLLMCalls[0].provider).toBe('anthropic');
90+
expect(mockLLMCalls[0].config.model).toBe('claude-env-pin');
91+
expect(output.content).toBe('stubbed:anthropic:claude-env-pin');
92+
93+
// The agent-level presence config for Bitcode boundary enforcement.
94+
expect(agentExec.llms.get('default')).toEqual({ model: 'claude-env-pin' });
95+
expect(agentExec.llms.ensureDefaultConfigured()).toBe(true);
96+
});
97+
98+
it('falls back to the API-key-derived provider and its default model when BITCODE_LLM_* are unset', async () => {
99+
delete process.env.BITCODE_LLM_PROVIDER;
100+
delete process.env.BITCODE_LLM_MODEL;
101+
process.env.OPENAI_API_KEY = 'test-key-openai';
102+
103+
const agentExec = new AgentExecution('agent:env-fallback');
104+
await agentExec.llms.getDefaultLLM()({ messages: [{ role: 'user', content: 'hi' }] });
105+
106+
expect(mockLLMCalls).toHaveLength(1);
107+
expect(mockLLMCalls[0].provider).toBe('openai');
108+
expect(mockLLMCalls[0].config.model).toBe('gpt-4.1-mini');
109+
});
110+
111+
it('child AgentExecutions resolve the same env default through their own registries', async () => {
112+
process.env.BITCODE_LLM_PROVIDER = 'google';
113+
process.env.BITCODE_LLM_MODEL = 'gemini-env-pin';
114+
115+
const parent = new AgentExecution('agent:parent');
116+
const child = parent.child('step:try');
117+
await child.llms.getDefaultLLM()({ messages: [{ role: 'user', content: 'hi' }] });
118+
119+
expect(mockLLMCalls[mockLLMCalls.length - 1]).toMatchObject({
120+
provider: 'google',
121+
config: { model: 'gemini-env-pin' },
122+
});
123+
});
124+
});
125+
126+
describe('AgentLLMsRegistry — execution tracking wrapper', () => {
127+
function stubRegistry(llm: any) {
128+
return { getLLM: () => llm };
129+
}
130+
131+
it('stores the llm-namespace telemetry keys on a llm:<key>:<model> child on success', async () => {
132+
delete process.env.BITCODE_LLM_CALL_TIMEOUT_MS;
133+
const execution = new Execution('agent:tracking');
134+
const registry = new AgentLLMsRegistry(
135+
execution,
136+
stubRegistry(async () => ({
137+
content: 'raw model prose',
138+
usage: { inputTokens: 1, outputTokens: 2, totalTokens: 3 },
139+
metadata: { finishReason: 'stop' },
140+
})),
141+
);
142+
143+
const input = {
144+
messages: [{ role: 'user', content: 'prompt body' }],
145+
config: { model: 'stub-model' },
146+
};
147+
const output = await registry.getDefaultLLM()(input);
148+
149+
// Provider-agnostic stopReason normalization from finishReason.
150+
expect(output.metadata.stopReason).toBe('stop');
151+
152+
const llmExec = findLlmChild(execution, 'llm:default:stub-model');
153+
expect(llmExec).toBeDefined();
154+
// The exact key set matters: content-bearing keys (messages/config/
155+
// response) are NOT in the streaming source-safe allowlist and get
156+
// withheld by sourceSafeStreamEvent; the rest are safe metadata.
157+
expect(Array.from(llmExec.getAll('llm').keys()).sort()).toEqual(
158+
['config', 'configKey', 'duration', 'messages', 'response', 'startTime', 'status', 'usage'].sort(),
159+
);
160+
expect(llmExec.get('llm', 'configKey')).toBe('default');
161+
expect(llmExec.get('llm', 'response')).toBe('raw model prose');
162+
expect(llmExec.get('llm', 'usage')).toEqual({ inputTokens: 1, outputTokens: 2, totalTokens: 3 });
163+
expect(llmExec.get('llm', 'status')).toBe('success');
164+
expect(typeof llmExec.get('llm', 'duration')).toBe('number');
165+
});
166+
167+
it('defaults metadata.stopReason to "unknown" when the provider supplies none', async () => {
168+
delete process.env.BITCODE_LLM_CALL_TIMEOUT_MS;
169+
const execution = new Execution('agent:stop-reason');
170+
const registry = new AgentLLMsRegistry(
171+
execution,
172+
stubRegistry(async () => ({ content: 'ok' })),
173+
);
174+
175+
const output = await registry.getDefaultLLM()({ messages: [], config: { model: 'stub-model' } });
176+
177+
expect(output.metadata.stopReason).toBe('unknown');
178+
});
179+
180+
it('BITCODE_LLM_CALL_TIMEOUT_MS bounds a hung call: rejects and stores failed status', async () => {
181+
process.env.BITCODE_LLM_CALL_TIMEOUT_MS = '25';
182+
const execution = new Execution('agent:timeout');
183+
const registry = new AgentLLMsRegistry(
184+
execution,
185+
stubRegistry(() => new Promise(() => {})), // never resolves
186+
);
187+
188+
await expect(
189+
registry.getDefaultLLM()({ messages: [], config: { model: 'stub-model' } }),
190+
).rejects.toThrow('LLM call (stub-model) timed out after 25ms');
191+
192+
const llmExec = findLlmChild(execution, 'llm:default:stub-model');
193+
expect(llmExec.get('llm', 'status')).toBe('failed');
194+
expect(llmExec.get('llm', 'error')).toContain('timed out after 25ms');
195+
expect(typeof llmExec.get('llm', 'duration')).toBe('number');
196+
expect(llmExec.get('llm', 'response')).toBeUndefined();
197+
});
198+
199+
it('BITCODE_LLM_CALL_TIMEOUT_MS=0 disables the bound entirely', async () => {
200+
process.env.BITCODE_LLM_CALL_TIMEOUT_MS = '0';
201+
const execution = new Execution('agent:timeout-disabled');
202+
const registry = new AgentLLMsRegistry(
203+
execution,
204+
stubRegistry(
205+
() =>
206+
new Promise((resolve) =>
207+
setTimeout(() => resolve({ content: 'slow but fine' }), 60),
208+
),
209+
),
210+
);
211+
212+
const output = await registry.getDefaultLLM()({ messages: [], config: { model: 'stub-model' } });
213+
214+
expect(output.content).toBe('slow but fine');
215+
const llmExec = findLlmChild(execution, 'llm:default:stub-model');
216+
expect(llmExec.get('llm', 'status')).toBe('success');
217+
});
218+
219+
it('a provider failure is recorded and rethrown (clean failure for PTRR retry)', async () => {
220+
delete process.env.BITCODE_LLM_CALL_TIMEOUT_MS;
221+
const execution = new Execution('agent:provider-failure');
222+
const registry = new AgentLLMsRegistry(
223+
execution,
224+
stubRegistry(async () => {
225+
throw new Error('provider unavailable');
226+
}),
227+
);
228+
229+
await expect(
230+
registry.getDefaultLLM()({ messages: [], config: { model: 'stub-model' } }),
231+
).rejects.toThrow('provider unavailable');
232+
233+
const llmExec = findLlmChild(execution, 'llm:default:stub-model');
234+
expect(llmExec.get('llm', 'status')).toBe('failed');
235+
expect(llmExec.get('llm', 'error')).toBe('provider unavailable');
236+
});
237+
});

0 commit comments

Comments
 (0)