mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: add stateless mode configuration for OpenAI Native provider
- Add openAiNativeStatelessMode boolean option to provider settings schema - Update OpenAI Native handler to respect stateless mode configuration - When enabled, forces store: false for all Responses API requests - Add comprehensive tests for stateless mode behavior Fixes #7789
This commit is contained in:
parent
0ce4e891fd
commit
753a66f171
3 changed files with 189 additions and 2 deletions
|
|
@ -228,6 +228,9 @@ 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(),
|
||||
// When true, forces the OpenAI Responses API to run in stateless mode (store: false)
|
||||
// This prevents responses from being stored for 30 days in OpenAI's Responses API
|
||||
openAiNativeStatelessMode: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const mistralSchema = apiModelIdProviderModelSchema.extend({
|
||||
|
|
|
|||
|
|
@ -1732,5 +1732,187 @@ describe("GPT-5 streaming event coverage (additional)", () => {
|
|||
expect(bodyStr).not.toContain('"verbosity"')
|
||||
})
|
||||
})
|
||||
|
||||
describe("Stateless mode configuration", () => {
|
||||
it("should use stateless mode when openAiNativeStatelessMode is true", async () => {
|
||||
const mockFetch = vitest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'),
|
||||
)
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
})
|
||||
;(global as any).fetch = mockFetch as any
|
||||
|
||||
// Force SDK path to fail so we use fetch fallback
|
||||
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
|
||||
|
||||
const handler = new OpenAiNativeHandler({
|
||||
apiModelId: "gpt-5-2025-08-07",
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
openAiNativeStatelessMode: true, // Enable stateless mode
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _ of stream) {
|
||||
// drain
|
||||
}
|
||||
|
||||
const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string
|
||||
const parsedBody = JSON.parse(bodyStr)
|
||||
expect(parsedBody.store).toBe(false) // Should be false when stateless mode is enabled
|
||||
})
|
||||
|
||||
it("should default to store: true when openAiNativeStatelessMode is false", async () => {
|
||||
const mockFetch = vitest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'),
|
||||
)
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
})
|
||||
;(global as any).fetch = mockFetch as any
|
||||
|
||||
// Force SDK path to fail so we use fetch fallback
|
||||
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
|
||||
|
||||
const handler = new OpenAiNativeHandler({
|
||||
apiModelId: "gpt-5-2025-08-07",
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
openAiNativeStatelessMode: false, // Explicitly disable stateless mode
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _ of stream) {
|
||||
// drain
|
||||
}
|
||||
|
||||
const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string
|
||||
const parsedBody = JSON.parse(bodyStr)
|
||||
expect(parsedBody.store).toBe(true) // Should be true when stateless mode is disabled
|
||||
})
|
||||
|
||||
it("should default to store: true when openAiNativeStatelessMode is not set", async () => {
|
||||
const mockFetch = vitest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'),
|
||||
)
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
})
|
||||
;(global as any).fetch = mockFetch as any
|
||||
|
||||
// Force SDK path to fail so we use fetch fallback
|
||||
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
|
||||
|
||||
const handler = new OpenAiNativeHandler({
|
||||
apiModelId: "gpt-5-2025-08-07",
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
// openAiNativeStatelessMode not set
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
|
||||
const stream = handler.createMessage(systemPrompt, messages)
|
||||
|
||||
for await (const _ of stream) {
|
||||
// drain
|
||||
}
|
||||
|
||||
const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string
|
||||
const parsedBody = JSON.parse(bodyStr)
|
||||
expect(parsedBody.store).toBe(true) // Should default to true
|
||||
})
|
||||
|
||||
it("should override metadata.store when openAiNativeStatelessMode is true", async () => {
|
||||
const mockFetch = vitest.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'),
|
||||
)
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
})
|
||||
;(global as any).fetch = mockFetch as any
|
||||
|
||||
// Force SDK path to fail so we use fetch fallback
|
||||
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
|
||||
|
||||
const handler = new OpenAiNativeHandler({
|
||||
apiModelId: "gpt-5-2025-08-07",
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
openAiNativeStatelessMode: true, // Enable stateless mode
|
||||
})
|
||||
|
||||
const systemPrompt = "You are a helpful assistant."
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
|
||||
// Even if metadata.store is true, stateless mode should override it
|
||||
const stream = handler.createMessage(systemPrompt, messages, { taskId: "test", store: true })
|
||||
|
||||
for await (const _ of stream) {
|
||||
// drain
|
||||
}
|
||||
|
||||
const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string
|
||||
const parsedBody = JSON.parse(bodyStr)
|
||||
expect(parsedBody.store).toBe(false) // Should be false even when metadata.store is true
|
||||
})
|
||||
|
||||
it("should use stateless mode in completePrompt when openAiNativeStatelessMode is true", async () => {
|
||||
// Mock the responses.create method
|
||||
mockResponsesCreate.mockResolvedValue({
|
||||
output: [
|
||||
{
|
||||
type: "message",
|
||||
content: [
|
||||
{
|
||||
type: "output_text",
|
||||
text: "Test response",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const handler = new OpenAiNativeHandler({
|
||||
apiModelId: "gpt-5-2025-08-07",
|
||||
openAiNativeApiKey: "test-api-key",
|
||||
openAiNativeStatelessMode: true, // Enable stateless mode
|
||||
})
|
||||
|
||||
await handler.completePrompt("Test prompt")
|
||||
|
||||
expect(mockResponsesCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
store: false, // Should always be false in completePrompt with stateless mode
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// Use stateless mode if configured, otherwise respect metadata.store (default true)
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue