fix(codex): keep native sessions out of custom provider imports

This commit is contained in:
paseo-bot[bot]
2026-10-15 21:43:17 +02:00
parent 4b6106a636
commit ff21dc2315
4 changed files with 63 additions and 19 deletions
+1 -1
View File
@@ -232,7 +232,7 @@ For pay-as-you-go, use `ANTHROPIC_API_KEY` with a standard Model Studio key (`sk
Codex talks to OpenAI's Responses API by default. Custom providers that extend `"codex"` can point Codex at any OpenAI-compatible endpoint (OpenRouter, LiteLLM, vLLM, llama.cpp server, an internal gateway, etc.) by setting `OPENAI_BASE_URL` and `OPENAI_API_KEY` in the provider `env`.
Paseo passes those variables through to the Codex app-server process **and** maps them into Codex's thread config under `model_provider` / `model_providers`, because Codex reads provider routing from config rather than from `OPENAI_BASE_URL` alone. The generated Codex provider key is `paseo:<provider-id>`, so a Paseo provider such as `ollama` does not override a Codex built-in. Existing sessions created with the original provider key remain available in Import session.
Paseo passes those variables through to the Codex app-server process **and** maps them into Codex's thread config under `model_provider` / `model_providers`, because Codex reads provider routing from config rather than from `OPENAI_BASE_URL` alone. The generated Codex provider key is `paseo:<provider-id>`, so a Paseo provider such as `ollama` does not override a Codex built-in. Existing custom sessions created with a non-built-in provider key remain available in Import session. Native Codex conversations using a built-in key stay separate from a custom provider of the same name.
### Setup
@@ -6942,6 +6942,33 @@ describe("Codex importable sessions", () => {
appServer.assertNoErrors();
});
test.each(["openai", "ollama", "lmstudio", "amazon-bedrock", "amazon-bedrock-runtime"])(
"a custom provider named %s excludes native built-in sessions",
async (providerId) => {
const appServer = createFakeCodexAppServer({
"thread/list": providerFilteringThreadListHandler([
{ id: "native-thread", cwd: "/workspace/project-a", modelProvider: providerId },
{
id: "custom-thread",
cwd: "/workspace/project-a",
modelProvider: `paseo:${providerId}`,
},
]),
});
const provider = createProviderWithFakeAppServer(appServer, {
runtimeSettings: {
env: { OPENAI_BASE_URL: "https://llm.example.test/v1", OPENAI_API_KEY: "test-key" },
},
customProvider: { id: providerId, label: "Custom Codex", extends: "codex" },
});
const sessions = await provider.listImportableSessions({ cwd: "/workspace/project-a" });
expect(sessions.map((session) => session.providerHandleId)).toEqual(["custom-thread"]);
appServer.assertNoErrors();
},
);
test("stock Codex keeps listing only its own threads", async () => {
const appServer = createFakeCodexAppServer({
"thread/list": providerFilteringThreadListHandler(threadsByModelProvider),
@@ -145,6 +145,15 @@ function isCodexAlreadyUnarchivedError(error: unknown, threadId: string): boolea
const TURN_START_TIMEOUT_MS = 90 * 1000;
const INTERRUPT_TIMEOUT_MS = 2_000;
const CODEX_PROVIDER = "codex" as const;
// These native provider identities belong to Codex, not to an older custom
// Paseo provider. Never include them in a custom provider's legacy import scan.
const CODEX_BUILTIN_MODEL_PROVIDER_IDS = new Set([
"openai",
"ollama",
"lmstudio",
"amazon-bedrock",
"amazon-bedrock-runtime",
]);
// Codex treats most app-server client names as the model-request originator.
// This reserved Codex name is non-originating, so requests keep Codex's default
// CLI identity instead of showing up as Paseo in provider usage logs.
@@ -7353,13 +7362,17 @@ export class CodexAppServerAgentClient implements AgentClient {
const listLimit = options?.cwd ? Math.max(scanLimit, 50) : scanLimit;
// Codex records each thread under the model provider that ran it and,
// unless told otherwise, lists only the provider its own config selects.
// Include the original id for sessions created before namespacing.
// Include legacy custom ids, but keep Codex built-in sessions separate.
const customProviderConfig = this.customProviderConfig();
const allThreads = await readCodexThreadWindow(client, this.logger, {
limit: listLimit,
cwd: options?.cwd,
...(customProviderConfig && this.deps.customProvider
? { modelProviders: [customProviderConfig.model_provider, this.deps.customProvider.id] }
? {
modelProviders: CODEX_BUILTIN_MODEL_PROVIDER_IDS.has(this.deps.customProvider.id)
? [customProviderConfig.model_provider]
: [customProviderConfig.model_provider, this.deps.customProvider.id],
}
: {}),
});
const threads = filterCodexThreadsByCwd(allThreads, options?.cwd);
@@ -90,12 +90,13 @@ describe("Codex custom provider import list", () => {
});
test.each([
{ providerId: "my-codex", seedLegacy: false },
{ providerId: "ollama", seedLegacy: false },
{ providerId: "my-codex", seedLegacy: true },
{ providerId: "my-codex", seedLegacy: false, seedBuiltin: false },
{ providerId: "ollama", seedLegacy: false, seedBuiltin: false },
{ providerId: "my-codex", seedLegacy: true, seedBuiltin: false },
{ providerId: "ollama", seedLegacy: false, seedBuiltin: true },
])(
"lists and imports sessions for $providerId (legacy session: $seedLegacy)",
async ({ providerId, seedLegacy }) => {
"lists and imports sessions for $providerId (legacy: $seedLegacy, native: $seedBuiltin)",
async ({ providerId, seedLegacy, seedBuiltin }) => {
const codexHome = mkdtempSync(path.join(os.tmpdir(), "codex-custom-import-home-"));
const cwd = mkdtempSync(path.join(os.tmpdir(), "codex-custom-import-cwd-"));
cleanups.push(() => rmSync(codexHome, { recursive: true, force: true }));
@@ -104,11 +105,12 @@ describe("Codex custom provider import list", () => {
cleanups.push(() => closeServer(server));
const logger = createTestLogger();
if (seedLegacy) {
// Recreate the config emitted before namespacing with the real app-server.
if (seedLegacy || seedBuiltin) {
// Seed an old custom session or a native built-in session with real Codex.
const environment = {
...process.env,
CODEX_HOME: codexHome,
CODEX_OSS_BASE_URL: baseUrl,
OPENAI_API_KEY: "test-key",
};
const legacy = new CodexAppServerAgentSession(
@@ -120,15 +122,17 @@ describe("Codex custom provider import list", () => {
environment,
customCodexConfig: {
model_provider: providerId,
model_providers: {
[providerId]: {
name: "My Codex",
base_url: baseUrl,
wire_api: "responses",
env_key: "OPENAI_API_KEY",
requires_openai_auth: false,
},
},
model_providers: seedBuiltin
? {}
: {
[providerId]: {
name: "My Codex",
base_url: baseUrl,
wire_api: "responses",
env_key: "OPENAI_API_KEY",
requires_openai_auth: false,
},
},
},
},
);