mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-10 22:41:14 +00:00
fix: prevent context mixing in Roo/Sonic model (fixes #7292)
- Disabled prompt caching for roo/sonic model to prevent cross-session contamination - Added unique session IDs to each RooHandler instance for request isolation - Added request-specific headers (X-Session-Id, X-Request-Id, X-No-Cache) to prevent caching - Added comprehensive tests to verify session isolation is working correctly - Updated model configuration to set supportsPromptCache to false This fix addresses the issue where the Roo/Sonic model was giving completely unrelated responses, suggesting context was being mixed between different users or sessions.
This commit is contained in:
parent
6fd261d3b6
commit
93fb46121f
3 changed files with 201 additions and 3 deletions
|
|
@ -10,7 +10,7 @@ export const rooModels = {
|
|||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsPromptCache: false, // Disabled to prevent context mixing between sessions
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ describe("RooHandler", () => {
|
|||
expect.objectContaining({ role: "user", content: "Second message" }),
|
||||
]),
|
||||
}),
|
||||
expect.any(Object), // Headers object
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -331,7 +332,7 @@ describe("RooHandler", () => {
|
|||
expect(modelInfo.info.maxTokens).toBe(16_384)
|
||||
expect(modelInfo.info.contextWindow).toBe(262_144)
|
||||
expect(modelInfo.info.supportsImages).toBe(false)
|
||||
expect(modelInfo.info.supportsPromptCache).toBe(true)
|
||||
expect(modelInfo.info.supportsPromptCache).toBe(false) // Should be false now to prevent context mixing
|
||||
expect(modelInfo.info.inputPrice).toBe(0)
|
||||
expect(modelInfo.info.outputPrice).toBe(0)
|
||||
})
|
||||
|
|
@ -361,6 +362,7 @@ describe("RooHandler", () => {
|
|||
expect.not.objectContaining({
|
||||
temperature: expect.anything(),
|
||||
}),
|
||||
expect.any(Object), // Headers object
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -378,6 +380,7 @@ describe("RooHandler", () => {
|
|||
expect.objectContaining({
|
||||
temperature: 0.9,
|
||||
}),
|
||||
expect.any(Object), // Headers object
|
||||
)
|
||||
})
|
||||
|
||||
|
|
@ -433,4 +436,143 @@ describe("RooHandler", () => {
|
|||
}).toThrow("Authentication required for Roo Code Cloud")
|
||||
})
|
||||
})
|
||||
|
||||
describe("session isolation", () => {
|
||||
beforeEach(() => {
|
||||
mockHasInstanceFn.mockReturnValue(true)
|
||||
mockGetSessionTokenFn.mockReturnValue("test-session-token")
|
||||
mockCreate.mockClear()
|
||||
})
|
||||
|
||||
it("should include session isolation headers in requests", async () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
|
||||
// Consume the stream
|
||||
for await (const _chunk of stream) {
|
||||
// Just consume
|
||||
}
|
||||
|
||||
// Verify that create was called with session isolation headers
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"X-Session-Id": expect.any(String),
|
||||
"X-Request-Id": expect.any(String),
|
||||
"X-No-Cache": "true",
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should generate unique session IDs for different handler instances", async () => {
|
||||
const handler1 = new RooHandler(mockOptions)
|
||||
const handler2 = new RooHandler(mockOptions)
|
||||
|
||||
// Create messages with both handlers
|
||||
const stream1 = handler1.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream1) {
|
||||
// Consume
|
||||
}
|
||||
|
||||
const stream2 = handler2.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream2) {
|
||||
// Consume
|
||||
}
|
||||
|
||||
// Get the session IDs from the calls
|
||||
const call1Headers = mockCreate.mock.calls[0][1].headers
|
||||
const call2Headers = mockCreate.mock.calls[1][1].headers
|
||||
|
||||
// Session IDs should be different for different handler instances
|
||||
expect(call1Headers["X-Session-Id"]).toBeDefined()
|
||||
expect(call2Headers["X-Session-Id"]).toBeDefined()
|
||||
expect(call1Headers["X-Session-Id"]).not.toBe(call2Headers["X-Session-Id"])
|
||||
})
|
||||
|
||||
it("should generate unique request IDs for each request", async () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
|
||||
// Make two requests with the same handler
|
||||
const stream1 = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream1) {
|
||||
// Consume
|
||||
}
|
||||
|
||||
const stream2 = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream2) {
|
||||
// Consume
|
||||
}
|
||||
|
||||
// Get the request IDs from the calls
|
||||
const call1Headers = mockCreate.mock.calls[0][1].headers
|
||||
const call2Headers = mockCreate.mock.calls[1][1].headers
|
||||
|
||||
// Request IDs should be different for each request
|
||||
expect(call1Headers["X-Request-Id"]).toBeDefined()
|
||||
expect(call2Headers["X-Request-Id"]).toBeDefined()
|
||||
expect(call1Headers["X-Request-Id"]).not.toBe(call2Headers["X-Request-Id"])
|
||||
|
||||
// But session IDs should be the same for the same handler
|
||||
expect(call1Headers["X-Session-Id"]).toBe(call2Headers["X-Session-Id"])
|
||||
})
|
||||
|
||||
it("should include metadata in request params", async () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _chunk of stream) {
|
||||
// Consume
|
||||
}
|
||||
|
||||
// Verify metadata is included in the request
|
||||
expect(mockCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
metadata: expect.objectContaining({
|
||||
session_id: expect.any(String),
|
||||
request_id: expect.any(String),
|
||||
timestamp: expect.any(String),
|
||||
}),
|
||||
}),
|
||||
expect.any(Object),
|
||||
)
|
||||
})
|
||||
|
||||
it("should have prompt caching disabled for roo/sonic model", () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
const modelInfo = handler.getModel()
|
||||
|
||||
// Verify that prompt caching is disabled
|
||||
expect(modelInfo.info.supportsPromptCache).toBe(false)
|
||||
})
|
||||
|
||||
it("should maintain session ID consistency across multiple requests", async () => {
|
||||
handler = new RooHandler(mockOptions)
|
||||
|
||||
// Make multiple requests
|
||||
const requests = []
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
for await (const _chunk of stream) {
|
||||
// Consume
|
||||
}
|
||||
requests.push(i)
|
||||
}
|
||||
|
||||
// All requests should have the same session ID
|
||||
const sessionIds = mockCreate.mock.calls.map((call) => call[1].headers["X-Session-Id"])
|
||||
const firstSessionId = sessionIds[0]
|
||||
|
||||
expect(sessionIds.every((id) => id === firstSessionId)).toBe(true)
|
||||
|
||||
// But all request IDs should be unique
|
||||
const requestIds = mockCreate.mock.calls.map((call) => call[1].headers["X-Request-Id"])
|
||||
const uniqueRequestIds = new Set(requestIds)
|
||||
|
||||
expect(uniqueRequestIds.size).toBe(requestIds.length)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
import { randomUUID } from "crypto"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { t } from "../../i18n"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
|
||||
import type { ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
|
||||
export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
||||
private sessionId: string
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
// Check if CloudService is available and get the session token.
|
||||
if (!CloudService.hasInstance()) {
|
||||
|
|
@ -22,6 +27,9 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
|||
throw new Error(t("common:errors.roo.authenticationRequired"))
|
||||
}
|
||||
|
||||
// Generate a unique session ID for this handler instance to ensure request isolation
|
||||
const sessionId = randomUUID()
|
||||
|
||||
super({
|
||||
...options,
|
||||
providerName: "Roo Code Cloud",
|
||||
|
|
@ -31,6 +39,53 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
|||
providerModels: rooModels,
|
||||
defaultTemperature: 0.7,
|
||||
})
|
||||
|
||||
this.sessionId = sessionId
|
||||
}
|
||||
|
||||
protected override createStream(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
metadata?: ApiHandlerCreateMessageMetadata,
|
||||
) {
|
||||
const {
|
||||
id: model,
|
||||
info: { maxTokens: max_tokens },
|
||||
} = this.getModel()
|
||||
|
||||
// Generate unique request ID for this specific request
|
||||
const requestId = randomUUID()
|
||||
|
||||
// Create the request with session isolation metadata
|
||||
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model,
|
||||
max_tokens,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)],
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
// Add session isolation metadata to prevent context mixing
|
||||
metadata: {
|
||||
session_id: this.sessionId,
|
||||
request_id: requestId,
|
||||
timestamp: new Date().toISOString(),
|
||||
} as any,
|
||||
}
|
||||
|
||||
// Only include temperature if explicitly set
|
||||
if (this.options.modelTemperature !== undefined) {
|
||||
params.temperature = this.options.modelTemperature
|
||||
}
|
||||
|
||||
// Create the stream with additional headers for session isolation
|
||||
return this.client.chat.completions.create(params, {
|
||||
headers: {
|
||||
"X-Session-Id": this.sessionId,
|
||||
"X-Request-Id": requestId,
|
||||
"X-No-Cache": "true", // Prevent any server-side caching
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate",
|
||||
Pragma: "no-cache",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
|
|
@ -78,13 +133,14 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
|||
}
|
||||
|
||||
// Return the requested model ID even if not found, with fallback info.
|
||||
// Note: supportsPromptCache is now false to prevent context mixing
|
||||
return {
|
||||
id: modelId as RooModelId,
|
||||
info: {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: true,
|
||||
supportsPromptCache: false, // Disabled to prevent context mixing
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue