mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-09-10 01:51:30 +08:00
- Normalize complex constant `oneOf` and `anyOf` tool parameter schemas into equivalent enums to prevent upstream aborts. - Escape property keys containing dots and colons during schema updates to prevent invalid path splitting. - Identify terminal `response.incomplete` events with zero output tokens and no content as upstream failures. Closes: #5551
61 lines
2.3 KiB
Go
61 lines
2.3 KiB
Go
package helps
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
const (
|
|
// CodexEmptyIncompleteStreamMessage is the error message emitted when upstream ChatGPT Codex
|
|
// terminates with response.incomplete having 0 output tokens and 0 content output.
|
|
CodexEmptyIncompleteStreamMessage = "stream error: upstream terminated with incomplete empty response (0 tokens)"
|
|
)
|
|
|
|
// HasMeaningfulCodexOutputDelta reports whether an event carries non-empty generated content
|
|
// (such as non-empty text, reasoning, or function call arguments delta).
|
|
func HasMeaningfulCodexOutputDelta(eventData []byte) bool {
|
|
eventType := gjson.GetBytes(eventData, "type").String()
|
|
switch eventType {
|
|
case "response.output_text.delta", "response.reasoning_text.delta", "response.reasoning_summary_text.delta":
|
|
delta := gjson.GetBytes(eventData, "delta")
|
|
return delta.Exists() && len(strings.TrimSpace(delta.String())) > 0
|
|
case "response.function_call_arguments.delta":
|
|
delta := gjson.GetBytes(eventData, "delta")
|
|
return delta.Exists() && len(strings.TrimSpace(delta.String())) > 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsCodexTerminalEmptyIncomplete reports whether a response.incomplete event represents
|
|
// an upstream silent abort where explicitly zero output tokens were generated and zero
|
|
// output content (no output items and no non-empty output deltas) was produced.
|
|
func IsCodexTerminalEmptyIncomplete(eventData []byte, outputItemsCount int, sawOutputDelta bool) bool {
|
|
eventType := gjson.GetBytes(eventData, "type").String()
|
|
if eventType != "response.incomplete" {
|
|
return false
|
|
}
|
|
// If any non-empty text delta, reasoning delta, or tool argument delta was emitted, content was produced.
|
|
if sawOutputDelta {
|
|
return false
|
|
}
|
|
// If any completed output items exist, output was produced.
|
|
if outputItemsCount > 0 {
|
|
return false
|
|
}
|
|
// If response.output contains any items, output was produced.
|
|
output := gjson.GetBytes(eventData, "response.output")
|
|
if output.Exists() && output.IsArray() && len(output.Array()) > 0 {
|
|
return false
|
|
}
|
|
// Require explicit numeric zero for output tokens (reject floats like 0.5, non-numbers, missing, or null).
|
|
outputTokens := gjson.GetBytes(eventData, "response.usage.output_tokens")
|
|
if !outputTokens.Exists() || outputTokens.Type != gjson.Number {
|
|
return false
|
|
}
|
|
if outputTokens.Num != 0 || strings.TrimSpace(outputTokens.Raw) != "0" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|