Let the AI features go out through a corporate proxy - #185
Merged
Conversation
The LLM HttpClient hard-coded UseProxy=false, so in networks where outbound traffic is only allowed through a proxy the AI features could not reach a cloud endpoint at all — and no configuration key existed to change that. Adds a global Llm:Proxy block with Mode = Off (default, unchanged behaviour) | System (the proxy the service account's OS is configured with) | Custom (own address plus bypass globs), with Basic or Windows-integrated credentials. One block per installation rather than one per profile: the mixed case — cloud model through the proxy, local Ollama direct — is what the bypass list is for, and a single block keeps one handler with one connection pool. The proxy deliberately does not live in the SocketsHttpHandler. That handler is built once per handler lifetime, so binding the config there would have made the whole Llm settings section restart-required, the way RestApi is. Instead LlmConfiguredProxy implements IWebProxy and resolves IOptionsMonitor per request, which keeps the section hot-reloadable including the Enabled kill-switch. In Off mode it bypasses every destination, making "no proxy configured" byte-for-byte the direct connection this client made before. Note the security trade-off, documented at the type and in the reference: with a proxy in the path the proxy resolves destination DNS, so the connect-time link-local guard only covers the proxy endpoint. The destination stays protected by the literal BaseUrl check that runs on every save and at boot. No mandatory allow-list as restApi has — the LLM BaseUrl is a single Admin-only value, not a per-step URL assembled from trigger payloads. Proxy rules are validated in LlmProfileValidation, which both AddNodePilotAi and LlmConfigBootValidator run, so a save that is accepted cannot block the next boot. The bypass-glob translation moves to NodePilot.Core.Net.ProxyBypassPattern so the Engine and Ai stacks share it instead of drifting apart.
The LLM HttpClient hard-codes UseProxy=false, so in a network where outbound traffic is only allowed through a proxy the AI features cannot reach a cloud endpoint at all and no configuration key exists to change that. The fix was written on 2026-08-06, pushed, and then never opened as a pull request, so it sat outside main while the symptom kept being reported. This merges that work into current main. Everything auto-merged except the IntegrationsSection imports, where main had since added refreshAiCapabilities next to the SectionFormHelpers import the proxy form needs; both are kept.
…the proxy
The LLM card renders its heading while the section snapshot is still loading, so
`getByRole('button', {name: /test/}).last()` could resolve to SMTP's Test button
in the window before the profile form mounts — the test then drove the SMTP probe
and timed out waiting for the LLM one. It was already a race on main; the proxy
form widened it enough to fail a full-file run. Scoping the lookup to the LLM
card removes the ordering assumption instead of adding a wait.
Also adds the missing pointer to Llm:Proxy:Mode in the docs-site appsettings
overview, which lists the other opt-in connection settings.
Sev7eNup
added a commit
that referenced
this pull request
Aug 11, 2026
Carries the corporate-proxy support for the AI features (#185): Llm:Proxy:Mode = Off | System | Custom, so an installation whose outbound traffic only leaves through a proxy can reach a cloud LLM endpoint at all. Bumped through `npm version --no-git-tag-version` per package rather than a text replace over the tree, so only the top-level version and its lockfile root entry move.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
In a network where outbound traffic is only allowed through a proxy, the AI features cannot reach a cloud endpoint at all: the LLM
HttpClienthard-codesUseProxy = false, and no configuration key exists to change that. Every call runs into its timeout and surfaces as "LLM endpoint did not respond within {TimeoutSeconds}s", including the Settings "Test connection" button, which shares the same named client.The fix was written on 2026-08-06 and pushed to
claude/llm-endpoint-test-context-wsl22m, but no pull request was ever opened, so it stayed outsidemainwhile the symptom kept being reported. This lands it on currentmain.What
A global
Llm:Proxyblock covering every outbound LLM call — both chats, script and workflow generation, thellmQueryactivity, and the settings probe.ModeOffOff= direct,System= the proxy the service account's OS is configured with (incl. its own bypass rules),Custom= the address belowAddress""CustomBypassList[]CustomonlyUsername/PasswordnullUseDefaultCredentialsfalseOne block per installation rather than one per profile: the mixed case — cloud model through the proxy, local Ollama direct — is what the bypass list is for, and a single block keeps one handler with one connection pool.
The proxy deliberately does not live in the
SocketsHttpHandler. That handler is built once per handler lifetime, so binding the config there would have made the wholeLlmsettings section restart-required, the wayRestApiis. InsteadLlmConfiguredProxyimplementsIWebProxyand resolvesIOptionsMonitorper request, which keeps the section hot-reloadable. InOffmode it bypasses every destination, making "no proxy configured" byte-for-byte the direct connection this client made before.Security trade-off, documented at the type and in the reference: with a proxy in the path the proxy resolves destination DNS, so the connect-time link-local guard only covers the proxy endpoint. The destination stays protected by the literal
BaseUrlcheck that runs on every save and at boot. No mandatory allow-list asrestApihas — the LLMBaseUrlis a single Admin-only value, not a per-step URL assembled from trigger payloads.Proxy rules are validated in
LlmProfileValidation, which bothAddNodePilotAiandLlmConfigBootValidatorrun, so a save that is accepted cannot block the next boot. The bypass-glob translation moves toNodePilot.Core.Net.ProxyBypassPatternso the Engine and Ai stacks share it instead of drifting apart.Landing notes
Only one merge conflict: the
IntegrationsSectionimports, wheremainhad since addedrefreshAiCapabilitiesnext to theSectionFormHelpersimport the proxy form needs. Both are kept.One E2E test needed a fix that is not part of the original commit.
38.3located the LLM Test button with a page-wide.last(). The LLM card renders its heading while the section snapshot is still loading, so that could resolve to SMTP's Test button before the profile form mounts — the test then drove the SMTP probe and timed out. It was already a race onmain; the extra proxy form widened it enough to fail a full-file run. The lookup is now scoped to the LLM card.Verification
dotnet buildclean; full backend suite green — 5404 tests across six projects.tsc --noEmitclean, 2540 vitest tests green,eslintat 0 errors / 13 warnings (thelint:cicap).admin-settingsverified againstmainfirst to confirm the failure was introduced here and not pre-existing.Mode: Offstill connects directly, covered byLlmConfiguredProxyTests.