fix(coding-agent): handle invalid models json during migration
closes #5418
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed invalid `models.json` syntax to skip startup config migrations and report the normal file-path-aware models error instead of a raw JSON parse stack trace ([#5418](https://github.com/earendil-works/pi/issues/5418)).
|
||||
- Fixed GitHub release notes and interactive changelog links to resolve package-relative documentation URLs correctly ([#5516](https://github.com/earendil-works/pi/issues/5516)).
|
||||
|
||||
## [0.79.0] - 2026-06-08
|
||||
|
||||
@@ -144,6 +144,7 @@ function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[]
|
||||
const modelsPath = join(agentDir, "models.json");
|
||||
if (!existsSync(modelsPath)) return [];
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(stripJsonComments(readFileSync(modelsPath, "utf-8"))) as unknown;
|
||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
|
||||
const modelsData = parsed as Record<string, unknown>;
|
||||
@@ -171,7 +172,8 @@ function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[]
|
||||
const modelOverrides = providerRecord.modelOverrides;
|
||||
if (typeof modelOverrides === "object" && modelOverrides !== null && !Array.isArray(modelOverrides)) {
|
||||
for (const [modelId, modelOverride] of Object.entries(modelOverrides)) {
|
||||
if (typeof modelOverride !== "object" || modelOverride === null || Array.isArray(modelOverride)) continue;
|
||||
if (typeof modelOverride !== "object" || modelOverride === null || Array.isArray(modelOverride))
|
||||
continue;
|
||||
const modelOverrideRecord = modelOverride as Record<string, unknown>;
|
||||
migrateHeadersConfig(
|
||||
modelOverrideRecord.headers,
|
||||
@@ -185,6 +187,9 @@ function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[]
|
||||
if (migrations.length === 0) return [];
|
||||
writeFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
|
||||
return migrations;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function migrateExplicitEnvVarConfigValues(): void {
|
||||
|
||||
@@ -3,6 +3,8 @@ import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { ENV_AGENT_DIR } from "../src/config.ts";
|
||||
import { AuthStorage } from "../src/core/auth-storage.ts";
|
||||
import { ModelRegistry } from "../src/core/model-registry.ts";
|
||||
import { runMigrations } from "../src/migrations.ts";
|
||||
|
||||
describe("config value env var syntax migration", () => {
|
||||
@@ -68,6 +70,23 @@ describe("config value env var syntax migration", () => {
|
||||
expect(logMessage).toContain('auth.json["anthropic"].key: ANTHROPIC_API_KEY -> $ANTHROPIC_API_KEY');
|
||||
});
|
||||
|
||||
it.each([
|
||||
["malformed", '{\n "providers": {\n'],
|
||||
["blank", ""],
|
||||
])("does not throw on %s models.json during config migration", (_name, content) => {
|
||||
const agentDir = createAgentDir();
|
||||
const modelsPath = path.join(agentDir, "models.json");
|
||||
fs.writeFileSync(modelsPath, content, "utf-8");
|
||||
|
||||
withAgentDir(agentDir, () => expect(() => runMigrations(agentDir)).not.toThrow());
|
||||
|
||||
expect(fs.readFileSync(modelsPath, "utf-8")).toBe(content);
|
||||
const registry = ModelRegistry.create(AuthStorage.create(path.join(agentDir, "auth.json")), modelsPath);
|
||||
const loadError = registry.getError();
|
||||
expect(loadError).toContain("Failed to parse models.json");
|
||||
expect(loadError).toContain(`File: ${modelsPath}`);
|
||||
});
|
||||
|
||||
it("rewrites legacy uppercase models.json API key and header values", () => {
|
||||
const agentDir = createAgentDir();
|
||||
fs.writeFileSync(
|
||||
|
||||
Reference in New Issue
Block a user