fix: strip prompt_cache_key for providers that reject unknown fields#49727
fix: strip prompt_cache_key for providers that reject unknown fields#49727ShaunTsai wants to merge 1 commit into
Conversation
Greptile SummaryThis PR fixes a real HTTP 400 failure for Volcano Engine DeepSeek (and any other non-OpenAI provider using the The implementation reuses the existing Key observations:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/pi-embedded-runner/extra-params.ts
Line: 44-51
Comment:
**Missing API-type guard, unlike sibling wrappers**
`createParallelToolCallsWrapper` (line 172) short-circuits immediately when `model.api` is neither `"openai-completions"` nor `"openai-responses"`:
```ts
if (model.api !== "openai-completions" && model.api !== "openai-responses") {
return underlying(model, context, options);
}
```
`createStripPromptCacheFieldsWrapper` has no such guard. `prompt_cache_key` / `prompt_cache_retention` are only injected by pi-ai for `openai-responses` payloads, so the `delete` calls are no-ops for every other API type — but the wrapper still installs an extra `onPayload` interceptor on every Anthropic, Google, Moonshot, etc. call made through providers not in the allowlist.
Adding the guard would be consistent with the existing pattern and make the intent explicit:
```suggestion
function createStripPromptCacheFieldsWrapper(baseStreamFn: StreamFn | undefined): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
if (model.api !== "openai-responses") {
return underlying(model, context, options);
}
return streamWithPayloadPatch(underlying, model, context, options, (payload) => {
delete payload.prompt_cache_key;
delete payload.prompt_cache_retention;
});
};
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/extra-params.ts
Line: 248-250
Comment:
**No test coverage for the new stripping logic**
The `extra-params.*.test.ts` suite is fairly comprehensive — there are dedicated files for OpenAI, Google, xAI, OpenRouter cache-control, and cache-retention defaults. No test file was added (or updated) to assert that `prompt_cache_key` and `prompt_cache_retention` are stripped for unlisted providers (e.g. a "volcano" or "custom-openai-responses" provider), and that they are _kept_ for providers in `PROMPT_CACHE_KEY_PROVIDERS`.
A failing assertion here would surface if the allowlist accidentally drops a supported provider or if `streamWithPayloadPatch` changes its interception order.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "fix: strip prompt_ca..." |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 752dc1fbe6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
752dc1f to
4a44ca8
Compare
The pi-ai library unconditionally adds prompt_cache_key and prompt_cache_retention to OpenAI Responses API request bodies. Providers like Volcano Engine DeepSeek reject these unknown fields with HTTP 400. Strip both fields via an onPayload wrapper for any provider not in the known-supported set (openai, openai-codex, opencode, azure-openai-responses, github-copilot). Fixes openclaw#48155
Review Conversations StatusAll bot review comments have been addressed: greptile-apps[bot] —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 672148d51e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "openai", | ||
| "openai-codex", | ||
| "opencode", | ||
| "azure-openai-responses", | ||
| "github-copilot", |
There was a problem hiding this comment.
Add azure-openai to prompt cache allowlist
applyExtraParamsToAgent now strips prompt_cache_key/prompt_cache_retention for every provider not listed here, but azure-openai is a supported OpenAI Responses provider in this codebase (OPENAI_RESPONSES_PROVIDERS includes it in openai-stream-wrappers.ts). As written, Azure OpenAI models configured under provider azure-openai will silently lose prompt-cache fields on every request, which regresses caching behavior for that provider.
Useful? React with 👍 / 👎.
| function createStripPromptCacheFieldsWrapper(baseStreamFn: StreamFn | undefined): StreamFn { | ||
| const underlying = baseStreamFn ?? streamSimple; | ||
| return (model, context, options) => | ||
| streamWithPayloadPatch(underlying, model, context, options, (payload) => { | ||
| delete payload.prompt_cache_key; | ||
| delete payload.prompt_cache_retention; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Missing API-type guard, unlike sibling wrappers
createParallelToolCallsWrapper (line 172) short-circuits immediately when model.api is neither "openai-completions" nor "openai-responses":
if (model.api !== "openai-completions" && model.api !== "openai-responses") {
return underlying(model, context, options);
}createStripPromptCacheFieldsWrapper has no such guard. prompt_cache_key / prompt_cache_retention are only injected by pi-ai for openai-responses payloads, so the delete calls are no-ops for every other API type — but the wrapper still installs an extra onPayload interceptor on every Anthropic, Google, Moonshot, etc. call made through providers not in the allowlist.
Adding the guard would be consistent with the existing pattern and make the intent explicit:
| function createStripPromptCacheFieldsWrapper(baseStreamFn: StreamFn | undefined): StreamFn { | |
| const underlying = baseStreamFn ?? streamSimple; | |
| return (model, context, options) => | |
| streamWithPayloadPatch(underlying, model, context, options, (payload) => { | |
| delete payload.prompt_cache_key; | |
| delete payload.prompt_cache_retention; | |
| }); | |
| } | |
| function createStripPromptCacheFieldsWrapper(baseStreamFn: StreamFn | undefined): StreamFn { | |
| const underlying = baseStreamFn ?? streamSimple; | |
| return (model, context, options) => { | |
| if (model.api !== "openai-responses") { | |
| return underlying(model, context, options); | |
| } | |
| return streamWithPayloadPatch(underlying, model, context, options, (payload) => { | |
| delete payload.prompt_cache_key; | |
| delete payload.prompt_cache_retention; | |
| }); | |
| }; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/extra-params.ts
Line: 44-51
Comment:
**Missing API-type guard, unlike sibling wrappers**
`createParallelToolCallsWrapper` (line 172) short-circuits immediately when `model.api` is neither `"openai-completions"` nor `"openai-responses"`:
```ts
if (model.api !== "openai-completions" && model.api !== "openai-responses") {
return underlying(model, context, options);
}
```
`createStripPromptCacheFieldsWrapper` has no such guard. `prompt_cache_key` / `prompt_cache_retention` are only injected by pi-ai for `openai-responses` payloads, so the `delete` calls are no-ops for every other API type — but the wrapper still installs an extra `onPayload` interceptor on every Anthropic, Google, Moonshot, etc. call made through providers not in the allowlist.
Adding the guard would be consistent with the existing pattern and make the intent explicit:
```suggestion
function createStripPromptCacheFieldsWrapper(baseStreamFn: StreamFn | undefined): StreamFn {
const underlying = baseStreamFn ?? streamSimple;
return (model, context, options) => {
if (model.api !== "openai-responses") {
return underlying(model, context, options);
}
return streamWithPayloadPatch(underlying, model, context, options, (payload) => {
delete payload.prompt_cache_key;
delete payload.prompt_cache_retention;
});
};
}
```
How can I resolve this? If you propose a fix, please make it concise.| if (!PROMPT_CACHE_KEY_PROVIDERS.has(provider)) { | ||
| agent.streamFn = createStripPromptCacheFieldsWrapper(agent.streamFn); | ||
| } |
There was a problem hiding this comment.
No test coverage for the new stripping logic
The extra-params.*.test.ts suite is fairly comprehensive — there are dedicated files for OpenAI, Google, xAI, OpenRouter cache-control, and cache-retention defaults. No test file was added (or updated) to assert that prompt_cache_key and prompt_cache_retention are stripped for unlisted providers (e.g. a "volcano" or "custom-openai-responses" provider), and that they are kept for providers in PROMPT_CACHE_KEY_PROVIDERS.
A failing assertion here would surface if the allowlist accidentally drops a supported provider or if streamWithPayloadPatch changes its interception order.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/extra-params.ts
Line: 248-250
Comment:
**No test coverage for the new stripping logic**
The `extra-params.*.test.ts` suite is fairly comprehensive — there are dedicated files for OpenAI, Google, xAI, OpenRouter cache-control, and cache-retention defaults. No test file was added (or updated) to assert that `prompt_cache_key` and `prompt_cache_retention` are stripped for unlisted providers (e.g. a "volcano" or "custom-openai-responses" provider), and that they are _kept_ for providers in `PROMPT_CACHE_KEY_PROVIDERS`.
A failing assertion here would surface if the allowlist accidentally drops a supported provider or if `streamWithPayloadPatch` changes its interception order.
How can I resolve this? If you propose a fix, please make it concise.|
Closing in favor of a cleaner approach. The allowlist-based fix duplicates provider knowledge already encoded in |
Summary
openai-responsesAPI) returns HTTP 400unknown field "prompt_cache_key"because the pi-ai library unconditionally injectsprompt_cache_keyandprompt_cache_retentioninto OpenAI Responses request bodies.onPayloadwrapper that stripsprompt_cache_keyandprompt_cache_retentionfrom the request body for providers not in the known-supported set (openai,openai-codex,opencode,azure-openai-responses,github-copilot).cacheRetentionoption) and is unaffected. The existingcreateBedrockNoCacheWrapperfor non-Anthropic Bedrock models is also unchanged.Change Type
Scope
Linked Issue
User-visible / Behavior Changes
Volcano Engine DeepSeek (and other non-OpenAI providers using
openai-responsesAPI) will no longer fail with HTTP 400 on unknownprompt_cache_keyfield.Security Impact (required)
Evidence
prompt_cache_keyinjection from pi-aiopenai-responses.js:140through to the HTTP request body; confirmed the field is only meaningful for OpenAI's own APIHuman Verification (required)
applyExtraParamsToAgentthroughstreamWithPayloadPatchto confirm the wrapper correctly strips the fields before the request is sentpnpm checkcurrently fails on existing main typing debt unrelated to this PRAI Disclosure
Review Conversations
Compatibility / Migration
Failure Recovery (if this breaks)
src/agents/pi-embedded-runner/extra-params.tsprompt_cache_keyis missing fromPROMPT_CACHE_KEY_PROVIDERS, prompt caching would silently stop working for that providerRisks and Mitigations
prompt_cache_keywould need to be added to the allowlist.