feat(copilot): opt-in HTTP cache for the Node fetch fetcher (#317721)
* feat(copilot): opt-in HTTP cache for the Node fetch fetcher
Adds an opportunistic cache support to the Copilot Node fetch path. The cache
is strictly opt-in per request and composes with the existing VSCode proxy
and CA-injection patch.
- `__vscodeCreateFetchPatch({ interceptors })` lets the extension host
build a second proxy-aware `fetch` with extra undici interceptors. The
default `__vscodePatchedFetch` is unchanged.
- `NodeFetchFetcher` builds an undici cache interceptor once at
construction time and uses the factory to produce a `cachedFetch` that
routes through both the proxy patch and the cache. Requests are tagged
with an internal `__copilotCachePatch` marker (stripped before fetch);
unmarked requests keep going through the regular patched fetch. When
the host lacks the factory, caching is silently disabled so requests
never bypass the proxy patch.
- `FetchOptions.cache?: boolean` — opportunistic hint. Fetchers without
cache support ignore it; fallback to other fetchers is unaffected.
- `Response.cacheStatus` and `FetchTelemetryEvent.cacheStatus`:
`'hit' | 'stale-hit' | 'revalidated' | 'miss' | 'bypass'`.
- New setting `github.copilot.advanced.debug.nodeFetchCache`:
`'off' | 'memory' | 'persistent'` (default `'memory'`). `'persistent'`
uses undici's SQLite store under the extension's global storage
(`undici-cache.v1.sqlite`) when available, otherwise falls back to
memory.
- New `taggedCacheInterceptor` wraps `undici.interceptors.cache` and
stamps a private `VSCODE_CACHE_STATUS_HEADER` on the response so the
base fetcher can read the outcome without parsing undici internals.
- `BaseFetchFetcher` exposes an overridable `_buildRequestInit` hook and
reports `cacheStatus` on `Response` and `fetchTelemetry`.
Notes
- No behavior change for callers that don't set `cache: true`.
- The cache interceptor is constructed once per fetcher instance; the
composed dispatcher chain is reused so connection pooling is preserved.
- Depends on https://github.com/microsoft/vscode-proxy-agent/pull/100
For https://github.com/microsoft/vscode/issues/308310
* fix: undici integration tests
- drop the `age` header gate from classify(): undici's cache interceptor
only adds if-modified-since / if-none-match when revalidating a stored
entry, so `state.conditional` alone is a sufficient signal. The age header
is not guaranteed on a revalidated 200, which caused 'revalidated' to be
misreported as 'miss'.
- the etag integration test used
`cache-control: max-age=0, must-revalidate`, which undici treats as
already-stale on arrival and refuses to store (cache-handler.js bails when
`now >= absoluteStaleAt`), so there was nothing to revalidate on the second
call. Switch the origin to `public, max-age=60` and pass
`cache-control: no-cache` on the second request to drive undici's
needsRevalidation() path, which dispatches with if-none-match and serves
the cached body on 304.
* chore: fix lint
* chore: cleanup cache marker in favor of function arg R
Robo committed
9cd7264dd10d3705199ea6c6ec732af8cc8a24a5
Parent: bd01ba3
Committed by GitHub <noreply@github.com>
on 5/21/2026, 6:07:34 PM