feat(core): diagnose prompt cache prefix changes (#39139)

This commit is contained in:
Kit Langton 2026-07-27 13:44:02 -04:00 committed by GitHub
commit 7eb51d0507
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 180 additions and 1 deletions

View file

@ -0,0 +1,54 @@
import { describe, expect, test } from "bun:test"
import { GenerationOptions, LLM, LLMRequest, Message, Model, ToolDefinition } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { PromptCacheDiagnostics } from "@opencode-ai/core/session/prompt-cache-diagnostics"
const model = Model.make({ id: "test", provider: "test", route: OpenAIChat.route })
const tool = ToolDefinition.make({
name: "read",
description: "Read a file",
inputSchema: { type: "object", properties: {} },
})
const request = LLM.request({
model,
system: "System",
prompt: "First",
tools: [tool],
})
const compare = (current: LLMRequest) =>
PromptCacheDiagnostics.compare(PromptCacheDiagnostics.snapshot(request), PromptCacheDiagnostics.snapshot(current))
describe("PromptCacheDiagnostics", () => {
test("distinguishes initial and stable requests", () => {
const snapshot = PromptCacheDiagnostics.snapshot(request)
expect(PromptCacheDiagnostics.compare(undefined, snapshot)).toEqual({ status: "initial" })
expect(PromptCacheDiagnostics.compare(snapshot, snapshot)).toEqual({ status: "stable", messages: 1 })
})
test("recognizes append-only history", () => {
const current = LLMRequest.update(request, { messages: [...request.messages, Message.assistant("Second")] })
expect(compare(current)).toEqual({ status: "append-only", previousMessages: 1, currentMessages: 2 })
})
test("detects cache-sensitive setting changes", () => {
const current = LLMRequest.update(request, { generation: GenerationOptions.make({ temperature: 0.5 }) })
expect(compare(current)).toEqual({ status: "changed", component: "settings", index: 0, label: "model settings" })
})
test("finds the first changed prefix component", () => {
const changedTool = ToolDefinition.make({ ...tool, description: "Read one file" })
const current = LLMRequest.update(request, { tools: [changedTool] })
expect(compare(current)).toEqual({ status: "changed", component: "tools", index: 0, label: "read" })
})
test("treats appended tools as a prefix change", () => {
const write = ToolDefinition.make({
name: "write",
description: "Write a file",
inputSchema: { type: "object", properties: {} },
})
const current = LLMRequest.update(request, { tools: [...request.tools, write] })
expect(compare(current)).toEqual({ status: "changed", component: "tools", index: 1, label: "write" })
})
})

View file

@ -42,6 +42,7 @@ import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator
import { SessionRunner } from "@opencode-ai/core/session/runner"
import * as SessionRunnerLLM from "@opencode-ai/core/session/runner/llm"
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
import { PromptCacheDiagnostics } from "@opencode-ai/core/session/prompt-cache-diagnostics"
import { SessionUsage } from "@opencode-ai/core/session/usage"
import { PluginSupervisor } from "@opencode-ai/core/plugin/supervisor"
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
@ -1315,6 +1316,12 @@ describe("SessionRunnerLLM", () => {
yield* admit(session, "Second")
yield* session.resume(sessionID)
expect(
PromptCacheDiagnostics.compare(
PromptCacheDiagnostics.snapshot(requests[0]!),
PromptCacheDiagnostics.snapshot(requests[1]!),
),
).toEqual({ status: "append-only", previousMessages: 1, currentMessages: 3 })
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
[defaultSystem, "Initial context"],
[defaultSystem, "Initial context"],