Skip to content

Commit 53df91c

Browse files
committed
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
1 parent 0ce4e89 commit 53df91c

File tree

3 files changed

+111
-2
lines changed

3 files changed

+111
-2
lines changed

packages/types/src/provider-settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ const openAiNativeSchema = apiModelIdProviderModelSchema.extend({
228228
// OpenAI Responses API service tier for openai-native provider only.
229229
// UI should only expose this when the selected model supports flex/priority.
230230
openAiNativeServiceTier: serviceTierSchema.optional(),
231+
// Enable stateless mode for OpenAI Responses API (sets store: false)
232+
// When enabled, responses won't be stored for 30 days and can't be referenced
233+
// in future requests using previous_response_id
234+
openAiNativeStatelessMode: z.boolean().optional(),
231235
})
232236

233237
const mistralSchema = apiModelIdProviderModelSchema.extend({

src/api/providers/__tests__/openai-native.spec.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,109 @@ describe("OpenAiNativeHandler", () => {
859859
expect(secondCallBody.previous_response_id).toBe("resp_789")
860860
})
861861

862+
it("should respect openAiNativeStatelessMode configuration", async () => {
863+
// Test with stateless mode enabled
864+
const statelessHandler = new OpenAiNativeHandler({
865+
...mockOptions,
866+
openAiNativeStatelessMode: true,
867+
})
868+
869+
// Mock fetch for Responses API
870+
const mockFetch = vitest.fn().mockResolvedValue({
871+
ok: true,
872+
body: new ReadableStream({
873+
start(controller) {
874+
controller.enqueue(
875+
new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Test"}\n\n'),
876+
)
877+
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
878+
controller.close()
879+
},
880+
}),
881+
})
882+
global.fetch = mockFetch as any
883+
884+
// Mock SDK to fail
885+
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
886+
887+
const stream = statelessHandler.createMessage(systemPrompt, messages, { taskId: "test-task" })
888+
const chunks = []
889+
for await (const chunk of stream) {
890+
chunks.push(chunk)
891+
}
892+
893+
// Verify that store is set to false when stateless mode is enabled
894+
const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
895+
expect(requestBody.store).toBe(false)
896+
})
897+
898+
it("should use metadata.store when stateless mode is disabled", async () => {
899+
// Test with stateless mode disabled (default)
900+
const handler = new OpenAiNativeHandler(mockOptions)
901+
902+
// Mock fetch for Responses API
903+
const mockFetch = vitest.fn().mockResolvedValue({
904+
ok: true,
905+
body: new ReadableStream({
906+
start(controller) {
907+
controller.enqueue(
908+
new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Test"}\n\n'),
909+
)
910+
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
911+
controller.close()
912+
},
913+
}),
914+
})
915+
global.fetch = mockFetch as any
916+
917+
// Mock SDK to fail
918+
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
919+
920+
// Test with metadata.store = false
921+
const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task", store: false })
922+
const chunks = []
923+
for await (const chunk of stream) {
924+
chunks.push(chunk)
925+
}
926+
927+
// Verify that store is set to false when metadata.store is false
928+
const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
929+
expect(requestBody.store).toBe(false)
930+
})
931+
932+
it("should default to store:true when stateless mode is disabled and metadata.store is not set", async () => {
933+
// Test with stateless mode disabled and no metadata.store
934+
const handler = new OpenAiNativeHandler(mockOptions)
935+
936+
// Mock fetch for Responses API
937+
const mockFetch = vitest.fn().mockResolvedValue({
938+
ok: true,
939+
body: new ReadableStream({
940+
start(controller) {
941+
controller.enqueue(
942+
new TextEncoder().encode('data: {"type":"response.text.delta","delta":"Test"}\n\n'),
943+
)
944+
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
945+
controller.close()
946+
},
947+
}),
948+
})
949+
global.fetch = mockFetch as any
950+
951+
// Mock SDK to fail
952+
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
953+
954+
const stream = handler.createMessage(systemPrompt, messages, { taskId: "test-task" })
955+
const chunks = []
956+
for await (const chunk of stream) {
957+
chunks.push(chunk)
958+
}
959+
960+
// Verify that store defaults to true
961+
const requestBody = JSON.parse(mockFetch.mock.calls[0][1].body)
962+
expect(requestBody.store).toBe(true)
963+
})
964+
862965
it("should retry with full conversation when previous_response_id fails", async () => {
863966
// This test verifies the fix for context loss bug when previous_response_id becomes invalid
864967
const mockFetch = vitest

src/api/providers/openai-native.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
255255
model: model.id,
256256
input: formattedInput,
257257
stream: true,
258-
store: metadata?.store !== false, // Default to true unless explicitly set to false
258+
// Check if stateless mode is enabled in configuration, otherwise use metadata.store
259+
store: this.options.openAiNativeStatelessMode ? false : metadata?.store !== false,
259260
// Always include instructions (system prompt) for Responses API.
260261
// Unlike Chat Completions, system/developer roles in input have no special semantics here.
261262
// The official way to set system behavior is the top-level `instructions` field.
@@ -1286,7 +1287,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
12861287
},
12871288
],
12881289
stream: false, // Non-streaming for completePrompt
1289-
store: false, // Don't store prompt completions
1290+
// Use stateless mode if configured, otherwise don't store prompt completions
1291+
store: this.options.openAiNativeStatelessMode ? false : false,
12901292
}
12911293

12921294
// Include service tier if selected and supported

0 commit comments

Comments
 (0)