fix(agents): honor explicit long Anthropic cache TTL on custom hosts (#67800)

Merged via squash.

Prepared head SHA: 0ffde15713
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
Reviewed-by: @hxy91819
This commit is contained in:
Ted Li
2026-04-21 02:45:27 -07:00
committed by GitHub
parent f1df354222
commit 4bacdc8824
3 changed files with 69 additions and 2 deletions

View File

@@ -2,6 +2,15 @@
Docs: https://docs.openclaw.ai
## Unreleased
### Changes
### Fixes
- Agents/Anthropic: honor explicit `cacheRetention: "long"` for custom `anthropic-messages` endpoints by applying the 1-hour ephemeral cache TTL independently of the Anthropic/Vertex hostname allowlist. Implicit and env-driven long retention still require an allowlisted host. (#67800) Thanks @MonkeyLeeT.
- fix(agents): honor explicit long Anthropic cache TTL on custom hosts (#67800). Thanks @MonkeyLeeT
## 2026.4.20
### Changes

View File

@@ -86,7 +86,7 @@ describe("anthropic payload policy", () => {
});
});
it("denies proxied Anthropic service tier and omits long-TTL upgrades for custom hosts", () => {
it("denies proxied Anthropic service tier but honors explicit long TTL for custom hosts", () => {
const policy = resolveAnthropicPayloadPolicy({
provider: "anthropic",
api: "anthropic-messages",
@@ -103,6 +103,59 @@ describe("anthropic payload policy", () => {
applyAnthropicPayloadPolicyToParams(payload, policy);
expect(payload).not.toHaveProperty("service_tier");
expect(payload.system).toEqual([textBlock("Follow policy.", { type: "ephemeral", ttl: "1h" })]);
expect(payload.messages[0]).toEqual({
role: "user",
content: [{ type: "text", text: "Hello", cache_control: { type: "ephemeral", ttl: "1h" } }],
});
});
it("keeps implicit env-driven long retention conservative for custom hosts", () => {
const previous = process.env.PI_CACHE_RETENTION;
process.env.PI_CACHE_RETENTION = "long";
try {
const policy = resolveAnthropicPayloadPolicy({
provider: "anthropic",
api: "anthropic-messages",
baseUrl: "https://proxy.example.com/anthropic",
enableCacheControl: true,
});
const payload: TestPayload = {
system: [{ type: "text", text: "Follow policy." }],
messages: [{ role: "user", content: "Hello" }],
};
applyAnthropicPayloadPolicyToParams(payload, policy);
expect(payload.system).toEqual([textBlock("Follow policy.", { type: "ephemeral" })]);
expect(payload.messages[0]).toEqual({
role: "user",
content: [{ type: "text", text: "Hello", cache_control: { type: "ephemeral" } }],
});
} finally {
if (previous === undefined) {
delete process.env.PI_CACHE_RETENTION;
} else {
process.env.PI_CACHE_RETENTION = previous;
}
}
});
it("keeps explicit short retention unchanged for custom hosts", () => {
const policy = resolveAnthropicPayloadPolicy({
provider: "anthropic",
api: "anthropic-messages",
baseUrl: "https://proxy.example.com/anthropic",
cacheRetention: "short",
enableCacheControl: true,
});
const payload: TestPayload = {
system: [{ type: "text", text: "Follow policy." }],
messages: [{ role: "user", content: "Hello" }],
};
applyAnthropicPayloadPolicyToParams(payload, policy);
expect(payload.system).toEqual([textBlock("Follow policy.", { type: "ephemeral" })]);
expect(payload.messages[0]).toEqual({
role: "user",

View File

@@ -58,7 +58,12 @@ function resolveAnthropicEphemeralCacheControl(
if (retention === "none") {
return undefined;
}
const ttl = retention === "long" && isLongTtlEligibleEndpoint(baseUrl) ? "1h" : undefined;
// Trust explicit long-retention opt-ins for Anthropic-compatible custom providers.
// Keep hostname gating for implicit/env-driven long retention so defaults stay conservative.
const ttl =
retention === "long" && (cacheRetention === "long" || isLongTtlEligibleEndpoint(baseUrl))
? "1h"
: undefined;
return { type: "ephemeral", ...(ttl ? { ttl } : {}) };
}