diff --git a/backend/internal/provider/adobe/client.go b/backend/internal/provider/adobe/client.go index b377cff..ceaf531 100644 --- a/backend/internal/provider/adobe/client.go +++ b/backend/internal/provider/adobe/client.go @@ -38,15 +38,6 @@ var ( ErrDeadUpstream = errors.New("adobe upstream fatal error") ) -// bodyHasInsufficientCredits reports whether an Adobe response body signals the -// account has run out of generation credits ("insufficient credits"). Such an -// account is out of quota for this kind of generation, so it's treated as -// ErrQuotaExhausted: the pool marks it 限额 (per image/video kind) and fails over -// to the next account instead of surfacing a hard error to the caller. -func bodyHasInsufficientCredits(body []byte) bool { - return strings.Contains(strings.ToLower(string(body)), "insufficient credit") -} - var profileURLs = []string{ "https://ims-na1.adobelogin.com/ims/profile/v1", "https://adobeid-na1.services.adobe.com/ims/profile/v1", @@ -468,9 +459,6 @@ func (c *Client) submitImage(ctx context.Context, client tlsclient.HttpClient, t if err != nil { return nil, "", err } - if bodyHasInsufficientCredits(respBody) { - return respBody, "", ErrQuotaExhausted - } if resp.StatusCode == 401 || resp.StatusCode == 403 { if strings.EqualFold(resp.Header.Get("x-access-error"), "taste_exhausted") { return respBody, "", ErrQuotaExhausted @@ -545,9 +533,6 @@ func (c *Client) pollImage(ctx context.Context, client tlsclient.HttpClient, tok if readErr != nil { return nil, nil, readErr } - if bodyHasInsufficientCredits(body) { - return nil, nil, ErrQuotaExhausted - } if b := string(body); strings.Contains(b, "system under load") || strings.Contains(b, "timeout_error") { return nil, nil, ErrTemporaryUpstream } @@ -646,9 +631,6 @@ func (c *Client) submitVideo(ctx context.Context, client tlsclient.HttpClient, t if err != nil { return nil, "", err } - if bodyHasInsufficientCredits(respBody) { - return respBody, "", ErrQuotaExhausted - } if resp.StatusCode == 401 || resp.StatusCode == 403 { if strings.EqualFold(resp.Header.Get("x-access-error"), "taste_exhausted") { return respBody, "", ErrQuotaExhausted @@ -725,9 +707,6 @@ func (c *Client) pollVideo(ctx context.Context, client tlsclient.HttpClient, tok if readErr != nil { return nil, nil, readErr } - if bodyHasInsufficientCredits(body) { - return nil, nil, ErrQuotaExhausted - } 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)) } diff --git a/backend/internal/provider/chatgpt/client.go b/backend/internal/provider/chatgpt/client.go index 01adbca..995a7c8 100644 --- a/backend/internal/provider/chatgpt/client.go +++ b/backend/internal/provider/chatgpt/client.go @@ -14,6 +14,7 @@ import ( "io" stdhttp "net/http" "net/url" + "os" "path/filepath" "strconv" "strings" @@ -879,6 +880,9 @@ func (c *Client) getConversation(ctx context.Context, session tlsclient.HttpClie if err := ensureOK(resp.StatusCode, body, "conversation_get"); err != nil { return nil, err } + if dir := os.Getenv("CHATGPT_DEBUG_DUMP"); dir != "" { + _ = os.WriteFile(filepath.Join(dir, fmt.Sprintf("conv-%s-%d.json", conversationID, time.Now().UnixMilli())), body, 0o644) + } var payload map[string]any if err := json.Unmarshal(body, &payload); err != nil { return nil, err diff --git a/backend/internal/provider/chatgpt/util.go b/backend/internal/provider/chatgpt/util.go index ad62cdb..1f0cfe8 100644 --- a/backend/internal/provider/chatgpt/util.go +++ b/backend/internal/provider/chatgpt/util.go @@ -157,8 +157,12 @@ func conversationEndedWithoutImage(conversation map[string]any) bool { } if role == "assistant" { if content, _ := message["content"].(map[string]any); content != nil { - if ct := stringValue(content["content_type"]); ct != "" && ct != "text" { - return false // code/multimodal turn — generation underway + // Only tool-invoking content types mean generation is underway. + // Context nodes (model_editable_context, thoughts, …) also appear + // on refused turns and must NOT suppress the detection. + switch stringValue(content["content_type"]) { + case "code", "multimodal_text": + return false } } }