feat(hub): 抽出运行时配置

This commit is contained in:
2026-07-08 15:25:49 +08:00
parent a766d99573
commit 3f486232a8
9 changed files with 297 additions and 66 deletions
+30
View File
@@ -91,6 +91,36 @@ describe("runAgent", () => {
expect(call?.options).not.toHaveProperty("resume");
});
it("passes provider env per run without mutating process env", async () => {
delete process.env["CPH_TEST_PROVIDER_ENV_MUTATION"];
queryMock.mockReturnValue(messages(assistantMessage("ok"), resultMessage("sdk-session-1")));
await runAgent({
prompt: "你好",
model: undefined,
project: { projectId: "p", boundChatId: "c", workspaceDir: "/tmp/ws" },
systemPrompt: undefined,
providerEnv: {
ANTHROPIC_BASE_URL: "https://openrouter.ai/api",
ANTHROPIC_AUTH_TOKEN: "test-token",
ANTHROPIC_API_KEY: "",
CPH_TEST_PROVIDER_ENV_MUTATION: "per-run",
},
runId: "run-1",
sessionId: "hub-session-1",
prisma: stubPrisma,
});
const call = queryMock.mock.calls[0]?.[0] as { options?: { env?: Record<string, string | undefined> } } | undefined;
expect(call?.options?.env).toMatchObject({
ANTHROPIC_BASE_URL: "https://openrouter.ai/api",
ANTHROPIC_AUTH_TOKEN: "test-token",
ANTHROPIC_API_KEY: "",
CPH_TEST_PROVIDER_ENV_MUTATION: "per-run",
});
expect(process.env["CPH_TEST_PROVIDER_ENV_MUTATION"]).toBeUndefined();
});
it("persists structured user and assistant messages best-effort", async () => {
queryMock.mockReturnValue(messages(assistantMessage("ok"), resultMessage("sdk-session-1")));
const createMessage = vi.fn().mockResolvedValue({});
+65
View File
@@ -0,0 +1,65 @@
import { describe, expect, it } from "vitest";
import { createEnvRuntimeSettings } from "../../src/settings/runtime.js";
describe("runtime settings", () => {
it("reads the model registry from the current env at call time", async () => {
const env: Record<string, string | undefined> = {
ANTHROPIC_DEFAULT_SONNET_MODEL: "anthropic/claude-sonnet-5",
};
const settings = createEnvRuntimeSettings(env);
expect((await settings.modelRegistry()).resolve(undefined, "draft")).toBe("anthropic/claude-sonnet-5");
env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = "z-ai/glm-4.7";
expect((await settings.modelRegistry()).resolve(undefined, "draft")).toBe("z-ai/glm-4.7");
});
it("returns provider SDK env without requiring global process env mutation", async () => {
const settings = createEnvRuntimeSettings({
ANTHROPIC_BASE_URL: "https://example.test/api",
ANTHROPIC_AUTH_TOKEN: "openrouter-token",
ANTHROPIC_API_KEY: "explicit-api-key",
});
await expect(settings.provider("openrouter")).resolves.toMatchObject({
id: "openrouter",
baseUrl: "https://example.test/api",
authToken: "openrouter-token",
anthropicApiKey: "explicit-api-key",
sdkEnv: {
ANTHROPIC_BASE_URL: "https://example.test/api",
ANTHROPIC_AUTH_TOKEN: "openrouter-token",
ANTHROPIC_API_KEY: "explicit-api-key",
},
});
});
it("defaults Anthropic API key to empty for OpenRouter's Anthropic skin", async () => {
const settings = createEnvRuntimeSettings({
ANTHROPIC_AUTH_TOKEN: "openrouter-token",
});
await expect(settings.provider("openrouter")).resolves.toMatchObject({
baseUrl: "https://openrouter.ai/api",
anthropicApiKey: "",
sdkEnv: {
ANTHROPIC_API_KEY: "",
},
});
});
it("requires an auth token for the OpenRouter provider", async () => {
const settings = createEnvRuntimeSettings({});
await expect(settings.provider("openrouter")).rejects.toThrow("missing required runtime setting: ANTHROPIC_AUTH_TOKEN");
});
it("honors HUB_AGENT_MAX_TURNS as a runtime run policy", async () => {
const settings = createEnvRuntimeSettings({
HUB_AGENT_MAX_TURNS: "9",
});
await expect(settings.runPolicy({ projectId: "p", roleId: "draft" })).resolves.toEqual({ maxTurns: 9 });
});
});