diff --git a/packages/server/src/server/agent/providers/paseo-agent/agent.test.ts b/packages/server/src/server/agent/providers/paseo-agent/agent.test.ts index 27df8b7c06..58b7e4ec7d 100644 --- a/packages/server/src/server/agent/providers/paseo-agent/agent.test.ts +++ b/packages/server/src/server/agent/providers/paseo-agent/agent.test.ts @@ -8,6 +8,7 @@ import { createTestLogger } from "../../../../test-utils/test-logger.js"; import type { AgentSessionConfig } from "../../agent-sdk-types.js"; import { PaseoAgentClient } from "./agent.js"; import { PaseoAgentConfigSchema, type PaseoAgentConfig } from "./config.js"; +import { storeCodexOAuthCredential } from "./oauth-store.js"; function makeConfig(): PaseoAgentConfig { return PaseoAgentConfigSchema.parse({ @@ -65,6 +66,38 @@ describe("PaseoAgentClient", () => { expect(await empty.isAvailable()).toBe(false); }); + it("checks ChatGPT OAuth credentials in the configured Paseo home", async () => { + const paseoHome = mkdtempSync(join(tmpdir(), "paseo-agent-client-")); + const wrongHome = mkdtempSync(join(tmpdir(), "paseo-agent-wrong-home-")); + tempDirs.push(paseoHome, wrongHome); + const previousPaseoHome = process.env.PASEO_HOME; + process.env.PASEO_HOME = wrongHome; + storeCodexOAuthCredential({ + providerInstance: "chatgpt", + credential: { type: "oauth", access: "access-token", refresh: "refresh-token", expires: 0 }, + env: { PASEO_HOME: paseoHome }, + }); + const config = PaseoAgentConfigSchema.parse({ + providers: { + chatgpt: { + type: "openai-codex", + options: { models: [{ id: "gpt-5.3-codex" }] }, + }, + }, + }); + + try { + const client = new PaseoAgentClient({ logger: createTestLogger(), config, paseoHome }); + expect(await client.isAvailable()).toBe(true); + } finally { + if (previousPaseoHome === undefined) { + delete process.env.PASEO_HOME; + } else { + process.env.PASEO_HOME = previousPaseoHome; + } + } + }); + it("lists only configured models, never Pi disk/default models", async () => { const client = new PaseoAgentClient({ logger: createTestLogger(), config: makeConfig() }); const models = await client.listModels({ cwd: process.cwd(), force: false }); diff --git a/packages/server/src/server/agent/providers/paseo-agent/agent.ts b/packages/server/src/server/agent/providers/paseo-agent/agent.ts index d02e9644eb..da29acc3f6 100644 --- a/packages/server/src/server/agent/providers/paseo-agent/agent.ts +++ b/packages/server/src/server/agent/providers/paseo-agent/agent.ts @@ -83,6 +83,10 @@ function resolveIsolatedAgentDir(): string { return join(base, "pi-harness"); } +function envForPaseoHome(paseoHome: string | undefined): NodeJS.ProcessEnv { + return paseoHome ? { ...process.env, PASEO_HOME: paseoHome } : process.env; +} + interface PaseoAgentClientOptions { logger: Logger; config: PaseoAgentConfig; @@ -539,7 +543,9 @@ export class PaseoAgentClient implements AgentClient { // OAuth providers (ChatGPT/Codex) use a Paseo-owned, file-backed AuthStorage so Pi // reads the stored credential and persists refreshed tokens (rotation) back to it. const usesOAuth = inferenceProviders.some((provider) => provider.oauth); - const authStorage = usesOAuth ? createPaseoAgentAuthStorage() : undefined; + const authStorage = usesOAuth + ? createPaseoAgentAuthStorage(envForPaseoHome(this.paseoHome)) + : undefined; // Bridge Paseo-injected MCP servers (e.g. the `paseo` HTTP server) into Pi tools. const mcpBridge = await createMcpToolBridge({ @@ -574,8 +580,9 @@ export class PaseoAgentClient implements AgentClient { } async isAvailable(): Promise { - return paseoAgentHasUsableModel(this.config, process.env, (providerInstance) => - hasStoredOAuthCredential(providerInstance), + const env = envForPaseoHome(this.paseoHome); + return paseoAgentHasUsableModel(this.config, env, (providerInstance) => + hasStoredOAuthCredential(providerInstance, env), ); }