fix: allow config.patch with defaulted provider baseUrl

This commit is contained in:
momothemage
2026-07-01 18:08:40 +08:00
parent 5113fbf4cc
commit 68fbf67640
2 changed files with 99 additions and 3 deletions

View File

@@ -498,7 +498,7 @@ function parseValidateConfigFromRawOrRespond(
: restored.result;
const validationCandidate = stripBundledProviderRuntimeDefaults({
candidate: projectedValidationCandidate,
sourceConfig: snapshot.parsed,
sourceConfig: snapshot.sourceConfig,
});
const sourceValidated = validateConfigObjectRawWithPlugins(validationCandidate);
if (!sourceValidated.ok) {
@@ -859,7 +859,27 @@ export const configHandlers: GatewayRequestHandlers = {
});
return;
}
const validated = validateConfigObjectWithPlugins(restoredMerge.result);
const validationCandidate = stripBundledProviderRuntimeDefaults({
candidate: restoredMerge.result,
sourceConfig: snapshot.sourceConfig,
});
const sourceValidated = validateConfigObjectRawWithPlugins(validationCandidate);
if (!sourceValidated.ok) {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
summarizeConfigValidationIssues(sourceValidated.issues),
{
details: { issues: sourceValidated.issues },
},
),
);
return;
}
const writeConfig = validationCandidate as OpenClawConfig;
const validated = validateConfigObjectWithPlugins(validationCandidate);
if (!validated.ok) {
respond(
false,
@@ -908,7 +928,7 @@ export const configHandlers: GatewayRequestHandlers = {
const writeResult = await commitGatewayConfigWrite({
snapshot,
writeOptions,
nextConfig: validated.config,
nextConfig: writeConfig,
context,
disconnectSharedAuthClients,
});

View File

@@ -280,6 +280,82 @@ describe("gateway config methods", () => {
}
});
it("accepts config.patch when bundled provider baseUrl was only defaulted", async () => {
const { createConfigIO, resetConfigRuntimeState } = await import("../config/config.js");
const configPath = createConfigIO().configPath;
try {
await writeJsonFile(configPath, {
models: {
providers: {
openai: {
agentRuntime: { id: "openclaw" },
},
},
},
});
resetConfigRuntimeState();
const current = await getCurrentConfigObject();
const res = await rpcReq<{
ok?: boolean;
error?: { message?: string };
}>(requireWs(), "config.patch", {
raw: JSON.stringify({ gateway: { port: 19003 } }),
baseHash: current.hash,
});
expect(res.error).toBeUndefined();
expect(res.ok).toBe(true);
const persisted = await fs.readFile(configPath, "utf-8");
expect(persisted).toContain('"port": 19003');
expect(persisted).not.toContain('"baseUrl"');
expect(persisted).not.toContain('"models": []');
} finally {
await fs.rm(configPath, { force: true });
resetConfigRuntimeState();
}
});
it("preserves authored empty bundled provider models during config.patch", async () => {
const { createConfigIO, resetConfigRuntimeState } = await import("../config/config.js");
const configPath = createConfigIO().configPath;
try {
await writeJsonFile(configPath, {
models: {
providers: {
openai: {
agentRuntime: { id: "openclaw" },
models: [],
},
},
},
});
resetConfigRuntimeState();
const current = await getCurrentConfigObject();
const res = await rpcReq<{
ok?: boolean;
error?: { message?: string };
}>(requireWs(), "config.patch", {
raw: JSON.stringify({ gateway: { port: 19004 } }),
baseHash: current.hash,
});
expect(res.error).toBeUndefined();
expect(res.ok).toBe(true);
const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as {
models?: { providers?: { openai?: { baseUrl?: unknown; models?: unknown } } };
};
expect(persisted.models?.providers?.openai?.baseUrl).toBeUndefined();
expect(persisted.models?.providers?.openai?.models).toEqual([]);
} finally {
await fs.rm(configPath, { force: true });
resetConfigRuntimeState();
}
});
it("redacts browser cdpUrl credentials from config.get responses", async () => {
const { createConfigIO, resetConfigRuntimeState } = await import("../config/config.js");
const configPath = createConfigIO().configPath;