From e588f2c0ce36bb6d2379b114bf8de018934d14cb Mon Sep 17 00:00:00 2001 From: krissding Date: Tue, 14 Jul 2026 14:54:56 +0800 Subject: [PATCH] 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 * 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 Co-authored-by: Peter Steinberger --- .../extra-params.sampling.test.ts | 48 +++++++++++++++++++ .../embedded-agent-runner/extra-params.ts | 8 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/agents/embedded-agent-runner/extra-params.sampling.test.ts b/src/agents/embedded-agent-runner/extra-params.sampling.test.ts index 1a434135679e..d79c7737b953 100644 --- a/src/agents/embedded-agent-runner/extra-params.sampling.test.ts +++ b/src/agents/embedded-agent-runner/extra-params.sampling.test.ts @@ -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); + }, + ); }); diff --git a/src/agents/embedded-agent-runner/extra-params.ts b/src/agents/embedded-agent-runner/extra-params.ts index 6966c2ae47ba..3f128df9a5e8 100644 --- a/src/agents/embedded-agent-runner/extra-params.ts +++ b/src/agents/embedded-agent-runner/extra-params.ts @@ -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 } : {}), }); };