Skip to content

feat(sidecar): OpenCode Zen (opencode.ai/zen/go/v1) as web-search sidecar backend — natively serves hosted web_search, no ChatGPT/Claude quota #1616

Description

@carlosqwqqwq

Area

Proxy and routing · Web-search sidecar

What are you trying to accomplish?

When the main model is routed through a third-party provider, the web-search sidecar currently has only two backends: openai (gpt-mini via the ChatGPT forward path) and anthropic (stored Claude OAuth). Both borrow an account the user may have very little quota on — and when that quota is exhausted, search turns degrade (the exact scenario from #398).

I'd like to point the web-search sidecar at a provider I actually have quota on: OpenCode Zen (https://opencode.ai/zen/go/v1, the built-in "opencode-go" provider), using e.g. deepseek-v4-flash to execute the search — with zero ChatGPT/Claude quota consumption.

What prevents this today?

  • OcxWebSearchSidecarConfig.backend is hardcoded to "openai" | "anthropic" (src/types.ts, src/web-search/index.ts resolveSidecarBackend); there is no keyed/custom-provider option.
  • The hosted web_search tool is unconditionally stripped for routed providers (src/responses/parser.ts buildTools), and the sidecar executor only knows how to POST to the ChatGPT forward endpoint with forwarded OAuth headers (src/web-search/executor.ts runWebSearch).
  • I verified there is no config-only workaround (changing webSearchSidecar.model, disabling the sidecar, or repointing the openai provider's baseUrl all fail — the latter breaks isCanonicalOpenAiForwardProvider).

Key fact: OpenCode Zen natively serves hosted web_search

Unlike a plain OpenAI-compatible chat gateway, OpenCode Zen executes the Responses API's hosted web_search tool server-side. Tested 2026-08-13 with a opencode-go API key (model deepseek-v4-flash):

  • POST https://opencode.ai/zen/go/v1/responses with tools: [{type: "web_search", max_results: 5}] + tool_choice: {type: "web_search"}200.
  • The model ran real multi-step searches (web_search_call with search and open_page actions) and returned a summary with source URLs.
  • Streaming emits standard Responses SSE — response.output_text.delta, response.output_text.done, response.completed (plus ping heartbeats) — exactly the event shapes parseSidecarSSE already handles.
  • The gateway reports "cost": "0" for these calls; either way, the spend lands on the user's Zen key, not ChatGPT/Claude.

This is the "provider-native search capability" category of #415 — Zen just isn't on the list there yet. It also fits #414's "decouple which account backs search" goal without adding a dedicated search vendor.

Proposed change (minimal, additive)

Add a third sidecar backend, e.g. backend: "keyed" with provider + model config, that POSTs to the named provider's {baseUrl}/responses with Authorization: Bearer <apiKey> and the same hosted tool + BASE_INSTRUCTION, reusing the existing loop, parseSidecarSSE, and format-result unchanged:

  • src/types.ts — extend OcxWebSearchSidecarConfig (backend: "keyed", provider?: string)
  • src/web-search/executor.ts — add runKeyedWebSearch() (~30 lines, mirrors runWebSearch with Bearer auth)
  • src/web-search/index.tsplanWebSearch keyed branch, resolving a configured key-auth openai-responses provider
  • src/web-search/loop.ts + src/server/responses/core.ts — thread the keyed provider through and dispatch

Roughly 60–80 additive lines; the ChatGPT forward path and anthropic path stay untouched.

Acceptance criteria

  • Config like webSearchSidecar: { backend: "keyed", provider: "opencode-go", model: "deepseek-v4-flash" } makes search runs on the Zen key with no ChatGPT/Claude quota involvement.
  • Search cells, citations/sources, and the loop behavior match the existing openai backend.

Related

#398 (original fixed-backend bug), #414 (dedicated search vendors like Exa), #415 (provider-native search, e.g. Gemini Grounding — Zen is a sibling case), #1276 (superseded umbrella; maintainer's note that arbitrary LLMs can't auto-back search is exactly why Zen's hosted tool is special).

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requesttoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions