File size: 7,809 Bytes
445204c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3277c3
aca634b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3277c3
 
445204c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c81e03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445204c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c81e03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445204c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
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 TraceRequired(APIError):
    def __init__(self, agent_id: str, session_id: str, *, reason: str = "missing"):
        if reason == "stats_only":
            message = (
                f"session '{session_id}' was only shared as stats (token/tool-call "
                "counts) — a full trace (native session log) is required"
            )
            hint = (
                "re-run share_trace.py --full for this session (upgrades "
                "stats→full), then retry POST /v1/results with the same session_id"
            )
        else:
            message = (
                f"no trace found for session '{session_id}' — share it before "
                f"submitting a result for it (traces/{agent_id}/{session_id}/manifest.md "
                "does not exist)"
            )
            hint = (
                "run share_trace.py --full for this session, then retry "
                "POST /v1/results with the same session_id"
            )
        super().__init__(400, "TRACE_REQUIRED", message, hint)


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 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}")


class AgentIdTaken(APIError):
    def __init__(self, agent_id: str):
        super().__init__(
            409,
            "AGENT_ID_TAKEN",
            f"agent_id '{agent_id}' is already registered to another hf_user",
            "pick a different agent_id",
        )


class TaskforceNotFound(APIError):
    def __init__(self, name: str):
        super().__init__(
            404,
            "TASKFORCE_NOT_FOUND",
            f"no such taskforce: '{name}'",
            "create it via POST /v1/taskforces with the name and README content; "
            "GET /v1/taskforces lists what exists",
        )


class TaskforceExists(APIError):
    def __init__(self, name: str, creator: str | None):
        super().__init__(
            409,
            "TASKFORCE_EXISTS",
            f"taskforce '{name}' already exists"
            + (f" (creator: {creator})" if creator else ""),
            "only the creator can update the README; contribute via "
            f"POST /v1/taskforces/{name}/files, or pick another name",
        )


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 BucketMissing(APIError):
    def __init__(self, bucket: str):
        super().__init__(
            412,
            "BUCKET_MISSING",
            f"scratch bucket '{bucket}' does not exist",
            f"run: hf buckets create {bucket}",
        )


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"}