File size: 2,951 Bytes
bdc2878
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
"""Reverse pipeline feedback — translate ReverseResult into account + proxy feedback.

Called after every upstream call to update account health/quota and proxy state.
"""

from app.control.account.commands import AccountPatch
from app.control.proxy.models import ProxyFeedback, ProxyFeedbackKind
from app.platform.runtime.clock import now_ms
from .types import ResultCategory, ReverseResult


# ---------------------------------------------------------------------------
# Account feedback
# ---------------------------------------------------------------------------

def build_account_feedback(
    token: str,
    result: ReverseResult,
    *,
    mode_id: int = 0,
) -> AccountPatch:
    """Build an AccountPatch reflecting the outcome of a request.

    SUCCESS  → increment use_count, update last_use_at
    RATE_LIMITED → decrement quota for the mode
    AUTH_FAILURE → increment fail_count, set last_fail_reason
    Others → increment fail_count
    """
    ts = now_ms()

    if result.category == ResultCategory.SUCCESS:
        return AccountPatch(
            token=token,
            usage_use_delta=1,
            last_use_at=ts,
        )

    if result.category == ResultCategory.RATE_LIMITED:
        # Signal quota exhaustion for the specific mode.
        quota_update: dict[str, dict] = {}
        mode_key = {0: "quota_auto", 1: "quota_fast", 2: "quota_expert"}.get(mode_id)
        if mode_key:
            quota_update[mode_key] = {"remaining": 0}
        return AccountPatch(
            token=token,
            usage_fail_delta=1,
            last_fail_at=ts,
            last_fail_reason=f"rate_limited (mode={mode_id})",
            **({mode_key: quota_update[mode_key]} if mode_key and quota_update else {}),
        )

    return AccountPatch(
        token=token,
        usage_fail_delta=1,
        last_fail_at=ts,
        last_fail_reason=result.error or result.category.name,
    )


# ---------------------------------------------------------------------------
# Proxy feedback
# ---------------------------------------------------------------------------

_CATEGORY_TO_PROXY: dict[ResultCategory, ProxyFeedbackKind] = {
    ResultCategory.SUCCESS:      ProxyFeedbackKind.SUCCESS,
    ResultCategory.RATE_LIMITED:  ProxyFeedbackKind.RATE_LIMITED,
    ResultCategory.AUTH_FAILURE:  ProxyFeedbackKind.UNAUTHORIZED,
    ResultCategory.FORBIDDEN:     ProxyFeedbackKind.CHALLENGE,
    ResultCategory.UPSTREAM_5XX:  ProxyFeedbackKind.UPSTREAM_5XX,
    ResultCategory.TRANSPORT_ERR: ProxyFeedbackKind.TRANSPORT_ERROR,
}


def build_proxy_feedback(result: ReverseResult) -> ProxyFeedback:
    """Build a ProxyFeedback from a ReverseResult."""
    kind = _CATEGORY_TO_PROXY.get(result.category, ProxyFeedbackKind.TRANSPORT_ERROR)
    return ProxyFeedback(
        kind=kind,
        status_code=result.status_code,
        reason=result.error,
    )


__all__ = ["build_account_feedback", "build_proxy_feedback"]