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    EvalModelState,
 10    EvalRunState,
 11    VendorKeyState,
 12    VendorState,
 13)
 14from ctfy.sdk._helpers import _raise_for_status
 15from ctfy.sdk.base import BaseHttpClient
 16from ctfy.server.models import NodeInfo, 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[EvalModelState]:
 35        resp = self._http.request("GET", "/vendor/models")
 36        _raise_for_status(resp)
 37        return [EvalModelState.model_validate(row) for row in resp.json()]
 38
 39    def runs(self, *, limit: int = 100) -> builtins.list[EvalRunState]:
 40        resp = self._http.request("GET", "/vendor/runs", params={"limit": limit})
 41        _raise_for_status(resp)
 42        return [EvalRunState.model_validate(row) for row in resp.json()]
 43
 44    def standard_sets(self) -> builtins.list[VendorStandardSet]:
 45        """The operator eval standard sets a vendor may launch a campaign over
 46        (id / title / challenge count / corpus pin — the launch-picker source)."""
 47        resp = self._http.request("GET", "/vendor/standard-sets")
 48        _raise_for_status(resp)
 49        return [VendorStandardSet.model_validate(row) for row in resp.json()]
 50
 51    def nodes(self) -> builtins.list[NodeInfo]:
 52        """The vendor's own worker nodes (bring-your-own-node), with live
 53        health — so the vendor can confirm a node is online before launching a
 54        BYO-node campaign that would run on it."""
 55        resp = self._http.request("GET", "/vendor/nodes")
 56        _raise_for_status(resp)
 57        return [NodeInfo.model_validate(row) for row in resp.json()]
 58
 59    def register_model(
 60        self,
 61        *,
 62        id: str,
 63        flavor: str,
 64        display_name: str = "",
 65        provider_model: str = "",
 66        base_url: str = "",
 67        notes: str = "",
 68    ) -> EvalModelState:
 69        """Self-register (or edit) one of the vendor's own models. Requires a
 70        ``submit``-scoped ``pv_`` token. The model lands ``pending`` review —
 71        an operator must approve it before it can run official campaigns."""
 72        resp = self._http.request(
 73            "POST",
 74            "/vendor/models",
 75            json={
 76                "id": id,
 77                "flavor": flavor,
 78                "display_name": display_name,
 79                "provider_model": provider_model,
 80                "base_url": base_url,
 81                "notes": notes,
 82            },
 83        )
 84        _raise_for_status(resp)
 85        return EvalModelState.model_validate(resp.json())
 86
 87    def upload_key(self, *, flavor: str, api_key: str, label: str = "") -> VendorKeyState:
 88        """Upload (or replace) the vendor's model API key for a flavor. Requires
 89        a ``submit``-scoped token. The key is encrypted at rest; the returned
 90        row carries only metadata (never the ciphertext)."""
 91        resp = self._http.request(
 92            "POST",
 93            "/vendor/keys",
 94            json={"flavor": flavor, "api_key": api_key, "label": label},
 95        )
 96        _raise_for_status(resp)
 97        return VendorKeyState.model_validate(resp.json())
 98
 99    def keys(self) -> builtins.list[VendorKeyState]:
100        """List the vendor's uploaded key metadata (ciphertext blanked)."""
101        resp = self._http.request("GET", "/vendor/keys")
102        _raise_for_status(resp)
103        return [VendorKeyState.model_validate(row) for row in resp.json()]
104
105    def delete_key(self, vendor_key_id: str) -> bool:
106        resp = self._http.request("DELETE", f"/vendor/keys/{vendor_key_id}")
107        _raise_for_status(resp)
108        return bool(resp.json().get("deleted"))
109
110    def campaigns(self) -> builtins.list[EvalCampaignState]:
111        """The vendor's own campaigns (sandbox + self-reported), newest first."""
112        resp = self._http.request("GET", "/vendor/campaigns")
113        _raise_for_status(resp)
114        return [EvalCampaignState.model_validate(row) for row in resp.json()]
115
116    def submit_campaign(
117        self,
118        *,
119        model_id: str,
120        standard_set_id: str,
121        name: str = "",
122        num_samples: int = 1,
123        max_cost_usd: float = 0.0,
124        publish: bool = False,
125    ) -> EvalCampaignState:
126        """Self-trigger a batch over an operator standard set on the vendor's
127        own key. ``publish=False`` → a private ``sandbox`` run; ``publish=True``
128        → a public but unverified ``self_reported`` run (requires an approved
129        model). Requires a ``submit``-scoped token + an uploaded key for the
130        model's flavor. Poll :meth:`campaigns` for progress."""
131        resp = self._http.request(
132            "POST",
133            "/vendor/campaigns",
134            json={
135                "model_id": model_id,
136                "standard_set_id": standard_set_id,
137                "name": name,
138                "num_samples": num_samples,
139                "max_cost_usd": max_cost_usd,
140                "publish": publish,
141            },
142        )
143        _raise_for_status(resp)
144        return EvalCampaignState.model_validate(resp.json())
class VendorResource:
 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[EvalModelState]:
 36        resp = self._http.request("GET", "/vendor/models")
 37        _raise_for_status(resp)
 38        return [EvalModelState.model_validate(row) for row in resp.json()]
 39
 40    def runs(self, *, limit: int = 100) -> builtins.list[EvalRunState]:
 41        resp = self._http.request("GET", "/vendor/runs", params={"limit": limit})
 42        _raise_for_status(resp)
 43        return [EvalRunState.model_validate(row) for row in resp.json()]
 44
 45    def standard_sets(self) -> builtins.list[VendorStandardSet]:
 46        """The operator eval standard sets a vendor may launch a campaign over
 47        (id / title / challenge count / corpus pin — the launch-picker source)."""
 48        resp = self._http.request("GET", "/vendor/standard-sets")
 49        _raise_for_status(resp)
 50        return [VendorStandardSet.model_validate(row) for row in resp.json()]
 51
 52    def nodes(self) -> builtins.list[NodeInfo]:
 53        """The vendor's own worker nodes (bring-your-own-node), with live
 54        health — so the vendor can confirm a node is online before launching a
 55        BYO-node campaign that would run on it."""
 56        resp = self._http.request("GET", "/vendor/nodes")
 57        _raise_for_status(resp)
 58        return [NodeInfo.model_validate(row) for row in resp.json()]
 59
 60    def register_model(
 61        self,
 62        *,
 63        id: str,
 64        flavor: str,
 65        display_name: str = "",
 66        provider_model: str = "",
 67        base_url: str = "",
 68        notes: str = "",
 69    ) -> EvalModelState:
 70        """Self-register (or edit) one of the vendor's own models. Requires a
 71        ``submit``-scoped ``pv_`` token. The model lands ``pending`` review —
 72        an operator must approve it before it can run official campaigns."""
 73        resp = self._http.request(
 74            "POST",
 75            "/vendor/models",
 76            json={
 77                "id": id,
 78                "flavor": flavor,
 79                "display_name": display_name,
 80                "provider_model": provider_model,
 81                "base_url": base_url,
 82                "notes": notes,
 83            },
 84        )
 85        _raise_for_status(resp)
 86        return EvalModelState.model_validate(resp.json())
 87
 88    def upload_key(self, *, flavor: str, api_key: str, label: str = "") -> VendorKeyState:
 89        """Upload (or replace) the vendor's model API key for a flavor. Requires
 90        a ``submit``-scoped token. The key is encrypted at rest; the returned
 91        row carries only metadata (never the ciphertext)."""
 92        resp = self._http.request(
 93            "POST",
 94            "/vendor/keys",
 95            json={"flavor": flavor, "api_key": api_key, "label": label},
 96        )
 97        _raise_for_status(resp)
 98        return VendorKeyState.model_validate(resp.json())
 99
100    def keys(self) -> builtins.list[VendorKeyState]:
101        """List the vendor's uploaded key metadata (ciphertext blanked)."""
102        resp = self._http.request("GET", "/vendor/keys")
103        _raise_for_status(resp)
104        return [VendorKeyState.model_validate(row) for row in resp.json()]
105
106    def delete_key(self, vendor_key_id: str) -> bool:
107        resp = self._http.request("DELETE", f"/vendor/keys/{vendor_key_id}")
108        _raise_for_status(resp)
109        return bool(resp.json().get("deleted"))
110
111    def campaigns(self) -> builtins.list[EvalCampaignState]:
112        """The vendor's own campaigns (sandbox + self-reported), newest first."""
113        resp = self._http.request("GET", "/vendor/campaigns")
114        _raise_for_status(resp)
115        return [EvalCampaignState.model_validate(row) for row in resp.json()]
116
117    def submit_campaign(
118        self,
119        *,
120        model_id: str,
121        standard_set_id: str,
122        name: str = "",
123        num_samples: int = 1,
124        max_cost_usd: float = 0.0,
125        publish: bool = False,
126    ) -> EvalCampaignState:
127        """Self-trigger a batch over an operator standard set on the vendor's
128        own key. ``publish=False`` → a private ``sandbox`` run; ``publish=True``
129        → a public but unverified ``self_reported`` run (requires an approved
130        model). Requires a ``submit``-scoped token + an uploaded key for the
131        model's flavor. Poll :meth:`campaigns` for progress."""
132        resp = self._http.request(
133            "POST",
134            "/vendor/campaigns",
135            json={
136                "model_id": model_id,
137                "standard_set_id": standard_set_id,
138                "name": name,
139                "num_samples": num_samples,
140                "max_cost_usd": max_cost_usd,
141                "publish": publish,
142            },
143        )
144        _raise_for_status(resp)
145        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.

VendorResource(http: ctfy.sdk.base.BaseHttpClient)
27    def __init__(self, http: BaseHttpClient) -> None:
28        self._http = http
def me(self) -> ctfy.core.state.models.VendorState:
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())
def models(self) -> list[ctfy.core.state.models.EvalModelState]:
35    def models(self) -> builtins.list[EvalModelState]:
36        resp = self._http.request("GET", "/vendor/models")
37        _raise_for_status(resp)
38        return [EvalModelState.model_validate(row) for row in resp.json()]
def runs(self, *, limit: int = 100) -> list[ctfy.core.state.models.EvalRunState]:
40    def runs(self, *, limit: int = 100) -> builtins.list[EvalRunState]:
41        resp = self._http.request("GET", "/vendor/runs", params={"limit": limit})
42        _raise_for_status(resp)
43        return [EvalRunState.model_validate(row) for row in resp.json()]
def standard_sets(self) -> list[ctfy.server.models.VendorStandardSet]:
45    def standard_sets(self) -> builtins.list[VendorStandardSet]:
46        """The operator eval standard sets a vendor may launch a campaign over
47        (id / title / challenge count / corpus pin — the launch-picker source)."""
48        resp = self._http.request("GET", "/vendor/standard-sets")
49        _raise_for_status(resp)
50        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).

def nodes(self) -> list[ctfy.server.models.NodeInfo]:
52    def nodes(self) -> builtins.list[NodeInfo]:
53        """The vendor's own worker nodes (bring-your-own-node), with live
54        health — so the vendor can confirm a node is online before launching a
55        BYO-node campaign that would run on it."""
56        resp = self._http.request("GET", "/vendor/nodes")
57        _raise_for_status(resp)
58        return [NodeInfo.model_validate(row) for row in resp.json()]

The vendor's own worker nodes (bring-your-own-node), with live health — so the vendor can confirm a node is online before launching a BYO-node campaign that would run on it.

def register_model( self, *, id: str, flavor: str, display_name: str = '', provider_model: str = '', base_url: str = '', notes: str = '') -> ctfy.core.state.models.EvalModelState:
60    def register_model(
61        self,
62        *,
63        id: str,
64        flavor: str,
65        display_name: str = "",
66        provider_model: str = "",
67        base_url: str = "",
68        notes: str = "",
69    ) -> EvalModelState:
70        """Self-register (or edit) one of the vendor's own models. Requires a
71        ``submit``-scoped ``pv_`` token. The model lands ``pending`` review —
72        an operator must approve it before it can run official campaigns."""
73        resp = self._http.request(
74            "POST",
75            "/vendor/models",
76            json={
77                "id": id,
78                "flavor": flavor,
79                "display_name": display_name,
80                "provider_model": provider_model,
81                "base_url": base_url,
82                "notes": notes,
83            },
84        )
85        _raise_for_status(resp)
86        return EvalModelState.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.

def upload_key( self, *, flavor: str, api_key: str, label: str = '') -> ctfy.core.state.models.VendorKeyState:
88    def upload_key(self, *, flavor: str, api_key: str, label: str = "") -> VendorKeyState:
89        """Upload (or replace) the vendor's model API key for a flavor. Requires
90        a ``submit``-scoped token. The key is encrypted at rest; the returned
91        row carries only metadata (never the ciphertext)."""
92        resp = self._http.request(
93            "POST",
94            "/vendor/keys",
95            json={"flavor": flavor, "api_key": api_key, "label": label},
96        )
97        _raise_for_status(resp)
98        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).

def keys(self) -> list[ctfy.core.state.models.VendorKeyState]:
100    def keys(self) -> builtins.list[VendorKeyState]:
101        """List the vendor's uploaded key metadata (ciphertext blanked)."""
102        resp = self._http.request("GET", "/vendor/keys")
103        _raise_for_status(resp)
104        return [VendorKeyState.model_validate(row) for row in resp.json()]

List the vendor's uploaded key metadata (ciphertext blanked).

def delete_key(self, vendor_key_id: str) -> bool:
106    def delete_key(self, vendor_key_id: str) -> bool:
107        resp = self._http.request("DELETE", f"/vendor/keys/{vendor_key_id}")
108        _raise_for_status(resp)
109        return bool(resp.json().get("deleted"))
def campaigns(self) -> list[ctfy.core.state.models.EvalCampaignState]:
111    def campaigns(self) -> builtins.list[EvalCampaignState]:
112        """The vendor's own campaigns (sandbox + self-reported), newest first."""
113        resp = self._http.request("GET", "/vendor/campaigns")
114        _raise_for_status(resp)
115        return [EvalCampaignState.model_validate(row) for row in resp.json()]

The vendor's own campaigns (sandbox + self-reported), newest first.

def submit_campaign( self, *, model_id: str, standard_set_id: str, name: str = '', num_samples: int = 1, max_cost_usd: float = 0.0, publish: bool = False) -> ctfy.core.state.models.EvalCampaignState:
117    def submit_campaign(
118        self,
119        *,
120        model_id: str,
121        standard_set_id: str,
122        name: str = "",
123        num_samples: int = 1,
124        max_cost_usd: float = 0.0,
125        publish: bool = False,
126    ) -> EvalCampaignState:
127        """Self-trigger a batch over an operator standard set on the vendor's
128        own key. ``publish=False`` → a private ``sandbox`` run; ``publish=True``
129        → a public but unverified ``self_reported`` run (requires an approved
130        model). Requires a ``submit``-scoped token + an uploaded key for the
131        model's flavor. Poll :meth:`campaigns` for progress."""
132        resp = self._http.request(
133            "POST",
134            "/vendor/campaigns",
135            json={
136                "model_id": model_id,
137                "standard_set_id": standard_set_id,
138                "name": name,
139                "num_samples": num_samples,
140                "max_cost_usd": max_cost_usd,
141                "publish": publish,
142            },
143        )
144        _raise_for_status(resp)
145        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.