package providers import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "time" ) func openAICompatDefaultModel(base *HTTPProvider) string { if base == nil { return "" } return base.GetDefaultModel() } func runQwenChat(ctx context.Context, base *HTTPProvider, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { if base == nil { return nil, fmt.Errorf("provider not configured") } requestBody := buildQwenChatRequest(base, messages, tools, model, options, false) body, statusCode, contentType, err := doOpenAICompatJSONWithAttempts(ctx, base, "/chat/completions", requestBody, qwenProviderHooks{}) if err != nil { return nil, err } if statusCode != http.StatusOK { return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(body)) } if !json.Valid(body) { return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(body)) } return parseOpenAICompatResponse(body) } func runQwenChatStream(ctx context.Context, base *HTTPProvider, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}, onDelta func(string)) (*LLMResponse, error) { if base == nil { return nil, fmt.Errorf("provider not configured") } if onDelta == nil { onDelta = func(string) {} } requestBody := buildQwenChatRequest(base, messages, tools, model, options, true) body, statusCode, contentType, err := doOpenAICompatStreamWithAttempts(ctx, base, "/chat/completions", requestBody, onDelta, qwenProviderHooks{}) if err != nil { return nil, err } if statusCode != http.StatusOK { return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(body)) } if !json.Valid(body) { return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(body)) } return parseOpenAICompatResponse(body) } func runOpenAICompatChat(ctx context.Context, base *HTTPProvider, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}) (*LLMResponse, error) { if base == nil { return nil, fmt.Errorf("provider not configured") } body, statusCode, contentType, err := doOpenAICompatJSONWithAttempts(ctx, base, "/chat/completions", base.buildOpenAICompatChatRequest(messages, tools, model, options), nil) if err != nil { return nil, err } if statusCode != http.StatusOK { return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(body)) } if !json.Valid(body) { return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(body)) } return parseOpenAICompatResponse(body) } func runOpenAICompatChatStream(ctx context.Context, base *HTTPProvider, messages []Message, tools []ToolDefinition, model string, options map[string]interface{}, onDelta func(string)) (*LLMResponse, error) { if base == nil { return nil, fmt.Errorf("provider not configured") } if onDelta == nil { onDelta = func(string) {} } chatBody := base.buildOpenAICompatChatRequest(messages, tools, model, options) chatBody["stream"] = true chatBody["stream_options"] = map[string]interface{}{"include_usage": true} body, statusCode, contentType, err := doOpenAICompatStreamWithAttempts(ctx, base, "/chat/completions", chatBody, onDelta, nil) if err != nil { return nil, err } if statusCode != http.StatusOK { return nil, fmt.Errorf("API error (status %d, content-type %q): %s", statusCode, contentType, previewResponseBody(body)) } if !json.Valid(body) { return nil, fmt.Errorf("API error (status %d, content-type %q): non-JSON response: %s", statusCode, contentType, previewResponseBody(body)) } return parseOpenAICompatResponse(body) } type openAICompatHooks interface { beforeAttempt(attempt authAttempt) (int, []byte, string, bool) endpoint(base *HTTPProvider, attempt authAttempt, path string) string classifyFailure(status int, body []byte) (int, oauthFailureReason, bool, *time.Duration) afterFailure(base *HTTPProvider, attempt authAttempt, reason oauthFailureReason, retryAfter *time.Duration) } func doOpenAICompatJSONWithAttempts(ctx context.Context, base *HTTPProvider, path string, payload map[string]interface{}, hooks openAICompatHooks) ([]byte, int, string, error) { if base == nil { return nil, 0, "", fmt.Errorf("provider not configured") } jsonData, err := json.Marshal(payload) if err != nil { return nil, 0, "", fmt.Errorf("failed to marshal request: %w", err) } attempts, err := base.authAttempts(ctx) if err != nil { return nil, 0, "", err } var lastBody []byte var lastStatus int var lastType string for _, attempt := range attempts { attemptEndpoint := endpointFor(base.compatBase(), path) if hooks != nil { attemptEndpoint = hooks.endpoint(base, attempt, path) } if hooks != nil { if status, body, contentType, blocked := hooks.beforeAttempt(attempt); blocked { lastBody, lastStatus, lastType = body, status, contentType _, reason, retry, retryAfter := hooks.classifyFailure(status, body) if retry { hooks.afterFailure(base, attempt, reason, retryAfter) continue } return body, status, contentType, nil } } req, err := http.NewRequestWithContext(ctx, http.MethodPost, attemptEndpoint, bytes.NewReader(jsonData)) if err != nil { return nil, 0, "", fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") applyAttemptAuth(req, attempt) applyAttemptProviderHeaders(req, attempt, base, false) body, status, contentType, err := base.doJSONAttempt(req, attempt) if err != nil { return nil, 0, "", err } mappedStatus := status reason, retry := oauthFailureReason(""), false var retryAfter *time.Duration if hooks != nil { mappedStatus, reason, retry, retryAfter = hooks.classifyFailure(status, body) } else { reason, retry = classifyOAuthFailure(status, body) } if !retry { base.markAttemptSuccess(attempt) return body, mappedStatus, contentType, nil } lastBody, lastStatus, lastType = body, mappedStatus, contentType if hooks != nil { hooks.afterFailure(base, attempt, reason, retryAfter) } else { applyAttemptFailure(base, attempt, reason, nil) } } return lastBody, lastStatus, lastType, nil } func doOpenAICompatStreamWithAttempts(ctx context.Context, base *HTTPProvider, path string, payload map[string]interface{}, onDelta func(string), hooks openAICompatHooks) ([]byte, int, string, error) { if base == nil { return nil, 0, "", fmt.Errorf("provider not configured") } jsonData, err := json.Marshal(payload) if err != nil { return nil, 0, "", fmt.Errorf("failed to marshal request: %w", err) } attempts, err := base.authAttempts(ctx) if err != nil { return nil, 0, "", err } var lastBody []byte var lastStatus int var lastType string for _, attempt := range attempts { attemptEndpoint := endpointFor(base.compatBase(), path) if hooks != nil { attemptEndpoint = hooks.endpoint(base, attempt, path) } if hooks != nil { if status, body, contentType, blocked := hooks.beforeAttempt(attempt); blocked { lastBody, lastStatus, lastType = body, status, contentType _, reason, retry, retryAfter := hooks.classifyFailure(status, body) if retry { hooks.afterFailure(base, attempt, reason, retryAfter) continue } return body, status, contentType, nil } } req, err := http.NewRequestWithContext(ctx, http.MethodPost, attemptEndpoint, bytes.NewReader(jsonData)) if err != nil { return nil, 0, "", fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "text/event-stream") applyAttemptAuth(req, attempt) applyAttemptProviderHeaders(req, attempt, base, true) body, status, contentType, _, err := base.doStreamAttempt(req, attempt, func(event string) { var obj map[string]interface{} if err := json.Unmarshal([]byte(event), &obj); err != nil { return } choices, _ := obj["choices"].([]interface{}) for _, choice := range choices { item, _ := choice.(map[string]interface{}) delta, _ := item["delta"].(map[string]interface{}) if txt := strings.TrimSpace(fmt.Sprintf("%v", delta["content"])); txt != "" { onDelta(txt) } } }) if err != nil { return nil, 0, "", err } mappedStatus := status reason, retry := oauthFailureReason(""), false var retryAfter *time.Duration if hooks != nil { mappedStatus, reason, retry, retryAfter = hooks.classifyFailure(status, body) } else { reason, retry = classifyOAuthFailure(status, body) } if !retry { base.markAttemptSuccess(attempt) return body, mappedStatus, contentType, nil } lastBody, lastStatus, lastType = body, mappedStatus, contentType if hooks != nil { hooks.afterFailure(base, attempt, reason, retryAfter) } else { applyAttemptFailure(base, attempt, reason, nil) } } return lastBody, lastStatus, lastType, nil } func applyAttemptFailure(base *HTTPProvider, attempt authAttempt, reason oauthFailureReason, retryAfter *time.Duration) { if base == nil { return } if attempt.kind == "oauth" && attempt.session != nil && base.oauth != nil { if retryAfter != nil { base.oauth.mu.Lock() until := time.Now().Add(*retryAfter) if strings.TrimSpace(attempt.session.FilePath) != "" { base.oauth.cooldowns[strings.TrimSpace(attempt.session.FilePath)] = until } attempt.session.CooldownUntil = until.Format(time.RFC3339) attempt.session.FailureCount++ attempt.session.LastFailure = string(reason) if attempt.session.HealthScore == 0 { attempt.session.HealthScore = 100 } attempt.session.HealthScore = maxInt(1, attempt.session.HealthScore-healthPenaltyForReason(reason)) base.oauth.mu.Unlock() recordProviderOAuthError(base.providerName, attempt.session, reason) recordProviderRuntimeChange(base.providerName, "oauth", firstNonEmpty(attempt.session.Email, attempt.session.AccountID, attempt.session.FilePath), "oauth_cooldown_"+string(reason), "oauth credential entered provider-specific cooldown after request failure") return } base.oauth.markExhausted(attempt.session, reason) recordProviderOAuthError(base.providerName, attempt.session, reason) return } if attempt.kind == "api_key" { base.markAPIKeyFailure(reason) } } func estimateOpenAICompatTokenCount(body map[string]interface{}) (int, error) { data, err := json.Marshal(body) if err != nil { return 0, fmt.Errorf("failed to encode request for token count: %w", err) } const charsPerToken = 4 count := (len(data) + charsPerToken - 1) / charsPerToken if count < 1 { count = 1 } return count, nil }