Skip to content

Commit 2022e28

Browse files
committed
openai-native: gate text.verbosity by supportsVerbosity, remove DEBUG_RESPONSES_API, fix Gpt5RequestBody input type, add tests for non-verbosity models
1 parent c4c0b17 commit 2022e28

File tree

2 files changed

+90
-16
lines changed

2 files changed

+90
-16
lines changed

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,5 +1375,86 @@ describe("GPT-5 streaming event coverage (additional)", () => {
13751375
}
13761376
}).rejects.toThrow("Responses API error: Model overloaded")
13771377
})
1378+
1379+
// New tests: ensure text.verbosity is omitted for models without supportsVerbosity
1380+
describe("Verbosity gating for non-GPT-5 models", () => {
1381+
it("should omit text.verbosity for gpt-4.1", async () => {
1382+
const mockFetch = vitest.fn().mockResolvedValue({
1383+
ok: true,
1384+
body: new ReadableStream({
1385+
start(controller) {
1386+
controller.enqueue(
1387+
new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'),
1388+
)
1389+
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
1390+
controller.close()
1391+
},
1392+
}),
1393+
})
1394+
;(global as any).fetch = mockFetch as any
1395+
1396+
// Force SDK path to fail so we use fetch fallback
1397+
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
1398+
1399+
const handler = new OpenAiNativeHandler({
1400+
apiModelId: "gpt-4.1",
1401+
openAiNativeApiKey: "test-api-key",
1402+
verbosity: "high",
1403+
})
1404+
1405+
const systemPrompt = "You are a helpful assistant."
1406+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
1407+
const stream = handler.createMessage(systemPrompt, messages)
1408+
1409+
for await (const _ of stream) {
1410+
// drain
1411+
}
1412+
1413+
const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string
1414+
const parsedBody = JSON.parse(bodyStr)
1415+
expect(parsedBody.model).toBe("gpt-4.1")
1416+
expect(parsedBody.text).toBeUndefined()
1417+
expect(bodyStr).not.toContain('"verbosity"')
1418+
})
1419+
1420+
it("should omit text.verbosity for gpt-4o", async () => {
1421+
const mockFetch = vitest.fn().mockResolvedValue({
1422+
ok: true,
1423+
body: new ReadableStream({
1424+
start(controller) {
1425+
controller.enqueue(
1426+
new TextEncoder().encode('data: {"type":"response.done","response":{}}\n\n'),
1427+
)
1428+
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"))
1429+
controller.close()
1430+
},
1431+
}),
1432+
})
1433+
;(global as any).fetch = mockFetch as any
1434+
1435+
// Force SDK path to fail so we use fetch fallback
1436+
mockResponsesCreate.mockRejectedValue(new Error("SDK not available"))
1437+
1438+
const handler = new OpenAiNativeHandler({
1439+
apiModelId: "gpt-4o",
1440+
openAiNativeApiKey: "test-api-key",
1441+
verbosity: "low",
1442+
})
1443+
1444+
const systemPrompt = "You are a helpful assistant."
1445+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hello!" }]
1446+
const stream = handler.createMessage(systemPrompt, messages)
1447+
1448+
for await (const _ of stream) {
1449+
// drain
1450+
}
1451+
1452+
const bodyStr = (mockFetch.mock.calls[0][1] as any).body as string
1453+
const parsedBody = JSON.parse(bodyStr)
1454+
expect(parsedBody.model).toBe("gpt-4o")
1455+
expect(parsedBody.text).toBeUndefined()
1456+
expect(bodyStr).not.toContain('"verbosity"')
1457+
})
1458+
})
13781459
})
13791460
})

src/api/providers/openai-native.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ export type OpenAiNativeModel = ReturnType<OpenAiNativeHandler["getModel"]>
3131
// Constants for model identification
3232
const GPT5_MODEL_PREFIX = "gpt-5"
3333

34-
// Debug flag for logging (can be controlled via environment variable or config)
35-
const DEBUG_RESPONSES_API = process.env.DEBUG_RESPONSES_API === "true"
36-
3734
export class OpenAiNativeHandler extends BaseProvider implements SingleCompletionHandler {
3835
protected options: ApiHandlerOptions
3936
private client: OpenAI
@@ -198,7 +195,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
198195
// so requests do not default to very large limits (e.g., 120k).
199196
interface Gpt5RequestBody {
200197
model: string
201-
input: string
198+
input: Array<{ role: "user" | "assistant"; content: any[] }>
202199
stream: boolean
203200
reasoning?: { effort: ReasoningEffortWithMinimal; summary?: "auto" }
204201
text?: { verbosity: VerbosityLevel }
@@ -209,7 +206,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
209206
instructions?: string
210207
}
211208

212-
return {
209+
const body: Gpt5RequestBody = {
213210
model: model.id,
214211
input: formattedInput,
215212
stream: true,
@@ -224,7 +221,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
224221
...(this.options.enableGpt5ReasoningSummary ? { summary: "auto" as const } : {}),
225222
},
226223
}),
227-
text: { verbosity: (verbosity || "medium") as VerbosityLevel },
228224
// Only include temperature if the model supports it
229225
...(model.info.supportsTemperature !== false && {
230226
temperature:
@@ -238,6 +234,13 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
238234
...(model.maxTokens ? { max_output_tokens: model.maxTokens } : {}),
239235
...(requestPreviousResponseId && { previous_response_id: requestPreviousResponseId }),
240236
}
237+
238+
// Include text.verbosity only when the model explicitly supports it
239+
if (model.info.supportsVerbosity === true) {
240+
body.text = { verbosity: (verbosity || "medium") as VerbosityLevel }
241+
}
242+
243+
return body
241244
}
242245

243246
private async *executeRequest(
@@ -269,11 +272,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
269272

270273
if (is400Error && requestBody.previous_response_id && isPreviousResponseError) {
271274
// Log the error and retry without the previous_response_id
272-
if (DEBUG_RESPONSES_API) {
273-
console.debug(
274-
`[Responses API] Previous response ID not found (${requestBody.previous_response_id}), retrying without it`,
275-
)
276-
}
277275

278276
// Remove the problematic previous_response_id and retry
279277
const retryRequestBody = { ...requestBody }
@@ -440,11 +438,6 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
440438

441439
if (response.status === 400 && requestBody.previous_response_id && isPreviousResponseError) {
442440
// Log the error and retry without the previous_response_id
443-
if (DEBUG_RESPONSES_API) {
444-
console.debug(
445-
`[Responses API] Previous response ID not found (${requestBody.previous_response_id}), retrying without it`,
446-
)
447-
}
448441

449442
// Remove the problematic previous_response_id and retry
450443
const retryRequestBody = { ...requestBody }

0 commit comments

Comments
 (0)