61f4a2505a
Per-model override headers were stored verbatim, so !command values in modelOverrides.<model>.headers reached the request as literal strings. Resolve them at parse time like provider headers. Addresses review feedback on #2205.
88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
|
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
|
|
import { Snowflake } from "@oh-my-pi/pi-utils";
|
|
|
|
function stdoutCommand(value: string): string {
|
|
return `${JSON.stringify(process.execPath)} -e ${JSON.stringify(`process.stdout.write(${JSON.stringify(value)})`)}`;
|
|
}
|
|
|
|
describe("ModelRegistry command-resolved models.yml values", () => {
|
|
let tempDir = "";
|
|
let authStorage: AuthStorage;
|
|
let modelsPath = "";
|
|
|
|
beforeEach(async () => {
|
|
tempDir = path.join(os.tmpdir(), `pi-test-model-command-values-${Snowflake.next()}`);
|
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
modelsPath = path.join(tempDir, "models.json");
|
|
authStorage = await AuthStorage.create(":memory:");
|
|
});
|
|
|
|
afterEach(() => {
|
|
authStorage.close();
|
|
if (!tempDir || !fs.existsSync(tempDir)) return;
|
|
try {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code !== "EBUSY") throw error;
|
|
}
|
|
});
|
|
|
|
test("provider apiKey and headers resolve from command stdout", async () => {
|
|
fs.writeFileSync(
|
|
modelsPath,
|
|
JSON.stringify({
|
|
providers: {
|
|
anthropic: {
|
|
baseUrl: "https://anthropic-proxy.example.com/v1",
|
|
apiKey: `!${stdoutCommand("cmd-api-key")}`,
|
|
authHeader: true,
|
|
headers: { "X-Api-Key": `!${stdoutCommand("cmd-header")}` },
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
const registry = new ModelRegistry(authStorage, modelsPath);
|
|
const models = registry.getAll().filter(model => model.provider === "anthropic");
|
|
|
|
expect(models.length).toBeGreaterThan(1);
|
|
for (const model of models) {
|
|
expect(model.headers?.Authorization).toBe("Bearer cmd-api-key");
|
|
expect(model.headers?.["X-Api-Key"]).toBe("cmd-header");
|
|
}
|
|
expect(await registry.getApiKey(models[0])).toBe("cmd-api-key");
|
|
});
|
|
|
|
test("modelOverrides headers resolve from command stdout", async () => {
|
|
fs.writeFileSync(
|
|
modelsPath,
|
|
JSON.stringify({
|
|
providers: {
|
|
"custom-proxy": {
|
|
baseUrl: "https://custom-proxy.example.com/v1",
|
|
api: "openai-completions",
|
|
apiKey: `!${stdoutCommand("cmd-api-key")}`,
|
|
authHeader: true,
|
|
models: [{ id: "custom-model", name: "Custom Model" }],
|
|
modelOverrides: {
|
|
"custom-model": { headers: { "X-Model-Key": `!${stdoutCommand("cmd-model-header")}` } },
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
|
|
const registry = new ModelRegistry(authStorage, modelsPath);
|
|
const model = registry.find("custom-proxy", "custom-model");
|
|
|
|
expect(model).toBeDefined();
|
|
expect(model?.headers?.["X-Model-Key"]).toBe("cmd-model-header");
|
|
expect(model?.headers?.Authorization).toBe("Bearer cmd-api-key");
|
|
});
|
|
});
|