From 78f89d727544934718ff6de2d803582e1607b106 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 13 Nov 2026 21:19:45 +0200 Subject: [PATCH 1/2] feat(rpc): tell hosts when the prompt cache expires An assistant reply's message_end now carries promptCache: the tier, touch time and lifetime of the prompt-cache entry its response confirmed, while the next request would still read it. The session already tracks this entry for the status line and the cache warmer. Scient Desktop compacts an idle thread just before a one-hour entry expires, so the summary reads a warm cache instead of the next message re-writing the whole context. It needs the expiry to do that. The field is left out for an inferred entry, a changed conversation and a cache that never expires (logged-in Codex). The agent's own idle compaction stays a terminal feature, so the two never race. Hosts that do not read the field are unaffected. Scient-Divergence: SA-PROMPT-CACHE Co-Authored-By: Claude Opus 5.5 --- SCIENT.md | 1 + .../coding-agent/src/modes/rpc/rpc-mode.ts | 7 +- .../coding-agent/src/modes/rpc/wire/events.ts | 17 ++- .../coding-agent/src/scient/prompt-cache.ts | 44 +++++++ .../test/scient/prompt-cache.test.ts | 116 ++++++++++++++++++ scripts/scient/divergences.json | 10 ++ 6 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 packages/coding-agent/src/scient/prompt-cache.ts create mode 100644 packages/coding-agent/test/scient/prompt-cache.test.ts diff --git a/SCIENT.md b/SCIENT.md index dfca20582a..b6dbf8055c 100644 --- a/SCIENT.md +++ b/SCIENT.md @@ -24,6 +24,7 @@ Everything else is upstream's code and behavior. | Browser relay | The relay listens on port 9324 (OMP uses 9224), so each product runs its own. | | Next message | `predict_next_message` gives an RPC host the prediction of the user's next message that OMP's terminal composer shows after a turn: OMP's prompt and parsing, on the session's model and context, without changing its history. It runs outside the command queue; a newer request replaces one in flight, and a turn or compaction that starts cancels it. | | Usage-limit reset | A `prompt_result` for a turn stopped by a usage limit carries `error.resetAt`, the time the provider said the limit resets (epoch ms): from its retry hint, the account's usage report, or an earlier stated block, whichever is latest. It is left out when the provider gave no time; the 30-minute guess for a hintless error is never reported. A host can resume the turn at that time. | +| Prompt cache | An assistant reply's `message_end` carries `promptCache` (`tier`, `touchedAt`, `ttlMs`): the prompt-cache entry its response confirmed, while the next request would still read it. It is left out for a cache that never expires and for an entry the agent only inferred. A host can compact an idle conversation just before a one-hour entry expires, so the summary reads a warm cache. The agent's own idle compaction stays a terminal feature and never runs over RPC. | | Failure kinds | A `prompt_result` for a failed turn carries `error.kinds`, the error classifier's labels for that failure (`auth-failed`, `context-overflow`, `transient`, `usage-limit`, ...), so a host can report why turns fail. It is left out when the classifier recognized no kind; the labels carry no provider text. | | macOS Accessibility prompt | The agent sends no Accessibility message while macOS doesn't trust it, so a computer call that refuses for lack of a permission never makes macOS ask. Only the host asks for permissions. | | Live voice delegation | `live_connect` with `hostDelegation` sends each request the voice model delegates to the host as a `live_delegation` frame instead of starting a turn, so a host that owns its turns (Scient Desktop) sends it as the user's message. The live session still reads that turn's answer. A host that runs the turn elsewhere, such as in the conversation's own agent while the call runs in a separate one, reports its progress and answer with `live_report`. Without the flag nothing changes. | diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts index 6d76396f75..1a45c46654 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts @@ -87,6 +87,9 @@ import { rpcFormDialog } from "../../scient/rpc-form"; // scient(SA-NEXT-MESSAGE): begin - A host shows the agent's prediction of the user's next message. import { RpcNextMessagePredictor } from "../../scient/next-message"; // scient(SA-NEXT-MESSAGE): end +// scient(SA-PROMPT-CACHE): begin - A host compacts an idle conversation before its prompt cache expires. +import { withPromptCache } from "../../scient/prompt-cache"; +// scient(SA-PROMPT-CACHE): end import { pageRpcMessages, RPC_MESSAGES_PAGE_BUSY_ERROR, RpcMessagesPageError } from "./rpc-messages"; import { RpcBtwController } from "./rpc-btw"; import { RpcGoalController } from "./rpc-goal"; @@ -1840,7 +1843,9 @@ export async function runRpcMode(session: AgentSession, options: RpcModeOptions // Output all agent events as JSON; prompt results follow the frame that settled them. session.subscribe(event => { - sessionEvents.forward(event); + // scient(SA-PROMPT-CACHE): begin - A host compacts an idle conversation before its prompt cache expires. + sessionEvents.forward(withPromptCache(event, session)); + // scient(SA-PROMPT-CACHE): end // Before the prompt-result and settle reports: a goal continuation decided at this // agent_end is scheduled (and reported as pending) before either reads settlement. goalController.observe(event); diff --git a/packages/coding-agent/src/modes/rpc/wire/events.ts b/packages/coding-agent/src/modes/rpc/wire/events.ts index 9c70cdd629..5e59a92dbb 100644 --- a/packages/coding-agent/src/modes/rpc/wire/events.ts +++ b/packages/coding-agent/src/modes/rpc/wire/events.ts @@ -47,7 +47,22 @@ export const eventDefs = { assistantMessageEvent: "AssistantMessageEvent", "messageId?": MESSAGE_ID, }, - MessageEndEvent: { type: "'message_end'", message: "AgentMessage", "messageId?": MESSAGE_ID }, + // scient(SA-PROMPT-CACHE): begin - A host compacts an idle conversation before its prompt cache expires. + MessageEndEvent: { + type: "'message_end'", + message: "AgentMessage", + "messageId?": MESSAGE_ID, + "promptCache?": doc( + "PromptCacheEntry", + "On an assistant reply: the prompt-cache entry its response confirmed, while the next request would still read it. Absent for a cache that never expires.", + ), + }, + PromptCacheEntry: { + tier: doc("'short' | 'long'", "Retention tier the provider wrote the entry under: minutes or about an hour."), + touchedAt: doc("number.integer", "Epoch ms when a request last wrote or read the entry (its send time)."), + ttlMs: doc("number.integer", "How long the provider keeps the entry after `touchedAt`."), + }, + // scient(SA-PROMPT-CACHE): end ToolExecutionStartEvent: { type: "'tool_execution_start'", toolCallId: "string", diff --git a/packages/coding-agent/src/scient/prompt-cache.ts b/packages/coding-agent/src/scient/prompt-cache.ts new file mode 100644 index 0000000000..49cf011104 --- /dev/null +++ b/packages/coding-agent/src/scient/prompt-cache.ts @@ -0,0 +1,44 @@ +/** + * The prompt-cache entry a reply confirmed, for an RPC host that compacts an idle conversation just + * before the entry expires (Scient Desktop's "Compact idle threads"). The session already tracks + * the entry for the status line and the cache warmer; this file puts it on the reply's `message_end`. + * + * Only an entry a response this process saw confirmed is reported, while the next request would + * still read it. An inferred entry, a changed conversation and a cache that never expires report + * nothing. The agent's own idle compaction stays a terminal feature, so the two never race. + */ +import type { AgentSession, AgentSessionEvent } from "../session/agent-session"; +import type { PromptCacheTier } from "../session/cache-warmer"; + +export interface RpcPromptCacheEntry { + /** Retention tier the provider wrote the entry under: `short` (minutes) or `long` (about an hour). */ + tier: PromptCacheTier; + /** Epoch ms when a request last wrote or read the entry: the send time, not the reply's end. */ + touchedAt: number; + /** How long the provider keeps the entry after `touchedAt`. */ + ttlMs: number; +} + +export type PromptCacheSession = Pick; + +/** The confirmed, still-readable entry at `nowMs`; undefined when there is none or it never expires. */ +export function confirmedPromptCache(session: PromptCacheSession, nowMs = Date.now()): RpcPromptCacheEntry | undefined { + // The token count only describes the next request; nothing here reads it. + const status = session.getPromptCacheStatus(nowMs, 0); + if (!status || status.inferred || status.state !== "warm" || status.ttlMs === undefined) return undefined; + return { tier: status.tier, touchedAt: status.touchedAt, ttlMs: status.ttlMs }; +} + +export type PromptCacheMessageEnd = Extract & { + promptCache: RpcPromptCacheEntry; +}; + +/** `event`, with the confirmed entry on an assistant reply's `message_end`. */ +export function withPromptCache( + event: AgentSessionEvent, + session: PromptCacheSession, +): AgentSessionEvent | PromptCacheMessageEnd { + if (event.type !== "message_end" || event.message.role !== "assistant") return event; + const promptCache = confirmedPromptCache(session); + return promptCache === undefined ? event : { ...event, promptCache }; +} diff --git a/packages/coding-agent/test/scient/prompt-cache.test.ts b/packages/coding-agent/test/scient/prompt-cache.test.ts new file mode 100644 index 0000000000..afebd6b8f9 --- /dev/null +++ b/packages/coding-agent/test/scient/prompt-cache.test.ts @@ -0,0 +1,116 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; +import * as path from "node:path"; +import { Agent } from "@oh-my-pi/pi-agent-core"; +import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai"; +import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; +import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry"; +import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { withPromptCache } from "@oh-my-pi/pi-coding-agent/scient/prompt-cache"; +import { SecretObfuscator } from "@oh-my-pi/pi-coding-agent/secrets"; +import { AgentSession, type AgentSessionEvent } from "@oh-my-pi/pi-coding-agent/session/agent-session"; +import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; +import { CacheWarmer } from "@oh-my-pi/pi-coding-agent/session/cache-warmer"; +import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; +import { TempDir } from "@oh-my-pi/pi-utils"; + +const LONG_TTL_MS = 3_600_000; + +function reply(model: Model): AssistantMessage { + return { + role: "assistant", + content: [{ type: "text", text: "done" }], + api: model.api, + provider: model.provider, + model: model.id, + stopReason: "stop", + timestamp: Date.now(), + usage: { + input: 1, + output: 1, + cacheRead: 0, + cacheWrite: 250_000, + totalTokens: 250_002, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + } satisfies Usage, + }; +} + +describe("message_end carries the confirmed prompt-cache entry", () => { + let tempDir: TempDir; + let authStorage: AuthStorage | undefined; + let session: AgentSession; + let warmer: CacheWarmer; + + async function createSession(model: Model): Promise { + warmer?.cancel(); + authStorage?.close(); + const agentDir = tempDir.path(); + authStorage = await AuthStorage.create(path.join(agentDir, "auth.db")); + const settings = await Settings.loadIsolated({ agentDir, cwd: agentDir }); + warmer = new CacheWarmer({ + getMode: () => "off", + getPromptTokens: () => 250_000, + stream: async () => { + throw new Error("no refresh expected"); + }, + }); + session = new AgentSession({ + agent: new Agent({ initialState: { model, systemPrompt: ["Test"], tools: [], messages: [] } }), + sessionManager: SessionManager.create(agentDir, agentDir), + settings, + cacheWarmer: warmer, + modelRegistry: new ModelRegistry(authStorage), + obfuscator: new SecretObfuscator([]), + }); + } + + /** One main-loop request whose response wrote the prompt cache; returns its message_end. */ + function respond(model: Model): AgentSessionEvent { + session.startCacheWarming(model, { messages: [] }, { cacheRetention: "long" }); + const message = reply(model); + warmer.onResponse(message); + return { type: "message_end", message }; + } + + beforeEach(() => { + vi.useFakeTimers(); + tempDir = TempDir.createSync("@scient-prompt-cache-"); + }); + + afterEach(async () => { + warmer.cancel(); + vi.useRealTimers(); + authStorage?.close(); + authStorage = undefined; + try { + await tempDir.remove(); + } catch {} + }); + + it("reports a confirmed one-hour entry from the request's send time, until it lapses", async () => { + const model = { ...getBundledModel("anthropic", "claude-sonnet-4-5"), promptCache: { long: 3600 } } as Model; + await createSession(model); + const sentAt = Date.now(); + const event = respond(model); + + expect(withPromptCache(event, session)).toMatchObject({ + promptCache: { tier: "long", touchedAt: sentAt, ttlMs: LONG_TTL_MS }, + }); + vi.advanceTimersByTime(LONG_TTL_MS); + expect(withPromptCache(event, session)).not.toHaveProperty("promptCache"); + }); + + it("reports nothing for a cache that never expires, or on a frame other than a reply's end", async () => { + const codex = getBundledModel("openai-codex", "gpt-5.6-sol") as Model; + await createSession(codex); + expect(withPromptCache(respond(codex), session)).not.toHaveProperty("promptCache"); + + const model = { ...getBundledModel("anthropic", "claude-sonnet-4-5"), promptCache: { long: 3600 } } as Model; + await createSession(model); + const end = respond(model); + if (end.type !== "message_end") throw new Error("expected message_end"); + expect(withPromptCache({ type: "message_start", message: end.message }, session)).not.toHaveProperty( + "promptCache", + ); + }); +}); diff --git a/scripts/scient/divergences.json b/scripts/scient/divergences.json index c355afce3e..c265c3691f 100644 --- a/scripts/scient/divergences.json +++ b/scripts/scient/divergences.json @@ -379,6 +379,16 @@ ], "upstream": ["packages/coding-agent/src/cli-commands.ts"], "verification": ["bun --cwd=packages/coding-agent test test/scient/dictation.test.ts"] + }, + "SA-PROMPT-CACHE": { + "status": "active", + "contract": "An assistant reply's message_end frame carries promptCache ({ tier, touchedAt, ttlMs }), the prompt-cache entry the reply's response confirmed, while the next request would still read it. It is absent for an inferred entry, a changed conversation, a model without a declared lifetime, and a cache that never expires. The agent's own idle compaction stays off over RPC, so a host that compacts before the entry expires never races it. Hosts that do not read it are unaffected.", + "owned": ["packages/coding-agent/src/scient/prompt-cache.ts", "packages/coding-agent/test/scient/prompt-cache.test.ts"], + "upstream": [ + "packages/coding-agent/src/modes/rpc/rpc-mode.ts", + "packages/coding-agent/src/modes/rpc/wire/events.ts" + ], + "verification": ["bun --cwd=packages/coding-agent test test/scient/prompt-cache.test.ts"] } } } From ede5fa078fbb384f90251aadc560ba1a4ebba1c4 Mon Sep 17 00:00:00 2001 From: Yaacov Date: Fri, 13 Nov 2026 21:19:45 +0200 Subject: [PATCH 2/2] chore(rpc): regenerate clients for the prompt-cache entry (generated) Scient-Generator: rpc Co-Authored-By: Claude Opus 5.5 --- .../src/modes/rpc/wire/rpc-wire.generated.ts | 11 +++++ .../src/modes/rpc/wire/rpc-wire.schema.json | 30 ++++++++++++ sdk/go/omp-rpc/wire.go | 49 +++++++++++++++++++ sdk/python/omp-rpc/src/omp_rpc/_wire.py | 24 +++++++++ sdk/rust/omp-rpc/src/wire.rs | 33 +++++++++++++ 5 files changed, 147 insertions(+) diff --git a/packages/coding-agent/src/modes/rpc/wire/rpc-wire.generated.ts b/packages/coding-agent/src/modes/rpc/wire/rpc-wire.generated.ts index efaf398693..3b91dd8665 100644 --- a/packages/coding-agent/src/modes/rpc/wire/rpc-wire.generated.ts +++ b/packages/coding-agent/src/modes/rpc/wire/rpc-wire.generated.ts @@ -870,6 +870,17 @@ export interface MessageEndEvent { message: AgentMessage; /** Shared by the start, updates, and end of one message; unique per process. */ messageId?: string; + /** On an assistant reply: the prompt-cache entry its response confirmed, while the next request would still read it. Absent for a cache that never expires. */ + promptCache?: PromptCacheEntry; +} + +export interface PromptCacheEntry { + /** Retention tier the provider wrote the entry under: minutes or about an hour. */ + tier: "short" | "long"; + /** Epoch ms when a request last wrote or read the entry (its send time). */ + touchedAt: number; + /** How long the provider keeps the entry after `touchedAt`. */ + ttlMs: number; } export interface ToolExecutionStartEvent { diff --git a/packages/coding-agent/src/modes/rpc/wire/rpc-wire.schema.json b/packages/coding-agent/src/modes/rpc/wire/rpc-wire.schema.json index ec6c64dcf9..8a5b353de9 100644 --- a/packages/coding-agent/src/modes/rpc/wire/rpc-wire.schema.json +++ b/packages/coding-agent/src/modes/rpc/wire/rpc-wire.schema.json @@ -3905,6 +3905,10 @@ "messageId": { "type": "string", "description": "Shared by the start, updates, and end of one message; unique per process." + }, + "promptCache": { + "$ref": "#/$defs/PromptCacheEntry", + "description": "On an assistant reply: the prompt-cache entry its response confirmed, while the next request would still read it. Absent for a cache that never expires." } }, "required": [ @@ -3912,6 +3916,32 @@ "message" ] }, + "PromptCacheEntry": { + "type": "object", + "properties": { + "tier": { + "enum": [ + "short", + "long" + ], + "type": "string", + "description": "Retention tier the provider wrote the entry under: minutes or about an hour." + }, + "touchedAt": { + "type": "integer", + "description": "Epoch ms when a request last wrote or read the entry (its send time)." + }, + "ttlMs": { + "type": "integer", + "description": "How long the provider keeps the entry after `touchedAt`." + } + }, + "required": [ + "tier", + "touchedAt", + "ttlMs" + ] + }, "ToolExecutionStartEvent": { "type": "object", "properties": { diff --git a/sdk/go/omp-rpc/wire.go b/sdk/go/omp-rpc/wire.go index 199fe673e7..bbfe90d937 100644 --- a/sdk/go/omp-rpc/wire.go +++ b/sdk/go/omp-rpc/wire.go @@ -3841,6 +3841,8 @@ type MessageEndEvent struct { Message AgentMessage `json:"message"` // Shared by the start, updates, and end of one message; unique per process. MessageID *string `json:"messageId,omitempty"` + // On an assistant reply: the prompt-cache entry its response confirmed, while the next request would still read it. Absent for a cache that never expires. + PromptCache *PromptCacheEntry `json:"promptCache,omitempty"` } func (v *MessageEndEvent) UnmarshalJSON(data []byte) error { @@ -3853,6 +3855,7 @@ func (v *MessageEndEvent) decodeFrom(raw map[string]json.RawMessage) error { d.constant("type", "message_end") d.required("message", &out.Message) d.optional("messageId", &out.MessageID) + d.optional("promptCache", &out.PromptCache) if d.err != nil { return d.err } @@ -3865,6 +3868,52 @@ func (v MessageEndEvent) MarshalJSON() ([]byte, error) { return encodeObject(plain(v), `"type":"message_end"`, nil) } +type PromptCacheEntry struct { + // Retention tier the provider wrote the entry under: minutes or about an hour. + Tier PromptCacheEntryTier `json:"tier"` + // Epoch ms when a request last wrote or read the entry (its send time). + TouchedAt int64 `json:"touchedAt"` + // How long the provider keeps the entry after `touchedAt`. + TtlMs int64 `json:"ttlMs"` +} + +func (v *PromptCacheEntry) UnmarshalJSON(data []byte) error { + return decodeWith(data, "PromptCacheEntry", v.decodeFrom) +} + +func (v *PromptCacheEntry) decodeFrom(raw map[string]json.RawMessage) error { + var out PromptCacheEntry + d := fieldDecoder{raw: raw, owner: "PromptCacheEntry"} + d.required("tier", &out.Tier) + d.required("touchedAt", &out.TouchedAt) + d.required("ttlMs", &out.TtlMs) + if d.err != nil { + return d.err + } + *v = out + return nil +} + +type PromptCacheEntryTier string + +const ( + PromptCacheEntryTierShort PromptCacheEntryTier = "short" + PromptCacheEntryTierLong PromptCacheEntryTier = "long" +) + +func (v *PromptCacheEntryTier) UnmarshalJSON(data []byte) error { + s, err := decodeString(data, "PromptCacheEntryTier") + if err != nil { + return err + } + switch value := PromptCacheEntryTier(s); value { + case PromptCacheEntryTierShort, PromptCacheEntryTierLong: + *v = value + return nil + } + return unknownValue("PromptCacheEntryTier", s) +} + type ToolExecutionStartEvent struct { ToolCallID string `json:"toolCallId"` ToolName string `json:"toolName"` diff --git a/sdk/python/omp-rpc/src/omp_rpc/_wire.py b/sdk/python/omp-rpc/src/omp_rpc/_wire.py index c3076046e8..f671cfa940 100644 --- a/sdk/python/omp-rpc/src/omp_rpc/_wire.py +++ b/sdk/python/omp-rpc/src/omp_rpc/_wire.py @@ -1049,6 +1049,18 @@ class MessageEndEvent: message: AgentMessage message_id: str | None = None """Shared by the start, updates, and end of one message; unique per process.""" + prompt_cache: PromptCacheEntry | None = None + """On an assistant reply: the prompt-cache entry its response confirmed, while the next request would still read it. Absent for a cache that never expires.""" + + +@dataclass(slots=True, frozen=True, kw_only=True) +class PromptCacheEntry: + tier: Literal["short", "long"] + """Retention tier the provider wrote the entry under: minutes or about an hour.""" + touched_at: int + """Epoch ms when a request last wrote or read the entry (its send time).""" + ttl_ms: int + """How long the provider keeps the entry after `touchedAt`.""" @dataclass(slots=True, frozen=True, kw_only=True) @@ -2495,6 +2507,16 @@ def parse_message_end_event(value: object, path: str = "MessageEndEvent") -> Mes return MessageEndEvent( message=required(payload, "message", parse_agent_message, path), message_id=optional(payload, "messageId", decode_str, path), + prompt_cache=optional(payload, "promptCache", parse_prompt_cache_entry, path), + ) + + +def parse_prompt_cache_entry(value: object, path: str = "PromptCacheEntry") -> PromptCacheEntry: + payload = expect_object(value, path) + return PromptCacheEntry( + tier=required(payload, "tier", cast('Decoder[Literal["short", "long"]]', literal(frozenset({"short", "long"}))), path), + touched_at=required(payload, "touchedAt", decode_int, path), + ttl_ms=required(payload, "ttlMs", decode_int, path), ) @@ -4207,6 +4229,7 @@ def on_queue_update(self, listener: Callable[[QueueUpdateEvent], None]) -> Calla "ParentIrc", "PromoteQueuedMessageResult", "PromptAck", + "PromptCacheEntry", "PromptError", "PromptResultEvent", "PromptStatus", @@ -4393,6 +4416,7 @@ def on_queue_update(self, listener: Callable[[QueueUpdateEvent], None]) -> Calla "parse_parent_irc", "parse_promote_queued_message_result", "parse_prompt_ack", + "parse_prompt_cache_entry", "parse_prompt_error", "parse_prompt_result_event", "parse_python_execution_message", diff --git a/sdk/rust/omp-rpc/src/wire.rs b/sdk/rust/omp-rpc/src/wire.rs index 233d10d16f..14df3f071f 100644 --- a/sdk/rust/omp-rpc/src/wire.rs +++ b/sdk/rust/omp-rpc/src/wire.rs @@ -3768,6 +3768,21 @@ pub struct MessageEndEvent { /// Shared by the start, updates, and end of one message; unique per process. #[serde(rename = "messageId", default, skip_serializing_if = "Option::is_none")] pub message_id: Option, + /// On an assistant reply: the prompt-cache entry its response confirmed, while the next request would still read it. Absent for a cache that never expires. + #[serde(rename = "promptCache", default, skip_serializing_if = "Option::is_none")] + pub prompt_cache: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PromptCacheEntry { + /// Retention tier the provider wrote the entry under: minutes or about an hour. + pub tier: PromptCacheEntryTier, + /// Epoch ms when a request last wrote or read the entry (its send time). + #[serde(rename = "touchedAt")] + pub touched_at: i64, + /// How long the provider keeps the entry after `touchedAt`. + #[serde(rename = "ttlMs")] + pub ttl_ms: i64, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -6070,6 +6085,24 @@ impl LogoutAccountType { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum PromptCacheEntryTier { + #[serde(rename = "short")] + Short, + #[serde(rename = "long")] + Long, +} + +impl PromptCacheEntryTier { + /// Wire value. + pub fn as_str(self) -> &'static str { + match self { + Self::Short => "short", + Self::Long => "long", + } + } +} + /// The constant `"cost_cap"`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct LitCostCap;