refactor(agent): runAgent uses Agent upstreams only; deprecate legacy settings.pi fields

pi.provider / baseUrl / model / thinkingLevel / apiKeySecretRef are now
DEPRECATED_SETTINGS_FIELDS: kept in the schema for stored JSON, dropped
from PATCH /settings and from the settings sections, never read by the
server. runAgent fails with a clear message when no upstream is set.
The agent demo gate checks the default upstream provider; e2e seeds a
fake upstream as default. OpenAPI summaries say Agent upstream.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 13:45:40 +08:00
1 parent fb7727cf66
commit 3e37ac8ae7
15 files changed
+122 -115

No files matched your search

@@ -94,24 +94,22 @@ describe.skipIf(!hasPg)("W2-A 管理 API(真实 PG)", () => {
expect((await json(r)).error.message).toMatch(/恢复水位/);
});
it("Pi apiKey 只写:存为 secret,响应/审计/日志不含明文;null 清除并删除 secret", async () => {
it("旧 pi.apiKey / provider / model 不再接受:被丢弃,不建 secret,不改设置", async () => {
const plain = "sk-live-super-secret-0001";
const r = await json(await admin("PATCH", "/settings", { pi: { apiKey: plain, model: "claude-x" } }));
const ref = r.data.pi.apiKeySecretRef;
expect(ref).toMatch(/^[0-9a-f-]{36}$/);
const before = (await json(await admin("GET", "/settings"))).data.pi;
const r = await json(
await admin("PATCH", "/settings", { pi: { apiKey: plain, provider: "openai", model: "claude-x" } }),
);
expect(r.data.pi).toMatchObject({
provider: before.provider,
model: before.model,
apiKeySecretRef: before.apiKeySecretRef,
});
expect(JSON.stringify(r)).not.toContain(plain);
expect(await h.ctx.secrets.resolve(ref)).toBe(plain);
// 再改一次:原地轮换,ref 不变
const r2 = await json(await admin("PATCH", "/settings", { pi: { apiKey: `${plain}-2` } }));
expect(r2.data.pi.apiKeySecretRef).toBe(ref);
expect(await h.ctx.secrets.resolve(ref)).toBe(`${plain}-2`);
const audits = await sql<{
payload: unknown;
}>`select payload from audit_event where action = 'settings.update'`.execute(h.db);
expect(JSON.stringify(audits.rows)).not.toContain(plain);
const r3 = await json(await admin("PATCH", "/settings", { pi: { apiKey: null } }));
expect(r3.data.pi.apiKeySecretRef).toBeNull();
await expect(h.ctx.secrets.resolve(ref)).rejects.toThrow(/不存在/);
const n = await sql<{ n: number }>`select count(*)::int as n from secret where purpose = 'llm_api_key'`.execute(
h.db,
);
expect(n.rows[0]!.n).toBe(0);
noLeak(plain);
});
});