Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/ui-core/src/annotator/suggestFlow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ function connectionRow(
// never been downloaded declares nothing, which is why the default only
// makes sense beside `setup`.
capabilities: setup === "ready" ? ["point_suggest"] : [],
// Not optional on the wire, so not optional here — the generated runtime
// check refuses a response missing it.
download: null,
created_at: "2026-08-08T00:00:00Z",
updated_at: "2026-08-08T00:00:00Z",
...overrides,
Expand Down
1 change: 1 addition & 0 deletions frontend/ui-core/src/annotator/suggestPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function connection(
// something has read one — which is why a row that never downloaded
// declares nothing.
capabilities: setup === "ready" ? ["point_suggest"] : [],
download: null,
created_at: "2026-08-08T00:00:00Z",
updated_at: "2026-08-08T00:00:00Z",
...overrides,
Expand Down
45 changes: 43 additions & 2 deletions frontend/ui-core/src/generated/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,11 @@ export interface paths {
* List Inference Connections
* @description Every configured connection in this workspace, in the order they were made.
*
* Each row carries its most recent weight download, so a client sees a transfer
* it did not start — after a reload, in a second tab, on another machine. This
* is therefore the read a screen polls while a download is live, and the reason
* it can stop polling the moment none is.
*
* A set-up connection that has never been asked what kind of model it holds is
* asked here, once, from files already on this disk — see
* ``visionset.inference.weights.with_families``. It is the backfill for rows
Expand Down Expand Up @@ -735,8 +740,9 @@ export interface paths {
* Get Inference Connection
* @description The connection with that id.
*
* Carries the same backfill the listing does, so that reading one connection
* and reading the list never disagree about what it can be asked for.
* Carries the same backfill the listing does, and the same weight download, so
* that reading one connection and reading the list never disagree about what it
* can be asked for or about what is happening to it.
*/
get: operations["get_inference_connection"];
put?: never;
Expand Down Expand Up @@ -2667,6 +2673,7 @@ export interface components {
created_at: string;
/** Device */
device: string | null;
download: components["schemas"]["WeightDownloadOut"] | null;
/** Endpoint Url */
endpoint_url: string | null;
/**
Expand Down Expand Up @@ -3660,6 +3667,40 @@ export interface components {
/** Width */
width: number;
};
/**
* WeightDownloadOut
* @description A connection's weight transfer: which job, how far it has got, how it ended.
*
* Present whenever a download has ever been asked for on this connection, and
* describing the most recent one. It is how a client shows a transfer it did not
* itself start: a download outlives the request that launched it and the page
* that asked, so a reload, a second tab or another machine all read the same
* progress from here rather than from a job id somebody happened to keep.
*
* Polling this — through the connection or through
* `GET /background-jobs/{job_id}` — never affects the run. The job is dispatched
* to a worker process the server owns; no client disconnect cancels or pauses
* it, and closing the browser during a download is not a way to stop one.
*
* **It is not a setup state.** `setup_state` says whether the weights are
* *here*; this says whether something is currently fetching them. The two are
* separate on purpose: a connection is `ready` only once a snapshot is complete,
* so there is no moment at which one is half set up.
*/
WeightDownloadOut: {
/** Bytes Done */
bytes_done: number;
/** Bytes Total */
bytes_total: number | null;
/** Error */
error: string | null;
/**
* Job Id
* Format: uuid
*/
job_id: string;
state: components["schemas"]["BackgroundJobState"];
};
};
responses: never;
parameters: never;
Expand Down
5 changes: 4 additions & 1 deletion frontend/ui-core/src/generated/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ export const checkModelCapability: Check<Schemas["ModelCapability"]> =
export const checkPrecision: Check<Schemas["Precision"]> =
/*#__PURE__*/ oneOf(["fp16", "fp32"] as const);

export const checkWeightDownloadOut: Check<Schemas["WeightDownloadOut"]> =
/*#__PURE__*/ object({ "bytes_done": [true, isInteger], "bytes_total": [true, either([isInteger, isNull] as const)], "error": [true, either([isString, isNull] as const)], "job_id": [true, isString], "state": [true, checkBackgroundJobState] } as const);

export const checkConnectionOut: Check<Schemas["ConnectionOut"]> =
/*#__PURE__*/ object({ "allowed_actions": [true, arrayOf(checkConnectionAction)], "capabilities": [true, arrayOf(checkModelCapability)], "connection_type": [true, checkConnectionType], "created_at": [true, isString], "device": [true, either([isString, isNull] as const)], "endpoint_url": [true, either([isString, isNull] as const)], "id": [true, isString], "model_id": [true, isString], "model_revision": [true, isString], "name": [true, isString], "precision": [true, either([checkPrecision, isNull] as const)], "setup_state": [true, checkConnectionSetupState], "updated_at": [true, isString] } as const);
/*#__PURE__*/ object({ "allowed_actions": [true, arrayOf(checkConnectionAction)], "capabilities": [true, arrayOf(checkModelCapability)], "connection_type": [true, checkConnectionType], "created_at": [true, isString], "device": [true, either([isString, isNull] as const)], "download": [true, either([checkWeightDownloadOut, isNull] as const)], "endpoint_url": [true, either([isString, isNull] as const)], "id": [true, isString], "model_id": [true, isString], "model_revision": [true, isString], "name": [true, isString], "precision": [true, either([checkPrecision, isNull] as const)], "setup_state": [true, checkConnectionSetupState], "updated_at": [true, isString] } as const);

export const checkConnectionPage: Check<Schemas["ConnectionPage"]> =
/*#__PURE__*/ object({ "items": [true, arrayOf(checkConnectionOut)], "total": [true, isInteger] } as const);
Expand Down
3 changes: 3 additions & 0 deletions frontend/ui-core/src/screens/inference.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ function connection(overrides: Partial<Connection> = {}): Connection {
// check refuses a response missing it, and a stub that omitted one rendered
// this screen's error card in every case — which reads as a component bug.
capabilities: [],
// Also not optional on the wire, and `null` is its ordinary value: nobody
// has ever asked this connection to fetch anything.
download: null,
created_at: "2026-08-08T00:00:00Z",
updated_at: "2026-08-08T00:00:00Z",
...overrides,
Expand Down
63 changes: 61 additions & 2 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,16 @@
],
"title": "Device"
},
"download": {
"anyOf": [
{
"$ref": "#/components/schemas/WeightDownloadOut"
},
{
"type": "null"
}
]
},
"endpoint_url": {
"anyOf": [
{
Expand Down Expand Up @@ -1658,6 +1668,7 @@
"setup_state",
"allowed_actions",
"capabilities",
"download",
"created_at",
"updated_at"
],
Expand Down Expand Up @@ -3527,6 +3538,54 @@
],
"title": "VideoProvenanceOut",
"type": "object"
},
"WeightDownloadOut": {
"description": "A connection's weight transfer: which job, how far it has got, how it ended.\n\nPresent whenever a download has ever been asked for on this connection, and\ndescribing the most recent one. It is how a client shows a transfer it did not\nitself start: a download outlives the request that launched it and the page\nthat asked, so a reload, a second tab or another machine all read the same\nprogress from here rather than from a job id somebody happened to keep.\n\nPolling this \u2014 through the connection or through\n`GET /background-jobs/{job_id}` \u2014 never affects the run. The job is dispatched\nto a worker process the server owns; no client disconnect cancels or pauses\nit, and closing the browser during a download is not a way to stop one.\n\n**It is not a setup state.** `setup_state` says whether the weights are\n*here*; this says whether something is currently fetching them. The two are\nseparate on purpose: a connection is `ready` only once a snapshot is complete,\nso there is no moment at which one is half set up.",
"properties": {
"bytes_done": {
"title": "Bytes Done",
"type": "integer"
},
"bytes_total": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Bytes Total"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Error"
},
"job_id": {
"format": "uuid",
"title": "Job Id",
"type": "string"
},
"state": {
"$ref": "#/components/schemas/BackgroundJobState"
}
},
"required": [
"job_id",
"state",
"bytes_done",
"bytes_total",
"error"
],
"title": "WeightDownloadOut",
"type": "object"
}
},
"securitySchemes": {
Expand Down Expand Up @@ -5962,7 +6021,7 @@
},
"/inference/connections": {
"get": {
"description": "Every configured connection in this workspace, in the order they were made.\n\nA set-up connection that has never been asked what kind of model it holds is\nasked here, once, from files already on this disk \u2014 see\n``visionset.inference.weights.with_families``. It is the backfill for rows\nwritten before a connection recorded that, and it is on the read path because\nthe kernel cannot reach a model cache and a migration runs in the kernel.",
"description": "Every configured connection in this workspace, in the order they were made.\n\nEach row carries its most recent weight download, so a client sees a transfer\nit did not start \u2014 after a reload, in a second tab, on another machine. This\nis therefore the read a screen polls while a download is live, and the reason\nit can stop polling the moment none is.\n\nA set-up connection that has never been asked what kind of model it holds is\nasked here, once, from files already on this disk \u2014 see\n``visionset.inference.weights.with_families``. It is the backfill for rows\nwritten before a connection recorded that, and it is on the read path because\nthe kernel cannot reach a model cache and a migration runs in the kernel.",
"operationId": "list_inference_connections",
"responses": {
"200": {
Expand Down Expand Up @@ -6194,7 +6253,7 @@
]
},
"get": {
"description": "The connection with that id.\n\nCarries the same backfill the listing does, so that reading one connection\nand reading the list never disagree about what it can be asked for.",
"description": "The connection with that id.\n\nCarries the same backfill the listing does, and the same weight download, so\nthat reading one connection and reading the list never disagree about what it\ncan be asked for or about what is happening to it.",
"operationId": "get_inference_connection",
"parameters": [
{
Expand Down
14 changes: 11 additions & 3 deletions src/visionset/cli/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,12 @@ def inference_show(
with opened_workspace(workspace) as service:
connections = InferenceConnectionService(service)
found = connections.get(_resolve(connections, connection))
# A download runs in the server's worker against this same workspace, so
# a terminal can watch one it did not start — the property the REST
# listing has, published by the surface that shares its projection.
downloading = connections.downloads().get(found.id)
if json_out:
document(wire.connection(found))
document(wire.connection(found, downloading))
return
table(_COLUMNS, [_row(found)])

Expand Down Expand Up @@ -311,10 +315,14 @@ def inference_list(
) -> None:
"""List this workspace's connections, oldest first."""
with opened_workspace(workspace) as service:
connections = InferenceConnectionService(service).list()
configured = InferenceConnectionService(service)
connections = configured.list()
# One queue read for the page, on the REST listing's terms: a terminal
# watches a transfer the server's worker is running.
downloads = configured.downloads()
root = service.root
if json_out:
document(wire.page([wire.connection(one) for one in connections]))
document(wire.page([wire.connection(one, downloads.get(one.id)) for one in connections]))
return
table(_COLUMNS, [_row(one) for one in connections])
if not connections:
Expand Down
Loading
Loading