feat(openai-native): add abort controller for request cancellation (#9276)

This commit is contained in:
Daniel 2025-11-14 19:38:30 -05:00 committed by GitHub
parent 93c6d97017
commit 44b96f9d10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 2 deletions

View file

@ -159,6 +159,9 @@ describe("OpenAiNativeHandler", () => {
},
],
}),
expect.objectContaining({
signal: expect.any(Object),
}),
)
})
@ -1136,6 +1139,9 @@ describe("GPT-5 streaming event coverage (additional)", () => {
stream: false,
store: false,
}),
expect.objectContaining({
signal: expect.any(Object),
}),
)
})

View file

@ -34,6 +34,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
private lastResponseOutput: any[] | undefined
// Last top-level response id from Responses API (for troubleshooting)
private lastResponseId: string | undefined
// Abort controller for cancelling ongoing requests
private abortController?: AbortController
// Event types handled by the shared event processor to avoid duplication
private readonly coreHandledEventTypes = new Set<string>([
@ -255,9 +257,14 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
systemPrompt?: string,
messages?: Anthropic.Messages.MessageParam[],
): ApiStream {
// Create AbortController for cancellation
this.abortController = new AbortController()
try {
// Use the official SDK
const stream = (await (this.client as any).responses.create(requestBody)) as AsyncIterable<any>
const stream = (await (this.client as any).responses.create(requestBody, {
signal: this.abortController.signal,
})) as AsyncIterable<any>
if (typeof (stream as any)[Symbol.asyncIterator] !== "function") {
throw new Error(
@ -266,6 +273,11 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
for await (const event of stream) {
// Check if request was aborted
if (this.abortController.signal.aborted) {
break
}
for await (const outChunk of this.processEvent(event, model)) {
yield outChunk
}
@ -273,6 +285,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
} catch (sdkErr: any) {
// For errors, fallback to manual SSE via fetch
yield* this.makeResponsesApiRequest(requestBody, model, metadata, systemPrompt, messages)
} finally {
this.abortController = undefined
}
}
@ -342,6 +356,9 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
const baseUrl = this.options.openAiNativeBaseUrl || "https://api.openai.com"
const url = `${baseUrl}/v1/responses`
// Create AbortController for cancellation
this.abortController = new AbortController()
try {
const response = await fetch(url, {
method: "POST",
@ -351,6 +368,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
Accept: "text/event-stream",
},
body: JSON.stringify(requestBody),
signal: this.abortController.signal,
})
if (!response.ok) {
@ -426,6 +444,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
// Handle non-Error objects
throw new Error(`Unexpected error connecting to Responses API`)
} finally {
this.abortController = undefined
}
}
@ -446,6 +466,11 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
try {
while (true) {
// Check if request was aborted
if (this.abortController?.signal.aborted) {
break
}
const { done, value } = await reader.read()
if (done) break
@ -1076,6 +1101,9 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
async completePrompt(prompt: string): Promise<string> {
// Create AbortController for cancellation
this.abortController = new AbortController()
try {
const model = this.getModel()
const { verbosity, reasoning } = model
@ -1135,7 +1163,9 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
}
// Make the non-streaming request
const response = await (this.client as any).responses.create(requestBody)
const response = await (this.client as any).responses.create(requestBody, {
signal: this.abortController.signal,
})
// Extract text from the response
if (response?.output && Array.isArray(response.output)) {
@ -1161,6 +1191,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
throw new Error(`OpenAI Native completion error: ${error.message}`)
}
throw error
} finally {
this.abortController = undefined
}
}
}