Remove GPT‑5 instructions/reasoning_summary from UI message metadata to prevent ui_messages.json bloat (#8756)

chore(gpt5): stop persisting instructions/reasoning_summary in UI message metadata

Problem: ui_messages.json was getting bloated with unused or duplicated content (system 'instructions' and 'reasoning_summary') that we do not read back. Root cause: earlier OpenAI Responses API implementation persisted these fields to per-message metadata; however, 'instructions' are already sent as top-level request instructions and 'reasoning_summary' is surfaced live via streaming events. Neither field is consumed from storage. Changes: (1) Task.persistGpt5Metadata now stores only previous_response_id; (2) removed instructions and reasoning_summary from types; (3) updated Zod schema; (4) persistence layer writes messages as-is (no sanitizer); (5) tests green. Impact: smaller ui_messages.json, no runtime behavior change for requests. Migration: old metadata fields will be ignored by schema.
This commit is contained in:
Hannes Rudolph 2025-10-21 14:55:29 -06:00 committed by GitHub
parent f3a505fd36
commit 6ffdd440ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 77 additions and 20 deletions

View file

@ -221,8 +221,6 @@ export const clineMessageSchema = z.object({
gpt5: z
.object({
previous_response_id: z.string().optional(),
instructions: z.string().optional(),
reasoning_summary: z.string().optional(),
})
.optional(),
})

View file

@ -0,0 +1,71 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import * as os from "os"
import * as path from "path"
import * as fs from "fs/promises"
// Mocks (use hoisted to avoid initialization ordering issues)
const hoisted = vi.hoisted(() => ({
safeWriteJsonMock: vi.fn().mockResolvedValue(undefined),
}))
vi.mock("../../../utils/safeWriteJson", () => ({
safeWriteJson: hoisted.safeWriteJsonMock,
}))
// Import after mocks
import { saveTaskMessages } from "../taskMessages"
let tmpBaseDir: string
beforeEach(async () => {
hoisted.safeWriteJsonMock.mockClear()
// Create a unique, writable temp directory to act as globalStoragePath
tmpBaseDir = await fs.mkdtemp(path.join(os.tmpdir(), "roo-test-"))
})
describe("taskMessages.saveTaskMessages", () => {
beforeEach(() => {
hoisted.safeWriteJsonMock.mockClear()
})
it("persists messages as-is", async () => {
const messages: any[] = [
{
role: "assistant",
content: "Hello",
metadata: {
gpt5: {
previous_response_id: "resp_123",
},
other: "keep",
},
},
{ role: "user", content: "Do thing" },
]
await saveTaskMessages({
messages,
taskId: "task-1",
globalStoragePath: tmpBaseDir,
})
expect(hoisted.safeWriteJsonMock).toHaveBeenCalledTimes(1)
const [, persisted] = hoisted.safeWriteJsonMock.mock.calls[0]
expect(persisted).toEqual(messages)
})
it("persists messages without modification when no metadata", async () => {
const messages: any[] = [
{ role: "assistant", content: "Hi" },
{ role: "user", content: "Yo" },
]
await saveTaskMessages({
messages,
taskId: "task-2",
globalStoragePath: tmpBaseDir,
})
const [, persisted] = hoisted.safeWriteJsonMock.mock.calls[0]
expect(persisted).toEqual(messages)
})
})

View file

@ -2267,7 +2267,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
}
await this.persistGpt5Metadata(reasoningMessage)
await this.persistGpt5Metadata()
await this.saveClineMessages()
await this.providerRef.deref()?.postStateToWebview()
@ -2853,10 +2853,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
/**
* Persist GPT-5 per-turn metadata (previous_response_id, instructions, reasoning_summary)
* Persist GPT-5 per-turn metadata (previous_response_id only)
* onto the last complete assistant say("text") message.
*
* Note: We do not persist system instructions or reasoning summaries.
*/
private async persistGpt5Metadata(reasoningMessage?: string): Promise<void> {
private async persistGpt5Metadata(): Promise<void> {
try {
const modelId = this.api.getModel().id
if (!modelId || !modelId.startsWith("gpt-5")) return
@ -2875,9 +2877,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
const gpt5Metadata: Gpt5Metadata = {
...(msg.metadata.gpt5 ?? {}),
previous_response_id: lastResponseId,
instructions: this.lastUsedInstructions,
reasoning_summary: (reasoningMessage ?? "").trim() || undefined,
...(lastResponseId ? { previous_response_id: lastResponseId } : {}),
}
msg.metadata.gpt5 = gpt5Metadata
}

View file

@ -12,18 +12,6 @@ export interface Gpt5Metadata {
* Used to maintain conversation continuity in subsequent requests
*/
previous_response_id?: string
/**
* The system instructions/prompt used for this response
* Stored to track what instructions were active when the response was generated
*/
instructions?: string
/**
* The reasoning summary from GPT-5's reasoning process
* Contains the model's internal reasoning if reasoning mode was enabled
*/
reasoning_summary?: string
}
/**