feat: wire interceptFetch into the chat pipeline + dashboard toggle (#7339)#7736
Merged
Merged
Conversation
…peline (#7339) Phases 3-4 of #3384 (Phases 1-2 shipped DB schema + resolveInterceptSearch in release/v3.8.47). Adds resolveInterceptFetch(provider, model) as a structural twin of resolveInterceptSearch, and open-sse/services/webFetchInterception.ts (mirroring webSearchFallback.ts) to rewrite a provider-native web_fetch tool declaration into a synthetic omniroute_web_fetch function tool. The synthetic tool call is dispatched through the existing handleToolCallExecution path (same as omniroute_web_search) to a new web_fetch builtin skill handler that resolves credentials and calls handleWebFetch() against /v1/web/fetch. Strictly opt-in: with no interceptFetch DB row configured (the default), the outgoing request body is byte-identical to pre-change behavior — no heuristic default bypass like the interceptSearch sibling, to guarantee zero overhead when disabled (Hard Rule #20). Also ships the dashboard toggle (owner decision, overriding the plan's backend-only recommendation): ProviderInterceptionSection.tsx on the provider detail page, backed by GET/PUT/DELETE /api/providers/[id]/interception-rules, covering both interceptSearch and interceptFetch from one control. chatCore.ts touch is minimal (frozen file): one resolver call + one prepareWebFetchFallbackBody call mirroring the existing interceptSearch block, plus threading provider/model into the existing handleToolCallExecution call.
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.
Summary
Tracks the deferred Phases 3-4 of #3384 (Phases 1-2 shipped the DB schema +
resolveInterceptSearchin release/v3.8.47).resolveInterceptFetch(provider, model)— a structural twin ofresolveInterceptSearch(src/lib/db/interceptionRules.ts), same per-model > per-provider >undefinedprecedence.open-sse/services/webFetchInterception.ts(mirrorswebSearchFallback.ts) — pure request-body transform that rewrites a provider-nativeweb_fetchtool declaration into a syntheticomniroute_web_fetchfunction tool. No HTTP, no SSE/streaming, no abort-signal handling in this module.handleToolCallExecutiondispatch path (same one already proven foromniroute_web_search) to a newweb_fetchbuiltin skill handler (src/lib/skills/builtins.ts+ newsrc/lib/skills/webFetchExecution.ts), which resolves web-fetch provider credentials and callshandleWebFetch()against/v1/web/fetch.open-sse/handlers/chatCore.ts(frozen file, ~5125-line cap) directly beside the existinginterceptSearchblock: one resolver call + oneprepareWebFetchFallbackBodycall, plus threadingprovider/effectiveModelinto the existinghandleToolCallExecutioncall so the builtin can resolve a pinned fetch backend.Owner decision — ships the dashboard toggle in this PR (overriding the plan's backend-only recommendation, since no such toggle exists for the
interceptSearchsibling either):ProviderInterceptionSection.tsx— new card on the provider detail page with two toggles (interceptSearch,interceptFetch), since they share one interception-rules DB row per provider. Mounted from the existingProviderExtraPanels.tsxescape valve (frozenProviderDetailPageClient.tsxuntouched).GET/PUT/DELETE /api/providers/[id]/interception-rulesroute, mirroring the existingparam-filtersroute pattern exactly (requireManagementAuth, Zod validation,buildErrorBody/sanitizeErrorMessage).en.jsonandpt-BR.json.modelsfield for now.Hot-path safety / zero overhead when disabled: unlike
interceptSearch(which predates #3384 and already ran a native-bypass heuristic in production before the override was added),interceptFetchis brand-new behavior. To guarantee true opt-in and avoid any surprise default conversion,supportsNativeWebFetchFallbackBypasswas simplified tointerceptFetchOverride !== true— strictly opt-in, no heuristic default. With nointerceptFetchDB row configured (the default/common case), the outgoing request body is byte-identical to pre-#7339 behavior.How validated (Hard Rule #18 — TDD)
Confirmed red-before-green against the base ref:
resolveInterceptFetchandopen-sse/services/webFetchInterception.tsdo not exist onorigin/release/v3.8.49(git show origin/release/v3.8.49:open-sse/services/webFetchInterception.ts→ fatal: path does not exist;grep -c resolveInterceptFetchon the baseinterceptionRules.ts→ 0). All new tests pass on this branch (green).New test files (21 tests total):
tests/unit/intercept-fetch-resolver.test.ts—resolveInterceptFetchprecedence (undefined / provider-level / model-override-wins / field independence frominterceptSearch/ empty-provider).tests/unit/web-fetch-interception.test.ts—prepareWebFetchFallbackBody/supportsNativeWebFetchFallbackBypass(no-op with no tools, no-op with no web_fetch tool, converts when override=true, flat-vs-nested shape by target format, untouched when override=false, preserves sibling tools, opt-in-only bypass semantics).tests/unit/web-fetch-dispatch.test.ts—handleToolCallExecutionroutesomniroute_web_fetchto theweb_fetchbuiltin and returns the OpenAI tool-result envelope; an unaliased tool name is left untouched; an error thrown mid-fetch (simulatedAbortError) is surfaced in the result, not silently dropped (Hard Rule fix(oauth): broaden upsert to match any existing connection #6).tests/unit/chat-core-intercept-fetch.test.ts— regression guard mirroring the exact two-call chatCore.ts sequence: with nointerceptFetchrule configured, the outgoing body is the same object reference (true no-op); a request with noweb_fetchtool is untouched regardless of rule state; the tool is only converted once a provider/model explicitly opts in.Commands run:
Gates run (all green)
node scripts/check/check-test-discovery.mjsnpm run typecheck:corenpm run typecheck:noimplicit:core(3 pre-existing unrelated errors on base, none in touched files)npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>npm run check:complexity-ratchetsnode scripts/check/check-file-size.mjs(chatCore.ts is well under its frozen 5125-line cap after this change — no rebaseline needed)node scripts/check/check-dashboard-typecheck.mjs(touchedsrc/app/(dashboard)/**; fixed one real bug this surfaced —notify.notify(...)doesn't exist onuseNotificationStore, corrected tonotify.error(...))npm run check:cyclesWhat was deferred
modelsfield).fetchBackend/fetchProxyUrlpinning is wired end-to-end (resolved inwebFetchExecution.tsfrom the interception-rules row) but not yet exposed in the UI toggle — only the boolean flags are.No live/VPS validation needed — this is pure application logic covered by unit tests (no OAuth, no upstream handshake, no OS-level behavior).
Refs #7339