mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
64378d29ed
* feat(web): add provider oauth management ui * feat: add OAuth callback support on port 1455 * feat: enhance reasoning effort options and support for OpenAI Codex OAuth * feat: update twilight-ai dependency to v0.3.4 * refactor: promote openai-codex to first-class client_type, remove auth_type Replace the previous openai-responses + metadata auth_type=openai-codex-oauth combo with a dedicated openai-codex client_type. OAuth requirement is now determined solely by client_type, eliminating the auth_type concept from the LLM provider domain entirely. - Add openai-codex to DB CHECK constraint (migration 0047) with data migration - Add ClientTypeOpenAICodex constant and dedicated SDK/probe branches - Remove AuthType from SDKModelConfig, ModelCredentials, TriggerConfig, etc. - Simplify supportsOAuth to check client_type == openai-codex - Add conf/providers/codex.yaml preset with Codex catalog models - Frontend: replace auth_type selector with client_type-driven OAuth UI --------- Co-authored-by: Acbox <acbox0328@gmail.com>
70 lines
1.8 KiB
Go
70 lines
1.8 KiB
Go
package providers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/memohai/memoh/internal/db/sqlc"
|
|
"github.com/memohai/memoh/internal/models"
|
|
)
|
|
|
|
const openAIAuthClaimPath = "https://api.openai.com/auth"
|
|
|
|
type ModelCredentials struct {
|
|
APIKey string //nolint:gosec // runtime credential material used to construct SDK providers
|
|
CodexAccountID string
|
|
}
|
|
|
|
func SupportsOpenAICodexOAuth(provider sqlc.LlmProvider) bool {
|
|
return supportsOAuth(provider)
|
|
}
|
|
|
|
func (s *Service) ResolveModelCredentials(ctx context.Context, provider sqlc.LlmProvider) (ModelCredentials, error) {
|
|
if models.ClientType(provider.ClientType) != models.ClientTypeOpenAICodex {
|
|
return ModelCredentials{
|
|
APIKey: provider.ApiKey,
|
|
}, nil
|
|
}
|
|
|
|
token, err := s.GetValidAccessToken(ctx, provider.ID.String())
|
|
if err != nil {
|
|
return ModelCredentials{}, err
|
|
}
|
|
accountID, err := codexAccountIDFromToken(token)
|
|
if err != nil {
|
|
return ModelCredentials{}, err
|
|
}
|
|
return ModelCredentials{
|
|
APIKey: token,
|
|
CodexAccountID: accountID,
|
|
}, nil
|
|
}
|
|
|
|
func codexAccountIDFromToken(token string) (string, error) {
|
|
parts := strings.Split(token, ".")
|
|
if len(parts) != 3 {
|
|
return "", errors.New("invalid oauth access token")
|
|
}
|
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
|
if err != nil {
|
|
return "", fmt.Errorf("decode oauth token payload: %w", err)
|
|
}
|
|
var claims struct {
|
|
OpenAIAuth struct {
|
|
ChatGPTAccountID string `json:"chatgpt_account_id"`
|
|
} `json:"https://api.openai.com/auth"`
|
|
}
|
|
if err := json.Unmarshal(payload, &claims); err != nil {
|
|
return "", fmt.Errorf("parse oauth token payload: %w", err)
|
|
}
|
|
accountID := strings.TrimSpace(claims.OpenAIAuth.ChatGPTAccountID)
|
|
if accountID == "" {
|
|
return "", fmt.Errorf("oauth access token missing %s.chatgpt_account_id", openAIAuthClaimPath)
|
|
}
|
|
return accountID, nil
|
|
}
|