ctfy.sdk.resources.vendor
client.vendor — read-only vendor tenancy (authenticate with a pv_ token).
1"""``client.vendor`` — read-only vendor tenancy (authenticate with a ``pv_`` token).""" 2 3from __future__ import annotations 4 5import builtins 6 7from ctfy.core.state.models import ( 8 EvalCampaignState, 9 EvalRunState, 10 LlmModelState, 11 VendorKeyState, 12 VendorState, 13) 14from ctfy.sdk._helpers import PagedList, _extract_items, _raise_for_status 15from ctfy.sdk.base import BaseHttpClient 16from ctfy.server.models import VendorStandardSet 17 18 19class VendorResource: 20 """The calling vendor's own profile / models / runs. 21 22 Requires a ``pv_`` bearer (minted by an admin); the token resolves to a 23 single vendor and exposes only that vendor's data — never the platform's. 24 """ 25 26 def __init__(self, http: BaseHttpClient) -> None: 27 self._http = http 28 29 def me(self) -> VendorState: 30 resp = self._http.request("GET", "/vendor/me") 31 _raise_for_status(resp) 32 return VendorState.model_validate(resp.json()) 33 34 def models(self) -> builtins.list[LlmModelState]: 35 resp = self._http.request("GET", "/vendor/models") 36 _raise_for_status(resp) 37 return [LlmModelState.model_validate(row) for row in resp.json()] 38 39 def runs( 40 self, *, offset: int = 0, limit: int = 100, fully_solved: bool | None = None 41 ) -> PagedList[EvalRunState]: 42 """This vendor's runs, newest first — one page, with the total. 43 44 ``fully_solved`` narrows to runs that did (``True``) or did not 45 (``False``) solve their whole challenge; ``.total`` then counts 46 the narrowed set, so ``runs(fully_solved=True, limit=1).total`` 47 is how many runs solved, without reading them. 48 """ 49 params: dict[str, int | bool] = {"offset": offset, "limit": limit} 50 if fully_solved is not None: 51 params["fully_solved"] = fully_solved 52 resp = self._http.request("GET", "/vendor/runs", params=params) 53 _raise_for_status(resp) 54 return _extract_items(resp.json(), EvalRunState) 55 56 def standard_sets(self) -> builtins.list[VendorStandardSet]: 57 """The operator eval standard sets a vendor may launch a campaign over 58 (id / title / challenge count / corpus pin — the launch-picker source).""" 59 resp = self._http.request("GET", "/vendor/standard-sets") 60 _raise_for_status(resp) 61 return [VendorStandardSet.model_validate(row) for row in resp.json()] 62 63 def register_model( 64 self, 65 *, 66 id: str, 67 flavor: str, 68 display_name: str = "", 69 provider_model: str = "", 70 base_url: str = "", 71 notes: str = "", 72 ) -> LlmModelState: 73 """Self-register (or edit) one of the vendor's own models. Requires a 74 ``submit``-scoped ``pv_`` token. The model lands ``pending`` review — 75 an operator must approve it before it can run official campaigns.""" 76 resp = self._http.request( 77 "POST", 78 "/vendor/models", 79 json={ 80 "id": id, 81 "flavor": flavor, 82 "display_name": display_name, 83 "provider_model": provider_model, 84 "base_url": base_url, 85 "notes": notes, 86 }, 87 ) 88 _raise_for_status(resp) 89 return LlmModelState.model_validate(resp.json()) 90 91 def upload_key(self, *, flavor: str, api_key: str, label: str = "") -> VendorKeyState: 92 """Upload (or replace) the vendor's model API key for a flavor. Requires 93 a ``submit``-scoped token. The key is encrypted at rest; the returned 94 row carries only metadata (never the ciphertext).""" 95 resp = self._http.request( 96 "POST", 97 "/vendor/keys", 98 json={"flavor": flavor, "api_key": api_key, "label": label}, 99 ) 100 _raise_for_status(resp) 101 return VendorKeyState.model_validate(resp.json()) 102 103 def keys(self) -> builtins.list[VendorKeyState]: 104 """List the vendor's uploaded key metadata (ciphertext blanked).""" 105 resp = self._http.request("GET", "/vendor/keys") 106 _raise_for_status(resp) 107 return [VendorKeyState.model_validate(row) for row in resp.json()] 108 109 def delete_key(self, vendor_key_id: str) -> bool: 110 resp = self._http.request("DELETE", f"/vendor/keys/{vendor_key_id}") 111 _raise_for_status(resp) 112 return bool(resp.json().get("deleted")) 113 114 def campaigns(self, *, offset: int = 0, limit: int = 50) -> PagedList[EvalCampaignState]: 115 """The vendor's own campaigns (sandbox + self-reported), newest 116 first — one page, with the total (``limit`` at most 200).""" 117 resp = self._http.request( 118 "GET", "/vendor/campaigns", params={"offset": offset, "limit": limit} 119 ) 120 _raise_for_status(resp) 121 return _extract_items(resp.json(), EvalCampaignState) 122 123 def submit_campaign( 124 self, 125 *, 126 model_id: str, 127 standard_set_id: str, 128 name: str = "", 129 num_samples: int = 1, 130 max_cost_usd: float = 0.0, 131 timeout_s: int = 0, 132 max_turns: int = 0, 133 publish: bool = False, 134 ) -> EvalCampaignState: 135 """Self-trigger a batch over an operator standard set on the vendor's 136 own key. ``publish=False`` → a private ``sandbox`` run; ``publish=True`` 137 → a public but unverified ``self_reported`` run (requires an approved 138 model). Requires a ``submit``-scoped token + an uploaded key for the 139 model's flavor. Poll :meth:`campaigns` for progress.""" 140 resp = self._http.request( 141 "POST", 142 "/vendor/campaigns", 143 json={ 144 "model_id": model_id, 145 "standard_set_id": standard_set_id, 146 "name": name, 147 "num_samples": num_samples, 148 "max_cost_usd": max_cost_usd, 149 "timeout_s": timeout_s, 150 "max_turns": max_turns, 151 "publish": publish, 152 }, 153 ) 154 _raise_for_status(resp) 155 return EvalCampaignState.model_validate(resp.json())
20class VendorResource: 21 """The calling vendor's own profile / models / runs. 22 23 Requires a ``pv_`` bearer (minted by an admin); the token resolves to a 24 single vendor and exposes only that vendor's data — never the platform's. 25 """ 26 27 def __init__(self, http: BaseHttpClient) -> None: 28 self._http = http 29 30 def me(self) -> VendorState: 31 resp = self._http.request("GET", "/vendor/me") 32 _raise_for_status(resp) 33 return VendorState.model_validate(resp.json()) 34 35 def models(self) -> builtins.list[LlmModelState]: 36 resp = self._http.request("GET", "/vendor/models") 37 _raise_for_status(resp) 38 return [LlmModelState.model_validate(row) for row in resp.json()] 39 40 def runs( 41 self, *, offset: int = 0, limit: int = 100, fully_solved: bool | None = None 42 ) -> PagedList[EvalRunState]: 43 """This vendor's runs, newest first — one page, with the total. 44 45 ``fully_solved`` narrows to runs that did (``True``) or did not 46 (``False``) solve their whole challenge; ``.total`` then counts 47 the narrowed set, so ``runs(fully_solved=True, limit=1).total`` 48 is how many runs solved, without reading them. 49 """ 50 params: dict[str, int | bool] = {"offset": offset, "limit": limit} 51 if fully_solved is not None: 52 params["fully_solved"] = fully_solved 53 resp = self._http.request("GET", "/vendor/runs", params=params) 54 _raise_for_status(resp) 55 return _extract_items(resp.json(), EvalRunState) 56 57 def standard_sets(self) -> builtins.list[VendorStandardSet]: 58 """The operator eval standard sets a vendor may launch a campaign over 59 (id / title / challenge count / corpus pin — the launch-picker source).""" 60 resp = self._http.request("GET", "/vendor/standard-sets") 61 _raise_for_status(resp) 62 return [VendorStandardSet.model_validate(row) for row in resp.json()] 63 64 def register_model( 65 self, 66 *, 67 id: str, 68 flavor: str, 69 display_name: str = "", 70 provider_model: str = "", 71 base_url: str = "", 72 notes: str = "", 73 ) -> LlmModelState: 74 """Self-register (or edit) one of the vendor's own models. Requires a 75 ``submit``-scoped ``pv_`` token. The model lands ``pending`` review — 76 an operator must approve it before it can run official campaigns.""" 77 resp = self._http.request( 78 "POST", 79 "/vendor/models", 80 json={ 81 "id": id, 82 "flavor": flavor, 83 "display_name": display_name, 84 "provider_model": provider_model, 85 "base_url": base_url, 86 "notes": notes, 87 }, 88 ) 89 _raise_for_status(resp) 90 return LlmModelState.model_validate(resp.json()) 91 92 def upload_key(self, *, flavor: str, api_key: str, label: str = "") -> VendorKeyState: 93 """Upload (or replace) the vendor's model API key for a flavor. Requires 94 a ``submit``-scoped token. The key is encrypted at rest; the returned 95 row carries only metadata (never the ciphertext).""" 96 resp = self._http.request( 97 "POST", 98 "/vendor/keys", 99 json={"flavor": flavor, "api_key": api_key, "label": label}, 100 ) 101 _raise_for_status(resp) 102 return VendorKeyState.model_validate(resp.json()) 103 104 def keys(self) -> builtins.list[VendorKeyState]: 105 """List the vendor's uploaded key metadata (ciphertext blanked).""" 106 resp = self._http.request("GET", "/vendor/keys") 107 _raise_for_status(resp) 108 return [VendorKeyState.model_validate(row) for row in resp.json()] 109 110 def delete_key(self, vendor_key_id: str) -> bool: 111 resp = self._http.request("DELETE", f"/vendor/keys/{vendor_key_id}") 112 _raise_for_status(resp) 113 return bool(resp.json().get("deleted")) 114 115 def campaigns(self, *, offset: int = 0, limit: int = 50) -> PagedList[EvalCampaignState]: 116 """The vendor's own campaigns (sandbox + self-reported), newest 117 first — one page, with the total (``limit`` at most 200).""" 118 resp = self._http.request( 119 "GET", "/vendor/campaigns", params={"offset": offset, "limit": limit} 120 ) 121 _raise_for_status(resp) 122 return _extract_items(resp.json(), EvalCampaignState) 123 124 def submit_campaign( 125 self, 126 *, 127 model_id: str, 128 standard_set_id: str, 129 name: str = "", 130 num_samples: int = 1, 131 max_cost_usd: float = 0.0, 132 timeout_s: int = 0, 133 max_turns: int = 0, 134 publish: bool = False, 135 ) -> EvalCampaignState: 136 """Self-trigger a batch over an operator standard set on the vendor's 137 own key. ``publish=False`` → a private ``sandbox`` run; ``publish=True`` 138 → a public but unverified ``self_reported`` run (requires an approved 139 model). Requires a ``submit``-scoped token + an uploaded key for the 140 model's flavor. Poll :meth:`campaigns` for progress.""" 141 resp = self._http.request( 142 "POST", 143 "/vendor/campaigns", 144 json={ 145 "model_id": model_id, 146 "standard_set_id": standard_set_id, 147 "name": name, 148 "num_samples": num_samples, 149 "max_cost_usd": max_cost_usd, 150 "timeout_s": timeout_s, 151 "max_turns": max_turns, 152 "publish": publish, 153 }, 154 ) 155 _raise_for_status(resp) 156 return EvalCampaignState.model_validate(resp.json())
The calling vendor's own profile / models / runs.
Requires a pv_ bearer (minted by an admin); the token resolves to a
single vendor and exposes only that vendor's data — never the platform's.
40 def runs( 41 self, *, offset: int = 0, limit: int = 100, fully_solved: bool | None = None 42 ) -> PagedList[EvalRunState]: 43 """This vendor's runs, newest first — one page, with the total. 44 45 ``fully_solved`` narrows to runs that did (``True``) or did not 46 (``False``) solve their whole challenge; ``.total`` then counts 47 the narrowed set, so ``runs(fully_solved=True, limit=1).total`` 48 is how many runs solved, without reading them. 49 """ 50 params: dict[str, int | bool] = {"offset": offset, "limit": limit} 51 if fully_solved is not None: 52 params["fully_solved"] = fully_solved 53 resp = self._http.request("GET", "/vendor/runs", params=params) 54 _raise_for_status(resp) 55 return _extract_items(resp.json(), EvalRunState)
This vendor's runs, newest first — one page, with the total.
fully_solved narrows to runs that did (True) or did not
(False) solve their whole challenge; .total then counts
the narrowed set, so runs(fully_solved=True, limit=1).total
is how many runs solved, without reading them.
57 def standard_sets(self) -> builtins.list[VendorStandardSet]: 58 """The operator eval standard sets a vendor may launch a campaign over 59 (id / title / challenge count / corpus pin — the launch-picker source).""" 60 resp = self._http.request("GET", "/vendor/standard-sets") 61 _raise_for_status(resp) 62 return [VendorStandardSet.model_validate(row) for row in resp.json()]
The operator eval standard sets a vendor may launch a campaign over (id / title / challenge count / corpus pin — the launch-picker source).
64 def register_model( 65 self, 66 *, 67 id: str, 68 flavor: str, 69 display_name: str = "", 70 provider_model: str = "", 71 base_url: str = "", 72 notes: str = "", 73 ) -> LlmModelState: 74 """Self-register (or edit) one of the vendor's own models. Requires a 75 ``submit``-scoped ``pv_`` token. The model lands ``pending`` review — 76 an operator must approve it before it can run official campaigns.""" 77 resp = self._http.request( 78 "POST", 79 "/vendor/models", 80 json={ 81 "id": id, 82 "flavor": flavor, 83 "display_name": display_name, 84 "provider_model": provider_model, 85 "base_url": base_url, 86 "notes": notes, 87 }, 88 ) 89 _raise_for_status(resp) 90 return LlmModelState.model_validate(resp.json())
Self-register (or edit) one of the vendor's own models. Requires a
submit-scoped pv_ token. The model lands pending review —
an operator must approve it before it can run official campaigns.
92 def upload_key(self, *, flavor: str, api_key: str, label: str = "") -> VendorKeyState: 93 """Upload (or replace) the vendor's model API key for a flavor. Requires 94 a ``submit``-scoped token. The key is encrypted at rest; the returned 95 row carries only metadata (never the ciphertext).""" 96 resp = self._http.request( 97 "POST", 98 "/vendor/keys", 99 json={"flavor": flavor, "api_key": api_key, "label": label}, 100 ) 101 _raise_for_status(resp) 102 return VendorKeyState.model_validate(resp.json())
Upload (or replace) the vendor's model API key for a flavor. Requires
a submit-scoped token. The key is encrypted at rest; the returned
row carries only metadata (never the ciphertext).
104 def keys(self) -> builtins.list[VendorKeyState]: 105 """List the vendor's uploaded key metadata (ciphertext blanked).""" 106 resp = self._http.request("GET", "/vendor/keys") 107 _raise_for_status(resp) 108 return [VendorKeyState.model_validate(row) for row in resp.json()]
List the vendor's uploaded key metadata (ciphertext blanked).
115 def campaigns(self, *, offset: int = 0, limit: int = 50) -> PagedList[EvalCampaignState]: 116 """The vendor's own campaigns (sandbox + self-reported), newest 117 first — one page, with the total (``limit`` at most 200).""" 118 resp = self._http.request( 119 "GET", "/vendor/campaigns", params={"offset": offset, "limit": limit} 120 ) 121 _raise_for_status(resp) 122 return _extract_items(resp.json(), EvalCampaignState)
The vendor's own campaigns (sandbox + self-reported), newest
first — one page, with the total (limit at most 200).
124 def submit_campaign( 125 self, 126 *, 127 model_id: str, 128 standard_set_id: str, 129 name: str = "", 130 num_samples: int = 1, 131 max_cost_usd: float = 0.0, 132 timeout_s: int = 0, 133 max_turns: int = 0, 134 publish: bool = False, 135 ) -> EvalCampaignState: 136 """Self-trigger a batch over an operator standard set on the vendor's 137 own key. ``publish=False`` → a private ``sandbox`` run; ``publish=True`` 138 → a public but unverified ``self_reported`` run (requires an approved 139 model). Requires a ``submit``-scoped token + an uploaded key for the 140 model's flavor. Poll :meth:`campaigns` for progress.""" 141 resp = self._http.request( 142 "POST", 143 "/vendor/campaigns", 144 json={ 145 "model_id": model_id, 146 "standard_set_id": standard_set_id, 147 "name": name, 148 "num_samples": num_samples, 149 "max_cost_usd": max_cost_usd, 150 "timeout_s": timeout_s, 151 "max_turns": max_turns, 152 "publish": publish, 153 }, 154 ) 155 _raise_for_status(resp) 156 return EvalCampaignState.model_validate(resp.json())
Self-trigger a batch over an operator standard set on the vendor's
own key. publish=False → a private sandbox run; publish=True
→ a public but unverified self_reported run (requires an approved
model). Requires a submit-scoped token + an uploaded key for the
model's flavor. Poll campaigns() for progress.