cff2906712
fix setup error messages feat: codex error handling
136 lines
4.1 KiB
Go
136 lines
4.1 KiB
Go
package adapter
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// codexErrorRegex matches Codex error log format:
|
|
// TIMESTAMP ERROR module: error=HTTP_ERROR: Some("JSON")
|
|
var codexErrorRegex = regexp.MustCompile(`error=(.+?):\s*Some\("(.+)"\)\s*$`)
|
|
|
|
// CodexParsedError contains the parsed error information from Codex stderr.
|
|
type CodexParsedError struct {
|
|
// Message is the user-friendly error message
|
|
Message string
|
|
// HTTPError is the HTTP error string (e.g., "http 429 Too Many Requests")
|
|
HTTPError string
|
|
// RawJSON contains all fields from the error JSON (generic, captures any structure)
|
|
RawJSON map[string]any
|
|
// ErrorType is the error type from the JSON (e.g., "usage_limit_reached") - may be empty
|
|
ErrorType string
|
|
// ResetsInSeconds is the number of seconds until the limit resets - may be 0
|
|
ResetsInSeconds int64
|
|
}
|
|
|
|
// parseCodexStderrError attempts to parse a Codex stderr error line and extract
|
|
// error information. Returns nil if parsing fails.
|
|
//
|
|
// Example input:
|
|
//
|
|
// 2026-01-23T22:57:08.953223Z ERROR codex_api::endpoint::responses: error=http 429 Too Many Requests: Some("{\"error\":{...}}")
|
|
func parseCodexStderrError(line string) *CodexParsedError {
|
|
matches := codexErrorRegex.FindStringSubmatch(line)
|
|
if len(matches) < 3 {
|
|
return nil
|
|
}
|
|
|
|
httpError := strings.TrimSpace(matches[1])
|
|
jsonStr := matches[2]
|
|
|
|
// Unescape the JSON string (it's double-escaped in the log)
|
|
unescaped := strings.ReplaceAll(jsonStr, `\"`, `"`)
|
|
unescaped = strings.ReplaceAll(unescaped, `\\`, `\`)
|
|
|
|
result := &CodexParsedError{
|
|
HTTPError: httpError,
|
|
}
|
|
|
|
// Try to parse the JSON into a generic map first (captures all fields)
|
|
var rawData map[string]any
|
|
if err := json.Unmarshal([]byte(unescaped), &rawData); err != nil {
|
|
// Couldn't parse JSON at all, just return HTTP error
|
|
result.Message = httpError
|
|
return result
|
|
}
|
|
|
|
result.RawJSON = rawData
|
|
|
|
// Extract common fields - check both nested "error" object and top-level fields
|
|
var errorType, errorMessage string
|
|
var resetsInSeconds int64
|
|
|
|
// First, check for nested error object (standard format)
|
|
if errObj, ok := rawData["error"].(map[string]any); ok {
|
|
if t, ok := errObj["type"].(string); ok {
|
|
errorType = t
|
|
}
|
|
if m, ok := errObj["message"].(string); ok {
|
|
errorMessage = m
|
|
}
|
|
// Handle both int and float64 (JSON numbers are float64 in Go)
|
|
if r, ok := errObj["resets_in_seconds"].(float64); ok {
|
|
resetsInSeconds = int64(r)
|
|
}
|
|
}
|
|
|
|
// Fall back to top-level fields if nested fields not found (flat error format)
|
|
if errorMessage == "" {
|
|
if m, ok := rawData["message"].(string); ok {
|
|
errorMessage = m
|
|
}
|
|
}
|
|
if errorType == "" {
|
|
if t, ok := rawData["type"].(string); ok {
|
|
errorType = t
|
|
}
|
|
}
|
|
|
|
result.ErrorType = errorType
|
|
result.ResetsInSeconds = resetsInSeconds
|
|
|
|
// Build a user-friendly message from available fields
|
|
var msg string
|
|
|
|
if errorMessage != "" {
|
|
// We have a specific error message from the API
|
|
msg = errorMessage
|
|
// Add reset time info if available
|
|
if resetsInSeconds > 0 {
|
|
duration := time.Duration(resetsInSeconds) * time.Second
|
|
if duration.Hours() >= 1 {
|
|
msg = fmt.Sprintf("%s (resets in %.0f hours)", msg, duration.Hours())
|
|
} else if duration.Minutes() >= 1 {
|
|
msg = fmt.Sprintf("%s (resets in %.0f minutes)", msg, duration.Minutes())
|
|
} else {
|
|
msg = fmt.Sprintf("%s (resets in %d seconds)", msg, int(duration.Seconds()))
|
|
}
|
|
}
|
|
} else if errorType != "" {
|
|
// We have an error type but no message
|
|
msg = fmt.Sprintf("Error: %s", errorType)
|
|
} else {
|
|
// Unknown error format - show HTTP error and include JSON body
|
|
jsonBytes, _ := json.MarshalIndent(rawData, "", " ")
|
|
msg = fmt.Sprintf("%s\n\n%s", httpError, string(jsonBytes))
|
|
}
|
|
|
|
result.Message = msg
|
|
return result
|
|
}
|
|
|
|
// parseCodexStderrLines attempts to parse Codex stderr lines and extract
|
|
// error information. Searches from the end (most recent) first.
|
|
// Returns nil if no parseable error is found.
|
|
func parseCodexStderrLines(lines []string) *CodexParsedError {
|
|
for i := len(lines) - 1; i >= 0; i-- {
|
|
if parsed := parseCodexStderrError(lines[i]); parsed != nil {
|
|
return parsed
|
|
}
|
|
}
|
|
return nil
|
|
}
|