mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 04:47:03 -06:00
fix(extra-params): preserve resolved cacheRetention against undefined own-property clobber (#106069)
* fix(extra-params): preserve resolved cacheRetention when options carries undefined own-property When the proxy transport emits cacheRetention as an own property set to undefined, JS spread semantics clobber the resolved cacheRetention value from per-model params. Re-assert the resolved value after the spread so it takes precedence over an undefined own-property in the caller's options. Fixes #106014 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(agents): strengthen cache retention precedence proof * refactor(agents): keep cache retention merge compact * style(agents): format cache retention matrix --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -511,4 +511,52 @@ describe("createStreamFnWithExtraParams sampling overrides", () => {
|
||||
expect(first.fastMode).toBe(firstFastMode);
|
||||
expect(second.fastMode).toBe(secondFastMode);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ requestRetention: undefined, expected: "long", name: "own undefined" },
|
||||
{ requestRetention: "none" as const, expected: "none", name: "explicit none" },
|
||||
{ requestRetention: "short" as const, expected: "short", name: "explicit short" },
|
||||
{ requestRetention: "long" as const, expected: "long", name: "explicit long" },
|
||||
])(
|
||||
"merges configured cache retention with $name request options",
|
||||
({ requestRetention, expected }) => {
|
||||
const underlying = vi.fn(() => ({
|
||||
push: vi.fn(),
|
||||
result: vi.fn(async () => undefined),
|
||||
[Symbol.asyncIterator]: vi.fn(async function* () {
|
||||
// empty stream
|
||||
}),
|
||||
})) as unknown as StreamFn;
|
||||
const agent: { streamFn?: StreamFn } = { streamFn: underlying };
|
||||
|
||||
applyExtraParamsToAgent(
|
||||
agent,
|
||||
undefined,
|
||||
"anthropic",
|
||||
"claude-sonnet-5",
|
||||
{ cacheRetention: "long" },
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ supportsPromptCacheKey: true } as never,
|
||||
);
|
||||
|
||||
if (!agent.streamFn) {
|
||||
throw new Error("expected extra params to wrap streamFn");
|
||||
}
|
||||
|
||||
const requestOptions = { cacheRetention: requestRetention };
|
||||
expect(requestOptions).toHaveProperty("cacheRetention");
|
||||
void agent.streamFn(
|
||||
{ id: "claude-sonnet-5", api: "anthropic-messages", provider: "anthropic" } as never,
|
||||
{ messages: [], tools: [] } as never,
|
||||
requestOptions,
|
||||
);
|
||||
|
||||
expect(underlying).toHaveBeenCalledTimes(1);
|
||||
const callOptions = (underlying as unknown as { mock: { calls: unknown[][] } }).mock
|
||||
.calls[0]?.[2] as { cacheRetention?: string } | undefined;
|
||||
expect(callOptions?.cacheRetention).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -553,15 +553,15 @@ function createStreamFnWithExtraParams(
|
||||
typeof callModel.id === "string" ? callModel.id : undefined,
|
||||
readSupportsPromptCacheKey(callModel),
|
||||
);
|
||||
const hasStreamParams = Object.keys(streamParams).length > 0 || cacheRetention;
|
||||
if (!hasStreamParams) {
|
||||
if (Object.keys(streamParams).length === 0 && !cacheRetention) {
|
||||
return underlying(callModel, context, options);
|
||||
}
|
||||
|
||||
const effectiveCacheRetention = options?.cacheRetention ?? cacheRetention;
|
||||
return underlying(callModel, context, {
|
||||
...streamParams,
|
||||
...(cacheRetention ? { cacheRetention } : {}),
|
||||
...options,
|
||||
// Own undefined means no request override; explicit none/short/long still wins.
|
||||
...(effectiveCacheRetention ? { cacheRetention: effectiveCacheRetention } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user