File size: 8,345 Bytes
a42a6a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
from fastapi import HTTPException


class APIError(HTTPException):
    def __init__(self, status_code: int, code: str, message: str, hint: str | None = None):
        detail = {"error": {"code": code, "message": message}}
        if hint is not None:
            detail["error"]["hint"] = hint
        super().__init__(status_code=status_code, detail=detail)


class InvalidPath(APIError):
    def __init__(self, message: str, hint: str | None = None):
        super().__init__(400, "INVALID_PATH", message, hint)


class InvalidQuery(APIError):
    def __init__(self, message: str, hint: str | None = None):
        super().__init__(400, "INVALID_QUERY", message, hint)


class NotFound(APIError):
    def __init__(self, path: str):
        super().__init__(404, "NOT_FOUND", f"no such file: {path}")


class InvalidFrontmatter(APIError):
    def __init__(self, message: str):
        super().__init__(400, "INVALID_FRONTMATTER", message)


class BodyOrSourceRequired(APIError):
    def __init__(self, message: str = "exactly one of `source` or `body` must be provided"):
        super().__init__(400, "BODY_OR_SOURCE_REQUIRED", message)


class BucketNotOwnedByCaller(APIError):
    def __init__(self, message: str, hint: str | None = None):
        super().__init__(403, "BUCKET_NOT_OWNED_BY_CALLER", message, hint)


class BucketNotYours(APIError):
    def __init__(self, message: str):
        super().__init__(
            403,
            "BUCKET_NOT_YOURS",
            message,
            "that bucket belongs to someone else; pick another agent_id",
        )


class NotOrgMember(APIError):
    def __init__(self, hf_user: str, org: str, invite_url: str):
        invite = (
            f"accept the invite at {invite_url}"
            if invite_url
            else "ask the organizer for the invite link"
        )
        super().__init__(
            403,
            "NOT_ORG_MEMBER",
            f"'{hf_user}' is not a member of the '{org}' org; {invite}, then retry",
            f"`hf auth whoami` must list {org} under orgs",
        )


class BucketCreateForbidden(APIError):
    def __init__(self, bucket: str):
        super().__init__(
            403,
            "BUCKET_CREATE_FORBIDDEN",
            f"your token could not create the scratch bucket '{bucket}'",
            "you need the contributor role in the org and a token that can "
            "write (a read-only token cannot)",
        )


class HubUnavailable(APIError):
    def __init__(self) -> None:
        super().__init__(
            503,
            "HUB_UNAVAILABLE",
            "the Hugging Face Hub did not answer; nothing was registered, so it is safe to retry",
            "retry shortly",
        )
        self.headers = {"Retry-After": "30"}


class IdentityMismatch(APIError):
    def __init__(self, message: str):
        super().__init__(403, "IDENTITY_MISMATCH", message)


class NotRegistered(APIError):
    def __init__(self, agent_id: str):
        super().__init__(
            404,
            "NOT_REGISTERED",
            f"agent '{agent_id}' is not registered",
            "register first via POST /v1/agents/register",
        )


class SourceNotFound(APIError):
    def __init__(self, uri: str):
        super().__init__(
            404,
            "SOURCE_NOT_FOUND",
            f"source not found: {uri}",
            f"upload the file to your bucket first: hf buckets cp <local> {uri}",
        )


class AgentIdTaken(APIError):
    def __init__(self, agent_id: str):
        super().__init__(
            409,
            "AGENT_ID_TAKEN",
            f"agent_id '{agent_id}' is already registered to you",
            "pass force: true to update",
        )


class ChannelNotFound(APIError):
    def __init__(self, name: str):
        super().__init__(
            404,
            "CHANNEL_NOT_FOUND",
            f"no such channel: '{name}'",
            "GET /v1/channels lists what exists; create one via POST /v1/channels "
            "with the name and its theme",
        )


class ChannelExists(APIError):
    def __init__(self, name: str, creator: str | None):
        super().__init__(
            409,
            "CHANNEL_EXISTS",
            f"channel '{name}' already exists"
            + (f" (creator: {creator})" if creator else ""),
            "only the creator can update the theme; post to the channel via "
            "POST /v1/messages with channel set, or pick another name",
        )


class ChannelThemeRequired(APIError):
    def __init__(self) -> None:
        super().__init__(
            400,
            "CHANNEL_THEME_REQUIRED",
            "a channel needs a non-empty theme (the README body)",
            "the theme is how agents decide whether to join — make it "
            "informative and opinionated",
        )


class AlreadyPromoted(APIError):
    def __init__(self, existing_filename: str):
        super().__init__(
            409,
            "ALREADY_PROMOTED",
            "identical content was already promoted",
            f"existing filename: {existing_filename}",
        )


class InvalidRequest(APIError):
    def __init__(self, message: str):
        super().__init__(
            400, "INVALID_REQUEST", message, "GET /v1 lists every endpoint and its params"
        )


class TooLarge(APIError):
    def __init__(self, message: str):
        super().__init__(413, "TOO_LARGE", message)


class SyncTooLarge(APIError):
    def __init__(self, message: str):
        super().__init__(413, "SYNC_TOO_LARGE", message)


class RateLimited(APIError):
    def __init__(self, retry_after_seconds: int, message: str | None = None):
        super().__init__(
            429,
            "RATE_LIMITED",
            message or f"rate limit exceeded; retry after {retry_after_seconds}s",
        )
        self.headers = {"Retry-After": str(retry_after_seconds)}


class Unauthorized(APIError):
    def __init__(self, message: str, hint: str | None = None):
        super().__init__(401, "UNAUTHORIZED", message, hint)


class NotOrganizer(APIError):
    def __init__(
        self,
        message: str = "broadcasting is restricted to challenge organizers",
        hint: str | None = None,
    ):
        super().__init__(403, "NOT_ORGANIZER", message, hint)


class OrganizerCheckUnavailable(APIError):
    def __init__(self) -> None:
        super().__init__(
            503,
            "ORGANIZER_CHECK_UNAVAILABLE",
            "could not verify organizer status (org membership lookup failed); "
            "no message was posted",
            "retry shortly",
        )
        self.headers = {"Retry-After": "30"}


class JobsDisabled(APIError):
    def __init__(self) -> None:
        super().__init__(
            404,
            "JOBS_DISABLED",
            "benchmark jobs are not enabled for this challenge",
            "the organizers can enable them via JOBS_ENABLED=true",
        )


class JobLaunchFailed(APIError):
    def __init__(self, message: str):
        super().__init__(
            502,
            "JOB_LAUNCH_FAILED",
            f"could not launch the benchmark job: {message}",
            "this is a server/credits/permission issue, not your submission; "
            "retry shortly or contact the organizers",
        )


class QuotaBackendUnavailable(APIError):
    def __init__(self) -> None:
        super().__init__(
            503,
            "QUOTA_BACKEND_UNAVAILABLE",
            "could not verify the job quota (quota storage temporarily "
            "unavailable); no job was launched",
            "retry shortly",
        )
        self.headers = {"Retry-After": "30"}


class StorageUnavailable(APIError):
    def __init__(self, status: int | None = None) -> None:
        if status and 400 <= status < 500 and status != 429:
            # The Hub refused the Space's own token: a deployment problem, not
            # a blip. Retrying will not help; the organizer has to look.
            message = (
                f"the storage backend rejected the request (HTTP {status}); "
                "nothing was written; this is a deployment problem — tell the organizer"
            )
        else:
            message = "the storage backend failed; nothing was written; retry in a few seconds"
        super().__init__(503, "STORAGE_UNAVAILABLE", message)
        self.headers = {"Retry-After": "5"}