Skip to content

feat: Serper/cache/concurrency + token reporting, stream-terminator, and Sonnet/model-map fixes#4

Open
andyzengmath wants to merge 10 commits into
samarth777:mainfrom
andyzengmath:feat/serper-cache-concurrency
Open

feat: Serper/cache/concurrency + token reporting, stream-terminator, and Sonnet/model-map fixes#4
andyzengmath wants to merge 10 commits into
samarth777:mainfrom
andyzengmath:feat/serper-cache-concurrency

Conversation

@andyzengmath

@andyzengmath andyzengmath commented Jul 13, 2026

Copy link
Copy Markdown

Summary

Builds on the MCP web search work (#3) and adds search improvements plus several proxy correctness fixes.

Web search

  • Serper.dev fallback provider (Google SERP, 2,500 free queries)
  • Search cache (5-min TTL) — identical queries from parallel subagents return instantly
  • Concurrency semaphore (max 2 concurrent) — prevents DDG/API rate-limit storms in multi-agent workflows

Accurate token reporting (fixes context HUD stuck at 0%)

  • Report input_tokens in streaming message_start and message_delta so Claude Code can track context-window usage
  • Reorder usage extraction before sendStartIfNeeded() so tokens land in message_start when the first chunk carries them
  • Use ?? instead of || for token counts (0 is valid; || wrongly treats it as falsy)

No duplicate stream terminators

  • The caller loop's if (isDone) break only broke the inner for, so the closing message_delta+message_stop were re-emitted on [DONE] and again at stream end (3×). Added a sentStop idempotency guard.

Unlock Sonnet + correct model map to real Copilot IDs

  • Root cause: Copilot gates Claude Sonnet behind the Editor-Version and Copilot-Integration-Id headers VS Code sends. Without them every claude-sonnet-* request returned model_not_supported.
  • Add both headers to both Copilot request paths (streaming + web-search collect)
  • Rewrite MODEL_MAP against the live Copilot /models list:
    • Sonnet 4.5/4/3.5claude-sonnet-4.6 (4.5 doesn't exist on Copilot), add claude-sonnet-5 passthrough
    • Opus 4.5/4.1/legacy → claude-opus-4.6, add 4.7/4.8 passthrough
    • Haiku → claude-haiku-4.5
  • Update advertised /models list to match reality

Testing

Verified live through the proxy: all opus/sonnet/haiku families return 200 (Sonnet, opus-4.5, opus-4.1 all errored before), streaming emits exactly one terminator, and message_delta reports real input_tokens.

Notes

andyzengmath and others added 5 commits June 30, 2026 14:47
Add PowerShell and CMD examples for running the proxy on Windows,
since launch.sh is bash-only.

Co-authored-by: Copilot <[email protected]>
DuckDuckGo Lite has become unreliable due to aggressive CAPTCHA/bot
detection on automated requests. This causes web search to silently
fail with empty results.

Replace with Exa and Parallel MCP endpoints (same approach used by
OpenCode) as the primary search backend:

- Uses JSON-RPC 2.0 MCP protocol (tools/call method)
- Traffic split 50/50 between Exa and Parallel by default
- Automatic cross-fallback (if one fails, tries the other)
- Works without API keys out of the box
- 25-second timeout per request

Provider priority chain:
  1. Brave Search API (if BRAVE_API_KEY set)
  2. Exa/Parallel MCP (primary, free, no key needed)
  3. DuckDuckGo Lite (fallback, may hit CAPTCHAs)
  4. DuckDuckGo Instant Answer (last resort)

New env vars (all optional):
  - WEBSEARCH_PROVIDER: force "exa" or "parallel"
  - EXA_API_KEY: optional key for Exa
  - PARALLEL_API_KEY: optional key for Parallel
- Add Serper.dev (Google SERP API) as fallback search provider via SERPER_API_KEY env var
- Reorder provider chain: Exa/Parallel MCP (free) → Brave → Serper → DDG Lite → DDG Instant
- Add 5-min search result cache to deduplicate identical queries across parallel agents
- Add concurrency semaphore (max 2 concurrent searches) to prevent DDG rate-limit storms
- Route all search calls through throttled wrapper (executeWebSearchThrottled)

Fixes cascading 'DDG Lite error: fetch failed' / 'All search providers failed' errors
that occur when multi-agent workflows (e.g. Workflow tool) fire many parallel web searches.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Reorder provider list to match new priority (MCP first, paid APIs as fallback)
- Add Serper.dev to provider list and config table
- Document rate-limit protection (cache + concurrency semaphore)

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- Report input_tokens in streaming message_start/message_delta so Claude
  Code tracks context window usage (fixes HUD stuck at 0%)
- Reorder usage extraction before sendStartIfNeeded()
- Use ?? instead of || for token counts (0 is valid)
- Add claude-opus-4.8 model mapping (was falling back to 4.6)
@andyzengmath andyzengmath changed the title feat: add Serper search fallback + search cache + concurrency limiter feat: Serper fallback + search cache/concurrency + token reporting fix + Opus 4.8 Jul 21, 2026
The caller loop's 'if (isDone) break' only breaks the inner for-loop, so
after a finish_reason chunk emits the closing sequence, the outer while-loop
continues and re-emits it on the [DONE] sentinel and again on stream end.

Add a sentStop idempotency guard so the closing message_delta + message_stop
are emitted exactly once regardless of how many terminal signals arrive.
Restore the concise per-request diagnostic line (model → mapped model |
stream/sync | N messages) so model routing is visible at a glance.
Root cause: Copilot gates Claude Sonnet behind the Editor-Version and
Copilot-Integration-Id headers that VS Code sends. Without them, every
claude-sonnet-* request returned model_not_supported.

- Add Editor-Version + Copilot-Integration-Id headers to both Copilot
  request paths (streaming + web-search collect) so gated models work
- Rewrite MODEL_MAP against the live Copilot /models list:
  - Sonnet 4.5/4/3.5 -> claude-sonnet-4.6 (real supported ID; 4.5 does not exist on Copilot)
  - add claude-sonnet-5 passthrough
  - Opus 4.5/4.1/legacy -> claude-opus-4.6 (nearest supported)
  - add Opus 4.7 passthrough
- Update advertised /models list to match reality

Verified: all opus/sonnet/haiku families now return 200 through the proxy.
@andyzengmath andyzengmath changed the title feat: Serper fallback + search cache/concurrency + token reporting fix + Opus 4.8 feat: Serper/cache/concurrency + token reporting, stream-terminator, and Sonnet/model-map fixes Jul 22, 2026
Address code-review findings on the model/token/header changes:

- Sonnet fallback: match the major version after the 'sonnet' token
  (sonnet-5 -> 5) instead of a loose includes('5'), which previously
  upgraded unmapped Sonnet-4.5 / dated builds to Sonnet-5.
- Search cache: skip caching empty results (a transient all-providers-fail
  no longer suppresses a query for the full TTL), and bound the cache with
  expired-entry eviction + a 500-entry cap to stop unbounded growth.
- Editor headers: make Editor-Version and Copilot-Integration-Id
  env-overridable (COPILOT_EDITOR_VERSION / COPILOT_INTEGRATION_ID) so a
  future Copilot version-gate can be fixed without a code change.
…timeout

Four proxy-experience improvements from a full review:

- stream_options: include_usage on streaming requests so Copilot reliably
  returns token usage in the final chunk (input_tokens was previously
  best-effort, depending on whether usage happened to arrive)
- /count_tokens now estimates from actual message/system/tool text (~4
  chars/token) instead of raw JSON byte length, so Claude Code's
  auto-compaction fires at the right time
- Extended thinking: drop thinking / redacted_thinking content blocks
  instead of JSON-stringifying them into the conversation; the thinking
  request param is not forwarded (Copilot handles reasoning internally)
- Request timeout: 120s AbortController (COPILOT_REQUEST_TIMEOUT_MS) on both
  Copilot fetch paths, and a mid-stream error handler that emits a proper
  terminator so a dropped connection no longer hangs or truncates silently
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant