fix(coding-agent): handle invalid models json during migration
closes #5418
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### 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)).
|
- 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
|
## [0.79.0] - 2026-06-08
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[]
|
|||||||
const modelsPath = join(agentDir, "models.json");
|
const modelsPath = join(agentDir, "models.json");
|
||||||
if (!existsSync(modelsPath)) return [];
|
if (!existsSync(modelsPath)) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
const parsed = JSON.parse(stripJsonComments(readFileSync(modelsPath, "utf-8"))) as unknown;
|
const parsed = JSON.parse(stripJsonComments(readFileSync(modelsPath, "utf-8"))) as unknown;
|
||||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
|
||||||
const modelsData = parsed as Record<string, unknown>;
|
const modelsData = parsed as Record<string, unknown>;
|
||||||
@@ -171,7 +172,8 @@ function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[]
|
|||||||
const modelOverrides = providerRecord.modelOverrides;
|
const modelOverrides = providerRecord.modelOverrides;
|
||||||
if (typeof modelOverrides === "object" && modelOverrides !== null && !Array.isArray(modelOverrides)) {
|
if (typeof modelOverrides === "object" && modelOverrides !== null && !Array.isArray(modelOverrides)) {
|
||||||
for (const [modelId, modelOverride] of Object.entries(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>;
|
const modelOverrideRecord = modelOverride as Record<string, unknown>;
|
||||||
migrateHeadersConfig(
|
migrateHeadersConfig(
|
||||||
modelOverrideRecord.headers,
|
modelOverrideRecord.headers,
|
||||||
@@ -185,6 +187,9 @@ function migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[]
|
|||||||
if (migrations.length === 0) return [];
|
if (migrations.length === 0) return [];
|
||||||
writeFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
|
writeFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\n`, "utf-8");
|
||||||
return migrations;
|
return migrations;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrateExplicitEnvVarConfigValues(): void {
|
function migrateExplicitEnvVarConfigValues(): void {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import * as os from "node:os";
|
|||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { ENV_AGENT_DIR } from "../src/config.ts";
|
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";
|
import { runMigrations } from "../src/migrations.ts";
|
||||||
|
|
||||||
describe("config value env var syntax migration", () => {
|
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');
|
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", () => {
|
it("rewrites legacy uppercase models.json API key and header values", () => {
|
||||||
const agentDir = createAgentDir();
|
const agentDir = createAgentDir();
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
|
|||||||
Reference in New Issue
Block a user