X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/522d7fd117285e4e4ce3ea5ec9b5a1722521b681..c781f79f3c8cb9a7577ee1574d196988da2719b3:/chatgpt.go diff --git a/chatgpt.go b/chatgpt.go index 8849ada..ad40abd 100644 --- a/chatgpt.go +++ b/chatgpt.go @@ -51,37 +51,6 @@ const ( // must run "onechat login". var errChatGPTLoginNeeded = errors.New(`login needed: run "onechat login"`) -// chatgptLogin runs "onechat login": the device code login into the token file -// of the data directory. It holds the lock of the token file, so it does not -// run while the daemon runs. -func chatgptLogin(ctx context.Context, out io.Writer) (err error) { - dir, err := dataDir() - if err != nil { - return err - } - - tokens, err := lockTokens(dir) - if err != nil { - return err - } - - defer func() { - err = errors.Join(err, tokens.Close()) - }() - - return newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer).Login(ctx, out) -} - -// newChatGPTAuth returns the authorizer for the token file at path, with the -// login service at issuer. -func newChatGPTAuth(path, issuer string) *ChatGPTAuth { - return &ChatGPTAuth{ - path: path, - issuer: issuer, - http: &http.Client{}, - } -} - // ChatGPTAuth authorizes model calls to the ChatGPT backend with the tokens in // the token file. The tokens belong to OneChat only. The Codex CLI rotates the // refresh token on each refresh, so two programs with one token file log each @@ -98,6 +67,47 @@ type ChatGPTAuth struct { http *http.Client } +// ChatGPTDeviceCode is the answer of the user code endpoint. The interval is a +// number or a string of seconds. +type ChatGPTDeviceCode struct { + DeviceAuthID string `json:"device_auth_id"` + UserCode string `json:"user_code"` + UserCodeAlt string `json:"usercode"` + Interval json.RawMessage `json:"interval"` +} + +// ChatGPTTokenFile is the content of the token file. +type ChatGPTTokenFile struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + IDToken string `json:"id_token"` + AccountID string `json:"account_id"` +} + +// ChatGPTTokenResponse is the answer of the token endpoint. +type ChatGPTTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + IDToken string `json:"id_token"` +} + +// ChatGPTDeviceGrant is the answer of the device token endpoint after the user +// approves the code. +type ChatGPTDeviceGrant struct { + AuthorizationCode string `json:"authorization_code"` + CodeVerifier string `json:"code_verifier"` +} + +// newChatGPTAuth returns the authorizer for the token file at path, with the +// login service at issuer. +func newChatGPTAuth(path, issuer string) *ChatGPTAuth { + return &ChatGPTAuth{ + path: path, + issuer: issuer, + http: &http.Client{}, + } +} + // Authorize sets the access token and the account ID, after a refresh when the // token expires within chatgptRefreshMargin. func (a *ChatGPTAuth) Authorize(ctx context.Context, header http.Header) error { @@ -365,35 +375,25 @@ func (a *ChatGPTAuth) post(ctx context.Context, path, contentType string, body [ return response.StatusCode, nil } -// ChatGPTDeviceCode is the answer of the user code endpoint. The interval is a -// number or a string of seconds. -type ChatGPTDeviceCode struct { - DeviceAuthID string `json:"device_auth_id"` - UserCode string `json:"user_code"` - UserCodeAlt string `json:"usercode"` - Interval json.RawMessage `json:"interval"` -} +// chatgptLogin runs "onechat login": the device code login into the token file +// of the data directory. It holds the lock of the token file, so it does not +// run while the daemon runs. +func chatgptLogin(ctx context.Context, out io.Writer) (err error) { + dir, err := dataDir() + if err != nil { + return err + } -// ChatGPTTokenFile is the content of the token file. -type ChatGPTTokenFile struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - IDToken string `json:"id_token"` - AccountID string `json:"account_id"` -} + tokens, err := lockTokens(dir) + if err != nil { + return err + } -// ChatGPTTokenResponse is the answer of the token endpoint. -type ChatGPTTokenResponse struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - IDToken string `json:"id_token"` -} + defer func() { + err = errors.Join(err, tokens.Close()) + }() -// ChatGPTDeviceGrant is the answer of the device token endpoint after the user -// approves the code. -type ChatGPTDeviceGrant struct { - AuthorizationCode string `json:"authorization_code"` - CodeVerifier string `json:"code_verifier"` + return newChatGPTAuth(filepath.Join(dir, chatgptTokenFileName), chatgptIssuer).Login(ctx, out) } // chatgptTokenExpiry returns the expiry of a JWT from its exp claim.