Skip to content

[Provider compatibility] Ollama Cloud Responses passthrough skips web-search sidecar; hosted web_search is echoed as a client function_call #3761

Closed

Description

@foo1maker

Deferred; no hosted-search execution fix landed

The four-track delivery did not implement a raw Responses passthrough search/continuation bridge. Search-result insertion, mixed-tool continuity and replay semantics remain outstanding. This issue stays open.

Verified against dev 5759d9ea2f1e7281cdc01eb9628f2e0a123fb59c. Original report by @foo1maker. The attribution record was added in #3811.

Client or integration

Codex App

Provider or upstream service

Ollama Cloud (https://ollama.com/v1, key-auth openai-responses passthrough)

OpenCodex version

2.43.0 (@bitkyc08/opencodex)

Endpoint or capability

/v1/responses + hosted {type:"web_search"} / webSearchSidecar

Current behaviour

Codex App always speaks Responses to OpenCodex (POST /v1/responses) and advertises the hosted tool {type:"web_search"}.

For a key-auth provider with adapter: "openai-responses" (Ollama Cloud GLM / DeepSeek, etc.) OpenCodex takes the passthrough branch in src/server/responses/core.ts and returns before runWithWebSearch. planWebSearch / shouldResolveOpenAiWebSearchSidecar also fail closed on isPassthrough:

// src/web-search/index.ts
if (!parsed._webSearch || isPassthrough) return undefined;

The comment assumes passthrough means ChatGPT, which already executes hosted search server-side.

Ollama Cloud does not execute OpenAI hosted {type:"web_search"} on /v1/responses. The model instead emits a client function_call named web_search. Two failures follow:

  1. Stock responses-undeclared-tool-guard treats that echo as an undeclared client tool and aborts the SSE stream:
    routed provider emitted undeclared client tool "web_search"; only request-declared tools may be called
    Codex App then reconnects 5/5 and the worker/turn dies (last_agent_message: null).

  2. If the guard is relaxed, Codex App still does not execute a function named web_search (it expects the provider to run hosted search). The next hop is unsupported call: web_search. The model can continue without live results. Dashboard webSearchSidecar (e.g. gpt-5.6-luna) stays idle.

Ollama does have a working search API, but it is a separate client-executed REST endpoint, not ChatGPT hosted search:

Official OpenCodex backends (openai / anthropic / xai / gemini / exa) never call it. The sidecar loop that would intercept a synthetic web_search function tool never runs on this passthrough path.

Switching the provider to openai-chat would enter the sidecar loop, but Codex App cannot be switched off Responses, and translating GLM/DeepSeek off /v1/responses drops reasoning summaries and the native tool surface that this client requires.

Expected behaviour

On key-auth Responses passthrough to a gateway that does not execute OpenAI hosted web_search:

  1. Do not abort the stream for a function_call named web_search when the request catalog already declared hosted {type:"web_search"} (same catalog entry, different wire shape).
  2. Intercept that call in the proxy (do not relay it to Codex as an unsupported client tool).
  3. Execute search and inject results as function_call_output (and/or a hosted web_search_call item Codex understands).
  4. Prefer Ollama's own POST /api/web_search when the route is Ollama Cloud (same credential, no ChatGPT sidecar spend). Luna/Exa remain valid fallbacks for translated routes, which is the current sidecar design.

Passthrough should keep meaning “preserve the Responses wire”, not “the upstream is ChatGPT and already searched”.

Minimal redacted request or reproduction

{
  "defaultProvider": "ollama-relay",
  "webSearchSidecar": { "enabled": true },
  "providers": {
    "ollama-relay": {
      "adapter": "openai-responses",
      "baseUrl": "https://ollama.com/v1",
      "authMode": "key",
      "selectedModels": ["glm-5.3-flash"]
    },
    "openai": {
      "adapter": "openai-responses",
      "baseUrl": "https://chatgpt.com/backend-api/codex",
      "authMode": "forward"
    }
  }
}
# Codex App
model = "glm-5.3-flash"
openai_base_url = "http://127.0.0.1:10100/v1"
  1. ocx start (2.43.0 stock).
  2. In Codex App, ask a routed GLM/DeepSeek turn for current web facts (pricing, docs, anything that triggers search).
  3. Observe either the undeclared-tool abort or unsupported call: web_search with no sidecar / no POST https://ollama.com/api/web_search.

Actual response or error

stream disconnected before completion: routed provider emitted undeclared client tool "web_search"; only request-declared tools may be called

If that guard is bypassed, the Codex client hop is:

unsupported call: web_search

Upstream GLM item shape (redacted):

{
  "type": "function_call",
  "name": "web_search",
  "arguments": "{\"query\":\"site:example.com current pricing\"}"
}

OpenCodex logs show no [web-search] sidecar enter on these turns. Ollama /api/web_search is not requested.

Upstream documentation

Suggested mapping or implementation notes

Stock code already has the intercept loop for translated adapters (runWithWebSearch + buildWebSearchTool). The gap is that adapter.passthrough both:

  • skips that loop (planWebSearch(..., isPassthrough) / early return in core.ts), and
  • forwards hosted {type:"web_search"} unchanged, which Ollama echoes as a named function_call.

A minimal passthrough-aware path:

  1. In addNamelessClientCallTypes, treat hosted web_search / web_search_preview as authorizing a client function_call named web_search (same catalog, echo shape). This at least stops Mode 2 / App workers from 5/5-dying.
  2. On non-forward (authMode: "key") passthrough SSE, intercept function_call name === "web_search", run an executor, hide the synthetic call from Codex, inject the tool result into the next upstream hop.
  3. Add an ollama web-search backend that POSTs https://ollama.com/api/web_search with the existing provider key. Keep luna for openai-chat / Anthropic routes.

Do not require operators to change Codex App to Chat Completions. The App wire is Responses; Ollama Cloud also speaks /v1/responses. The missing piece is executing search for that pair.

Additional context

Searched existing issues. Closest prior art is passthrough tool-surface gaps (e.g. #1950 tool_search rewrite on Responses passthrough) and web-search sidecar bugs on translated openai-chat routes (#265, #1001). I did not find an open issue for Ollama Cloud hosted-web_search echo vs /api/web_search.

Related but separate: Ollama /v1/responses also lacks previous_response_id store (documented). statelessResponses addresses replay; it does not execute search.

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

  1. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    streamingSSE, WebSocket, terminal stream frames
    toolstool_calls, MCP, web-search / sidecar tools
    on Sep 6, 2026
  2. Ingwannu commented on Sep 6, 2026

    @Ingwannu
    Owner

    Confirmed the OCX-side gap at 96094c384: hosted search is parsed, but the passthrough flag prevents sidecar planning and the passthrough branch returns before the translated search loop. Three isolated checks passed: this exclusion, rejection of a hosted-search echo as a client function, and acceptance of an explicitly declared client function. No live Ollama request was made.

    One part of the suggested fix must not land by itself: declaring hosted web_search does not declare a client function with that name. OpenAI's hosted contract returns web_search_call; Ollama documents a separate application-executed /api/web_search. Permitting the echo through the client-tool guard without actually intercepting and executing it simply changes the failure to the unsupported client call you already observed.

    The appropriate proposal is an explicit, destination-scoped search bridge that owns the synthetic function, executes it, hides it from the client, and continues the upstream conversation. Keep ordinary undeclared-function rejection intact. It needs SSE and JSON coverage, cancellation/search budgets, mixed real client tools, tool-choice restrictions, and credential/endpoint scoping. Using the provider's key at another endpoint and selecting paid fallback backends also require explicit policy and review; do not infer that authority from a custom provider name.

    Also, changing the upstream adapter to openai-chat does not require Codex App to stop sending Responses to OCX. Whether that translation preserves the native reasoning/tool details you need is a separate compatibility question; I am not claiming it is an equivalent workaround.

    Keeping this open rather than applying the guard-only change. Sources: https://developers.openai.com/api/docs/guides/tools-web-search and https://docs.ollama.com/capabilities/web-search.

  3. lidge-jun commented on Sep 6, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 62 / 80

    이 이슈는 Codex App이 항상 OpenCodex로 Responses(POST /v1/responses)를 보내고, 호스티드 도구 {type:"web_search"}를 광고하는 상황에서 생깁니다. 목적지가 Ollama Cloud처럼 adapter: "openai-responses" 키 인증 패스스루(예: https://ollama.com/v1)이면, 현재 dev(HEAD c8470eff4, release-244, #3763 문서 정렬 직후)의 웹서치 경로가 의도적으로 꺼집니다.

    src/web-search/index.ts의 planWebSearch와 shouldResolveOpenAiWebSearchSidecar는 isPassthrough가 참이면 바로 undefined/false를 반환합니다. 주석도 “패스스루면 이미 ChatGPT가 서버에서 검색한다”는 전제입니다. 그래서 src/server/responses/core.ts 패스스루 분기는 runWithWebSearch 루프에 들어가지 않고, 대시보드 webSearchSidecar(Luna/Exa 등)도 돌지 않습니다.

    문제는 Ollama Cloud가 OpenAI 호스티드 web_search를 실행하지 않는다는 점입니다. 모델은 대신 이름 있는 function_call(name: "web_search")을 내보냅니다. 그러면 src/bridge.ts / src/server/responses-undeclared-tool-guard.ts 쪽이 “요청에 선언되지 않은 클라이언트 도구”로 보고 SSE를 끊습니다. Codex App은 5/5 재연결 후 워커가 죽고, 가드를 느슨하게만 하면 클라이언트가 unsupported call: web_search를 봅니다. Ollama 쪽 실제 검색은 별도 REST POST https://ollama.com/api/web_search인데, 공식 백엔드(openai/anthropic/xai/gemini/exa)에는 아직 없습니다.

    Ingwannu 확인 댓글과도 맞습니다. 호스티드 web_search 선언만으로 같은 이름의 클라이언트 함수를 허용하면, 가드만 우회하고 실제로는 실행하지 않아 실패 형태만 바뀝니다. 올바른 방향은 목적지 범위의 검색 브리지입니다. 합성 function_call을 프록시가 가로채고, 실행한 뒤 클라이언트에는 숨기고, 업스트림 대화를 이어가야 합니다. 일반 미선언 함수 거부는 그대로 둡니다. SSE/JSON, 취소·검색 예산, 진짜 클라이언트 도구 혼재, tool-choice 제한, 자격증명·엔드포인트 스코프가 필요합니다. 제공자 키를 다른 호스트로 재사용하거나 유료 폴백을 고르는 권한은 이름만으로 추론하면 안 됩니다.

    release-244 기차는 대시보드(#3764)·릴리스 쪽이 다음이지만, 이 이슈는 패스스루=ChatGPT라는 오래된 가정을 깨는 실질 호환 버그입니다. openai-chat로 어댑터만 바꾸는 우회는 Codex App이 OCX로 Responses를 보내는 것과는 별개이고, 추론/도구 표면 보존은 별 호환 질문입니다. statelessResponses도 검색 실행과는 무관합니다.

    라인 없음 - planWebSearch/shouldResolveOpenAiWebSearchSidecar의 isPassthrough 조기 반환이 key-auth 게이트웨이까지 ChatGPT로 취급함
    src/server/responses/core.ts - 패스스루 분기가 runWithWebSearch 전에 돌아가 사이드카 루프 자체가 시작되지 않음
    src/server/responses-undeclared-tool-guard.ts / addNamelessClientCallTypes - 호스티드 web_search가 이름 있는 function_call “web_search”를 인가하지 않음(의도적). 가드만 풀면 안 됨
    src/web-search/ - Ollama POST /api/web_search 실행기/백엔드 없음. 키를 다른 엔드포인트로 쓰는 정책도 미정
    src/bridge.ts - 미선언 클라이언트 도구 abort 문구가 재현 로그와 일치. 인터셉트 없이 완화하면 Codex unsupported call로만 이동

    메인테이너의 판단이 필요한 지점

    • 패스스루 key-auth 목적지에 “검색 브리지”를 넣는다면, 허용 조건을 baseUrl/호스트 allowlist로 할지 명시 플래그로 할지
    • Ollama /api/web_search에 기존 provider key를 재사용해도 되는지, 별도 설정·동의 문구가 필요한지
    • Luna/Exa 유료 폴백을 이 경로에서도 허용할지, Ollama-only로 막을지
    • #1950류 패스스루 도구 표면 작업과 한 설계로 묶을지, 이슈 단독 스펙으로 둘지
    • 가드 완화만 하는 PR이 오면 즉시 거절할지(오너 코멘트와 동일 입장 권장)

    너의 추천
    열린 채로 두고 needs-design에 가깝게 취급하세요. 가드-only 패치는 받지 마세요. 다음 구현 단위는 (1) key-auth Responses 패스스루 SSE에서 function_call name web_search를 가로채 실행·은닉하는 브리지, (2) 선택적 ollama 웹서치 백엔드(POST https://ollama.com/api/web_search, 자격증명·엔드포인트 명시 스코프), (3) 취소/예산/혼재 도구/tool-choice 테스트입니다. Codex App을 Chat Completions로 바꾸라고 안내하지 마세요. release-244 대시보드 기차와는 독립이니 기차 뒤에 스펙 이슈/PR로 받는 편이 안전합니다.

    이 댓글은 grok-bot이 작성했습니다

  4. lidge-jun commented on Sep 6, 2026

    @lidge-jun
    Owner

    Deferred from track 2. The current normalized-message search loop cannot safely complete the raw Responses passthrough conversation: search-result insertion, reasoning/tool continuity, mixed tools and replay need a defined raw continuation contract. Official local Ollama middleware support also does not establish the direct cloud endpoint contract. No production change or claimed fix was made for this issue.

  5. added a commit that references this issue on Sep 9, 2026
    aff8a22
  6. lidge-jun commented on Sep 9, 2026

    @lidge-jun
    Owner

    Fixed on dev by #4142 (merge commit aff8a22).

    providers.<name>.webSearchBridge = { enabled: true, backend: "ollama" } opts a key-auth openai-responses passthrough provider into a proxy-run web search: the function_call named web_search is intercepted out of the upstream stream, the search runs, the call and its result are appended to the same raw outbound body for a continuation leg, and Codex receives the hosted web_search_call cell instead. Default OFF, never armed for authMode: "forward", and web_search is not added to the undeclared-tool guard's allowed names.

    Closing manually: PRs here target dev, so GitHub does not auto-close the linked issue.

  7. added a commit that references this issue on Sep 17, 2026
    f03c220
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions