From e1a4d48f8f85dbb097c08f91bbb85ae27d754dc4 Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Wed, 21 May 2025 00:25:47 +0000 Subject: [PATCH 1/4] Adds @azure/identity dependency --- codex-cli/package.json | 1 + pnpm-lock.yaml | 245 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/codex-cli/package.json b/codex-cli/package.json index d60b6ee97..bbe2aa424 100644 --- a/codex-cli/package.json +++ b/codex-cli/package.json @@ -27,6 +27,7 @@ "dist" ], "dependencies": { + "@azure/identity": "^4.10.0", "@inkjs/ui": "^2.0.0", "chalk": "^5.2.0", "diff": "^7.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e4db6952..91ed1fb18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: codex-cli: dependencies: + '@azure/identity': + specifier: ^4.10.0 + version: 4.10.0 '@inkjs/ui': specifier: ^2.0.0 version: 2.0.0(ink@5.2.0(@types/react@18.3.20)(react@18.3.1)) @@ -197,6 +200,50 @@ packages: resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==} engines: {node: '>=14.13.1'} + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.9.0': + resolution: {integrity: sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==} + engines: {node: '>=18.0.0'} + + '@azure/core-client@1.9.4': + resolution: {integrity: sha512-f7IxTD15Qdux30s2qFARH+JxgwxWLG2Rlr4oSkPGuLWm+1p5y1+C04XGLA0vmX6EtqfutmjvpNmAfgwVIS5hpw==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.20.0': + resolution: {integrity: sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g==} + engines: {node: '>=18.0.0'} + + '@azure/core-tracing@1.2.0': + resolution: {integrity: sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==} + engines: {node: '>=18.0.0'} + + '@azure/core-util@1.12.0': + resolution: {integrity: sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==} + engines: {node: '>=18.0.0'} + + '@azure/identity@4.10.0': + resolution: {integrity: sha512-iT53Sre2NJK6wzMWnvpjNiR3md597LZ3uK/5kQD2TkrY9vqhrY5bt2KwELNjkOWQ9n8S/92knj/QEykTtjMNqQ==} + engines: {node: '>=18.0.0'} + + '@azure/logger@1.2.0': + resolution: {integrity: sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==} + engines: {node: '>=18.0.0'} + + '@azure/msal-browser@4.12.0': + resolution: {integrity: sha512-WD1lmVWchg7wn1mI7Tr4v7QPyTwK+8Nuyje3jRpOFENLRLEBsdK8VVdTw3C+TypZmYn4cOAdj3zREnuFXgvfIA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@15.6.0': + resolution: {integrity: sha512-EotmBz42apYGjqiIV9rDUdptaMptpTn4TdGf3JfjLvFvinSe9BJ6ywU92K9ky+t/b0ghbeTSe9RfqlgLh8f2jA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@3.5.3': + resolution: {integrity: sha512-c5mifzHX5mwm5JqMIlURUyp6LEEdKF1a8lmcNRLBo0lD7zpSYPHupa4jHyhJyg9ccLwszLguZJdk2h3ngnXwNw==} + engines: {node: '>=16'} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -674,6 +721,10 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typespec/ts-http-runtime@0.2.2': + resolution: {integrity: sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==} + engines: {node: '>=18.0.0'} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -847,6 +898,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -1082,6 +1136,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -1497,6 +1554,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1749,10 +1810,20 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1777,9 +1848,30 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -2462,6 +2554,9 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2530,6 +2625,10 @@ packages: peerDependencies: react: '>=16.8.0 || ^17' + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -2728,6 +2827,89 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-auth@1.9.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.12.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-client@1.9.4': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-rest-pipeline': 1.20.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.12.0 + '@azure/logger': 1.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-rest-pipeline@1.20.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.12.0 + '@azure/logger': 1.2.0 + '@typespec/ts-http-runtime': 0.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.12.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@typespec/ts-http-runtime': 0.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/identity@4.10.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.4 + '@azure/core-rest-pipeline': 1.20.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.12.0 + '@azure/logger': 1.2.0 + '@azure/msal-browser': 4.12.0 + '@azure/msal-node': 3.5.3 + open: 10.1.1 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.2.0': + dependencies: + '@typespec/ts-http-runtime': 0.2.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/msal-browser@4.12.0': + dependencies: + '@azure/msal-common': 15.6.0 + + '@azure/msal-common@15.6.0': {} + + '@azure/msal-node@3.5.3': + dependencies: + '@azure/msal-common': 15.6.0 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + '@colors/colors@1.5.0': optional: true @@ -3123,6 +3305,14 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 + '@typespec/ts-http-runtime@0.2.2': + dependencies: + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + '@ungap/structured-clone@1.3.0': {} '@vitest/expect@3.1.2': @@ -3340,6 +3530,8 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-equal-constant-time@1.0.1: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.0.0 @@ -3549,6 +3741,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} emoji-regex@10.4.0: {} @@ -4141,6 +4337,13 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 @@ -4389,6 +4592,19 @@ snapshots: dependencies: minimist: 1.2.8 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.1 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -4396,6 +4612,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -4435,8 +4662,22 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + log-update@6.1.0: dependencies: ansi-escapes: 7.0.0 @@ -5158,6 +5399,8 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@2.8.1: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5232,6 +5475,8 @@ snapshots: dependencies: react: 18.3.1 + uuid@8.3.2: {} + v8-compile-cache-lib@3.0.1: {} vary@1.1.2: {} From 0799cd39d967a2898194ace6a9a2281e3146e457 Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Wed, 21 May 2025 00:49:09 +0000 Subject: [PATCH 2/4] extends Azure OpenAI client support - uses Entra/AD auth if azure API key is not set - supports AZURE_OPENAI_DEPLOYMENT to specify deployment name - refactors agent-loop to use `createOpenAIClient` - supports HTTPS_PROXY, from the implementation in agent-loop --- README.md | 5 +- codex-cli/src/utils/agent/agent-loop.ts | 66 +++---------------------- codex-cli/src/utils/config.ts | 7 +-- codex-cli/src/utils/openai-client.ts | 63 +++++++++++++++++------ 4 files changed, 61 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 24f362f77..32738c0ac 100644 --- a/README.md +++ b/README.md @@ -468,8 +468,9 @@ For each AI provider, you need to set the corresponding API key in your environm export OPENAI_API_KEY="your-api-key-here" # Azure OpenAI -export AZURE_OPENAI_API_KEY="your-azure-api-key-here" -export AZURE_OPENAI_API_VERSION="2025-03-01-preview" (Optional) +export AZURE_OPENAI_API_KEY="your-azure-api-key-here" # (optional; uses Entra authentication if not set) +export AZURE_OPENAI_API_VERSION="2025-03-01-preview" # (optional) +export AZURE_OPENAI_DEPLOYMENT="my-deployment" # (optional; set this if your deployment name does not match the model name) # OpenRouter export OPENROUTER_API_KEY="your-openrouter-key-here" diff --git a/codex-cli/src/utils/agent/agent-loop.ts b/codex-cli/src/utils/agent/agent-loop.ts index cc57239b4..38734b7f3 100644 --- a/codex-cli/src/utils/agent/agent-loop.ts +++ b/codex-cli/src/utils/agent/agent-loop.ts @@ -13,13 +13,6 @@ import type { import type { Reasoning } from "openai/resources.mjs"; import { CLI_VERSION } from "../../version.js"; -import { - OPENAI_TIMEOUT_MS, - OPENAI_ORGANIZATION, - OPENAI_PROJECT, - getBaseUrl, - AZURE_OPENAI_API_VERSION, -} from "../config.js"; import { log } from "../logger/log.js"; import { parseToolCallArguments } from "../parsers.js"; import { responsesCreateViaChatCompletions } from "../responses.js"; @@ -31,10 +24,10 @@ import { } from "../session.js"; import { applyPatchToolInstructions } from "./apply-patch.js"; import { handleExecCommand } from "./handle-exec-command.js"; -import { HttpsProxyAgent } from "https-proxy-agent"; +import { createOpenAIClient } from "../openai-client.js"; import { spawnSync } from "node:child_process"; import { randomUUID } from "node:crypto"; -import OpenAI, { APIConnectionTimeoutError, AzureOpenAI } from "openai"; +import OpenAI, { APIConnectionTimeoutError } from "openai"; import os from "os"; // Wait time before retrying after rate limit errors (ms). @@ -43,9 +36,6 @@ const RATE_LIMIT_RETRY_WAIT_MS = parseInt( 10, ); -// See https://github.com/openai/openai-node/tree/v4?tab=readme-ov-file#configuring-an-https-agent-eg-for-proxies -const PROXY_URL = process.env["HTTPS_PROXY"]; - export type CommandConfirmation = { review: ReviewDecision; applyPatch?: ApplyPatchCommand | undefined; @@ -115,7 +105,6 @@ const localShellTool: Tool = { export class AgentLoop { private model: string; - private provider: string; private instructions?: string; private approvalPolicy: ApprovalPolicy; private config: AppConfig; @@ -283,7 +272,6 @@ export class AgentLoop { additionalWritableRoots, }: AgentLoopParams & { config?: AppConfig }) { this.model = model; - this.provider = provider; this.instructions = instructions; this.approvalPolicy = approvalPolicy; @@ -293,6 +281,7 @@ export class AgentLoop { // `instructions` that have already been passed explicitly so that // downstream consumers (e.g. telemetry) still observe the correct values. this.config = config ?? { + provider, model, instructions: instructions ?? "", }; @@ -304,51 +293,12 @@ export class AgentLoop { this.disableResponseStorage = disableResponseStorage ?? false; this.sessionId = getSessionId() || randomUUID().replaceAll("-", ""); - // Configure OpenAI client with optional timeout (ms) from environment - const timeoutMs = OPENAI_TIMEOUT_MS; - const apiKey = this.config.apiKey ?? process.env["OPENAI_API_KEY"] ?? ""; - const baseURL = getBaseUrl(this.provider); - - this.oai = new OpenAI({ - // The OpenAI JS SDK only requires `apiKey` when making requests against - // the official API. When running unit‑tests we stub out all network - // calls so an undefined key is perfectly fine. We therefore only set - // the property if we actually have a value to avoid triggering runtime - // errors inside the SDK (it validates that `apiKey` is a non‑empty - // string when the field is present). - ...(apiKey ? { apiKey } : {}), - baseURL, - defaultHeaders: { - originator: ORIGIN, - version: CLI_VERSION, - session_id: this.sessionId, - ...(OPENAI_ORGANIZATION - ? { "OpenAI-Organization": OPENAI_ORGANIZATION } - : {}), - ...(OPENAI_PROJECT ? { "OpenAI-Project": OPENAI_PROJECT } : {}), - }, - httpAgent: PROXY_URL ? new HttpsProxyAgent(PROXY_URL) : undefined, - ...(timeoutMs !== undefined ? { timeout: timeoutMs } : {}), - }); - if (this.provider.toLowerCase() === "azure") { - this.oai = new AzureOpenAI({ - apiKey, - baseURL, - apiVersion: AZURE_OPENAI_API_VERSION, - defaultHeaders: { - originator: ORIGIN, - version: CLI_VERSION, - session_id: this.sessionId, - ...(OPENAI_ORGANIZATION - ? { "OpenAI-Organization": OPENAI_ORGANIZATION } - : {}), - ...(OPENAI_PROJECT ? { "OpenAI-Project": OPENAI_PROJECT } : {}), - }, - httpAgent: PROXY_URL ? new HttpsProxyAgent(PROXY_URL) : undefined, - ...(timeoutMs !== undefined ? { timeout: timeoutMs } : {}), - }); - } + this.oai = createOpenAIClient(this.config, { + originator: ORIGIN, + version: CLI_VERSION, + session_id: this.sessionId, + }); setSessionId(this.sessionId); setCurrentModel(this.model); diff --git a/codex-cli/src/utils/config.ts b/codex-cli/src/utils/config.ts index 51761bf6d..edef7391f 100644 --- a/codex-cli/src/utils/config.ts +++ b/codex-cli/src/utils/config.ts @@ -70,10 +70,12 @@ export let OPENAI_API_KEY = process.env["OPENAI_API_KEY"] || ""; export const AZURE_OPENAI_API_VERSION = process.env["AZURE_OPENAI_API_VERSION"] || "2025-03-01-preview"; +export const AZURE_OPENAI_DEPLOYMENT = process.env["AZURE_OPENAI_DEPLOYMENT"]; export const DEFAULT_REASONING_EFFORT = "high"; export const OPENAI_ORGANIZATION = process.env["OPENAI_ORGANIZATION"] || ""; export const OPENAI_PROJECT = process.env["OPENAI_PROJECT"] || ""; +export const HTTPS_PROXY_URL = process.env["HTTPS_PROXY"] || ""; // Can be set `true` when Codex is running in an environment that is marked as already // considered sufficiently locked-down so that we allow running without an explicit sandbox. @@ -126,11 +128,6 @@ export function getApiKey(provider: string = "openai"): string | undefined { return customApiKey; } - // If the provider not found in the providers list and `OPENAI_API_KEY` is set, use it - if (OPENAI_API_KEY !== "") { - return OPENAI_API_KEY; - } - // We tried. return undefined; } diff --git a/codex-cli/src/utils/openai-client.ts b/codex-cli/src/utils/openai-client.ts index fb8117fed..103924f27 100644 --- a/codex-cli/src/utils/openai-client.ts +++ b/codex-cli/src/utils/openai-client.ts @@ -4,10 +4,17 @@ import { getBaseUrl, getApiKey, AZURE_OPENAI_API_VERSION, + AZURE_OPENAI_DEPLOYMENT, OPENAI_TIMEOUT_MS, OPENAI_ORGANIZATION, OPENAI_PROJECT, + HTTPS_PROXY_URL, } from "./config.js"; +import { + DefaultAzureCredential, + getBearerTokenProvider, +} from "@azure/identity"; +import { HttpsProxyAgent } from "https-proxy-agent"; import OpenAI, { AzureOpenAI } from "openai"; type OpenAIClientConfig = { @@ -23,29 +30,55 @@ type OpenAIClientConfig = { */ export function createOpenAIClient( config: OpenAIClientConfig | AppConfig, + headers: Record = {}, ): OpenAI | AzureOpenAI { - const headers: Record = {}; - if (OPENAI_ORGANIZATION) { - headers["OpenAI-Organization"] = OPENAI_ORGANIZATION; - } - if (OPENAI_PROJECT) { - headers["OpenAI-Project"] = OPENAI_PROJECT; - } + const defaultHeaders: Record = { + ...(OPENAI_ORGANIZATION + ? { "OpenAI-Organization": OPENAI_ORGANIZATION } + : {}), + ...(OPENAI_PROJECT ? { "OpenAI-Project": OPENAI_PROJECT } : {}), + ...headers, + }; + + const apiKey = getApiKey(config.provider); + const httpAgent = HTTPS_PROXY_URL + ? new HttpsProxyAgent(HTTPS_PROXY_URL) + : undefined; + const baseURL = getBaseUrl(config.provider); + const timeout = OPENAI_TIMEOUT_MS; if (config.provider?.toLowerCase() === "azure") { + if (apiKey === undefined) { + const credential = new DefaultAzureCredential(); + const azureADTokenProvider = getBearerTokenProvider( + credential, + "https://cognitiveservices.azure.com/.default", + ); + return new AzureOpenAI({ + azureADTokenProvider, + baseURL, + timeout, + defaultHeaders, + httpAgent, + deployment: AZURE_OPENAI_DEPLOYMENT, + apiVersion: AZURE_OPENAI_API_VERSION, + }); + } + return new AzureOpenAI({ - apiKey: getApiKey(config.provider), - baseURL: getBaseUrl(config.provider), + apiKey, + baseURL, + timeout, + defaultHeaders, + httpAgent, apiVersion: AZURE_OPENAI_API_VERSION, - timeout: OPENAI_TIMEOUT_MS, - defaultHeaders: headers, }); } return new OpenAI({ - apiKey: getApiKey(config.provider), - baseURL: getBaseUrl(config.provider), - timeout: OPENAI_TIMEOUT_MS, - defaultHeaders: headers, + apiKey, + baseURL, + timeout, + defaultHeaders, }); } From cac382f8d8b6d8a36decac52718bd82bce4ea2b6 Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Wed, 21 May 2025 00:50:09 +0000 Subject: [PATCH 3/4] fix: Skips prompt for OpenAI key If provider is not openai --- codex-cli/src/cli.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codex-cli/src/cli.tsx b/codex-cli/src/cli.tsx index c7e5d9ff3..13a691997 100644 --- a/codex-cli/src/cli.tsx +++ b/codex-cli/src/cli.tsx @@ -340,7 +340,7 @@ if (cli.flags.login) { } catch { /* ignore */ } -} else if (!apiKey) { +} else if (!apiKey && provider.toLowerCase() === "openai") { apiKey = await fetchApiKey(client.issuer, client.client_id); } // Ensure the API key is available as an environment variable for legacy code @@ -363,7 +363,7 @@ if (cli.flags.free) { } // Set of providers that don't require API keys -const NO_API_KEY_REQUIRED = new Set(["ollama"]); +const NO_API_KEY_REQUIRED = new Set(["ollama", "azure"]); // Skip API key validation for providers that don't require an API key if (!apiKey && !NO_API_KEY_REQUIRED.has(provider.toLowerCase())) { From 451220b1673a28db9c139a907bf0b8f59e1a9fcc Mon Sep 17 00:00:00 2001 From: Mark Waddle Date: Wed, 21 May 2025 00:51:54 +0000 Subject: [PATCH 4/4] Removes redundant api-key check --- codex-cli/src/utils/model-utils.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/codex-cli/src/utils/model-utils.ts b/codex-cli/src/utils/model-utils.ts index 01a21c0a7..46a566918 100644 --- a/codex-cli/src/utils/model-utils.ts +++ b/codex-cli/src/utils/model-utils.ts @@ -1,7 +1,6 @@ import type { ResponseItem } from "openai/resources/responses/responses.mjs"; import { approximateTokensUsed } from "./approximate-tokens-used.js"; -import { getApiKey } from "./config.js"; import { type SupportedModelId, openAiModelInfo } from "./model-info.js"; import { createOpenAIClient } from "./openai-client.js"; @@ -16,11 +15,6 @@ export const RECOMMENDED_MODELS: Array = ["o4-mini", "o3"]; * lifetime of the process and the results are cached for subsequent calls. */ async function fetchModels(provider: string): Promise> { - // If the user has not configured an API key we cannot retrieve the models. - if (!getApiKey(provider)) { - throw new Error("No API key configured for provider: " + provider); - } - try { const openai = createOpenAIClient({ provider }); const list = await openai.models.list();