Repository navigation
[Provider compatibility] Ollama Cloud Responses passthrough skips web-search sidecar; hosted web_search is echoed as a client function_call #3761
Description
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:
-
Stock
responses-undeclared-tool-guardtreats 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). -
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 isunsupported call: web_search. The model can continue without live results. DashboardwebSearchSidecar(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:
POST https://ollama.com/api/web_search- docs: https://docs.ollama.com/capabilities/web-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:
- Do not abort the stream for a
function_callnamedweb_searchwhen the request catalog already declared hosted{type:"web_search"}(same catalog entry, different wire shape). - Intercept that call in the proxy (do not relay it to Codex as an unsupported client tool).
- Execute search and inject results as
function_call_output(and/or a hostedweb_search_callitem Codex understands). - Prefer Ollama's own
POST /api/web_searchwhen 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"ocx start(2.43.0 stock).- In Codex App, ask a routed GLM/DeepSeek turn for current web facts (pricing, docs, anything that triggers search).
- Observe either the undeclared-tool abort or
unsupported call: web_searchwith no sidecar / noPOST 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
- Ollama web search REST (client-executed): https://docs.ollama.com/capabilities/web-search
- Ollama OpenAI compatibility (
/v1/responsesis non-stateful; hosted ChatGPTweb_searchis not listed as provider-executed): https://docs.ollama.com/api/openai-compatibility - OpenAI hosted
web_searchtool (what Codex App sends): https://platform.openai.com/docs/guides/tools-web-search
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 incore.ts), and - forwards hosted
{type:"web_search"}unchanged, which Ollama echoes as a namedfunction_call.
A minimal passthrough-aware path:
- In
addNamelessClientCallTypes, treat hostedweb_search/web_search_previewas authorizing a clientfunction_callnamedweb_search(same catalog, echo shape). This at least stops Mode 2 / App workers from 5/5-dying. - On non-forward (
authMode: "key") passthrough SSE, interceptfunction_callname === "web_search", run an executor, hide the synthetic call from Codex, inject the tool result into the next upstream hop. - Add an
ollamaweb-search backend that POSTshttps://ollama.com/api/web_searchwith the existing provider key. Keep luna foropenai-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
Ingwannu commentedon Sep 6, 2026
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_searchdoes not declare a client function with that name. OpenAI's hosted contract returnsweb_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-chatdoes 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.
lidge-jun commentedon Sep 6, 2026
리뷰 · 우선순위 62 / 80
이 이슈는 Codex App이 항상 OpenCodex로 Responses(
POST /v1/responses)를 보내고, 호스티드 도구{type:"web_search"}를 광고하는 상황에서 생깁니다. 목적지가 Ollama Cloud처럼adapter: "openai-responses"키 인증 패스스루(예:https://ollama.com/v1)이면, 현재dev(HEADc8470eff4, 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 쪽 실제 검색은 별도 RESTPOST 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/- OllamaPOST /api/web_search실행기/백엔드 없음. 키를 다른 엔드포인트로 쓰는 정책도 미정src/bridge.ts- 미선언 클라이언트 도구 abort 문구가 재현 로그와 일치. 인터셉트 없이 완화하면 Codexunsupported call로만 이동메인테이너의 판단이 필요한 지점
/api/web_search에 기존 provider key를 재사용해도 되는지, 별도 설정·동의 문구가 필요한지#1950류 패스스루 도구 표면 작업과 한 설계로 묶을지, 이슈 단독 스펙으로 둘지너의 추천
열린 채로 두고
needs-design에 가깝게 취급하세요. 가드-only 패치는 받지 마세요. 다음 구현 단위는 (1) key-auth Responses 패스스루 SSE에서function_callnameweb_search를 가로채 실행·은닉하는 브리지, (2) 선택적ollama웹서치 백엔드(POST https://ollama.com/api/web_search, 자격증명·엔드포인트 명시 스코프), (3) 취소/예산/혼재 도구/tool-choice 테스트입니다. Codex App을 Chat Completions로 바꾸라고 안내하지 마세요. release-244 대시보드 기차와는 독립이니 기차 뒤에 스펙 이슈/PR로 받는 편이 안전합니다.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 6, 2026
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.
Merge pull request #4142 from lidge-jun/codex/passthrough-web-search-…
lidge-jun commentedon Sep 9, 2026
Fixed on
devby #4142 (merge commit aff8a22).providers.<name>.webSearchBridge = { enabled: true, backend: "ollama" }opts a key-authopenai-responsespassthrough provider into a proxy-run web search: thefunction_callnamedweb_searchis 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 hostedweb_search_callcell instead. Default OFF, never armed forauthMode: "forward", andweb_searchis 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.Merge pull request lidge-jun#4142 from lidge-jun/codex/passthrough-we…