Documentation
¶
Overview ¶
bots.go mounts the Hanzo Cloud BOT surface (/v1/bots) plus the machine agent-binding proxies (/v1/machines/:id/{bind-agent,agent-binding}, /v1/agent-bindings). It is the SIBLING of machines: a Bot is not a new state this subsystem owns, it is a composition of two things vm already owns — a kind=bot Machine and an AgentBinding. So every route here is a thin, org-scoped translation over the SAME Visor client the machines routes use (client.go), never a second store.
A Bot = Agent (cloud /v1/agents) + Machine (vm, kind=bot) + the binding between them. Composition, one way per verb:
launch = vm POST /v1/machines/launch {kind:bot} THEN vm POST .../bind-agent
list = vm GET /v1/machines?kind=bot joined with the org's bindings
get = vm GET /v1/machines/:id joined with its binding
delete = vm DELETE .../agent-binding (unbind) THEN vm DELETE /v1/machines/:id
message = the AGENT path: run the bot's bound agent via /v1/agents/:agent/run
stop = vm DELETE .../agent-binding — halt the bot's @hanzo/bot runtime
pause = the same halt: DigitalOcean/vm expose no VM-suspend primitive, so a
bot's stop and pause are one honest capability (detach the agent
runtime); powering the underlying machine off/on is a machine-lifecycle
concern handled by launch/delete, not a fabricated bot state.
Tenancy is identical to machines: the org is the VALIDATED principal (principal.Tenant, taken from the IAM owner claim), forwarded to vm as ?owner=<org>, so a caller can only ever read or mutate its OWN bots. No validated principal ⇒ 403, before anything reaches vm.
client.go is the ONE HTTP path from this subsystem to Visor (the cloud OS at visor.hanzo.svc:19000 that owns compute — machines and DOKS node pools). Every handler in visor.go routes through this client, so the wire contract (base URL, auth, the casibase {status,msg,data} envelope, error mapping) lives once here and can never drift between six hand-rolled fetches.
AUTH (one rule): a request carries a Visor identity that is EITHER the service credential (VISOR_CLIENT_ID + VISOR_CLIENT_SECRET, KMS-sourced, sent as Basic auth so Visor's ApiFilter authorizes cloud as the `app/<visorApp>` subject) OR the caller's forwarded Authorization bearer when no service credential is configured. The tenant is ALWAYS pinned by ?owner=<org> (the validated principal's org, never a client field) plus the forwarded identity headers, so Visor scopes to exactly the caller's tenant on both paths.
ENVELOPE: Visor (casibase) returns HTTP 200 with {status:"ok"|"error", msg, data}. A logical failure is status:"error" at HTTP 200, NOT a 4xx/5xx — so a bare status-code check would read an error as success. call() inspects the status field and surfaces msg as an honest error; it never fabricates data.
types.go holds the Visor wire structs (what upstream returns) and the console view structs (what this subsystem emits), plus the PURE mapping between them. The view JSON keys mirror the console normalizers EXACTLY so the Machines, GPUs and Clusters pages render with no front-end change:
- machineView -> console2 src/lib/api/visor.ts normalizeMachine
- gpuView -> console2 src/lib/api/compute.ts normalizeGpu
- clusterView -> console2 src/lib/api/platform.ts Cluster + NodePool
Every field is a REAL Visor value or an honest omission. Telemetry Visor does not carry (GPU utilization/temperature/power) is left off the gpuView so the UI shows "—", never a fabricated 0.
Package visor mounts the Hanzo Cloud COMPUTE surface: the tenant's machines, GPUs and DOKS clusters, served as clean REST off the unified cloud binary and fronting Visor (the cloud OS at visor.hanzo.svc that OWNS compute). It exists so the console's Machines / GPUs / Clusters pages read real per-org compute from ONE place (api.hanzo.ai/v1/*) instead of the god-mode /paas admin proxy that 501s until a service token is wired.
This subsystem OWNS no compute state — Visor does. It is a thin, tenant-scoped translator: it maps Visor's verb-style + resell endpoints to the clean REST the console already speaks, and re-shapes Visor's objects into the exact JSON the console normalizers consume (see types.go). It never fabricates: a GPU row is a real GPU machine's accelerator, a cluster is real node pools, and telemetry Visor does not carry is honestly omitted (renders "—"), not invented.
Surface (every route org-scoped by the validated principal; HIP-0026):
GET /v1/machines list the org's machines -> {machines:[machineView]}
POST /v1/machines launch (or dryRun quote) -> machineView | quote
GET /v1/machines/:id one machine by name -> machineView (404 if absent)
DELETE /v1/machines/:id terminate a machine -> 204
GET /v1/gpus per-accelerator inventory -> {gpus:[gpuView]}
GET /v1/gpus/alerts GPU alerts (honest empty) -> {alerts:[]}
GET /v1/clusters DOKS clusters (from pools) -> {clusters:[clusterView]}
POST /v1/clusters/:clusterId/pools add a node pool -> nodePoolView
POST /v1/clusters/:clusterId/pools/:poolId/scale scale a node pool -> nodePoolView
DELETE /v1/clusters/:clusterId/pools/:poolId delete a node pool -> 204
POST /v1/machines/:id/bind-agent bind a cloud Agent to a machine -> agentBinding
GET /v1/machines/:id/agent-binding the machine's agent binding -> agentBinding (404 if none)
DELETE /v1/machines/:id/agent-binding unbind the agent -> 204
GET /v1/agent-bindings the org's agent bindings -> {agentBindings:[agentBinding]}
GET /v1/bots the org's bots (kind=bot) -> {bots:[botView]}
POST /v1/bots/launch launch a bot (machine+bind) -> botView | quote
GET /v1/bots/:id one bot by id -> botView (404 if not a bot)
DELETE /v1/bots/:id terminate a bot (unbind+delete) -> 204
POST /v1/bots/:id/:action stop|pause|message the bot -> action result
The tenant (principal.Tenant) is passed to Visor as ?owner=<org>, so a caller can only ever read or mutate their OWN tenant's compute; the org is taken from the validated IAM owner claim, never a client field.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
This section is empty.