fix: keep runtime model auth alias after build

This commit is contained in:
Peter Steinberger
2026-05-02 23:54:57 +01:00
parent 61349d6bdc
commit f352caf07e
5 changed files with 61 additions and 10 deletions

View File

@@ -2,15 +2,16 @@ import { describe, expect, it } from "vitest";
import { buildQaImageGenerationConfigPatch } from "./image-generation.js";
describe("QA provider image generation config", () => {
it("uses the selected mock provider for mock-openai image generation", () => {
it("routes mock-openai image generation through the OpenAI image provider", () => {
const patch = buildQaImageGenerationConfigPatch({
providerMode: "mock-openai",
providerBaseUrl: "http://127.0.0.1:44080/v1",
requiredPluginIds: ["qa-channel"],
});
expect(patch.plugins.allow).toEqual(["acpx", "memory-core", "qa-channel"]);
expect(patch.agents.defaults.imageGenerationModel.primary).toBe("mock-openai/gpt-image-1");
expect(patch.plugins.allow).toEqual(["acpx", "memory-core", "openai", "qa-channel"]);
expect(patch.plugins.entries).toEqual({ openai: { enabled: true } });
expect(patch.agents.defaults.imageGenerationModel.primary).toBe("openai/gpt-image-1");
expect(patch.models?.providers["mock-openai"]?.baseUrl).toBe("http://127.0.0.1:44080/v1");
});
@@ -30,14 +31,16 @@ describe("QA provider image generation config", () => {
"qa-channel",
]);
});
it("uses the selected mock provider for AIMock image generation", () => {
it("routes AIMock image generation through the OpenAI image provider", () => {
const patch = buildQaImageGenerationConfigPatch({
providerMode: "aimock",
providerBaseUrl: "http://127.0.0.1:45080/v1",
requiredPluginIds: [],
});
expect(patch.agents.defaults.imageGenerationModel.primary).toBe("aimock/gpt-image-1");
expect(patch.plugins.allow).toEqual(["acpx", "memory-core", "openai"]);
expect(patch.plugins.entries).toEqual({ openai: { enabled: true } });
expect(patch.agents.defaults.imageGenerationModel.primary).toBe("openai/gpt-image-1");
expect(patch.models?.providers.aimock?.baseUrl).toBe("http://127.0.0.1:45080/v1");
expect(patch.models?.providers["mock-openai"]).toBeUndefined();
});

View File

@@ -42,7 +42,7 @@ export function buildQaImageGenerationConfigPatch(input: QaImageGenerationPatchI
providerBaseUrl: input.providerBaseUrl,
});
})();
const providerPluginIds = provider.usesModelProviderPlugins ? [imageProviderId] : [];
const providerPluginIds = imageProviderId ? [imageProviderId] : [];
const enabledPluginIds = uniqueNonEmpty(providerPluginIds);
return {

View File

@@ -25,8 +25,9 @@ export function createMockQaProviderDefinition(
serverLabel: params.serverLabel,
},
defaultModel: (options) => mockModelRef(params.mode, options?.alternate),
defaultImageGenerationProviderIds: [],
defaultImageGenerationModel: () => `${params.mode}/gpt-image-1`,
defaultImageGenerationProviderIds: ["openai"],
defaultImageGenerationModel: ({ modelProviderIds }) =>
modelProviderIds.includes("openai") ? "openai/gpt-image-1" : null,
usesFastModeByDefault: () => false,
resolveModelParams: () => ({
transport: "sse",

View File

@@ -122,13 +122,38 @@ export function writeStableRootRuntimeAliases(params = {}) {
candidatesByAlias.set(aliasFileName, candidates);
}
const resolveAliasCandidate = (candidates) => {
if (candidates.length === 1) {
return candidates[0];
}
const candidateSet = new Set(candidates);
const wrappers = candidates.filter((candidate) => {
const filePath = path.join(distDir, candidate);
let source;
try {
source = fsImpl.readFileSync(filePath, "utf8");
} catch {
return false;
}
return candidates.some(
(target) =>
target !== candidate &&
candidateSet.has(target) &&
source.includes(`"./${target}"`) &&
!source.includes("\n//#region "),
);
});
return wrappers.length === 1 ? wrappers[0] : null;
};
for (const [aliasFileName, candidates] of candidatesByAlias) {
const aliasPath = path.join(distDir, aliasFileName);
if (candidates.length !== 1) {
const candidate = resolveAliasCandidate(candidates);
if (!candidate) {
fsImpl.rmSync?.(aliasPath, { force: true });
continue;
}
writeTextFileIfChanged(aliasPath, `export * from "./${candidates[0]}";\n`);
writeTextFileIfChanged(aliasPath, `export * from "./${candidate}";\n`);
}
}

View File

@@ -113,6 +113,28 @@ describe("runtime postbuild static assets", () => {
await expect(fs.stat(path.join(distDir, "install.runtime.js"))).rejects.toThrow();
});
it("keeps stable aliases when one colliding root runtime chunk re-exports the implementation", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");
await fs.mkdir(distDir, { recursive: true });
await fs.writeFile(
path.join(distDir, "runtime-model-auth.runtime-Impl123.js"),
"export const auth = true;\n",
"utf8",
);
await fs.writeFile(
path.join(distDir, "runtime-model-auth.runtime-Wrap456.js"),
'import { auth } from "./runtime-model-auth.runtime-Impl123.js";\nexport { auth };\n',
"utf8",
);
writeStableRootRuntimeAliases({ rootDir });
expect(await fs.readFile(path.join(distDir, "runtime-model-auth.runtime.js"), "utf8")).toBe(
'export * from "./runtime-model-auth.runtime-Wrap456.js";\n',
);
});
it("rewrites root runtime imports to stable aliases", async () => {
const rootDir = createTempDir("openclaw-runtime-postbuild-");
const distDir = path.join(rootDir, "dist");