Spaces:
Paused
Paused
File size: 7,199 Bytes
ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b cfea6f8 ddd660b | 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 | """Resolve and prepare per-user Hugging Face bucket workspaces."""
from __future__ import annotations
import hashlib
import json
import os
import re
from contextvars import ContextVar
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any, Mapping
from uuid import uuid4
from huggingface_hub import HfApi, get_token
from huggingface_hub.errors import BucketNotFoundError
from fast_agent import AgentAuth
from fast_agent.mcp.server.common import normalize_serve_oauth_provider
_SAFE_SEGMENT = re.compile(r"[^A-Za-z0-9._-]+")
@dataclass(frozen=True, slots=True)
class ResearchWorkspace:
username: str
session_id: str
bucket_id: str
root: str
scratch: str
output: str
bucket_created: bool
marker_paths: tuple[str, ...]
bearer_token: str | None
archive_space_id: str | None = None
archive_space_url: str | None = None
archive_app_url: str | None = None
archive_status: str | None = None
archive_template_version: str | None = None
archive_installed_version: str | None = None
archive_error: str | None = None
current_research_workspace: ContextVar[ResearchWorkspace | None] = ContextVar(
"current_research_workspace",
default=None,
)
def ensure_workspace(
*,
auth: AgentAuth | None,
request_metadata: Mapping[str, Any],
open_metadata: Mapping[str, object],
create_bucket: bool = True,
write_markers: bool = True,
api: HfApi | None = None,
) -> ResearchWorkspace:
"""Resolve identity/session, ensure the bucket exists, and write markers."""
token = _token(auth)
whoami = _whoami(auth, token)
username = _username(whoami)
session_id = _session_id(request_metadata, open_metadata)
bucket_id = f"{username}/research-agent"
root = f"hf://buckets/{bucket_id}/{session_id}/"
api = api or HfApi()
bucket_created = False
try:
api.bucket_info(bucket_id, token=token)
except BucketNotFoundError as exc:
if not create_bucket:
raise RuntimeError(
f"Bucket {bucket_id!r} is not accessible: {exc}"
) from exc
try:
api.create_bucket(bucket_id, private=True, exist_ok=True, token=token)
bucket_created = True
except Exception as create_exc:
raise RuntimeError(
f"Could not create/access bucket {bucket_id!r}: {create_exc}"
) from create_exc
marker_paths: tuple[str, ...] = ()
if write_markers:
marker = {
"server": "research-agent",
"username": username,
"session_id": session_id,
"bucket_id": bucket_id,
"checked_at": datetime.now(UTC).isoformat(),
}
try:
api.batch_bucket_files(
bucket_id,
add=[
(
json.dumps(marker, indent=2).encode("utf-8"),
f"{session_id}/scratch/.workspace.json",
),
(b"", f"{session_id}/output/.keep"),
],
token=token,
)
except Exception as exc:
raise RuntimeError(
f"Bucket {bucket_id!r} is accessible but marker write failed: {exc}"
) from exc
marker_paths = (
f"{root}scratch/.workspace.json",
f"{root}output/.keep",
)
return ResearchWorkspace(
username=username,
session_id=session_id,
bucket_id=bucket_id,
root=root,
scratch=f"{root}scratch/",
output=f"{root}output/",
bucket_created=bucket_created,
marker_paths=marker_paths,
bearer_token=token,
)
def _token(auth: AgentAuth | None) -> str | None:
if auth is not None and auth.token:
return auth.token
oauth_provider = normalize_serve_oauth_provider(os.getenv("FAST_AGENT_SERVE_OAUTH"))
if oauth_provider == "huggingface":
raise RuntimeError(
"Hugging Face OAuth is enabled, but this request has no caller token."
)
env_token = os.getenv("HF_TOKEN")
if env_token:
return env_token
return get_token()
def _whoami(auth: AgentAuth | None, token: str | bool | None) -> Mapping[str, Any]:
"""Return the authoritative Hugging Face whoami payload for this caller."""
claims = dict(auth.claims) if auth is not None else {}
whoami = claims.get("huggingface_whoami")
if isinstance(whoami, dict) and whoami:
return whoami
try:
return HfApi().whoami(token=token)
except Exception as exc:
raise RuntimeError(
"Could not determine the Hugging Face user. Provide a bearer token, "
"enable Hugging Face OAuth, set HF_TOKEN, or run `hf auth login`."
) from exc
def _username(whoami: Mapping[str, Any]) -> str:
username = safe_segment(whoami.get("name"))
if username:
return username
raise RuntimeError(
f"Hugging Face whoami response did not include a usable name: {dict(whoami)!r}."
)
def _session_id(
request_metadata: Mapping[str, Any],
open_metadata: Mapping[str, object],
) -> str:
candidates = [
request_metadata.get("research_workspace_id"),
open_metadata.get("research_workspace_id"),
request_metadata.get("request_session_id"),
request_metadata.get("harness_session_id"),
request_metadata.get("requested_session_id"),
request_metadata.get("mcp_session_id"),
open_metadata.get("harness_session_id"),
open_metadata.get("requested_session_id"),
open_metadata.get("mcp_session_id"),
]
for candidate in candidates:
value = _safe_session_segment(candidate)
if value:
return value
# No usable session identity was supplied. Never fall back to a shared
# constant ("default") — concurrent runs would collide on one bucket path
# and leak one run's report into another's UI. Mint a unique id instead.
return f"session-{uuid4().hex}"
def _safe_session_segment(value: object) -> str | None:
"""Sanitize a session id, keeping distinct inputs on distinct segments.
``safe_segment`` truncates to 96 chars and maps disallowed characters to
``-``, so two different client-supplied ids can collapse to the same
segment. When sanitization loses information, append a short stable hash of
the original so the mapping stays collision-resistant (and deterministic, so
the same input still resolves to the same workspace across requests).
"""
if value is None:
return None
raw = str(value).strip().strip("/")
if not raw:
return None
safe = safe_segment(raw)
if safe is None:
return None
if safe != raw:
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()[:8]
safe = f"{safe[:87].rstrip('.-_')}-{digest}"
return safe
def safe_segment(value: object) -> str | None:
if value is None:
return None
text = str(value).strip().strip("/")
if not text:
return None
return _SAFE_SEGMENT.sub("-", text)[:96].strip(".-_") or None
|