···
19
19
20
20
export interface ChatClient {
21
21
send: (content: string) => Promise<{ ok: true }>
22
22
-
getStatus: () => Promise<{ running: boolean }>
22
22
+
getStatus: () => Promise<{ running: boolean; idle: boolean }>
23
23
stream: (options: StreamOptions) => Promise<void>
24
24
}
25
25
···
129
129
throw new Error(await parseError(res))
130
130
}
131
131
132
132
-
const data = (await res.json()) as { running?: unknown }
133
133
-
return { running: data.running === true }
132
132
+
const data = (await res.json()) as { running?: unknown; idle?: unknown }
133
133
+
return { running: data.running === true, idle: data.idle === true }
134
134
}
135
135
136
136
const stream: ChatClient["stream"] = async ({ signal, onEvent }) => {
···
152
152
return /function call should not be used with prefix/i.test(apiErrorSearchText(err))
153
153
}
154
154
155
155
+
function shouldRetryWithReasoningEnabled(err: unknown): boolean {
156
156
+
if (!(err instanceof OpenAI.APIError)) return false
157
157
+
return /reasoning is mandatory|reasoning cannot be disabled|reasoning is required/i.test(apiErrorSearchText(err))
158
158
+
}
159
159
+
160
160
+
function enableReasoningForRequest(request: CompletionRequest): CompletionRequest {
161
161
+
const next: CompletionRequest = {
162
162
+
...request,
163
163
+
include_reasoning: true,
164
164
+
reasoning: { enabled: true, effort: "low" },
165
165
+
}
166
166
+
delete next.enable_thinking
167
167
+
if (next.chat_template_kwargs) {
168
168
+
const { enable_thinking: _ignored, ...rest } = next.chat_template_kwargs
169
169
+
next.chat_template_kwargs = Object.keys(rest).length > 0 ? rest : undefined
170
170
+
if (!next.chat_template_kwargs) delete next.chat_template_kwargs
171
171
+
}
172
172
+
return next
173
173
+
}
174
174
+
155
175
function toolCompatibleReasoningExtras(
156
176
request?: Pick<CompletionRequest, "provider" | "chat_template_kwargs">,
157
177
): Partial<CompletionRequest> {
···
443
463
let currentRequest = request
444
464
let retriedAutoToolChoice = false
445
465
let retriedWithoutReasoning = false
466
466
+
let retriedWithReasoning = false
446
467
while (true) {
447
468
try {
448
469
return await createStreamedCompletion(fallbackClient, currentRequest)
···
462
483
retriedWithoutReasoning = true
463
484
console.warn("[fallback] provider rejected function calling in reasoning/prefix mode; retrying fallback with tool-compatible reasoning disabled")
464
485
currentRequest = disableReasoningForToolCalls(currentRequest)
486
486
+
continue
487
487
+
}
488
488
+
if (!retriedWithReasoning && shouldRetryWithReasoningEnabled(err)) {
489
489
+
retriedWithReasoning = true
490
490
+
console.warn(`[fallback] model ${currentRequest.model} requires reasoning; retrying fallback with reasoning enabled`)
491
491
+
currentRequest = enableReasoningForRequest(currentRequest)
465
492
continue
466
493
}
467
494
throw err
···
482
509
let currentRequest = request
483
510
let retriedAutoToolChoice = false
484
511
let retriedWithoutReasoning = false
512
512
+
let retriedWithReasoning = false
485
513
while (true) {
486
514
try {
487
515
return await createStreamedCompletion(client!, currentRequest)
···
499
527
retriedWithoutReasoning = true
500
528
console.warn("[api] provider rejected function calling in reasoning/prefix mode; retrying primary with tool-compatible reasoning disabled")
501
529
currentRequest = disableReasoningForToolCalls(currentRequest)
530
530
+
continue
531
531
+
}
532
532
+
if (!retriedWithReasoning && shouldRetryWithReasoningEnabled(err)) {
533
533
+
retriedWithReasoning = true
534
534
+
console.warn(`[api] model ${currentRequest.model} requires reasoning; retrying primary with reasoning enabled`)
535
535
+
currentRequest = enableReasoningForRequest(currentRequest)
502
536
continue
503
537
}
504
538
throw err