From 7d5fd0f139b05119bbeb812b8526cbacf2b41729 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 21 Oct 2026 16:03:28 -0500 Subject: [PATCH] Verify native Pi OpenRouter accounts without the Legacy CLI Co-Authored-By: Paperclip --- server/src/__tests__/ai-connections.test.ts | 21 +++++++++++++++++++ server/src/services/agent-environment-test.ts | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/ai-connections.test.ts b/server/src/__tests__/ai-connections.test.ts index b1599467775..0e832ca4cbd 100644 --- a/server/src/__tests__/ai-connections.test.ts +++ b/server/src/__tests__/ai-connections.test.ts @@ -100,6 +100,27 @@ describe("managed AI connections", () => { await expect(prepareManagedAiRuntime(db, { ...piInput, config: { ...piInput.config, env: { PAPERCLIP_PI_PROVIDERS: "{}" } } })).rejects.toThrow("routing is incompatible"); }); + it.each([200, 401])("reverifies native Pi OpenRouter keys without requiring the Legacy Pi CLI (HTTP %s)", async status => { + const owner = `pi-adoption-${status}`; + await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" }); + const token = `fixture-pi-adoption-${status}`; + const account = await service.save(companyId, owner, { provider: "openrouter", method: "api_key", ownership: "personal", name: owner, apiKey: token, allAgents: true, agentIds: [], routing: { kind: "openrouter", protocol: "chat", auth: "bearer", models: [] } }, token); + const selected = { provider: "openrouter", method: "api_key", mode: "delegated", ...account } as const; + const runtime = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner", binding: selected, config: { provider: "acpx", acpxAgent: "pi", model: "openrouter/anthropic/claude-sonnet-4.5" } }); + const { requireServerAdapter } = await import("../adapters/index.js"); + const { agentEnvironmentTestService } = await import("../services/agent-environment-test.js"); + const native = vi.spyOn(requireServerAdapter("paperclip_runner"), "testEnvironment").mockResolvedValue({ adapterType: "paperclip_runner", status: "pass", testedAt: new Date().toISOString(), checks: [{ code: "acpx_runtime_ready", level: "info", message: "Pinned Pi runtime verified" }] }); + const legacy = vi.spyOn(requireServerAdapter("pi_local"), "testEnvironment").mockRejectedValue(new Error("Legacy Pi CLI must not be required")); + const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({ data: {} }), { status })); + try { + const result = await agentEnvironmentTestService(db).testManagedEnvironment("paperclip_runner", { companyId, adapterType: "paperclip_runner", config: runtime.config }, selected, runtime, agentId); + expect(result.status).toBe(status === 200 ? "pass" : "fail"); + expect(result.checks).toContainEqual(expect.objectContaining({ code: status === 200 ? "ai_connection_api_key_reverified" : "ai_connection_api_key_rejected" })); + expect(legacy).not.toHaveBeenCalled(); + expect(request).toHaveBeenCalled(); + } finally { native.mockRestore(); legacy.mockRestore(); request.mockRestore(); await runtime.cleanup(); } + }); + it("defers a new plugin agent's Copilot check until its saved-agent lifecycle can acquire the computer", async () => { const { errorHandler } = await import("../middleware/index.js"); const owner = "copilot-plugin-bootstrap", pluginId = randomUUID(); diff --git a/server/src/services/agent-environment-test.ts b/server/src/services/agent-environment-test.ts index a92ae12f668..f079e736f2d 100644 --- a/server/src/services/agent-environment-test.ts +++ b/server/src/services/agent-environment-test.ts @@ -548,7 +548,10 @@ export function agentEnvironmentTestService(db: Db, pluginWorkerManager?: Plugin // requirement stays for subscriptions: a stored login is a file layout // only a provider CLI reads, so proving the runtime lane can consume it // takes a real hello turn. - if (resolvedMethod === "api_key" && !context.config.managedAiRouting) { + const nativePiOpenRouter = adapterType === "paperclip_runner" && context.config.provider === "acpx" + && context.config.acpxAgent === "pi" && binding.provider === "openrouter" + && asRecord(context.config.managedAiRouting)?.kind === "openrouter"; + if (resolvedMethod === "api_key" && (!context.config.managedAiRouting || nativePiOpenRouter)) { const envKey = AI_CONNECTION_CAPABILITIES[binding.provider].methods.api_key?.envKey; const key = envKey ? parseObject(context.config.env)[envKey] : undefined; try {