feat: add openAiNativeStatelessMode configuration option

- Add openAiNativeStatelessMode boolean option to OpenAI Native provider settings
- Update provider to check this setting and override metadata.store accordingly
- Add comprehensive tests for the new configuration option
- Maintain backward compatibility (defaults to current behavior)

Fixes #7789
This commit is contained in:
Roo Code 2025-09-08 18:55:14 +00:00
parent 0ce4e891fd
commit 53df91c0a8
3 changed files with 111 additions and 2 deletions

View file

@ -228,6 +228,10 @@ const openAiNativeSchema = apiModelIdProviderModelSchema.extend({
// OpenAI Responses API service tier for openai-native provider only.
// UI should only expose this when the selected model supports flex/priority.
openAiNativeServiceTier: serviceTierSchema.optional(),
// Enable stateless mode for OpenAI Responses API (sets store: false)
// When enabled, responses won't be stored for 30 days and can't be referenced
// in future requests using previous_response_id
openAiNativeStatelessMode: z.boolean().optional(),
})
const mistralSchema = apiModelIdProviderModelSchema.extend({

View file

@ -859,6 +859,109 @@ describe("OpenAiNativeHandler", () => {
expect(secondCallBody.previous_response_id).toBe("resp_789")
})
it("should respect openAiNativeStatelessMode configuration", async () => {
// Test with stateless mode enabled
const statelessHandler = new OpenAiNativeHandler({
...mockOptions,
openAiNativeStatelessMode: true,
})
// Mock fetch for Responses API
const mockFetch = vitest.fn().mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(
new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Test"}\n\n'),
)
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
controller.close()
},
}),
})
global.fetch = mockFetch as any
// Mock SDK to fail
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
const stream = statelessHandler.createMessage(systemPrompt, messages, { taskId: "test-task" })
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify that store is set to false when stateless mode is enabled
const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
expect(requestBody.store).toBe(false)
})
it("should use metadata.store when stateless mode is disabled", async () => {
// Test with stateless mode disabled (default)
const handler = new OpenAiNativeHandler(mockOptions)
// Mock fetch for Responses API
const mockFetch = vitest.fn().mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(
new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Test"}\n\n'),
)
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
controller.close()
},
}),
})
global.fetch = mockFetch as any
// Mock SDK to fail
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
// Test with metadata.store = false
const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task", store: false })
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify that store is set to false when metadata.store is false
const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
expect(requestBody.store).toBe(false)
})
it("should default to store:true when stateless mode is disabled and metadata.store is not set", async () => {
// Test with stateless mode disabled and no metadata.store
const handler = new OpenAiNativeHandler(mockOptions)
// Mock fetch for Responses API
const mockFetch = vitest.fn().mockResolvedValue({
ok: true,
body: new ReadableStream({
start(controller) {
controller.enqueue(
new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Test"}\n\n'),
)
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
controller.close()
},
}),
})
global.fetch = mockFetch as any
// Mock SDK to fail
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task" })
const chunks = []
for await (const chunk of stream) {
chunks.push(chunk)
}
// Verify that store defaults to true
const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
expect(requestBody.store).toBe(true)
})
it("should retry with full conversation when previous_response_id fails", async () => {
// This test verifies the fix for context loss bug when previous_response_id becomes invalid
const mockFetch = vitest

View file

@ -255,7 +255,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
model: model.id,
input: formattedInput,
stream: true,
store: metadata?.store !== false, // Default to true unless explicitly set to false
// Check if stateless mode is enabled in configuration, otherwise use metadata.store
store: this.options.openAiNativeStatelessMode ? false : metadata?.store !== false,
// Always include instructions (system prompt) for Responses API.
// Unlike Chat Completions, system/developer roles in input have no special semantics here.
// The official way to set system behavior is the top-level `instructions` field.
@ -1286,7 +1287,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
},
],
stream: false, // Non-streaming for completePrompt
store: false, // Don't store prompt completions
// Use stateless mode if configured, otherwise don't store prompt completions
store: this.options.openAiNativeStatelessMode ? false : false,
}
// Include service tier if selected and supported