mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 14:02:55 +00:00
fix(agents): bound google prompt cache expiry
This commit is contained in:
@@ -385,6 +385,53 @@ describe("google prompt cache", () => {
|
||||
expect(getCapturedPayload()?.cachedContent).toBe("cachedContents/system-cache-3");
|
||||
});
|
||||
|
||||
it("does not bypass failed-cache backoff when the process clock is invalid", async () => {
|
||||
const systemPromptDigest = crypto.createHash("sha256").update("Follow policy.").digest("hex");
|
||||
const sessionManager = makeSessionManager([
|
||||
{
|
||||
id: "entry-1",
|
||||
parentId: null,
|
||||
timestamp: new Date(1_000).toISOString(),
|
||||
type: "custom",
|
||||
customType: "openclaw.google-prompt-cache",
|
||||
data: {
|
||||
status: "failed",
|
||||
timestamp: 1_000,
|
||||
provider: "google",
|
||||
modelId: "gemini-3.1-pro-preview",
|
||||
modelApi: "google-generative-ai",
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
systemPromptDigest,
|
||||
cacheRetention: "long",
|
||||
retryAfter: Date.parse("2030-01-01T00:00:00.000Z"),
|
||||
},
|
||||
},
|
||||
]);
|
||||
const fetchMock = createCacheFetchMock({
|
||||
name: "cachedContents/system-cache-invalid-clock",
|
||||
expireTime: "2030-01-01T00:00:00.000Z",
|
||||
});
|
||||
const innerStreamFn = vi.fn(() => "stream" as never);
|
||||
|
||||
const wrapped = await preparePromptCacheStream({
|
||||
fetchMock,
|
||||
now: Number.NaN,
|
||||
sessionManager,
|
||||
streamFn: innerStreamFn,
|
||||
});
|
||||
|
||||
await Promise.resolve(
|
||||
wrapped?.(
|
||||
makeGoogleModel(),
|
||||
{ systemPrompt: "Follow policy.", messages: [] } as never,
|
||||
{} as never,
|
||||
),
|
||||
);
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
expect(innerStreamFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("stays out of the way when cachedContent is already configured explicitly", async () => {
|
||||
const fetchMock = vi.fn();
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ import { parseGeminiAuth } from "../../infra/gemini-auth.js";
|
||||
import { normalizeGoogleApiBaseUrl } from "../../infra/google-api-base-url.js";
|
||||
import { streamWithPayloadPatch } from "../../llm/providers/stream-wrappers/stream-payload-utils.js";
|
||||
import type { Model } from "../../llm/types.js";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
isFutureDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
} from "../../shared/number-coercion.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { buildGuardedModelFetch } from "../provider-transport-fetch.js";
|
||||
import type { StreamFn } from "../runtime/index.js";
|
||||
@@ -185,8 +190,7 @@ function parseExpireTimeMs(expireTime: string | undefined): number | null {
|
||||
if (!expireTime) {
|
||||
return null;
|
||||
}
|
||||
const timestamp = Date.parse(expireTime);
|
||||
return Number.isFinite(timestamp) ? timestamp : null;
|
||||
return asDateTimestampMs(Date.parse(expireTime)) ?? null;
|
||||
}
|
||||
|
||||
function convertManagedGoogleTools(tools: NonNullable<GooglePromptCacheContext["tools"]>) {
|
||||
@@ -342,7 +346,10 @@ async function ensureGooglePromptCache(
|
||||
deps: GooglePromptCacheDeps,
|
||||
): Promise<string | null> {
|
||||
const baseUrl = normalizeGoogleApiBaseUrl(params.model.baseUrl);
|
||||
const now = deps.now?.() ?? Date.now();
|
||||
const now = asDateTimestampMs(deps.now?.() ?? Date.now());
|
||||
if (now === undefined) {
|
||||
return null;
|
||||
}
|
||||
const systemPromptDigest = digestSystemPrompt(params.systemPrompt);
|
||||
const matchKey = buildGooglePromptCacheMatchKey({
|
||||
provider: params.provider,
|
||||
@@ -354,7 +361,10 @@ async function ensureGooglePromptCache(
|
||||
});
|
||||
const latestEntry = readLatestGooglePromptCacheEntry(params.sessionManager, matchKey);
|
||||
|
||||
if (latestEntry?.status === "failed" && latestEntry.retryAfter > now) {
|
||||
if (
|
||||
latestEntry?.status === "failed" &&
|
||||
isFutureDateTimestampMs(latestEntry.retryAfter, { nowMs: now })
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -362,7 +372,7 @@ async function ensureGooglePromptCache(
|
||||
const refreshWindowMs = resolveGooglePromptCacheRefreshWindowMs(params.cacheRetention);
|
||||
if (latestEntry?.status === "ready" && latestEntry.cachedContent) {
|
||||
const expiresAt = parseExpireTimeMs(latestEntry.expireTime);
|
||||
const isExpired = expiresAt !== null && expiresAt <= now;
|
||||
const isExpired = expiresAt !== null && !isFutureDateTimestampMs(expiresAt, { nowMs: now });
|
||||
if (!isExpired) {
|
||||
const needsRefresh = expiresAt !== null && expiresAt - now <= refreshWindowMs;
|
||||
if (!needsRefresh) {
|
||||
@@ -420,7 +430,8 @@ async function ensureGooglePromptCache(
|
||||
systemPromptDigest,
|
||||
cacheConfigDigest: params.cacheConfigDigest,
|
||||
cacheRetention: params.cacheRetention,
|
||||
retryAfter: now + GOOGLE_PROMPT_CACHE_RETRY_BACKOFF_MS,
|
||||
retryAfter:
|
||||
resolveExpiresAtMsFromDurationMs(GOOGLE_PROMPT_CACHE_RETRY_BACKOFF_MS, { nowMs: now }) ?? 0,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user