Summary
In createGoogleThinkingPayloadWrapper (provider-stream), Google OpenAI-compat requests are dispatched with top-level store, service_tier, and metadata fields still attached. Google's OpenAI-compatible endpoint rejects these as unknown fields with HTTP 400. The wrapper already has a model.api === "google-generative-ai" guard for sanitizeGoogleThinkingPayload but does not strip the unsupported OpenAI-only top-level fields.
Downstream operators who patch this locally tend to strip those fields unconditionally in the wrapper, which then breaks the OpenAI Codex subscription path (openai-codex-responses api), because Codex requires store: false in its request body and returns HTTP 400 {"detail":"Store must be set to false"} when the field is missing.
The correct fix is to strip store / service_tier / metadata only when model.api === "google-generative-ai", co-located with the existing sanitizeGoogleThinkingPayload guard.
Environment
- OpenClaw:
2026.3.28
- Affected file (installed):
/usr/lib/node_modules/openclaw/dist/provider-stream-*.js
- Affected function:
createGoogleThinkingPayloadWrapper
- Affected provider api:
google-generative-ai (Google OpenAI-compat endpoint: https://generativelanguage.googleapis.com/v1beta/openai/)
- Colliding provider api:
openai-codex-responses (Codex subscription path, https://chatgpt.com/backend-api)
Current Upstream Code (2026.3.28)
function createGoogleThinkingPayloadWrapper(baseStreamFn, thinkingLevel) {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
return streamWithPayloadPatch(underlying, model, context, options, (payload) => {
if (model.api === "google-generative-ai") sanitizeGoogleThinkingPayload({
payload,
modelId: model.id,
thinkingLevel
});
});
};
}
Buggy Behavior
When a model with api: "google-generative-ai" is routed through this wrapper, the payload still contains OpenAI-compat-only top-level fields:
store (boolean)
service_tier (string)
metadata (object)
Google's OpenAI-compat endpoint rejects requests containing any of these with HTTP 400 INVALID_ARGUMENT / unknown field. This is the same failure class reported in #53658 (fallback/replay path).
Expected Behavior
For model.api === "google-generative-ai", the wrapper should scrub the OpenAI-only top-level fields before dispatch, the same place it already runs sanitizeGoogleThinkingPayload.
Why This Matters Beyond Gemini — Codex Collision
Naive downstream fixes strip these fields unconditionally inside the wrapper. That regresses the Codex subscription path because OpenAI Codex's openai-codex-responses api requires store: false in every request body. When the field is stripped:
HTTP 400
{"detail":"Store must be set to false"}
This means the fix must be narrow — Google-api-only — or it will take out Codex agents on the same gateway.
Smallest Correct Fix
function createGoogleThinkingPayloadWrapper(baseStreamFn, thinkingLevel) {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
return streamWithPayloadPatch(underlying, model, context, options, (payload) => {
if (model.api === "google-generative-ai") {
sanitizeGoogleThinkingPayload({
payload,
modelId: model.id,
thinkingLevel
});
if (payload && typeof payload === "object") {
delete payload.store;
delete payload.service_tier;
delete payload.metadata;
}
}
});
};
}
Key constraint: the delete block must be inside the model.api === "google-generative-ai" guard. Do not strip these fields for any other provider api.
Suggested Regression Tests
createGoogleThinkingPayloadWrapper with model.api === "google-generative-ai": asserts store/service_tier/metadata are stripped.
- Same wrapper with
model.api === "openai-codex-responses" and store: false on the payload: asserts store: false is preserved.
- Same wrapper with
model.api === "openai" and service_tier set: asserts it is preserved.
Related
Notes
Local operator workaround (retired on fix): a dist-level patch on provider-stream-*.js adds the Google-guarded delete block above. Tracked locally as OC016 with the removal trigger "upstream ships conditional guard natively."
Summary
In
createGoogleThinkingPayloadWrapper(provider-stream), Google OpenAI-compat requests are dispatched with top-levelstore,service_tier, andmetadatafields still attached. Google's OpenAI-compatible endpoint rejects these as unknown fields with HTTP 400. The wrapper already has amodel.api === "google-generative-ai"guard forsanitizeGoogleThinkingPayloadbut does not strip the unsupported OpenAI-only top-level fields.Downstream operators who patch this locally tend to strip those fields unconditionally in the wrapper, which then breaks the OpenAI Codex subscription path (
openai-codex-responsesapi), because Codex requiresstore: falsein its request body and returns HTTP 400{"detail":"Store must be set to false"}when the field is missing.The correct fix is to strip
store/service_tier/metadataonly whenmodel.api === "google-generative-ai", co-located with the existingsanitizeGoogleThinkingPayloadguard.Environment
2026.3.28/usr/lib/node_modules/openclaw/dist/provider-stream-*.jscreateGoogleThinkingPayloadWrappergoogle-generative-ai(Google OpenAI-compat endpoint:https://generativelanguage.googleapis.com/v1beta/openai/)openai-codex-responses(Codex subscription path,https://chatgpt.com/backend-api)Current Upstream Code (2026.3.28)
Buggy Behavior
When a model with
api: "google-generative-ai"is routed through this wrapper, the payload still contains OpenAI-compat-only top-level fields:store(boolean)service_tier(string)metadata(object)Google's OpenAI-compat endpoint rejects requests containing any of these with HTTP 400 INVALID_ARGUMENT / unknown field. This is the same failure class reported in #53658 (fallback/replay path).
Expected Behavior
For
model.api === "google-generative-ai", the wrapper should scrub the OpenAI-only top-level fields before dispatch, the same place it already runssanitizeGoogleThinkingPayload.Why This Matters Beyond Gemini — Codex Collision
Naive downstream fixes strip these fields unconditionally inside the wrapper. That regresses the Codex subscription path because OpenAI Codex's
openai-codex-responsesapi requiresstore: falsein every request body. When the field is stripped:This means the fix must be narrow — Google-api-only — or it will take out Codex agents on the same gateway.
Smallest Correct Fix
Key constraint: the
deleteblock must be inside themodel.api === "google-generative-ai"guard. Do not strip these fields for any other provider api.Suggested Regression Tests
createGoogleThinkingPayloadWrapperwithmodel.api === "google-generative-ai": assertsstore/service_tier/metadataare stripped.model.api === "openai-codex-responses"andstore: falseon the payload: assertsstore: falseis preserved.model.api === "openai"andservice_tierset: asserts it is preserved.Related
storeunknown-field 400 from the fallback/replay path (same root field-compat issue, different code site).Notes
Local operator workaround (retired on fix): a dist-level patch on
provider-stream-*.jsadds the Google-guarded delete block above. Tracked locally asOC016with the removal trigger "upstream ships conditional guard natively."