Repository navigation
feat(core): connect model providers over a Unix socket - #62743
divinity76 wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
There was a problem hiding this comment.
The feature works as described on Bun, but I couldn't check it end to end through the CLI. The added tests pass. That includes the core test that sends a request through the production app graph to a real Unix socket while a TCP listener on the base URL stays empty, plus the AI SDK path test. tsc --noEmit passes for packages/ai and packages/core. I couldn't run opencode run --standalone here because the standalone server failed to start (an environment problem).
Public API: it adds an optional socketPath provider setting to the config schema and the generated client types (ProviderSettings, ConfigProviderSettings), and exports unixSocketFetch from @opencode/ai/route. Both are additions, so existing configs and callers keep working.
Concerns (inline):
- The Node
node:httpfallback isn't tested. - The "a socket wins over a per-call fetch" rule is repeated at each request site.
- Media and decision models are copied without their constructors.
eed92b5 to
32e07aa
Compare
There was a problem hiding this comment.
The new commit only rewords the Unix socket paragraph in the docs. It doesn't touch the three earlier points (the untested Node fallback, the socket rule repeated at each request site, and copying models without their constructors), so those threads stay open. The public API is unchanged since the last review: the optional socketPath provider setting and the unixSocketFetch export, both backwards compatible. One small docs correction is inline.
32e07aa to
f8cdc83
Compare
There was a problem hiding this comment.
The earlier points are addressed: the untested node:http fallback is gone (non-Bun runtimes now get a clear rejection, not a TCP fallback); the socket-versus-per-call-fetch rule now lives in one place, RequestExecutor.withTransport, used by every request site; media and decision models are built with their constructors; and the docs now say the socket needs write permission. I reran the unix-socket, aisdk and provider tests in packages/ai and packages/core. All pass, including the production app graph test, where the TCP decoy stays empty. tsc --noEmit passes for both packages. Public API: adds the optional socketPath provider setting (config schema and generated client types) and exports unixSocketFetch from @opencode/ai/route; both are additions, so this is backwards compatible. No further concerns.
|
Thanks, I checked all three at
The unix-socket, aisdk and provider tests pass, including the production app-graph test, and |
# Conflicts: # packages/core/src/provider.ts
There was a problem hiding this comment.
The new commit is fine: an empty socketPath is now rejected when the config is read (socketPath must not be empty; omit it to use the base URL) instead of being ignored and sending requests to the base URL over TCP. The rest of the push is a merge of dev with no changes specific to this PR. All earlier points were already resolved. I reran the schema config test (including the new empty-path case), the unix-socket tests in packages/ai and packages/core, and the aisdk and provider tests; all pass. tsc --noEmit passes for packages/schema and packages/core. Public API: the optional socketPath provider setting (config schema and generated client types) must now be non-empty when set; the setting is new in this PR, so no existing config breaks. unixSocketFetch is still exported from @opencode/ai/route. No new concerns.
There was a problem hiding this comment.
The only new commit merges dev into the branch; this PR's own changes are the same as in the version I last reviewed. After the merge, the unix-socket, aisdk and provider tests in packages/ai and packages/core, and the schema config test, all still pass. The public API is unchanged since the last review: the optional, non-empty socketPath provider setting (config schema and generated client types) and the unixSocketFetch export from @opencode/ai/route, both backwards compatible. No new concerns.
There was a problem hiding this comment.
The new commit is a small, correct fix. An empty socketPath is no longer treated as unset in the model resolver, so a model with one doesn't keep a websocket transport (which would connect over TCP), and media and decision models pass it on for the socket transport to reject. This is a fallback; the config schema already rejects an empty socketPath. The new model-resolver test is small and clear. I ran the packages/core model-resolver, unix-socket, aisdk and provider tests (all pass), and tsc --noEmit passes for packages/core. No earlier points were open. The public API is unchanged since the last review: the optional, non-empty socketPath provider setting and the unixSocketFetch export from @opencode/ai/route, both backwards compatible. No new concerns.
There was a problem hiding this comment.
The only new commit merges dev; this PR's own changes are unchanged. The one spot where dev touched nearby code (the media transport in packages/ai/src/route/media.ts) merged cleanly. The unix-socket tests in packages/ai and packages/core, the model-resolver, aisdk and provider tests, and the schema config test all still pass, and tsc --noEmit passes for packages/ai. The public API is unchanged since the last review: the optional, non-empty socketPath provider setting (config schema and generated client types) and the unixSocketFetch export from @opencode/ai/route, both backwards compatible. No new concerns.

Issue for this PR
OpenCode can only reach a model provider over TCP. When a local server (llama.cpp, Ollama, vLLM, a company gateway) already listens on a Unix socket, the only option today is a
socatbridge that exposes a loopback TCP port. This adds a transport setting so provider traffic dials the socket directly: no port opened, no TCP handshake/overhead, no TLS encrypt/decrypt work, and access control is the socket's file permissions. OpenAI Chat Completions and the Responses API are covered on both the native and AI SDK paths.Type of change
What does this PR do?
Adds provider-level
settings.socketPath:{ "providers": { "local": { "package": "@opencode/ai/providers/openai-compatible", "settings": { "baseURL": "http://localhost/v1", "socketPath": "/run/llm.sock" }, "models": { "local-model": {} } } } }curl --unix-socket. It must behttp:, because the transport is plain HTTP over the socket, with no TLS layer to encrypt or decrypt.socketPathis a named setting, soProvider.nativeSettingsstrips it from provider package options.RequestExecutor.withTransportis the one place that decides the transport. It builds the per-call middleware withunixSocketFetch(needed because the app graph pins its own fetch per response) and makes a configured socket win over a per-callfetch, so a new send site cannot accidentally go over TCP.prepareOptionspicks the socket fetch oversettings.fetchandoutbound, keeping the existing timeout and SSE wrapper.fetch(url, { unix }). On non-Bun runtimessocketPathis rejected with a clear error instead of falling back to TCP; the first hand-writtennode:httpfallback was removed during review because it was untested client code.transport: "websocket"is forced to HTTP, and media/decision models get the setting too.Limitation: built-in Ollama, vLLM and LM Studio auto-discovery only probes HTTP URLs, so
socketPathapplies to explicitly configured providers and models. This is documented.How did you verify your code works?
packages/ai/test/unix-socket.test.ts: 8 tests against a realBun.serve({ unix }), including a TCP decoy that must stay empty. Covers JSON POST, path/headers, streaming SSE before completion, abort while waiting for headers and while reading the body, redirects, https rejection, empty path, and a missing socket. Reran 10x.packages/core/test/unix-socket.test.ts: production app graph, not a mock. This test fails with the first implementation (which the reviewer caught) and passes now; the TCP decoy receives nothing.packages/core/test/aisdk.test.tssends a request through a socket while the model's base URL points at an unused host.packages/core/test/provider.test.tsassertssocketPathis stripped from native package settings.socketPathis rejected at config decode; live, before the fix it silently hit a TCP decoy (HTTP 501), after the fix the decoy receives no request. A live TCP listener on the base URL port also receives nothing when a socket is configured, which exercises the provider-setting merge throughModelResolver.opencode run --standalonewithbaseURL: "http://127.0.0.1:59999/v1"(no listener on that port) plussocketPath, against a fake OpenAI-compatible streaming server on the socket. The model replied, so the request can only have gone over the socket.packages/ai,packages/core,packages/schema; fullpackages/aisuite 2056 pass / 0 fail; docs build succeeded; oxlint clean.Checklist
Implemented with AI assistance (OpenCode agent, DeepSeek V4.1 Flash). The submitter must review, comprehend and test every line.
Added by the opencode bot (AI-generated).
Public API: adds the optional non-empty
socketPathprovider setting (config schema, client types) and exportsunixSocketFetchfrom@opencode/ai/route(backwards compatible)