Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/types/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
})
Expand Down
71 changes: 71 additions & 0 deletions src/core/task-persistence/__tests__/taskMessages.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
12 changes: 6 additions & 6 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down
12 changes: 0 additions & 12 deletions src/core/task/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down