From a212f90094f367a468641f70947b71d8da951c1a Mon Sep 17 00:00:00 2001 From: haokunt Date: Thu, 30 Jul 2026 16:52:36 +0800 Subject: [PATCH] feat: custom API endpoint --- sdk/typescript/src/api.ts | 2 ++ sdk/typescript/src/cli.ts | 32 ++++++++++++++++++++++++++ sdk/typescript/src/config.ts | 1 + sdk/typescript/tests-ts/api.test.ts | 10 ++++++++ sdk/typescript/tests-ts/cli.test.ts | 7 ++++++ sdk/typescript/tests-ts/config.test.ts | 10 ++++++++ sdk/typescript/tsconfig.json | 1 - 7 files changed, 62 insertions(+), 1 deletion(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index fe59b1d94..6e76cef22 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -2166,6 +2166,7 @@ export class CodexSecurity { ? {} : { codexPathOverride: executablePathForSpawn(codexPathOverride) }), ...(externalProvider !== null || apiKey === null ? {} : { apiKey }), + ...(this.config.endpoint ? { baseUrl: this.config.endpoint } : {}), ...(commandAuth ? { configOverrides: modelProviderConfigOverride(sessionConfig) } : {}), @@ -4022,6 +4023,7 @@ export function scanPreflightCodexConfig(config: JsonObject): JsonObject { "model_reasoning_effort", "model_provider", "service_tier", + "openai_base_url", ]) { const value = source[key]; if (safeString(value)) result[key] = value; diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index d803ed425..2e3c648fd 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -264,6 +264,7 @@ const VALUE_OPTIONS = new Set([ "--model", "--effort", "--provider", + "--endpoint", "--output-dir", "--plugin-path", "--python", @@ -997,6 +998,7 @@ interface ScanArguments extends DeepScanOptions { model?: string; effort?: ScanReasoningEffort; provider?: "openai" | "amazon-bedrock" | ExternalModelProvider; + endpoint?: string; outputDir?: string; archiveExisting: boolean; pluginPath?: string; @@ -2879,6 +2881,11 @@ export async function main( ), effort: effortOption(), provider: PROVIDER_OPTION, + endpoint: optionValue("--endpoint") + .optional() + .describe( + "Custom LLM API endpoint URL. Also settable via CODEX_ENDPOINT env var.", + ), outputDir: optionValue("--output-dir") .optional() .describe( @@ -3006,6 +3013,13 @@ export async function main( ], }, }, + { + args: { repository: "." }, + options: { + model: "gpt-5.6-terra", + endpoint: "https://custom.api.example.com/v1", + }, + }, ], output: z.record(z.string(), z.unknown()).optional(), async run({ args, error: incurError, format, options }) { @@ -3041,6 +3055,7 @@ export async function main( model: options.model, effort: options.effort, provider: options.provider, + endpoint: options.endpoint, outputDir: options.outputDir, archiveExisting: options.archiveExisting, pluginPath: options.pluginPath, @@ -3543,6 +3558,11 @@ export async function main( ), effort: effortOption(), provider: PROVIDER_OPTION, + endpoint: optionValue("--endpoint") + .optional() + .describe( + "Custom LLM API endpoint URL. Also settable via CODEX_ENDPOINT env var.", + ), maxAttempts: z .number() .int() @@ -3572,6 +3592,14 @@ export async function main( args: {}, options: { model: "gpt-5.6-terra", effort: "high" }, }, + { + args: {}, + options: { + model: "gpt-5.6-terra", + effort: "high", + endpoint: "https://custom.api.example.com/v1", + }, + }, ], hint: "CSV example:\n" + @@ -3644,6 +3672,8 @@ export async function main( knowledgeBasePaths: options.knowledgeBase, ...prompts, config: { + endpoint: + options.endpoint ?? dependencies.environment["CODEX_ENDPOINT"], pluginPath: options.pluginPath, pythonPath: options.python, codexOverrides: parseCodexOverrides( @@ -6460,6 +6490,8 @@ async function executeScan( arguments_.validationPromptFile, ); const config: CodexSecurityConfig = { + endpoint: + arguments_.endpoint ?? dependencies.environment["CODEX_ENDPOINT"], pluginPath: arguments_.pluginPath, pythonPath: arguments_.pythonPath, codexOverrides: diff --git a/sdk/typescript/src/config.ts b/sdk/typescript/src/config.ts index 5209ab87e..c83dc811d 100644 --- a/sdk/typescript/src/config.ts +++ b/sdk/typescript/src/config.ts @@ -11,6 +11,7 @@ export interface JsonObject { } export interface CodexSecurityConfig { + endpoint?: string; pluginPath?: string; codexOverrides?: JsonObject; pythonPath?: string; diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 6d39f3690..ae15e0697 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -43,6 +43,7 @@ import { import { classifyConnectionFailure, initialCredentialsAvailable, + scanPreflightCodexConfig, } from "../src/api.js"; import { FIREWORKS_CODEX_PROVIDER, @@ -730,6 +731,15 @@ describe("CodexSecurity orchestration", () => { }, ); + test("preserves openai_base_url in sanitized preflight config", () => { + const config = scanPreflightCodexConfig({ + openai_base_url: "https://custom.api.example.com/v1", + model: "gpt-5.6-sol", + }); + expect(config["openai_base_url"]).toBe("https://custom.api.example.com/v1"); + }); + + test("selects a real-scan target in the active repository layout", async () => { await expect( stat(join(REPOSITORY_ROOT, INTEGRATION_TARGET)), diff --git a/sdk/typescript/tests-ts/cli.test.ts b/sdk/typescript/tests-ts/cli.test.ts index 897ca5041..af38bd343 100644 --- a/sdk/typescript/tests-ts/cli.test.ts +++ b/sdk/typescript/tests-ts/cli.test.ts @@ -2719,6 +2719,13 @@ describe("CLI", () => { expect(({} as Record)["polluted"]).toBeUndefined(); }); + test("parses openai_base_url override through --codex", () => { + const result = parseCodexOverrides([ + 'openai_base_url="https://custom.api.example.com/v1"', + ]); + expect(result["openai_base_url"]).toBe("https://custom.api.example.com/v1"); + }); + test("rejects invalid scan and export options before starting the SDK", async () => { const cases: ReadonlyArray<[readonly string[], string]> = [ [["scan", ".", "--path", "src", "--diff", "HEAD"], "mutually exclusive"], diff --git a/sdk/typescript/tests-ts/config.test.ts b/sdk/typescript/tests-ts/config.test.ts index e0c3f9d08..5974da9f4 100644 --- a/sdk/typescript/tests-ts/config.test.ts +++ b/sdk/typescript/tests-ts/config.test.ts @@ -14,6 +14,7 @@ import { scanRuntimeCodexConfig } from "../src/api.js"; import { scanModelConfiguration, scanModelProvider } from "../src/config.js"; import { ConfigurationError, + type CodexSecurityConfig, DEFAULT_CODEX_CONFIG, type JsonObject, mergedCodexConfig, @@ -615,6 +616,15 @@ describe("Codex configuration", () => { }); }); + test("passes an optional custom endpoint through configuration", () => { + const config: CodexSecurityConfig = {}; + expect(config.endpoint).toBeUndefined(); + const withEndpoint: CodexSecurityConfig = { + endpoint: "https://custom.api.example.com/v1", + }; + expect(withEndpoint.endpoint).toBe("https://custom.api.example.com/v1"); + }); + test("rejects owned plugin keys and incompatible v2 overrides", async () => { await expect( mergedCodexConfig({ codexOverrides: { features: false } }), diff --git a/sdk/typescript/tsconfig.json b/sdk/typescript/tsconfig.json index 18454c78e..9b61b5ab1 100644 --- a/sdk/typescript/tsconfig.json +++ b/sdk/typescript/tsconfig.json @@ -12,7 +12,6 @@ "exclude": ["dist", "node_modules", "tests-ts/package.test.ts"], "compilerOptions": { "allowJs": false, - "baseUrl": ".", "checkJs": false, "esModuleInterop": true, "forceConsistentCasingInFileNames": true,