Merge PR #7025: fix(coding-agent): coalesce models config resource probe (@paralin)
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
|
||||
- Reduced bash, grep, and glob tool guidance while preserving supported internal-URL routes and making shell/eval boundaries and broad-search timeout avoidance explicit.
|
||||
- Fixed the spawn-based models config validator laziness test flaking under CPU contention by giving its two sequential probe processes an explicit 60s per-test timeout instead of sharing bun's 5s default ([#7018](https://github.com/can1357/oh-my-pi/issues/7018)).
|
||||
- Coalesced the models configuration resource probe into one child process to avoid startup contention while preserving retained-resource coverage.
|
||||
|
||||
## [17.1.8] - 2026-07-28
|
||||
|
||||
|
||||
+76
-34
@@ -9,14 +9,52 @@ interface HeapSnapshot {
|
||||
snapshot: { meta: { node_fields: string[] } };
|
||||
}
|
||||
|
||||
const root = process.argv[2];
|
||||
const mode = process.argv[3];
|
||||
if (!root || (mode !== "missing" && mode !== "custom")) {
|
||||
throw new Error("Expected an isolated config root and missing|custom mode");
|
||||
interface ProbeResult {
|
||||
retainedHeapNodes: number;
|
||||
schemaIdentityStable?: boolean;
|
||||
model?: {
|
||||
provider: string;
|
||||
id: string;
|
||||
baseUrl: string;
|
||||
api: string;
|
||||
thinking?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
const configPath = path.join(root, mode, "models.yml");
|
||||
if (mode === "custom") {
|
||||
const root = process.argv[2];
|
||||
if (!root) {
|
||||
throw new Error("Expected an isolated config root");
|
||||
}
|
||||
|
||||
function retainedHeapNodes(): number {
|
||||
Bun.gc(true);
|
||||
const snapshot = JSON.parse(Bun.generateHeapSnapshot("v8")) as HeapSnapshot;
|
||||
return snapshot.nodes.length / snapshot.snapshot.meta.node_fields.length;
|
||||
}
|
||||
|
||||
async function measureMissing(): Promise<ProbeResult> {
|
||||
let model: ProbeResult["model"];
|
||||
{
|
||||
const authStorage = await AuthStorage.create(":memory:");
|
||||
try {
|
||||
const registry = new ModelRegistry(authStorage, path.join(root, "missing", "models.yml"));
|
||||
const found = registry.find("anthropic", "claude-sonnet-4-5");
|
||||
model = found && {
|
||||
provider: found.provider,
|
||||
id: found.id,
|
||||
baseUrl: found.baseUrl,
|
||||
api: found.api,
|
||||
thinking: found.thinking,
|
||||
};
|
||||
} finally {
|
||||
authStorage.close();
|
||||
}
|
||||
}
|
||||
return { retainedHeapNodes: retainedHeapNodes(), model };
|
||||
}
|
||||
|
||||
async function writeCustomConfig(): Promise<string> {
|
||||
const configPath = path.join(root, "custom", "models.yml");
|
||||
await Bun.write(
|
||||
configPath,
|
||||
YAML.stringify(
|
||||
@@ -45,34 +83,38 @@ if (mode === "custom") {
|
||||
2,
|
||||
),
|
||||
);
|
||||
return configPath;
|
||||
}
|
||||
|
||||
const authStorage = await AuthStorage.create(":memory:");
|
||||
try {
|
||||
const registry = new ModelRegistry(authStorage, configPath);
|
||||
const model =
|
||||
mode === "custom" ? registry.find("lazy-models", "lazy-model") : registry.find("anthropic", "claude-sonnet-4-5");
|
||||
const firstSchema = mode === "custom" ? ModelsConfigFile.relocate(configPath).schema : undefined;
|
||||
const secondSchema =
|
||||
mode === "custom" ? ModelsConfigFile.relocate(path.join(root, "second", "models.yml")).schema : undefined;
|
||||
|
||||
Bun.gc(true);
|
||||
const snapshot = JSON.parse(Bun.generateHeapSnapshot("v8")) as HeapSnapshot;
|
||||
const nodeWidth = snapshot.snapshot.meta.node_fields.length;
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
retainedHeapNodes: snapshot.nodes.length / nodeWidth,
|
||||
schemaIdentityStable: mode === "custom" ? firstSchema === secondSchema : undefined,
|
||||
model: model && {
|
||||
provider: model.provider,
|
||||
id: model.id,
|
||||
baseUrl: model.baseUrl,
|
||||
api: model.api,
|
||||
thinking: model.thinking,
|
||||
},
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
authStorage.close();
|
||||
async function measureCustom(configPath: string): Promise<ProbeResult> {
|
||||
let model: ProbeResult["model"];
|
||||
let schemaIdentityStable = false;
|
||||
{
|
||||
const authStorage = await AuthStorage.create(":memory:");
|
||||
try {
|
||||
const registry = new ModelRegistry(authStorage, configPath);
|
||||
const found = registry.find("lazy-models", "lazy-model");
|
||||
model = found && {
|
||||
provider: found.provider,
|
||||
id: found.id,
|
||||
baseUrl: found.baseUrl,
|
||||
api: found.api,
|
||||
thinking: found.thinking,
|
||||
};
|
||||
const firstSchema = ModelsConfigFile.relocate(configPath).schema;
|
||||
const secondSchema = ModelsConfigFile.relocate(path.join(root, "second", "models.yml")).schema;
|
||||
schemaIdentityStable = firstSchema === secondSchema;
|
||||
} finally {
|
||||
authStorage.close();
|
||||
}
|
||||
}
|
||||
return {
|
||||
retainedHeapNodes: retainedHeapNodes(),
|
||||
schemaIdentityStable,
|
||||
model,
|
||||
};
|
||||
}
|
||||
|
||||
const missing = await measureMissing();
|
||||
const custom = await measureCustom(await writeCustomConfig());
|
||||
process.stdout.write(JSON.stringify({ missing, custom }));
|
||||
|
||||
@@ -14,10 +14,15 @@ interface ProbeResult {
|
||||
};
|
||||
}
|
||||
|
||||
interface ProbeResults {
|
||||
missing: ProbeResult;
|
||||
custom: ProbeResult;
|
||||
}
|
||||
|
||||
const probePath = path.join(import.meta.dir, "fixtures", "models-config-validator-construction-probe.ts");
|
||||
|
||||
async function runProbe(root: string, mode: "missing" | "custom"): Promise<ProbeResult> {
|
||||
const proc = Bun.spawn([process.execPath, probePath, root, mode], {
|
||||
async function runProbe(root: string): Promise<ProbeResults> {
|
||||
const proc = Bun.spawn([process.execPath, probePath, root], {
|
||||
cwd: path.join(import.meta.dir, "../../.."),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
@@ -28,14 +33,13 @@ async function runProbe(root: string, mode: "missing" | "custom"): Promise<Probe
|
||||
proc.exited,
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
return JSON.parse(stdout) as ProbeResult;
|
||||
return JSON.parse(stdout) as ProbeResults;
|
||||
}
|
||||
|
||||
test("models config validation resources are retained only for a custom config", async () => {
|
||||
const tempDir = TempDir.createSync("@models-config-validator-");
|
||||
try {
|
||||
const missing = await runProbe(tempDir.path(), "missing");
|
||||
const custom = await runProbe(tempDir.path(), "custom");
|
||||
const { missing, custom } = await runProbe(tempDir.path());
|
||||
|
||||
expect(missing.model).toMatchObject({
|
||||
provider: "anthropic",
|
||||
@@ -55,6 +59,7 @@ test("models config validation resources are retained only for a custom config",
|
||||
},
|
||||
});
|
||||
expect(custom.schemaIdentityStable).toBe(true);
|
||||
expect(missing.retainedHeapNodes).toBeLessThan(custom.retainedHeapNodes);
|
||||
expect(
|
||||
custom.retainedHeapNodes - missing.retainedHeapNodes,
|
||||
"custom config validation should retain its schema bundle",
|
||||
|
||||
Reference in New Issue
Block a user