From 510981670f1776da3555061a9ceb3f991d76ac2b Mon Sep 17 00:00:00 2001 From: mldangelo Date: Sun, 30 Aug 2026 07:24:28 -0700 Subject: [PATCH] feat: add Azure OpenAI as a scan provider Azure's v1 surface (`/openai/v1`) needs no api-version and accepts a bearer token for both a resource key and a Microsoft Entra token, so Azure fits the existing external-provider shape with no new auth machinery. The only structural difference is that the endpoint is per-resource, so `base_url` is resolved from AZURE_OPENAI_BASE_URL or AZURE_OPENAI_ENDPOINT and normalized to the v1 path; the resource endpoint Azure prints works as-is. Adds `--provider azure` without adding any other CLI surface. Provider tables are now built in one place so each provider's endpoint, credential, env scrubbing, and recipe fields come from the registry rather than parallel literals. Saved recipes keep the resolved endpoint so a replay targets the same resource, and provider secrets are still stripped. Also stop advising `--auth chatgpt` when a scan runs through a third-party provider, where a ChatGPT sign-in cannot reach the endpoint. That guidance was already wrong for openrouter and fireworks. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RTv1bYexiubXSVTz2rVt6b --- README.md | 11 + plugins/codex-security/.mcp.json | 3 + .../mcp-app/tests/test_mcp_app_smoke.mjs | 3 + sdk/typescript/README.md | 19 ++ sdk/typescript/src/api.ts | 26 ++- sdk/typescript/src/cli.ts | 44 +++- sdk/typescript/src/config.ts | 63 ++++++ sdk/typescript/src/runtime.ts | 1 + .../tests-ts/azure-provider.test.ts | 205 ++++++++++++++++++ sdk/typescript/tests-ts/cli.test.ts | 4 +- 10 files changed, 368 insertions(+), 11 deletions(-) create mode 100644 sdk/typescript/tests-ts/azure-provider.test.ts diff --git a/README.md b/README.md index 15f44770f..c80e07451 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,10 @@ export AWS_BEARER_TOKEN_BEDROCK="" export AWS_REGION="us-east-2" codex-security scan . --provider amazon-bedrock --model openai.gpt-5.6-luna +export AZURE_OPENAI_ENDPOINT="https://.openai.azure.com" +export AZURE_OPENAI_API_KEY="" +codex-security scan . --provider azure --model + export OPENROUTER_API_KEY="" codex-security scan . --provider openrouter --model anthropic/claude-sonnet-4.5 @@ -86,6 +90,13 @@ export FIREWORKS_API_KEY="" codex-security scan . --provider fireworks --model accounts/fireworks/models/qwen3-235b-a22b ``` +For Azure, `--model` is the **deployment** name, not the underlying model name, +and the deployment must serve a reasoning model because scans always request a +reasoning effort. `AZURE_OPENAI_API_KEY` accepts either a resource key or a +Microsoft Entra access token, so +`az account get-access-token --resource https://ai.azure.com` works in place of +a static key. + ## Documentation **👉👉 See the [Codex Security documentation](https://learn.chatgpt.com/docs/security/cli)** for full documentation. diff --git a/plugins/codex-security/.mcp.json b/plugins/codex-security/.mcp.json index 1abf1d4e0..0b5280fed 100644 --- a/plugins/codex-security/.mcp.json +++ b/plugins/codex-security/.mcp.json @@ -17,6 +17,9 @@ "OPENAI_API_KEY", "OPENROUTER_API_KEY", "FIREWORKS_API_KEY", + "AZURE_OPENAI_API_KEY", + "AZURE_OPENAI_BASE_URL", + "AZURE_OPENAI_ENDPOINT", "AWS_BEARER_TOKEN_BEDROCK", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", diff --git a/plugins/codex-security/mcp-app/tests/test_mcp_app_smoke.mjs b/plugins/codex-security/mcp-app/tests/test_mcp_app_smoke.mjs index e776ebd5b..9c8a35fe4 100644 --- a/plugins/codex-security/mcp-app/tests/test_mcp_app_smoke.mjs +++ b/plugins/codex-security/mcp-app/tests/test_mcp_app_smoke.mjs @@ -69,6 +69,9 @@ assert.deepEqual( "OPENAI_API_KEY", "OPENROUTER_API_KEY", "FIREWORKS_API_KEY", + "AZURE_OPENAI_API_KEY", + "AZURE_OPENAI_BASE_URL", + "AZURE_OPENAI_ENDPOINT", "AWS_BEARER_TOKEN_BEDROCK", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index caaae2e60..548a4df25 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -189,6 +189,10 @@ Access-token environment variables are not scan API keys. For other inference providers: ```bash +export AZURE_OPENAI_ENDPOINT="https://.openai.azure.com" +export AZURE_OPENAI_API_KEY="" +npx @openai/codex-security scan . --provider azure --model + export OPENROUTER_API_KEY="" npx @openai/codex-security scan . --provider openrouter --model anthropic/claude-sonnet-4.5 @@ -200,6 +204,21 @@ export AWS_REGION="us-east-2" npx @openai/codex-security scan . --provider amazon-bedrock --model openai.gpt-5.6-luna ``` +Azure uses the resource's v1 endpoint, so no `api-version` is needed. Set +`AZURE_OPENAI_ENDPOINT` (or `AZURE_OPENAI_BASE_URL`) to the resource address +such as `https://.openai.azure.com`; `/openai/v1` is appended when +absent. `--model` is the deployment name. `AZURE_OPENAI_API_KEY` accepts a +resource key or a Microsoft Entra access token from +`az account get-access-token --resource https://ai.azure.com`. Saved scan +recipes keep the resolved endpoint so a replay targets the same resource. + +Deploy a reasoning model: scans always send a reasoning effort, so a +non-reasoning deployment fails with `Unsupported parameter: 'reasoning.effort'`. +Give the deployment enough tokens-per-minute for scan-sized prompts; a small +quota surfaces as repeated `Rate limit reached` retries. Deploying a model the +cost table already prices, such as `gpt-5.6-sol`, also keeps `--max-cost` and +cost reporting working; other deployments report no cost estimate. + Bedrock also accepts AWS access keys, profiles, web identity, container credentials, and the default AWS credential chain. Set `AWS_REGION` and choose a Bedrock model with `--model`; OpenAI models such as `openai.gpt-5.6-luna` diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index ca308aadf..0bbe9d74f 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -46,6 +46,7 @@ import { } from "./codex-prompt.js"; import { EXTERNAL_CODEX_PROVIDERS, + hasResolvedBaseUrl, isExternalModelProvider, mergedCodexConfig, scanApprovalPolicy, @@ -300,7 +301,8 @@ export type ScanAuthentication = | "OPENAI_API_KEY" | "CODEX_API_KEY" | "OPENROUTER_API_KEY" - | "FIREWORKS_API_KEY"; + | "FIREWORKS_API_KEY" + | "AZURE_OPENAI_API_KEY"; verified: false; } | { @@ -3308,6 +3310,10 @@ async function runtimeScanAuthentication( return authentication; } +const EXTERNAL_PROVIDER_KEYS: ReadonlySet = new Set( + Object.values(EXTERNAL_CODEX_PROVIDERS).map((provider) => provider.env_key), +); + /** @internal */ export function selectedScanEnvironment( environment: ProcessEnvironment, @@ -3325,7 +3331,7 @@ export function selectedScanEnvironment( Object.entries(environment).filter(([name]) => { const key = name.toUpperCase(); if (key === "OPENAI_API_KEY" || key === "CODEX_API_KEY") return false; - if (key === "OPENROUTER_API_KEY" || key === "FIREWORKS_API_KEY") { + if (EXTERNAL_PROVIDER_KEYS.has(key)) { return ( !bedrockProvider && (selectedProviderKey === null || key === selectedProviderKey) @@ -3365,7 +3371,8 @@ function environmentApiKeyEntry( | "OPENAI_API_KEY" | "CODEX_API_KEY" | "OPENROUTER_API_KEY" - | "FIREWORKS_API_KEY"; + | "FIREWORKS_API_KEY" + | "AZURE_OPENAI_API_KEY"; value: string; } | null { const keys = isExternalModelProvider(modelProvider) @@ -3624,8 +3631,19 @@ export function scanPreflightCodexConfig(config: JsonObject): JsonObject { } const modelProvider = scanModelProvider(result); if (isExternalModelProvider(modelProvider)) { + // Per-account endpoints must survive into saved recipes, or replaying a + // recipe would lose the resource the scan actually ran against. + const configured = isRecord(config["model_providers"]) + ? config["model_providers"][modelProvider] + : undefined; + const baseUrl = isRecord(configured) ? configured["base_url"] : undefined; result["model_providers"] = { - [modelProvider]: { ...EXTERNAL_CODEX_PROVIDERS[modelProvider] }, + [modelProvider]: { + ...EXTERNAL_CODEX_PROVIDERS[modelProvider], + ...(hasResolvedBaseUrl(modelProvider) && safeString(baseUrl) + ? { base_url: baseUrl } + : {}), + }, }; } else if (modelProvider === "amazon-bedrock") { const providers = config["model_providers"]; diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 33bf8aca9..a05cae79f 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -83,6 +83,7 @@ import { import { DEFAULT_CODEX_CONFIG, EXTERNAL_CODEX_PROVIDERS, + externalProviderTable, isExternalModelProvider, mergedCodexConfig, scanModelConfiguration, @@ -299,7 +300,7 @@ const VALUE_OPTIONS = new Set([ "--linear-assignee", ]); const PROVIDER_OPTION = z - .enum(["openai", "openrouter", "fireworks", "amazon-bedrock"]) + .enum(["openai", "openrouter", "fireworks", "azure", "amazon-bedrock"]) .default("openai") .describe("Inference provider for scans."); const CREATE_PR_OPTION = z @@ -6645,9 +6646,12 @@ async function executeScan( progress?.stage( `Authentication: API key from ${authentication.source}.`, ); - progress?.stage( - "To use your ChatGPT sign-in, retry with --auth chatgpt.", - ); + // A ChatGPT sign-in cannot reach a third-party provider endpoint. + if (externalProviderSource(authentication) === null) { + progress?.stage( + "To use your ChatGPT sign-in, retry with --auth chatgpt.", + ); + } } else if (authentication.method === "aws_credentials") { progress?.stage( `Authentication: AWS credentials from ${authentication.source}.`, @@ -7142,6 +7146,13 @@ function scanFailureMessage( "Check your Amazon Bedrock bearer token or AWS credential chain." ); } + const unauthorizedProvider = externalProviderSource(authentication); + if (unauthorizedProvider !== null) { + return ( + `Authentication failed using ${unauthorizedProvider}. ` + + "Check the credential and endpoint for the configured provider." + ); + } return authentication?.method === "api_key" ? `Authentication failed using ${authentication.source}. ` + "Retry with '--auth chatgpt' or provide a valid API key." @@ -7154,6 +7165,13 @@ function scanFailureMessage( "Check your AWS identity and Bedrock model permissions." ); } + const forbiddenProvider = externalProviderSource(authentication); + if (forbiddenProvider !== null) { + return ( + `The credential from ${forbiddenProvider} cannot access the configured model. ` + + "Confirm the deployment or model name and the credential's permissions." + ); + } return authentication?.method === "api_key" ? `The API key from ${authentication.source} cannot access the configured model. ` + "Retry with '--auth chatgpt' or use an API key with model access." @@ -7168,6 +7186,21 @@ function scanFailureMessage( } } +const EXTERNAL_PROVIDER_SOURCES: ReadonlySet = new Set( + Object.values(EXTERNAL_CODEX_PROVIDERS).map((provider) => provider.env_key), +); + +// ChatGPT sign-in is not an alternative when the scan runs through a +// third-party provider, so those failures need different guidance. +function externalProviderSource( + authentication: ScanAuthentication | null | undefined, +): string | null { + return authentication?.method === "api_key" && + EXTERNAL_PROVIDER_SOURCES.has(authentication.source) + ? authentication.source + : null; +} + function scanScope(arguments_: ScanArguments): string | null { if (arguments_.paths.length > 0) { const displayed = arguments_.paths.slice(0, 3).map((path) => { @@ -7447,6 +7480,7 @@ export function parseCodexOverrides( model?: string, effort?: ScanReasoningEffort, provider?: "openai" | "amazon-bedrock" | ExternalModelProvider, + environment: Readonly> = process.env, ): JsonObject { const result = Object.create(null) as JsonObject; if (model !== undefined) result["model"] = model; @@ -7454,7 +7488,7 @@ export function parseCodexOverrides( if (isExternalModelProvider(provider)) { result["model_provider"] = provider; result["model_providers"] = { - [provider]: { ...EXTERNAL_CODEX_PROVIDERS[provider] }, + [provider]: externalProviderTable(provider, environment), }; } else if (provider === "amazon-bedrock") { result["model_provider"] = provider; diff --git a/sdk/typescript/src/config.ts b/sdk/typescript/src/config.ts index 27cbddd1b..4f7a84c6d 100644 --- a/sdk/typescript/src/config.ts +++ b/sdk/typescript/src/config.ts @@ -35,9 +35,27 @@ export const FIREWORKS_CODEX_PROVIDER = { wire_api: "responses", } as const satisfies JsonObject; +// Azure resources each have their own endpoint, so base_url is resolved from +// the environment instead of being a fixed address. The v1 surface accepts the +// resource key or a Microsoft Entra token as a bearer token, so env_key covers +// both without extra header configuration. +export const AZURE_CODEX_PROVIDER = { + name: "Azure OpenAI", + env_key: "AZURE_OPENAI_API_KEY", + wire_api: "responses", +} as const satisfies JsonObject; + +export const AZURE_ENDPOINT_ENVIRONMENT_VARIABLES = [ + "AZURE_OPENAI_BASE_URL", + "AZURE_OPENAI_ENDPOINT", +] as const; + +const AZURE_V1_PATH = "/openai/v1"; + export const EXTERNAL_CODEX_PROVIDERS = { openrouter: OPENROUTER_CODEX_PROVIDER, fireworks: FIREWORKS_CODEX_PROVIDER, + azure: AZURE_CODEX_PROVIDER, } as const; export type ExternalModelProvider = keyof typeof EXTERNAL_CODEX_PROVIDERS; @@ -51,6 +69,51 @@ export function isExternalModelProvider( ); } +/** Providers whose endpoint is per-account rather than a fixed address. */ +export function hasResolvedBaseUrl(provider: ExternalModelProvider): boolean { + return provider === "azure"; +} + +export function azureBaseUrl( + environment: Readonly>, +): string { + const configured = AZURE_ENDPOINT_ENVIRONMENT_VARIABLES.map((name) => + environment[name]?.trim(), + ).find((value) => value !== undefined && value.length > 0); + if (configured === undefined) { + throw new ConfigurationError( + "Set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_ENDPOINT to the Azure OpenAI " + + "resource endpoint, such as https://.openai.azure.com.", + ); + } + let endpoint: URL; + try { + endpoint = new URL(configured); + } catch { + throw new ConfigurationError( + `The Azure OpenAI endpoint must be an absolute URL: ${configured}`, + ); + } + if (endpoint.protocol !== "https:" && endpoint.protocol !== "http:") { + throw new ConfigurationError( + `The Azure OpenAI endpoint must use http or https: ${configured}`, + ); + } + // Accept the resource endpoint Azure prints as well as a full v1 base URL. + const path = endpoint.pathname.replace(/\/+$/u, ""); + return `${endpoint.origin}${path.endsWith(AZURE_V1_PATH) ? path : `${path}${AZURE_V1_PATH}`}`; +} + +/** The complete `model_providers.` table Codex needs for a scan. */ +export function externalProviderTable( + provider: ExternalModelProvider, + environment: Readonly>, +): JsonObject { + const table: JsonObject = { ...EXTERNAL_CODEX_PROVIDERS[provider] }; + if (provider === "azure") table["base_url"] = azureBaseUrl(environment); + return table; +} + export const DEFAULT_CODEX_CONFIG: Readonly = { approval_policy: "on-request", approvals_reviewer: "auto_review", diff --git a/sdk/typescript/src/runtime.ts b/sdk/typescript/src/runtime.ts index 59898f0a9..3e12a2fbb 100644 --- a/sdk/typescript/src/runtime.ts +++ b/sdk/typescript/src/runtime.ts @@ -86,6 +86,7 @@ const PLUGIN_HELPER_SECRET_ENVIRONMENT_VARIABLES = new Set([ "CODEX_API_KEY", "OPENROUTER_API_KEY", "FIREWORKS_API_KEY", + "AZURE_OPENAI_API_KEY", ]); const PREPARE_SCAN_ARTIFACT_RESTORER_PROGRAM = ` from pathlib import Path diff --git a/sdk/typescript/tests-ts/azure-provider.test.ts b/sdk/typescript/tests-ts/azure-provider.test.ts new file mode 100644 index 000000000..2ef6d2939 --- /dev/null +++ b/sdk/typescript/tests-ts/azure-provider.test.ts @@ -0,0 +1,205 @@ +import { describe, expect, test } from "bun:test"; +import { + scanAuthentication, + scanPreflightCodexConfig, + selectedScanEnvironment, +} from "../src/api.js"; +import { + AZURE_CODEX_PROVIDER, + azureBaseUrl, + externalProviderTable, +} from "../src/config.js"; +import { parseCodexOverrides } from "../src/cli.js"; + +const RESOURCE = "https://synthetic-resource.openai.azure.com"; +const azureEnvironment = (overrides: Record = {}) => ({ + AZURE_OPENAI_BASE_URL: RESOURCE, + AZURE_OPENAI_API_KEY: "synthetic-azure-key", + ...overrides, +}); + +describe("Azure endpoint resolution", () => { + test.each([ + ["bare resource endpoint", RESOURCE], + ["trailing slash", `${RESOURCE}/`], + ["explicit v1 path", `${RESOURCE}/openai/v1`], + ["explicit v1 path with slash", `${RESOURCE}/openai/v1/`], + ])("normalizes the %s", (_name, configured) => { + expect(azureBaseUrl({ AZURE_OPENAI_BASE_URL: configured })).toBe( + `${RESOURCE}/openai/v1`, + ); + }); + + // Azure hands out at least three endpoint domains for the same v1 surface. + test.each([ + ["openai.azure.com", "https://synthetic.openai.azure.com"], + ["services.ai.azure.com", "https://synthetic.services.ai.azure.com"], + [ + "cognitiveservices.azure.com", + "https://synthetic.cognitiveservices.azure.com", + ], + ])("accepts a %s endpoint", (_name, endpoint) => { + expect(azureBaseUrl({ AZURE_OPENAI_BASE_URL: endpoint })).toBe( + `${endpoint}/openai/v1`, + ); + }); + + test("falls back to AZURE_OPENAI_ENDPOINT", () => { + expect(azureBaseUrl({ AZURE_OPENAI_ENDPOINT: RESOURCE })).toBe( + `${RESOURCE}/openai/v1`, + ); + }); + + test("prefers AZURE_OPENAI_BASE_URL over AZURE_OPENAI_ENDPOINT", () => { + expect( + azureBaseUrl({ + AZURE_OPENAI_BASE_URL: RESOURCE, + AZURE_OPENAI_ENDPOINT: "https://ignored.openai.azure.com", + }), + ).toBe(`${RESOURCE}/openai/v1`); + }); + + test.each([ + ["unset", {}], + ["blank", { AZURE_OPENAI_BASE_URL: " " }], + ["not a URL", { AZURE_OPENAI_BASE_URL: "synthetic-resource" }], + ["wrong protocol", { AZURE_OPENAI_BASE_URL: "ftp://synthetic/openai" }], + ])("rejects an endpoint that is %s", (_name, environment) => { + expect(() => azureBaseUrl(environment)).toThrow(); + }); +}); + +describe("Azure provider table", () => { + test("resolves a complete Codex provider table", () => { + expect(externalProviderTable("azure", azureEnvironment())).toEqual({ + ...AZURE_CODEX_PROVIDER, + base_url: `${RESOURCE}/openai/v1`, + }); + }); + + test("uses the Responses wire API", () => { + expect(AZURE_CODEX_PROVIDER.wire_api).toBe("responses"); + expect(AZURE_CODEX_PROVIDER.env_key).toBe("AZURE_OPENAI_API_KEY"); + }); + + test("--provider azure builds the Codex overrides", () => { + expect( + parseCodexOverrides( + [], + "gpt-4.1-mini", + undefined, + "azure", + azureEnvironment(), + ), + ).toEqual({ + model: "gpt-4.1-mini", + model_provider: "azure", + model_providers: { + azure: { ...AZURE_CODEX_PROVIDER, base_url: `${RESOURCE}/openai/v1` }, + }, + }); + }); + + test("requires a deployment name because Azure models are deployments", () => { + expect(() => + parseCodexOverrides( + [], + undefined, + undefined, + "azure", + azureEnvironment(), + ), + ).toThrow("--model is required when using --provider azure"); + }); + + test("reports a missing endpoint rather than building a broken provider", () => { + expect(() => + parseCodexOverrides([], "gpt-4.1-mini", undefined, "azure", { + AZURE_OPENAI_API_KEY: "synthetic-azure-key", + }), + ).toThrow(/AZURE_OPENAI_BASE_URL or AZURE_OPENAI_ENDPOINT/u); + }); +}); + +describe("Azure authentication", () => { + test("selects the Azure key, which carries a resource key or an Entra token", () => { + expect(scanAuthentication(azureEnvironment(), "auto", "azure")).toEqual({ + method: "api_key", + source: "AZURE_OPENAI_API_KEY", + verified: false, + }); + }); + + test("keeps only the selected provider's credential in the scan environment", () => { + const environment = selectedScanEnvironment( + azureEnvironment({ + OPENAI_API_KEY: "synthetic-openai-key", + CODEX_API_KEY: "synthetic-codex-key", + OPENROUTER_API_KEY: "synthetic-openrouter-key", + FIREWORKS_API_KEY: "synthetic-fireworks-key", + PATH: "/usr/bin", + }), + "auto", + "azure", + ); + expect(environment["AZURE_OPENAI_API_KEY"]).toBe("synthetic-azure-key"); + expect(environment["AZURE_OPENAI_BASE_URL"]).toBe(RESOURCE); + expect(environment["PATH"]).toBe("/usr/bin"); + for (const leaked of [ + "OPENAI_API_KEY", + "CODEX_API_KEY", + "OPENROUTER_API_KEY", + "FIREWORKS_API_KEY", + ]) { + expect(environment).not.toHaveProperty(leaked); + } + }); + + test("drops the Azure key when another provider is selected", () => { + const environment = selectedScanEnvironment( + azureEnvironment(), + "auto", + "openrouter", + ); + expect(environment).not.toHaveProperty("AZURE_OPENAI_API_KEY"); + }); +}); + +describe("Azure scan recipes", () => { + test("preserves the resource endpoint and strips provider secrets", () => { + const config = scanPreflightCodexConfig({ + model: "gpt-4.1-mini", + model_provider: "azure", + model_providers: { + azure: { + ...AZURE_CODEX_PROVIDER, + base_url: `${RESOURCE}/openai/v1`, + api_key: "synthetic-azure-secret", + }, + private: { bearer_token: "synthetic-unrelated-secret" }, + }, + }); + expect(config).toEqual({ + model: "gpt-4.1-mini", + model_provider: "azure", + model_providers: { + azure: { ...AZURE_CODEX_PROVIDER, base_url: `${RESOURCE}/openai/v1` }, + }, + }); + expect(JSON.stringify(config)).not.toContain("synthetic-azure-secret"); + expect(JSON.stringify(config)).not.toContain("synthetic-unrelated-secret"); + }); + + test("does not let a recipe override a fixed provider endpoint", () => { + const config = scanPreflightCodexConfig({ + model: "anthropic/claude-sonnet-4.5", + model_provider: "openrouter", + model_providers: { + openrouter: { base_url: "https://synthetic-attacker.example/v1" }, + }, + }); + expect(config["model_providers"]).toMatchObject({ + openrouter: { base_url: "https://openrouter.ai/api/v1" }, + }); + }); +}); diff --git a/sdk/typescript/tests-ts/cli.test.ts b/sdk/typescript/tests-ts/cli.test.ts index dcb5d2545..915894991 100644 --- a/sdk/typescript/tests-ts/cli.test.ts +++ b/sdk/typescript/tests-ts/cli.test.ts @@ -2353,7 +2353,7 @@ describe("CLI", () => { ); expect(help.text()).toContain("--model "); expect(help.text()).toContain( - "--provider ", + "--provider ", ); expect(help.text()).toContain( `OpenAI model to use (default: ${DEFAULT_SCAN_MODEL_CONFIGURATION.model}).`, @@ -2425,7 +2425,7 @@ describe("CLI", () => { expect(help.text()).not.toContain("--outputDir"); expect(help.text()).not.toContain("--maxAttempts"); expect(help.text()).toContain( - "--provider ", + "--provider ", ); expect(stderr.text()).toBe(""); });