Merge pull request #56 from jmahotiedu/fix/openai-device-code-interval

fix(auth): support string interval in OpenAI device-code flow
This commit is contained in:
lxowalle
2026-02-12 22:51:37 +08:00
committed by GitHub
2 changed files with 97 additions and 6 deletions

View File

@@ -13,6 +13,7 @@ import (
"net/url" "net/url"
"os/exec" "os/exec"
"runtime" "runtime"
"strconv"
"strings" "strings"
"time" "time"
) )
@@ -92,10 +93,13 @@ func LoginBrowser(cfg OAuthProviderConfig) (*AuthCredential, error) {
server.Shutdown(ctx) server.Shutdown(ctx)
}() }()
fmt.Printf("Open this URL to authenticate:\n\n%s\n\n", authURL)
if err := openBrowser(authURL); err != nil { if err := openBrowser(authURL); err != nil {
fmt.Printf("Could not open browser automatically.\nPlease open this URL manually:\n\n%s\n\n", authURL) fmt.Printf("Could not open browser automatically.\nPlease open this URL manually:\n\n%s\n\n", authURL)
} }
fmt.Println("If you're running in a headless environment, use: picoclaw auth login --provider openai --device-code")
fmt.Println("Waiting for authentication in browser...") fmt.Println("Waiting for authentication in browser...")
select { select {
@@ -114,6 +118,57 @@ type callbackResult struct {
err error err error
} }
type deviceCodeResponse struct {
DeviceAuthID string
UserCode string
Interval int
}
func parseDeviceCodeResponse(body []byte) (deviceCodeResponse, error) {
var raw struct {
DeviceAuthID string `json:"device_auth_id"`
UserCode string `json:"user_code"`
Interval json.RawMessage `json:"interval"`
}
if err := json.Unmarshal(body, &raw); err != nil {
return deviceCodeResponse{}, err
}
interval, err := parseFlexibleInt(raw.Interval)
if err != nil {
return deviceCodeResponse{}, err
}
return deviceCodeResponse{
DeviceAuthID: raw.DeviceAuthID,
UserCode: raw.UserCode,
Interval: interval,
}, nil
}
func parseFlexibleInt(raw json.RawMessage) (int, error) {
if len(raw) == 0 || string(raw) == "null" {
return 0, nil
}
var interval int
if err := json.Unmarshal(raw, &interval); err == nil {
return interval, nil
}
var intervalStr string
if err := json.Unmarshal(raw, &intervalStr); err == nil {
intervalStr = strings.TrimSpace(intervalStr)
if intervalStr == "" {
return 0, nil
}
return strconv.Atoi(intervalStr)
}
return 0, fmt.Errorf("invalid integer value: %s", string(raw))
}
func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) { func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) {
reqBody, _ := json.Marshal(map[string]string{ reqBody, _ := json.Marshal(map[string]string{
"client_id": cfg.ClientID, "client_id": cfg.ClientID,
@@ -134,12 +189,8 @@ func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) {
return nil, fmt.Errorf("device code request failed: %s", string(body)) return nil, fmt.Errorf("device code request failed: %s", string(body))
} }
var deviceResp struct { deviceResp, err := parseDeviceCodeResponse(body)
DeviceAuthID string `json:"device_auth_id"` if err != nil {
UserCode string `json:"user_code"`
Interval int `json:"interval"`
}
if err := json.Unmarshal(body, &deviceResp); err != nil {
return nil, fmt.Errorf("parsing device code response: %w", err) return nil, fmt.Errorf("parsing device code response: %w", err)
} }

View File

@@ -197,3 +197,43 @@ func TestOpenAIOAuthConfig(t *testing.T) {
t.Errorf("Port = %d, want 1455", cfg.Port) t.Errorf("Port = %d, want 1455", cfg.Port)
} }
} }
func TestParseDeviceCodeResponseIntervalAsNumber(t *testing.T) {
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":5}`)
resp, err := parseDeviceCodeResponse(body)
if err != nil {
t.Fatalf("parseDeviceCodeResponse() error: %v", err)
}
if resp.DeviceAuthID != "abc" {
t.Errorf("DeviceAuthID = %q, want %q", resp.DeviceAuthID, "abc")
}
if resp.UserCode != "DEF-1234" {
t.Errorf("UserCode = %q, want %q", resp.UserCode, "DEF-1234")
}
if resp.Interval != 5 {
t.Errorf("Interval = %d, want %d", resp.Interval, 5)
}
}
func TestParseDeviceCodeResponseIntervalAsString(t *testing.T) {
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"5"}`)
resp, err := parseDeviceCodeResponse(body)
if err != nil {
t.Fatalf("parseDeviceCodeResponse() error: %v", err)
}
if resp.Interval != 5 {
t.Errorf("Interval = %d, want %d", resp.Interval, 5)
}
}
func TestParseDeviceCodeResponseInvalidInterval(t *testing.T) {
body := []byte(`{"device_auth_id":"abc","user_code":"DEF-1234","interval":"abc"}`)
if _, err := parseDeviceCodeResponse(body); err == nil {
t.Fatal("expected error for invalid interval")
}
}