Files
Memoh/internal/providers/credentials.go
T
Acbox a04b8fd564 refactor: unify providers and models tables
- Rename `llm_providers` → `providers`, `llm_provider_oauth_tokens` → `provider_oauth_tokens`
- Remove `tts_providers` and `tts_models` tables; speech models now live in the unified `models` table with `type = 'speech'`
- Replace top-level `api_key`/`base_url` columns with a JSONB `config` field on `providers`
- Rename `llm_provider_id` → `provider_id` across all references
- Add `edge-speech` client type and `conf/providers/edge.yaml` default provider
- Create new read-only speech endpoints (`/speech-providers`, `/speech-models`) backed by filtered views of the unified tables
- Remove old TTS CRUD handlers; simplify speech page to read-only + test
- Update registry loader to skip malformed YAML files instead of failing entirely
- Fix YAML quoting for model names containing colons in openrouter.yaml
- Regenerate sqlc, swagger, and TypeScript SDK
2026-04-07 00:26:06 +08:00

71 lines
1.9 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.Provider) bool {
return supportsOAuth(provider)
}
func (s *Service) ResolveModelCredentials(ctx context.Context, provider sqlc.Provider) (ModelCredentials, error) {
if models.ClientType(provider.ClientType) != models.ClientTypeOpenAICodex {
apiKey := ProviderConfigString(provider, "api_key")
return ModelCredentials{
APIKey: 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
}