Repository navigation
[FEATURE]: Plugins can't intercept outbound requests for AI-SDK models (http.request hook excludes the AI-SDK path) #42846
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
The runtime hook ctx.session.hook("http.request", (event) => { … event.request … }) lets a plugin mutate the outbound Web Request immediately before provider dispatch. Per the docs, though, it applies to native models only — AI-SDK-routed models don't pass through it:
From https://opencode.ai/v2/docs/build/plugins:
"HTTP hooks can modify requests and responses. They apply to native models; AI SDK models do not currently pass through these hooks. Request and response bodies are one-shot streams."
The separate ctx.aisdk.hook("sdk" | "language", …) capability covers the AI-SDK seam but exposes only the SDK/model objects, not the outbound HTTP request/body. So for any provider configured with a custom OpenAI-compatible baseURL (self-hosted proxies, gateways like a LiteLLM/router layer, local model servers) — all of which route through the AI SDK — a plugin currently cannot inspect or rewrite the outbound request at all.
Why it matters
This is the same motivation as #19425 ("pre/post API call hooks … for secret redaction and exfiltration prevention"), but for the AI-SDK path that the shipped hook skips:
-
Security / governance (primary). Redact secrets from outbound bodies, enforce egress policy, and audit/log exactly what leaves the machine — for self-hosted-proxy and gateway deployments, which are common in orgs that route everything through one controlled endpoint. Today none of this is possible for AI-SDK providers.
-
Payload shaping. Normalize or strip fields, inject required headers/tenant tags per
baseURL, etc. -
Request-body compression (concrete, broadly useful). Large, growing JSON contexts are re-uploaded every turn. Compressing the body + setting
Content-Encodingis a real, standardized optimization that multiple real targets already accept:- Google Vertex AI accepts
Content-Encoding: gzipongenerateContentrequest bodies (working... | gzip | curl -H "Content-Encoding: gzip"example: [Feature]: gzip upstream requests? BerriAI/litellm#3967). - Cerebras Inference documents accepting compressed request bodies on
/v1/chat/completions(Content-Encoding: gzip, msgpack+gzip): https://github.com/Cerebras/inference-examples/blob/main/payload-compression/simple_example.py. - Self-hosted OpenAI-compatible servers (vLLM/FastAPI/Uvicorn-based, and proxies) can decode
gzip/zstdrequest bodies — it's a standard server capability (e.g. FastAPI gzip-request middleware pattern).
Being explicit so this doesn't overclaim: OpenAI does not support request-body compression (confirmed in that same LiteLLM thread), and Anthropic/Azure/Bedrock don't document it either. The point is not "all providers" — it's that a plugin should be able to compress where the target accepts it (Vertex, Cerebras, self-hosted proxies), scoped by
baseURL, and leave the rest untouched. On a constrained uplink a ~600 KB (≈150K-token) body is tens of seconds of pure upload wait per round-trip; JSON compresses ~5–8×. - Google Vertex AI accepts
All three use cases require the one missing capability: a plugin-reachable seam that can rewrite the outbound request (headers + body) on the AI-SDK path, scoped by target.
Ask
Let plugins intercept/rewrite the outbound request for AI-SDK-routed models — either by extending http.request/http.response dispatch to cover the AI-SDK path, or by adding an outbound-request seam to ctx.aisdk.hook (whichever fits the architecture). The event should expose the request (headers + body, replaceable) and enough model/provider/baseURL context that a plugin can scope changes to a specific endpoint and leave all other providers untouched. If this is intentionally out of scope, documenting the supported alternative would also close the gap.
Happy to test a fix end-to-end against a live Content-Encoding: zstd/gzip-accepting endpoint.
Related
Narrows #19425 and #2661 (generic pre-provider / body-rewrite hook requests) to the now-actionable gap: the hook shipped, but the AI-SDK dispatch path is excluded.
Activity
github-actions commentedon Aug 16, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
[FEATURE]:.Please edit this issue to address the above within 2 hours, or it will be automatically closed.
If you believe this was flagged incorrectly, please let a maintainer know.
[-]Plugins can't intercept outbound requests for AI-SDK models (http.request hook excludes the AI-SDK path)[/-][+][FEATURE]: Plugins can't intercept outbound requests for AI-SDK models (http.request hook excludes the AI-SDK path)[/+]github-actions commentedon Aug 16, 2026
Thanks for updating the issue! It now meets the contributing guidelines. We appreciate the detailed write-up.
tespio commentedon Oct 12, 2026
Concrete instance of this gap, with a working implementation — because it is the same missing seam.
The case
A provider whose endpoint is reachable only through a corporate HTTP proxy, while every other
provider connects directly:
192.168.10.86:5005directly192.168.10.101:3128CONNECT 192.168.10.86:5005via the proxyHTTP/1.1 200 Connection establishedThe direct route is absent, not misconfigured. Exactly one destination is gated, so routing must be
scoped to one provider — which is the "scoped by target" requirement in this issue, not a separate
feature request.
Why the existing options do not cover it
HTTP_PROXYworks and is process-wide, so it applies to traffic that must not be proxied. Theconsequence is already tracked: #49796 is an open, reproduced issue where a process-wide proxy breaks
OpenCode's own local client, because it dials
http://0.0.0.0:<port>and the CLI does not rewritethat to loopback. A seam scoped by target cannot produce that failure.
NO_PROXYcan express "onedestination", but not two providers behind two different proxies, and it applies to every tool in the
process rather than to a request.
There is currently no hook that can express this, which is the claim of this issue. What exists
is a workaround, and it is worth reporting what it took, because all four points are properties of
the AI-SDK path rather than of the plugin:
What the workaround had to do
opencode-fetch-proxy(npm, MIT) wraps the globalfetchthat AI SDK providers fall back to, androutes per provider. It works in daily use against the topology above, including SSE streaming. Four
findings:
undici'sProxyAgentis not reliably importable from where a plugin runs — in the desktopsidecar it does not resolve, and
import { ProxyAgent } from "undici"then degrades to a directconnection. That is a silent failure: it presents as the proxy being unreachable. A hand-rolled
CONNECTtunnel overnode:net/node:tls(TLS negotiated inside the tunnel) is what actuallycarries traffic. A native seam should choose its transport by runtime detection, not assumption.
127.0.0.1with a proxyconfigured fails with
503 Service Unavailable, because the proxy has no route back to theworkstation's loopback — the same class as cli: service configured with hostname 0.0.0.0 is unreachable when an HTTP proxy is set #49796, and it surfaces as a hang rather than an error.
GET /v1/modelsmust be routed too, or the providerfails before the first completion. Any hook that only covers
/chat/completionsis insufficient.fetchpatch is process-wide, so the workaround reproduces the scope problem itexists to solve, one level down. That is the strongest argument for the seam this issue asks for.
The ask, restated from this use case
An outbound-request seam on the AI-SDK path that exposes enough provider/
baseURLcontext for aplugin to scope changes to one endpoint, with the ability to substitute the transport (not only
mutate headers/body). Header/body rewriting alone would not cover proxy routing, because the
connection itself has to go somewhere else.
Happy to test a fix end-to-end against a live corporate proxy and a live LAN target — the topology is
real and reproducible, and the workaround gives a reference implementation to compare against.
tespio commentedon Oct 12, 2026
Correction to the above: I described the implementation without linking it.
npm i opencode-fetch-proxy, MIT)The code referenced above is in
index.jsat that repo. Pointers to the four findings, for anyonereading this later:
CONNECTtunnel, becauseundici'sProxyAgentdoes not resolve here —https://github.com/tespio/opencode-fetch-proxy/blob/main/index.js#L577
https://github.com/tespio/opencode-fetch-proxy/blob/main/index.js#L375
options.proxyresolution —https://github.com/tespio/opencode-fetch-proxy/blob/main/index.js#L415
It is a workaround, not a proposed design, and the fourth finding above is the reason: a global
fetchpatch cannot scope itself, which is what this issue asks to fix properly.opencode-agent commentedon Oct 13, 2026
This is fixed on
dev: #50487 made the sessionhttp.request/http.responsehooks run on the AI SDK route too, and custom OpenAI-compatible providers (@ai-sdk/openai-compatible) now use the native route, where these hooks already applied. Plugins can inspect or replace outbound headers and bodies (redaction, compression, etc.) for both, scoped byproviderIDor the request URL.tespio commentedon Oct 14, 2026
Thanks — #50487 is the right fix for the header/body case, and routing custom
@ai-sdk/openai-compatibleproviders through the native path is a real improvement.One question, so I know whether the proxy case is covered or out of scope.
The note describes the seam as "inspect or replace outbound headers and bodies", with redaction and
compression as the examples. Both are transformations of a request that is already going to the same
place. A proxy changes where the connection goes — it is decided before any header exists, and for
httpsthe TLS session is established inside the tunnel to the proxy, not to the origin.Concretely, for this case the provider must be dialled through
CONNECT 192.168.10.86:5005at192.168.10.101:3128rather than directly. Nothing in the request changes; the socket does.So: does the new seam allow substituting the transport/fetch (connection level), or only rewriting
the request on the SDK's own fetch? If it is the former, this issue is genuinely closed and I will
retire the workaround. If it is the latter, the gap is narrower than when I filed this and worth
tracking separately as scope — not as a re-open.
The reason I ask rather than assume:
undici'sProxyAgentdoes not resolve where plugins run inthe desktop sidecar, and it fails by silently connecting directly, which presents as the proxy
being unreachable. Anything built on this seam for the proxy case needs the transport substitution to
be explicit, or that failure mode comes back.
Either way, happy to test on
devagainst the real topology and report back — a working referenceimplementation is linked in my earlier comment.
rekram1-node commentedon Oct 14, 2026
Yep, you can actually do this with
session.http.requestandsession.http.responsewithout patchingglobalThis.fetch.Since both
event.requestandevent.responseare replaceable WebRequest/Responseobjects, you have full control over what gets sent and what response stream gets handed back.Here's a basic example of how you could run your
CONNECTtunnel in-memory without opening a local port, by turning the defaultfetchinto a no-opdata:request and swapping in your tunnelled response: