修复bug
This commit is contained in:
@@ -19,22 +19,23 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
submitURL = "https://firefly-3p.ff.adobe.io/v2/3p-images/generate-async"
|
||||
submitURL = "https://firefly-3p.ff.adobe.io/v2/3p-images/generate-async"
|
||||
image5SubmitURL = "https://image-v5.ff.adobe.io/v1/images/generate-async"
|
||||
videoSubmitURL = "https://firefly-3p.ff.adobe.io/v2/3p-videos/generate-async"
|
||||
videoSubmitURL = "https://firefly-3p.ff.adobe.io/v2/3p-videos/generate-async"
|
||||
// Firefly-native video model (project id "firefly-video"): distinct host,
|
||||
// submit path and storage host from the 3p (veo/luma) video flow.
|
||||
fireflyVideoSubmitURL = "https://video-v1.ff.adobe.io/v2/videos/generate"
|
||||
fireflyVideoUploadURL = "https://video-v1.ff.adobe.io/v2/storage/image"
|
||||
uploadURL = "https://firefly-3p.ff.adobe.io/v2/storage/image"
|
||||
creditsURL = "https://firefly.adobe.io/v1/credits/balance"
|
||||
creditsAPIKey = "SunbreakWebUI1"
|
||||
uploadURL = "https://firefly-3p.ff.adobe.io/v2/storage/image"
|
||||
creditsURL = "https://firefly.adobe.io/v1/credits/balance"
|
||||
creditsAPIKey = "SunbreakWebUI1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAuth = errors.New("adobe auth failed")
|
||||
ErrQuotaExhausted = errors.New("adobe quota exhausted")
|
||||
ErrTemporaryUpstream = errors.New("adobe upstream temporary error")
|
||||
ErrDeadUpstream = errors.New("adobe upstream fatal error")
|
||||
)
|
||||
|
||||
var profileURLs = []string{
|
||||
@@ -193,6 +194,9 @@ func (c *Client) GenerateImage(ctx context.Context, token, modelID, prompt, aspe
|
||||
}
|
||||
// Preserve the temporary classification so the pool retries (overload / 5xx /
|
||||
// rate-limit) instead of failing the request outright.
|
||||
if errors.Is(lastErr, ErrDeadUpstream) {
|
||||
return nil, nil, fmt.Errorf("%w: adobe submit: %s", ErrDeadUpstream, clip(lastBody, 300))
|
||||
}
|
||||
if errors.Is(lastErr, ErrTemporaryUpstream) {
|
||||
return nil, nil, fmt.Errorf("%w: adobe submit: %s", ErrTemporaryUpstream, clip(lastBody, 300))
|
||||
}
|
||||
@@ -461,14 +465,14 @@ func (c *Client) submitImage(ctx context.Context, client tlsclient.HttpClient, t
|
||||
}
|
||||
return respBody, "", fmt.Errorf("%w (submit %d %s: %s)", ErrAuth, resp.StatusCode, resp.Header.Get("x-access-error"), clip(respBody, 300))
|
||||
}
|
||||
if resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
return respBody, "", ErrTemporaryUpstream
|
||||
}
|
||||
// "system under load" / timeout_error = adobe rate-limit/overload (can come on a
|
||||
// non-5xx) — treat as temporary so the pool retries instead of failing.
|
||||
if b := string(respBody); strings.Contains(b, "system under load") || strings.Contains(b, "timeout_error") {
|
||||
return respBody, "", ErrTemporaryUpstream
|
||||
}
|
||||
if resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
return respBody, "", ErrDeadUpstream
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return respBody, "", errors.New("submit rejected")
|
||||
}
|
||||
@@ -529,9 +533,12 @@ func (c *Client) pollImage(ctx context.Context, client tlsclient.HttpClient, tok
|
||||
if readErr != nil {
|
||||
return nil, nil, readErr
|
||||
}
|
||||
if resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
if b := string(body); strings.Contains(b, "system under load") || strings.Contains(b, "timeout_error") {
|
||||
return nil, nil, ErrTemporaryUpstream
|
||||
}
|
||||
if resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
return nil, nil, ErrDeadUpstream
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, nil, fmt.Errorf("adobe poll failed: %d %s", resp.StatusCode, clip(body, 300))
|
||||
}
|
||||
@@ -633,7 +640,7 @@ func (c *Client) submitVideo(ctx context.Context, client tlsclient.HttpClient, t
|
||||
return respBody, "", fmt.Errorf("%w (%d %s: %s)", ErrAuth, resp.StatusCode, resp.Header.Get("x-access-error"), clip(respBody, 300))
|
||||
}
|
||||
if resp.StatusCode == 408 || resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
return respBody, "", ErrTemporaryUpstream
|
||||
return respBody, "", ErrDeadUpstream
|
||||
}
|
||||
// "system under load" / timeout_error = adobe overload — treat as a temporary
|
||||
// error so the tempFailover policy moves to the next account (same as the image path).
|
||||
@@ -703,9 +710,12 @@ func (c *Client) pollVideo(ctx context.Context, client tlsclient.HttpClient, tok
|
||||
if resp.StatusCode == 401 || resp.StatusCode == 403 {
|
||||
return nil, nil, fmt.Errorf("%w (%d %s: %s)", ErrAuth, resp.StatusCode, resp.Header.Get("x-access-error"), clip(body, 300))
|
||||
}
|
||||
if resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
if b := string(body); strings.Contains(b, "system under load") || strings.Contains(b, "timeout_error") {
|
||||
return nil, nil, ErrTemporaryUpstream
|
||||
}
|
||||
if resp.StatusCode == 429 || resp.StatusCode == 451 || resp.StatusCode >= 500 {
|
||||
return nil, nil, ErrDeadUpstream
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, nil, fmt.Errorf("adobe video poll failed: %d %s", resp.StatusCode, clip(body, 300))
|
||||
}
|
||||
|
||||
@@ -142,7 +142,9 @@ func (c *Client) FetchCreditsBalance(ctx context.Context, token string) (map[str
|
||||
|
||||
// GetGrokCreditsConfig field #1 is the credits USED this period (not remaining):
|
||||
// an exhausted account reads 100, a fresh one reads ~0. Remaining = 100 - used.
|
||||
used, _, ok := parseCreditsConfig(raw)
|
||||
// Field #5 carries the credits' own reset timestamp (weekly grant refill) —
|
||||
// this is the 恢复时间 we surface, NOT the subscription's billing-period end.
|
||||
used, resetUnix, ok := parseCreditsConfig(raw)
|
||||
if !ok {
|
||||
return unknownBalance("unparsable credits config"), nil
|
||||
}
|
||||
@@ -154,15 +156,8 @@ func (c *Client) FetchCreditsBalance(ctx context.Context, token string) (map[str
|
||||
}
|
||||
remaining := fullCredits - used
|
||||
|
||||
// 恢复时间: taken solely from the subscription's billing-period end (when the
|
||||
// plan renews and credits reset). The credits-config weekly reset timestamp is
|
||||
// intentionally NOT used as a fallback — an account with no active subscription
|
||||
// has no recovery time.
|
||||
reset := ""
|
||||
sub, _ := c.FetchSubscription(ctx, token)
|
||||
if sub != nil {
|
||||
reset = strings.TrimSpace(sub.BillingPeriodEnd)
|
||||
}
|
||||
// 恢复时间: the credits-config weekly reset (when the free grant refills).
|
||||
reset := strings.TrimSpace(resetUnix)
|
||||
return map[string]any{
|
||||
"remaining": remaining,
|
||||
"used": used,
|
||||
@@ -175,7 +170,7 @@ func (c *Client) FetchCreditsBalance(ctx context.Context, token string) (map[str
|
||||
|
||||
// Subscription is the membership view parsed from GET /rest/subscriptions.
|
||||
type Subscription struct {
|
||||
Member bool // an active subscription exists
|
||||
Member bool // a subscription with status ACTIVE exists (INACTIVE entries don't count)
|
||||
Tier string // e.g. SUBSCRIPTION_TIER_GROK_PRO ("" for free)
|
||||
Status string // e.g. SUBSCRIPTION_STATUS_ACTIVE
|
||||
BillingPeriodEnd string // RFC3339; when the plan renews / credits reset
|
||||
@@ -183,7 +178,10 @@ type Subscription struct {
|
||||
}
|
||||
|
||||
// FetchSubscription reads GET /rest/subscriptions and reports the account's
|
||||
// membership. An empty subscriptions array means a free account (Member=false).
|
||||
// membership. Member is true only when an entry with SUBSCRIPTION_STATUS_ACTIVE
|
||||
// exists: a lapsed membership keeps its entry but flips to
|
||||
// SUBSCRIPTION_STATUS_INACTIVE, and an empty array means never subscribed —
|
||||
// both read as Member=false (the entry's tier/status are still surfaced).
|
||||
// A 401/403 maps to ErrAuth; other transport/HTTP errors are returned so callers
|
||||
// can treat them as best-effort (they already have the credit balance).
|
||||
func (c *Client) FetchSubscription(ctx context.Context, token string) (*Subscription, error) {
|
||||
@@ -229,15 +227,17 @@ func (c *Client) FetchSubscription(ctx context.Context, token string) (*Subscrip
|
||||
return nil, fmt.Errorf("%w: subscriptions non-json", ErrTemporaryUpstream)
|
||||
}
|
||||
out := &Subscription{}
|
||||
// Pick the active subscription (fall back to the first entry) as the membership.
|
||||
// Surface the ACTIVE subscription if any (falling back to the first entry
|
||||
// for tier/status info), but only an ACTIVE one sets Member.
|
||||
for i, s := range body.Subscriptions {
|
||||
if i == 0 || strings.EqualFold(s.Status, "SUBSCRIPTION_STATUS_ACTIVE") {
|
||||
out.Member = true
|
||||
active := strings.EqualFold(s.Status, "SUBSCRIPTION_STATUS_ACTIVE")
|
||||
if i == 0 || active {
|
||||
out.Member = active
|
||||
out.Tier = strings.TrimSpace(s.Tier)
|
||||
out.Status = strings.TrimSpace(s.Status)
|
||||
out.BillingPeriodEnd = strings.TrimSpace(s.BillingPeriodEnd)
|
||||
out.FreeTrial = s.ActiveOffer.FreeTrial != nil
|
||||
if strings.EqualFold(s.Status, "SUBSCRIPTION_STATUS_ACTIVE") {
|
||||
if active {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user