From a400486f16e25887b1fc3c19b8d050ba406ce4d0 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 6 Jul 2026 12:11:20 -0700 Subject: [PATCH 1/6] Add fetch-native proxy support (HTTPS_PROXY / HTTP_PROXY / NO_PROXY) Route model requests through a proxy using undici's ProxyAgent as the fetch dispatcher. createLanguageModel auto-detects the standard HTTPS_PROXY / HTTP_PROXY / ALL_PROXY variables and honors NO_PROXY via EnvHttpProxyAgent; createOpenAILanguageModel and createAzureOpenAILanguageModel accept an explicit { proxyUrl } via a new LanguageModelOptions argument. undici is an optional, lazily-imported dependency, so non-proxy users are unaffected. Supersedes #55 and #73, which were built on the removed axios stack. --- typescript/examples/README.md | 19 +++++ typescript/package-lock.json | 17 +++- typescript/package.json | 7 +- typescript/src/model.ts | 153 +++++++++++++++++++++++++++++++--- 4 files changed, 183 insertions(+), 13 deletions(-) diff --git a/typescript/examples/README.md b/typescript/examples/README.md index 01751304..34890ba3 100644 --- a/typescript/examples/README.md +++ b/typescript/examples/README.md @@ -92,6 +92,25 @@ AZURE_OPENAI_ENDPOINT=... AZURE_OPENAI_API_KEY=... ``` +### Using a proxy (optional) + +If the model endpoint can only be reached through a proxy (for example in restricted regions), +`createLanguageModel` honors the standard `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` environment +variables, and `NO_PROXY` for hosts that should bypass the proxy — set them (e.g. in your `.env` +file) and requests are routed accordingly: + +```ini +HTTPS_PROXY=http://127.0.0.1:7890 +# Optional: comma-separated hosts that should bypass the proxy +NO_PROXY=localhost,127.0.0.1 +``` + +Proxy support uses [`undici`](https://www.npmjs.com/package/undici) (which also powers Node's +built-in `fetch`). It is an optional dependency loaded only when a proxy is configured, so install +it when you need this: `npm install undici`. When calling `createOpenAILanguageModel` or +`createAzureOpenAILanguageModel` directly, pass the proxy explicitly via the `options` argument: +`createOpenAILanguageModel(apiKey, model, endPoint, org, undefined, { proxyUrl })`. + ## Step 4: Run the examples Examples can be found in the `examples` directory. diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 08af1c11..c70d5145 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -13,19 +13,24 @@ "./examples/*" ], "devDependencies": { - "@types/node": "^20.10.4" + "@types/node": "^20.10.4", + "undici": "^6.27.0" }, "engines": { "node": ">=18" }, "peerDependencies": { "typescript": "^5.3.3", + "undici": "^6.0.0", "zod": "^4.4.3" }, "peerDependenciesMeta": { "typescript": { "optional": true }, + "undici": { + "optional": true + }, "zod": { "optional": true } @@ -3087,6 +3092,16 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/typescript/package.json b/typescript/package.json index ec551934..f93df3b2 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -48,6 +48,7 @@ ], "peerDependencies": { "typescript": "^5.3.3", + "undici": "^6.0.0", "zod": "^4.4.3" }, "peerDependenciesMeta": { @@ -56,10 +57,14 @@ }, "zod": { "optional": true + }, + "undici": { + "optional": true } }, "devDependencies": { - "@types/node": "^20.10.4" + "@types/node": "^20.10.4", + "undici": "^6.27.0" }, "workspaces": [ "./", diff --git a/typescript/src/model.ts b/typescript/src/model.ts index 46f1cb95..f9987328 100644 --- a/typescript/src/model.ts +++ b/typescript/src/model.ts @@ -76,6 +76,44 @@ export interface TypeChatLanguageModel { complete(prompt: string | PromptSection[]): Promise>; } +/** + * Optional settings accepted by the language model factory functions. + */ +export interface LanguageModelOptions { + /** + * URL of an HTTP or HTTPS proxy to route model requests through + * (for example `"http://127.0.0.1:7890"`). This is useful in environments where the model + * endpoint can only be reached via a proxy. + * + * Requests are dispatched through the proxy using an `undici` `ProxyAgent`. `undici` is an + * optional dependency that is imported only when a proxy is configured; install it with + * `npm install undici`. + * + * When you use {@link createLanguageModel}, proxy configuration is read automatically from the + * standard `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` environment variables, and `NO_PROXY` is + * honored, so setting those (e.g. in a `.env` file) is usually all that is required. + */ + proxyUrl?: string; +} + +/** + * Internal proxy configuration threaded to the fetch helpers. + * - `url`: an explicit proxy URL supplied via {@link LanguageModelOptions.proxyUrl}. + * - `env`: values collected from the standard proxy environment variables, applied per request by + * an `undici` `EnvHttpProxyAgent` (which also honors `NO_PROXY` and the http/https split). + */ +type ProxySettings = + | { kind: "url"; url: string } + | { kind: "env"; httpProxy: string | undefined; httpsProxy: string | undefined; noProxy: string | undefined }; + +/** + * {@link LanguageModelOptions} plus internal, pre-resolved proxy settings passed down by + * {@link createLanguageModel}. Not part of the public API. + */ +interface InternalModelOptions extends LanguageModelOptions { + proxy?: ProxySettings; +} + /** * Creates a language model encapsulation of an OpenAI or Azure OpenAI REST API endpoint * chosen by environment variables. @@ -94,17 +132,19 @@ export interface TypeChatLanguageModel { * @returns An instance of `TypeChatLanguageModel`. */ export function createLanguageModel(env: Record): TypeChatLanguageModel { + const proxy = proxySettingsFromEnv(env); + const options: InternalModelOptions | undefined = proxy ? { proxy } : undefined; if (env.OPENAI_API_KEY) { const apiKey = env.OPENAI_API_KEY ?? missingEnvironmentVariable("OPENAI_API_KEY"); const model = env.OPENAI_MODEL ?? missingEnvironmentVariable("OPENAI_MODEL"); const org = env.OPENAI_ORGANIZATION ?? ""; const endPoint = env.OPENAI_ENDPOINT ?? "https://api.openai.com/v1/chat/completions"; - return createOpenAILanguageModel(apiKey, model, endPoint, org); + return createOpenAILanguageModel(apiKey, model, endPoint, org, undefined, options); } if (env.AZURE_OPENAI_API_KEY) { const apiKey = env.AZURE_OPENAI_API_KEY ?? missingEnvironmentVariable("AZURE_OPENAI_API_KEY"); const endPoint = env.AZURE_OPENAI_ENDPOINT ?? missingEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); - return createAzureOpenAILanguageModel(apiKey, endPoint); + return createAzureOpenAILanguageModel(apiKey, endPoint, options); } missingEnvironmentVariable("OPENAI_API_KEY or AZURE_OPENAI_API_KEY"); } @@ -125,17 +165,19 @@ export function createLanguageModel(env: Record): Ty * @param useResponsesApi When `true`, forces the Responses API regardless of the endpoint URL. * When `false`, forces Chat Completions. When `undefined` (default), the API variant is * inferred from the endpoint URL. + * @param options Optional settings such as a proxy URL. See {@link LanguageModelOptions}. * @returns An instance of `TypeChatLanguageModel`. */ -export function createOpenAILanguageModel(apiKey: string, model: string, endPoint = "https://api.openai.com/v1/chat/completions", org = "", useResponsesApi?: boolean): TypeChatLanguageModel { +export function createOpenAILanguageModel(apiKey: string, model: string, endPoint = "https://api.openai.com/v1/chat/completions", org = "", useResponsesApi?: boolean, options?: LanguageModelOptions): TypeChatLanguageModel { const headers = { "Authorization": `Bearer ${apiKey}`, "OpenAI-Organization": org }; + const proxy = resolveProxySettings(options); if ((useResponsesApi ?? isResponsesApiUrl(endPoint))) { - return createResponsesFetchLanguageModel(endPoint, headers, { model }); + return createResponsesFetchLanguageModel(endPoint, headers, { model }, proxy); } - return createFetchLanguageModel(endPoint, headers, { model }); + return createFetchLanguageModel(endPoint, headers, { model }, proxy); } /** @@ -144,34 +186,38 @@ export function createOpenAILanguageModel(apiKey: string, model: string, endPoin * "https://{your-resource-name}.openai.azure.com/openai/deployments/{your-deployment-name}/chat/completions?api-version={API-version}". * Example deployment names are "gpt-35-turbo" and "gpt-4". An example API versions is "2023-05-15". * @param apiKey The Azure OpenAI API key. + * @param options Optional settings such as a proxy URL. See {@link LanguageModelOptions}. * @returns An instance of `TypeChatLanguageModel`. */ -export function createAzureOpenAILanguageModel(apiKey: string, endPoint: string): TypeChatLanguageModel { +export function createAzureOpenAILanguageModel(apiKey: string, endPoint: string, options?: LanguageModelOptions): TypeChatLanguageModel { const headers = { // Needed when using managed identity "Authorization": `Bearer ${apiKey}`, // Needed when using regular API key "api-key": apiKey }; - return createFetchLanguageModel(endPoint, headers, {}); + return createFetchLanguageModel(endPoint, headers, {}, resolveProxySettings(options)); } /** * Common OpenAI REST API endpoint encapsulation using the fetch API. */ -function createFetchLanguageModel(url: string, headers: object, defaultParams: object) { +function createFetchLanguageModel(url: string, headers: object, defaultParams: object, proxy?: ProxySettings) { + let dispatcherPromise: Promise | undefined; const model: TypeChatLanguageModel = { complete }; return model; async function complete(prompt: string | PromptSection[]) { + dispatcherPromise ??= resolveProxyDispatcher(proxy); + const dispatcher = await dispatcherPromise; let retryCount = 0; const retryMaxAttempts = model.retryMaxAttempts ?? 3; const retryPauseMs = model.retryPauseMs ?? 1000; const messages = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt; while (true) { - const options = { + const options: RequestInit = { method: "POST", body: JSON.stringify({ ...defaultParams, @@ -183,6 +229,9 @@ function createFetchLanguageModel(url: string, headers: object, defaultParams: o "content-type": "application/json", ...headers } + }; + if (dispatcher) { + options.dispatcher = dispatcher; } const response = await fetch(url, options); if (response.ok) { @@ -228,19 +277,22 @@ function createFetchLanguageModel(url: string, headers: object, defaultParams: o * @param headers HTTP headers to include in every request (e.g. `Authorization`). * @param defaultParams Additional JSON body parameters merged into every request (e.g. `{ model }`). */ -function createResponsesFetchLanguageModel(url: string, headers: object, defaultParams: object) { +function createResponsesFetchLanguageModel(url: string, headers: object, defaultParams: object, proxy?: ProxySettings) { + let dispatcherPromise: Promise | undefined; const model: TypeChatLanguageModel = { complete }; return model; async function complete(prompt: string | PromptSection[]) { + dispatcherPromise ??= resolveProxyDispatcher(proxy); + const dispatcher = await dispatcherPromise; let retryCount = 0; const retryMaxAttempts = model.retryMaxAttempts ?? 3; const retryPauseMs = model.retryPauseMs ?? 1000; const input = typeof prompt === "string" ? prompt : (prompt as PromptSection[]); while (true) { - const options = { + const options: RequestInit = { method: "POST", body: JSON.stringify({ ...defaultParams, @@ -251,6 +303,9 @@ function createResponsesFetchLanguageModel(url: string, headers: object, default "content-type": "application/json", ...headers } + }; + if (dispatcher) { + options.dispatcher = dispatcher; } const response = await fetch(url, options); if (response.ok) { @@ -332,6 +387,82 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } +/** + * Collects proxy configuration from the standard proxy environment variables, or returns + * `undefined` when none are set. `HTTPS_PROXY`/`HTTP_PROXY` select the proxy per request protocol, + * `ALL_PROXY` is the fallback for both, and `NO_PROXY` lists hosts that bypass the proxy. Both + * upper- and lower-case names are recognized; matching is applied per request by an + * `EnvHttpProxyAgent`. + */ +function proxySettingsFromEnv(env: Record): ProxySettings | undefined { + const allProxy = env.ALL_PROXY ?? env.all_proxy; + const httpsProxy = env.HTTPS_PROXY ?? env.https_proxy ?? allProxy; + const httpProxy = env.HTTP_PROXY ?? env.http_proxy ?? allProxy; + if (!httpsProxy && !httpProxy) { + return undefined; + } + return { kind: "env", httpProxy, httpsProxy, noProxy: env.NO_PROXY ?? env.no_proxy }; +} + +/** + * Resolves the {@link ProxySettings} for a set of options: an explicit `proxyUrl` becomes a + * single-URL proxy, while {@link createLanguageModel} passes pre-resolved environment settings. + */ +function resolveProxySettings(options: LanguageModelOptions | undefined): ProxySettings | undefined { + const internal = options as InternalModelOptions | undefined; + if (internal?.proxy) { + return internal.proxy; + } + if (options?.proxyUrl) { + return { kind: "url", url: options.proxyUrl }; + } + return undefined; +} + +/** + * Dynamically imports the optional `undici` package, translating a missing-module error into an + * actionable message. `undici` also powers Node's built-in `fetch`, so it is the natural choice for + * proxy dispatching and is imported only when a proxy is actually configured. + */ +async function importUndici() { + try { + return await import("undici"); + } catch (e) { + if (e instanceof Error && /Cannot find module|ERR_MODULE_NOT_FOUND/.test(e.message)) { + throw new Error( + `A proxy was configured, but the optional "undici" package is not installed. ` + + `Run "npm install undici" to enable proxy support.` + ); + } + throw e; + } +} + +/** + * Lazily constructs an `undici` dispatcher for the given proxy settings, or returns `undefined` + * when no proxy is configured. Absent settings are a no-op, so an agent is never accidentally + * constructed from an empty proxy string. Node's `fetch` honors an undici `dispatcher` per request. + */ +async function resolveProxyDispatcher(proxy: ProxySettings | undefined): Promise { + if (!proxy) { + return undefined; + } + const { ProxyAgent, EnvHttpProxyAgent } = await importUndici(); + // `undici` bundles its own dispatcher types, which are structurally identical to the + // `undici-types` copy that Node's global `fetch`/`RequestInit` use but nominally distinct; + // bridge the two with a cast. + if (proxy.kind === "url") { + return new ProxyAgent(proxy.url) as unknown as RequestInit["dispatcher"]; + } + // Env-driven: EnvHttpProxyAgent applies NO_PROXY and the http/https split per request. + const envOptions = { + ...(proxy.httpProxy ? { httpProxy: proxy.httpProxy } : {}), + ...(proxy.httpsProxy ? { httpsProxy: proxy.httpsProxy } : {}), + ...(proxy.noProxy ? { noProxy: proxy.noProxy } : {}) + }; + return new EnvHttpProxyAgent(envOptions) as unknown as RequestInit["dispatcher"]; +} + /** * Throws an exception for a missing environment variable. */ From 19b58b84af6cedae75e46337f65ced87bd86f801 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 6 Jul 2026 12:26:33 -0700 Subject: [PATCH 2/6] Fold useResponsesApi into LanguageModelOptions Replace the positional useResponsesApi parameter on createOpenAILanguageModel with a useResponsesApi field on LanguageModelOptions, collapsing the trailing (flag, options) pair into a single options bag. Neither the parameter nor the options object shipped in the published 0.1.2, so this is not a breaking change. --- typescript/src/model.ts | 22 ++++++++++++++-------- typescript/tests/model.test.mjs | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/typescript/src/model.ts b/typescript/src/model.ts index f9987328..39abf6cb 100644 --- a/typescript/src/model.ts +++ b/typescript/src/model.ts @@ -94,6 +94,14 @@ export interface LanguageModelOptions { * honored, so setting those (e.g. in a `.env` file) is usually all that is required. */ proxyUrl?: string; + + /** + * Selects the OpenAI API variant. When `true`, the Responses API (`/v1/responses`) is used + * regardless of the endpoint URL; when `false`, the Chat Completions API is used. When omitted + * (default), the variant is inferred from the endpoint URL (a path ending in `/responses` + * selects the Responses API). Only applies to `createOpenAILanguageModel`. + */ + useResponsesApi?: boolean; } /** @@ -139,7 +147,7 @@ export function createLanguageModel(env: Record): Ty const model = env.OPENAI_MODEL ?? missingEnvironmentVariable("OPENAI_MODEL"); const org = env.OPENAI_ORGANIZATION ?? ""; const endPoint = env.OPENAI_ENDPOINT ?? "https://api.openai.com/v1/chat/completions"; - return createOpenAILanguageModel(apiKey, model, endPoint, org, undefined, options); + return createOpenAILanguageModel(apiKey, model, endPoint, org, options); } if (env.AZURE_OPENAI_API_KEY) { const apiKey = env.AZURE_OPENAI_API_KEY ?? missingEnvironmentVariable("AZURE_OPENAI_API_KEY"); @@ -152,7 +160,7 @@ export function createLanguageModel(env: Record): Ty /** * Creates a language model encapsulation of an OpenAI REST API endpoint. * - * When `endPoint` (or `useResponsesApi`) indicates the Responses API the function routes through + * When `endPoint` (or `options.useResponsesApi`) indicates the Responses API the function routes through * the `/v1/responses` request/response format; otherwise the Chat Completions format is used. * The Responses API is auto-detected when the endpoint URL path ends with `/responses` * (e.g. `https://api.openai.com/v1/responses`). @@ -162,19 +170,17 @@ export function createLanguageModel(env: Record): Ty * `"https://api.openai.com/v1/chat/completions"`. Supply a `/responses` URL to use the * Responses API instead. * @param org The OpenAI organization id. - * @param useResponsesApi When `true`, forces the Responses API regardless of the endpoint URL. - * When `false`, forces Chat Completions. When `undefined` (default), the API variant is - * inferred from the endpoint URL. - * @param options Optional settings such as a proxy URL. See {@link LanguageModelOptions}. + * @param options Optional settings such as a proxy URL or the API variant to use. + * See {@link LanguageModelOptions}. * @returns An instance of `TypeChatLanguageModel`. */ -export function createOpenAILanguageModel(apiKey: string, model: string, endPoint = "https://api.openai.com/v1/chat/completions", org = "", useResponsesApi?: boolean, options?: LanguageModelOptions): TypeChatLanguageModel { +export function createOpenAILanguageModel(apiKey: string, model: string, endPoint = "https://api.openai.com/v1/chat/completions", org = "", options?: LanguageModelOptions): TypeChatLanguageModel { const headers = { "Authorization": `Bearer ${apiKey}`, "OpenAI-Organization": org }; const proxy = resolveProxySettings(options); - if ((useResponsesApi ?? isResponsesApiUrl(endPoint))) { + if ((options?.useResponsesApi ?? isResponsesApiUrl(endPoint))) { return createResponsesFetchLanguageModel(endPoint, headers, { model }, proxy); } return createFetchLanguageModel(endPoint, headers, { model }, proxy); diff --git a/typescript/tests/model.test.mjs b/typescript/tests/model.test.mjs index a692fa19..a5f3e296 100644 --- a/typescript/tests/model.test.mjs +++ b/typescript/tests/model.test.mjs @@ -159,7 +159,7 @@ describe("createOpenAILanguageModel (Chat Completions API)", () => { test("uses Responses API when useResponsesApi=true regardless of URL", async () => { setupFetch([makeResponsesAPIResponse("Forced!")]); // Passing a chat/completions URL but forcing Responses API via the flag - const model = createOpenAILanguageModel("sk-test", "gpt-4", "https://api.openai.com/v1/chat/completions", "", true); + const model = createOpenAILanguageModel("sk-test", "gpt-4", "https://api.openai.com/v1/chat/completions", "", { useResponsesApi: true }); const result = await model.complete("test"); assert.equal(result.success, true); assert.equal(result.data, "Forced!"); From a3a0a48ebea69fa5ef39a5efc94ca20fd6d2e6bf Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 6 Jul 2026 12:26:34 -0700 Subject: [PATCH 3/6] Bump typechat version to 0.1.3 --- typescript/package-lock.json | 4 ++-- typescript/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/typescript/package-lock.json b/typescript/package-lock.json index c70d5145..5c329e08 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -1,12 +1,12 @@ { "name": "typechat", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "typechat", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "workspaces": [ "./", diff --git a/typescript/package.json b/typescript/package.json index f93df3b2..c283643e 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,7 +1,7 @@ { "name": "typechat", "author": "Microsoft", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "description": "TypeChat is an experimental library that makes it easy to build natural language interfaces using types.", "keywords": [ From 409b1c33214b189466049858355311ae34fb4d4e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:32:36 +0000 Subject: [PATCH 4/6] Fix pyright CI failure: pin pytest<9 for syrupy 5.x compatibility --- python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyproject.toml b/python/pyproject.toml index f8254042..ca57e56f 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ # Development-time dependencies. dev = [ "coverage[toml]>=6.5", - "pytest>=8.0.2", + "pytest>=8.0.2,<9", "syrupy>=5.0.0", ] From 182ebdb976524b0019949bd008be9d6929b8e7b5 Mon Sep 17 00:00:00 2001 From: robgruen Date: Mon, 6 Jul 2026 12:42:24 -0700 Subject: [PATCH 5/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- typescript/examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/examples/README.md b/typescript/examples/README.md index 34890ba3..62237a5a 100644 --- a/typescript/examples/README.md +++ b/typescript/examples/README.md @@ -109,7 +109,7 @@ Proxy support uses [`undici`](https://www.npmjs.com/package/undici) (which also built-in `fetch`). It is an optional dependency loaded only when a proxy is configured, so install it when you need this: `npm install undici`. When calling `createOpenAILanguageModel` or `createAzureOpenAILanguageModel` directly, pass the proxy explicitly via the `options` argument: -`createOpenAILanguageModel(apiKey, model, endPoint, org, undefined, { proxyUrl })`. +`createOpenAILanguageModel(apiKey, model, endPoint, org, { proxyUrl })` (or `createAzureOpenAILanguageModel(apiKey, endPoint, { proxyUrl })`). ## Step 4: Run the examples From 13cc91967a0c69e6cb28bc1401109897a914b6c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:47:20 +0000 Subject: [PATCH 6/6] Fix pyright CI pytest plugin failure by adding pytest-xdist --- python/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/python/pyproject.toml b/python/pyproject.toml index ca57e56f..ec6c955a 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ dev = [ "coverage[toml]>=6.5", "pytest>=8.0.2,<9", + "pytest-xdist>=3.8.0", "syrupy>=5.0.0", ]