mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 09:42:53 +00:00
fix(codex): keep native sessions out of custom provider imports
This commit is contained in:
@@ -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);
|
||||
|
||||
+20
-16
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user