Files
CLIProxyAPI/internal/runtime/executor/meta_executor.go
Luis Pater 65348b9594 feat(executor): add native Meta (Muse Code) provider integration
- Introduced `MetaExecutor` to support native Meta (Muse Code) API operations, including `Execute`, `ExecuteStream`, and `CountTokens`.
- Added logic to handle Meta-specific `responses` endpoint, including request preparation, enriched authentication, header management, and response translation.
- Implemented token counting functionality with custom `CountTokens` logic.
- Updated `meta_executor` to process streamed and non-streamed responses, maintaining compatibility with the Codex schema.
- Added utility functions to handle events, errors, and enriched metadata common to Meta API requests.
- Extended `models.json` to include Meta's updated configuration with additional thinking levels like "minimal" and "max."
- Introduced comprehensive tests to validate Meta-specific execution, streaming, header assignments, and response formatting.
2026-09-15 18:12:49 +08:00

374 lines
11 KiB
Go

package executor
import (
"context"
"fmt"
"net/http"
"strings"
"time"
metaauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/meta"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor/helps"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"golang.org/x/sync/singleflight"
)
var (
_ cliproxyauth.ProviderExecutor = (*MetaExecutor)(nil)
_ cliproxyauth.RequestAuthPreparer = (*MetaExecutor)(nil)
)
var metaRefreshGroup singleflight.Group
const metaUserAgent = "muse-build/1.3.0 (interactive; macos-aarch64; build ac7280f2aca67769d1455a8847bb502b617d50f6)"
// MetaExecutor implements the cliproxyauth.ProviderExecutor for Meta Muse models (api.meta.ai).
type MetaExecutor struct {
cfg *config.Config
}
// NewMetaExecutor constructs a new Meta executor.
func NewMetaExecutor(cfg *config.Config) *MetaExecutor {
return &MetaExecutor{cfg: cfg}
}
// Identifier returns the provider identifier "meta".
func (e *MetaExecutor) Identifier() string {
return "meta"
}
// PrepareRequest injects Meta credentials into the outgoing HTTP request.
func (e *MetaExecutor) PrepareRequest(req *http.Request, auth *cliproxyauth.Auth) error {
if req == nil {
return nil
}
_, token := metaCreds(auth)
if strings.TrimSpace(token) != "" {
req.Header.Set("Authorization", "Bearer "+token)
} else {
req.Header.Del("Authorization")
}
req.Header.Set("User-Agent", metaUserAgent)
req.Header.Set("X-Client-Id:", "tbh:tui")
var attrs map[string]string
if auth != nil {
attrs = auth.Attributes
}
util.ApplyCustomHeadersFromAttrs(req, attrs)
return nil
}
// HttpRequest injects Meta credentials into the request and executes it.
func (e *MetaExecutor) HttpRequest(ctx context.Context, auth *cliproxyauth.Auth, req *http.Request) (*http.Response, error) {
if req == nil {
return nil, fmt.Errorf("meta executor: request is nil")
}
if ctx == nil {
ctx = req.Context()
}
enriched, err := e.ensureAuth(ctx, auth)
if err != nil {
return nil, err
}
httpReq := req.WithContext(ctx)
if err := e.PrepareRequest(httpReq, enriched); err != nil {
return nil, err
}
httpClient := helps.NewProxyAwareHTTPClient(ctx, e.cfg, enriched, 0)
return httpClient.Do(httpReq)
}
// Refresh mints an API key from a DCA token if needed.
func (e *MetaExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
log.Debugf("meta executor: refresh called")
if refreshed, handled, err := helps.RefreshAuthViaHome(ctx, e.cfg, auth); handled {
return refreshed, err
}
if auth == nil {
return nil, statusErr{code: http.StatusInternalServerError, msg: "meta executor: auth is nil"}
}
dcaToken := extractDCAToken(auth)
if dcaToken == "" {
if _, token := metaCreds(auth); token != "" {
return auth, nil
}
return nil, statusErr{
code: http.StatusUnauthorized,
msg: "meta executor: missing API key or DCA token",
}
}
mintRes, err, _ := metaRefreshGroup.Do(dcaToken, func() (any, error) {
authSvc := metaauth.NewMetaAuthWithProxyURL(e.cfg, auth.ProxyURL)
return authSvc.MintAPIKey(ctx, dcaToken)
})
if err != nil {
return nil, fmt.Errorf("meta executor: mint API key failed: %w", err)
}
minted, ok := mintRes.(*metaauth.MintedKeyResponse)
if !ok || minted == nil || minted.APIKey == "" {
return nil, fmt.Errorf("meta executor: mint API key returned empty key")
}
baseURL, _ := metaCreds(auth)
if mintedURL := strings.TrimSpace(minted.BaseURL); mintedURL != "" {
baseURL = mintedURL
}
if auth.Metadata == nil {
auth.Metadata = make(map[string]any)
}
auth.Metadata["base_url"] = baseURL
auth.Metadata["api_key"] = minted.APIKey
auth.Metadata["access_token"] = minted.APIKey
auth.Metadata["dca_token"] = dcaToken
delete(auth.Metadata, "expired")
if minted.UserEmail != "" {
auth.Metadata["email"] = minted.UserEmail
}
if minted.UserFullName != "" {
auth.Metadata["name"] = minted.UserFullName
}
auth.Metadata["type"] = "meta"
nowStr := time.Now().Format(time.RFC3339)
auth.Metadata["last_refresh"] = nowStr
if auth.Attributes == nil {
auth.Attributes = make(map[string]string)
}
auth.Attributes["base_url"] = baseURL
auth.Attributes["api_key"] = minted.APIKey
auth.Attributes["access_token"] = minted.APIKey
// Auth.Clone does not clone Storage. Keep the candidate isolated from live
// login storage; file-loaded records already persist through Metadata.
if existing, ok := auth.Storage.(*metaauth.MetaTokenStorage); ok && existing != nil {
storage := *existing
storage.APIKey = minted.APIKey
storage.AccessToken = minted.APIKey
storage.DCAToken = dcaToken
storage.Expired = ""
storage.BaseURL = baseURL
storage.LastRefresh = nowStr
storage.Metadata = auth.Metadata
if minted.UserEmail != "" {
storage.Email = minted.UserEmail
}
if minted.UserFullName != "" {
storage.Name = minted.UserFullName
}
auth.Storage = &storage
}
// Only the manager may accept and persist the candidate.
auth.LastRefreshedAt = time.Now()
return auth, nil
}
// ShouldPrepareRequestAuth reports true when a Meta auth has a DCA token but no usable API key.
func (e *MetaExecutor) ShouldPrepareRequestAuth(auth *cliproxyauth.Auth) bool {
if auth == nil || cliproxyauth.IsConfigAPIKeyAuth(auth) {
return false
}
_, token := metaCreds(auth)
return token == "" && extractDCAToken(auth) != ""
}
// PrepareRequestAuth exchanges the DCA token for an API key before request execution,
// allowing the conductor to atomically install and persist the credential in the manager.
func (e *MetaExecutor) PrepareRequestAuth(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
if auth == nil || !e.ShouldPrepareRequestAuth(auth) {
return auth, nil
}
return e.Refresh(ctx, auth)
}
func (e *MetaExecutor) ensureAuth(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
if auth == nil {
return nil, statusErr{
code: http.StatusUnauthorized,
msg: "meta executor: missing auth",
}
}
_, token := metaCreds(auth)
if token == "" {
if dcaToken := extractDCAToken(auth); dcaToken != "" {
refreshed, err := e.Refresh(ctx, auth)
if err != nil {
return nil, err
}
auth = refreshed
_, token = metaCreds(auth)
}
}
if token == "" {
if cliproxyauth.IsConfigAPIKeyAuth(auth) {
return nil, statusErr{
code: http.StatusUnauthorized,
msg: "meta executor: meta-api-key requires a valid API key (DCA tokens require OAuth storage)",
}
}
return nil, statusErr{
code: http.StatusUnauthorized,
msg: "meta executor: missing API key or access token",
}
}
return e.enrichAuth(auth), nil
}
func (e *MetaExecutor) enrichAuth(auth *cliproxyauth.Auth) *cliproxyauth.Auth {
baseURL, token := metaCreds(auth)
var cloned *cliproxyauth.Auth
if auth != nil {
cloned = auth.Clone()
} else {
cloned = &cliproxyauth.Auth{
Provider: "meta",
}
}
if cloned.Attributes == nil {
cloned.Attributes = make(map[string]string)
}
if strings.TrimSpace(cloned.Attributes["base_url"]) == "" {
cloned.Attributes["base_url"] = baseURL
}
if strings.TrimSpace(cloned.Attributes["api_key"]) == "" {
cloned.Attributes["api_key"] = token
}
if _, hasHeader := cloned.Attributes["header:User-Agent"]; !hasHeader {
cloned.Attributes["header:User-Agent"] = metaUserAgent
}
return cloned
}
func extractDCAToken(a *cliproxyauth.Auth) string {
if a == nil || cliproxyauth.IsConfigAPIKeyAuth(a) {
return ""
}
if a.Attributes != nil {
if d := strings.TrimSpace(a.Attributes["dca_token"]); d != "" {
return d
}
if t := strings.TrimSpace(a.Attributes["access_token"]); strings.HasPrefix(t, "dca:") {
return t
}
}
if a.Metadata != nil {
if d, ok := a.Metadata["dca_token"].(string); ok && strings.TrimSpace(d) != "" {
return strings.TrimSpace(d)
}
if t, ok := a.Metadata["access_token"].(string); ok && strings.HasPrefix(strings.TrimSpace(t), "dca:") {
return strings.TrimSpace(t)
}
}
if a.Storage != nil {
if ms, ok := a.Storage.(*metaauth.MetaTokenStorage); ok && ms != nil {
if ms.DCAToken != "" {
return ms.DCAToken
}
if strings.HasPrefix(ms.AccessToken, "dca:") {
return ms.AccessToken
}
}
}
return ""
}
func metaCreds(a *cliproxyauth.Auth) (baseURL, token string) {
baseURL = metaauth.DefaultAPIBaseURL
if a == nil {
return baseURL, ""
}
if a.Attributes != nil {
if b := strings.TrimSpace(a.Attributes["base_url"]); b != "" {
baseURL = b
}
if k := strings.TrimSpace(a.Attributes["api_key"]); k != "" && !strings.HasPrefix(k, "dca:") {
token = k
} else if t := strings.TrimSpace(a.Attributes["access_token"]); t != "" && !strings.HasPrefix(t, "dca:") {
token = t
}
}
if a.Metadata != nil {
if baseURL == metaauth.DefaultAPIBaseURL {
if b, ok := a.Metadata["base_url"].(string); ok && strings.TrimSpace(b) != "" {
baseURL = strings.TrimSpace(b)
} else if b, ok := a.Metadata["api_base_url"].(string); ok && strings.TrimSpace(b) != "" {
baseURL = strings.TrimSpace(b)
}
}
if token == "" {
if k, ok := a.Metadata["api_key"].(string); ok && strings.TrimSpace(k) != "" && !strings.HasPrefix(strings.TrimSpace(k), "dca:") {
token = strings.TrimSpace(k)
} else if t, ok := a.Metadata["access_token"].(string); ok && strings.TrimSpace(t) != "" && !strings.HasPrefix(strings.TrimSpace(t), "dca:") {
token = strings.TrimSpace(t)
}
}
}
if token == "" && a.Storage != nil {
if ms, ok := a.Storage.(*metaauth.MetaTokenStorage); ok && ms != nil {
if ms.APIKey != "" {
token = ms.APIKey
} else if ms.AccessToken != "" && !strings.HasPrefix(ms.AccessToken, "dca:") {
token = ms.AccessToken
}
if ms.BaseURL != "" && baseURL == metaauth.DefaultAPIBaseURL {
baseURL = ms.BaseURL
}
}
}
return baseURL, token
}
func parseMetaRetryAfter(statusCode int, errorBody []byte, now time.Time) *time.Duration {
if statusCode != http.StatusTooManyRequests || len(errorBody) == 0 {
return nil
}
if resetsAt := gjson.GetBytes(errorBody, "error.resets_at").Int(); resetsAt > 0 {
resetAtTime := time.Unix(resetsAt, 0)
if resetAtTime.After(now) {
retryAfter := resetAtTime.Sub(now)
return &retryAfter
}
}
return nil
}
type metaRateLimitError struct {
statusErr
credentialScoped bool
}
func (e metaRateLimitError) Unwrap() error {
return e.statusErr
}
func (e metaRateLimitError) IsCredentialScoped() bool {
return e.credentialScoped
}
func isMetaSubscriptionQuota(statusCode int, body []byte) bool {
if statusCode != http.StatusTooManyRequests || len(body) == 0 {
return false
}
msg := strings.ToLower(gjson.GetBytes(body, "error.message").String())
code := strings.ToLower(gjson.GetBytes(body, "error.code").String())
if strings.Contains(msg, "subscription quota") || strings.Contains(msg, "quota exhausted") {
return true
}
if (code == "rate_limit_exceeded" || strings.Contains(code, "quota")) && gjson.GetBytes(body, "error.resets_at").Exists() {
return true
}
return false
}