From c88e5614f5fca02143e9b3bedaf60e766feb2cbe Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Fri, 14 Aug 2026 18:13:37 -0700 Subject: [PATCH] Reconcile tygo codegen with SPA and enforce in CI types.ts is now truly tygo-generated (make tygo) instead of the phase-8 hand-maintained stop-gap. Types with no adminapi Go struct move to a new hand-maintained ui/src/api/manual.ts: the Window/ Bucket/Dimension unions, the ApiError envelope, the trust-package mirrors (TrustOrg/TrustStatus), and the Bifrost pass-through chat/ usage shapes. SPA imports updated accordingly. evalRefResponse is exported as EvalRefResponse so tygo emits the binding the eval mutations decode. go.sum gains the bifrost core v1.6.2 hashes that were missed when go.mod was bumped. New gateway-check.yml workflow runs make tygo-check (tygo pinned at v0.2.21, same as the Makefile note) and make ui-build on gateway changes, so Go/TS drift fails CI as the phase-8 plan intended. Co-Authored-By: Claude Fable 5 --- .github/workflows/gateway-check.yml | 71 ++ gateway/Makefile | 5 +- gateway/go.sum | 2 + gateway/internal/adminapi/evals.go | 15 +- gateway/internal/adminapi/ui/dist/index.html | 4 +- .../internal/adminapi/ui/src/api/client.ts | 2 +- .../internal/adminapi/ui/src/api/manual.ts | 169 ++++ .../internal/adminapi/ui/src/api/queries.ts | 10 +- gateway/internal/adminapi/ui/src/api/types.ts | 823 ++++++++++-------- .../internal/adminapi/ui/src/api/window.ts | 2 +- .../src/components/controls/WindowPicker.tsx | 2 +- .../adminapi/ui/src/pages/AgentDetail.tsx | 3 +- .../internal/adminapi/ui/src/pages/Agents.tsx | 2 +- .../internal/adminapi/ui/src/pages/Canvas.tsx | 3 +- .../adminapi/ui/src/pages/Dashboard.tsx | 3 +- .../internal/adminapi/ui/src/pages/People.tsx | 3 +- .../adminapi/ui/src/pages/RunDetail.tsx | 5 +- .../adminapi/ui/src/pages/UserDetail.tsx | 3 +- .../internal/adminapi/ui/tsconfig.tsbuildinfo | 2 +- gateway/tygo.yaml | 13 +- 20 files changed, 738 insertions(+), 404 deletions(-) create mode 100644 .github/workflows/gateway-check.yml create mode 100644 gateway/internal/adminapi/ui/src/api/manual.ts diff --git a/.github/workflows/gateway-check.yml b/.github/workflows/gateway-check.yml new file mode 100644 index 000000000..7662b1d1c --- /dev/null +++ b/.github/workflows/gateway-check.yml @@ -0,0 +1,71 @@ +name: Gateway Checks + +# Guards the gateway's Go -> TypeScript codegen contract: +# - tygo-check regenerates ui/src/api/types.ts from the Go structs +# in gateway/internal/adminapi and fails on any diff, so a Go +# struct change can't land without its committed TS counterpart +# (drift would silently break the dashboard's typed fetch layer). +# - ui-build type-checks (tsc -b) and bundles the SPA, catching +# imports that the regenerated types.ts no longer satisfies. + +on: + pull_request: + paths: + - "gateway/**" + - ".github/workflows/gateway-check.yml" + push: + branches: [main] + paths: + - "gateway/**" + - ".github/workflows/gateway-check.yml" + +jobs: + tygo-check: + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: gateway + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: gateway/go.mod + cache-dependency-path: gateway/go.sum + + # Pinned so the generated output is byte-stable — a tygo release + # changing its formatting would otherwise fail every PR. Bump in + # lockstep with the version noted in gateway/Makefile. + - name: Install tygo + run: go install github.com/gzuidhof/tygo@v0.2.21 + + - name: Check generated TS types are up to date + run: make tygo-check + + ui-build: + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: gateway + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + # package-lock.json is git-ignored for this SPA, so key the + # npm cache on package.json instead. + cache-dependency-path: gateway/internal/adminapi/ui/package.json + + - name: Install SPA dependencies + run: make ui-install + + - name: Type-check and build SPA + run: make ui-build diff --git a/gateway/Makefile b/gateway/Makefile index 563923ff5..e8fa0439d 100644 --- a/gateway/Makefile +++ b/gateway/Makefile @@ -90,8 +90,9 @@ ui: ui-install ui-build # ─── tygo (Go -> TS struct codegen) ─────────────────────────────────── # -# Install once per dev machine: -# go install github.com/gzuidhof/tygo@latest +# Install once per dev machine (CI pins the same version in +# .github/workflows/gateway-check.yml — bump both together): +# go install github.com/gzuidhof/tygo@v0.2.21 tygo: tygo generate diff --git a/gateway/go.sum b/gateway/go.sum index 722d97016..0f6e99fce 100644 --- a/gateway/go.sum +++ b/gateway/go.sum @@ -54,6 +54,8 @@ github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw= github.com/maximhq/bifrost/core v1.5.18 h1:f1lX3sPesKTScUJHv+K9tWpd1wLLjd7CAzJY0BpbAaE= github.com/maximhq/bifrost/core v1.5.18/go.mod h1:7vry9xB5kmjT3smAVVQ2+mtfTmmeSN+7N1b8r1buaTI= +github.com/maximhq/bifrost/core v1.6.2 h1:dESW02/iyDznt/VnnjzYnevddUmplXw2YeaK/0dl3HA= +github.com/maximhq/bifrost/core v1.6.2/go.mod h1:GuRwPmx0Kh7lhZ+SnbLjVAclDI360DyKrJtioc3t5jg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/gateway/internal/adminapi/evals.go b/gateway/internal/adminapi/evals.go index cf5df79ab..19f5aef5a 100644 --- a/gateway/internal/adminapi/evals.go +++ b/gateway/internal/adminapi/evals.go @@ -104,8 +104,9 @@ type evalRunRequest struct { Agent string `json:"agent,omitempty"` } -// evalRefResponse is the create/link acknowledgement. -type evalRefResponse struct { +// EvalRefResponse is the create/link acknowledgement. Exported so +// tygo emits the TS binding the SPA's eval mutations decode. +type EvalRefResponse struct { RefID string `json:"ref_id"` Linked bool `json:"linked,omitempty"` } @@ -234,7 +235,7 @@ func (h *evalHandlers) createOrLinkForAgent(w http.ResponseWriter, r *http.Reque writeError(w, http.StatusBadGateway, "catalog_write_failed", "neo4j write failed") return } - writeJSON(w, http.StatusOK, evalRefResponse{RefID: req.SetID, Linked: true}) + writeJSON(w, http.StatusOK, EvalRefResponse{RefID: req.SetID, Linked: true}) return } @@ -243,7 +244,7 @@ func (h *evalHandlers) createOrLinkForAgent(w http.ResponseWriter, r *http.Reque writeError(w, http.StatusBadRequest, "missing_field", "name (or set_id) is required") return } - var created evalRefResponse + var created EvalRefResponse if err := h.hive.call(ctx, http.MethodPost, "/api/gateway/evals", map[string]any{"name": req.Name, "description": req.Description}, &created); err != nil { relayHiveError(w, err) @@ -262,7 +263,7 @@ func (h *evalHandlers) createOrLinkForAgent(w http.ResponseWriter, r *http.Reque "set created but linking to agent failed") return } - writeJSON(w, http.StatusOK, evalRefResponse{RefID: created.RefID}) + writeJSON(w, http.StatusOK, EvalRefResponse{RefID: created.RefID}) } // linkEdge MERGEs HiveAgent-[:HAS_EVAL_SET]->EvalSet and clears any @@ -457,7 +458,7 @@ func (h *evalHandlers) createSet(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, "missing_field", "name is required") return } - var created evalRefResponse + var created EvalRefResponse if err := h.hive.call(r.Context(), http.MethodPost, "/api/gateway/evals", map[string]any{"name": req.Name, "description": req.Description}, &created); err != nil { relayHiveError(w, err) @@ -513,7 +514,7 @@ func (h *evalHandlers) createRequirement(w http.ResponseWriter, r *http.Request, writeError(w, http.StatusBadRequest, "missing_field", "name is required") return } - var created evalRefResponse + var created EvalRefResponse if err := h.hive.call(r.Context(), http.MethodPost, "/api/gateway/evals/"+urlSeg(setID)+"/requirements", req, &created); err != nil { relayHiveError(w, err) diff --git a/gateway/internal/adminapi/ui/dist/index.html b/gateway/internal/adminapi/ui/dist/index.html index c37010c7d..48d0c60e5 100644 --- a/gateway/internal/adminapi/ui/dist/index.html +++ b/gateway/internal/adminapi/ui/dist/index.html @@ -5,8 +5,8 @@ Agent Mothership - - + +
diff --git a/gateway/internal/adminapi/ui/src/api/client.ts b/gateway/internal/adminapi/ui/src/api/client.ts index a2a392b58..93d19a5d4 100644 --- a/gateway/internal/adminapi/ui/src/api/client.ts +++ b/gateway/internal/adminapi/ui/src/api/client.ts @@ -12,7 +12,7 @@ // or auth-refresh logic here lights the trap of having two HTTP // stacks; Tanstack Query gives us all of that one layer up. -import type { ApiError } from "./types"; +import type { ApiError } from "./manual"; const PLUGIN_PREFIX = "/_plugin"; diff --git a/gateway/internal/adminapi/ui/src/api/manual.ts b/gateway/internal/adminapi/ui/src/api/manual.ts new file mode 100644 index 000000000..44949e273 --- /dev/null +++ b/gateway/internal/adminapi/ui/src/api/manual.ts @@ -0,0 +1,169 @@ +// Hand-maintained types that tygo cannot generate. Everything the +// SPA decodes from an exported Go struct in internal/adminapi lives +// in the sibling types.ts, which is generated by `make tygo` and +// checked in CI by `make tygo-check` — never hand-edit that file. +// +// What belongs here instead, and why: +// - String-literal unions (Window / Bucket / Dimension): the Go +// side validates these as plain strings, so there is no struct +// for tygo to translate. +// - ApiError: the phase-7 error envelope is built inline by +// adminapi's writeError helper (a map, not a struct). +// - TrustOrg / TrustStatus: mirror gateway/internal/trust, a +// package outside tygo.yaml's adminapi scope, re-named for the +// dashboard (trust.Org -> TrustOrg, trust.StatusResponse -> +// TrustStatus). +// - Chat / usage shapes (ChatMessage, TokenUsage, CacheDebug, …): +// Bifrost pass-through fields that Go carries as +// json.RawMessage (tygo emits `any`); typed here to the depth +// the RunDetail drawer actually renders. +// +// Keep each block in lockstep with the Go source it names. + +// ─── enums validated Go-side as strings ───────────────────────────── + +// Window options the SPA exposes to the operator. Kept in lockstep +// with the Go-side validation in observability.go > parseWindow. +export type Window = "1h" | "6h" | "24h" | "7d" | "30d"; + +// Bucket options for the histogram endpoints — same source-of-truth +// note as Window. +export type Bucket = "1m" | "5m" | "10m" | "1h" | "6h" | "1d"; + +// Dimension values the histogram endpoint accepts. Phase 11 removed +// `realm-id` — every row in a swarm's logs.db is implicitly for +// that swarm's realm, and the realm is surfaced on the trust-status +// card instead of as a per-row column. +export type Dimension = + | "agent-name" + | "run-id" + | "session-id" + | "user-id"; + +// ─── error envelope ───────────────────────────────────────────────── + +// Phase-7 error envelope (returned on 4xx/5xx). Mirrors the map +// built by internal/adminapi's writeError helper. +export interface ApiError { + error: { + code: string; + message: string; + }; +} + +// ─── trust registry (gateway/internal/trust) ──────────────────────── + +// Trust-registry Org entry — mirrors gateway/internal/trust.Org. +// Surfaced on the dashboard's Provenance card so an operator can +// see which org's signature authorized a run, plus the pubkey / +// issuer URL the plugin would verify against. +export interface TrustOrg { + org_id: string; + pubkey: string; + issuer_url: string; + revocation_poll_seconds: number; + grace_pubkeys?: string[]; + grace_until?: string; +} + +// Trust-registry status — mirrors gateway/internal/trust.StatusResponse. +// The Provenance card on RunDetail uses `realm_id` to show the +// swarm's self-identity ("this run was processed by swarm w1"), +// since phase 11 dropped the per-row realm-id metadata column. +export interface TrustStatus { + claimed: boolean; + org_count: number; + orgs: string[]; + seed_source: "" | "env" | "api"; + last_modified: string; + /** Set on multi-swarm deployments; absent / empty on single-swarm. */ + realm_id?: string; +} + +// ─── Bifrost pass-through shapes (json.RawMessage on the Go side) ─── + +// One message in a chat-style input_history / output_message. +// Mirrors Bifrost's `schemas.ChatMessage` to the depth the drawer +// renders: role, content (string OR an array of content blocks), +// optional tool-call list (assistant) and tool_call_id (tool reply). +// Everything is optional because providers vary in which fields +// they populate, and the drawer falls back to JSON for anything it +// doesn't recognize. +export interface ChatMessage { + role?: string; + name?: string; + /** OpenAI/Anthropic-style: either a plain string or an array of + * typed content blocks. */ + content?: string | ChatContentBlock[] | null; + /** Tool messages: which prior tool_call this is the result for. */ + tool_call_id?: string; + /** Assistant tool calls. */ + tool_calls?: ChatToolCall[]; + /** Anthropic / OpenAI reasoning summaries. */ + reasoning?: string; + refusal?: string; +} + +export interface ChatContentBlock { + type: string; + text?: string; + refusal?: string; + /** Anthropic-style cache marker. When present on a block the + * provider charged this block as a cache write (or read on a + * subsequent call). */ + cache_control?: { type?: string } | null; + cachePoint?: { type?: string } | null; + image_url?: unknown; + input_audio?: unknown; + file?: unknown; +} + +export interface ChatToolCall { + id?: string; + type?: string; + function?: { name?: string; arguments?: string }; +} + +/** Provider-reported usage breakdown. Source: Bifrost + * `schemas.BifrostLLMUsage`. The `prompt_tokens_details` sub- + * object is where the cache split lives — Anthropic populates + * `cached_write_tokens` (prompt cache writes) and + * `cached_read_tokens`; OpenAI uses `cached_read_tokens` only. */ +export interface TokenUsage { + prompt_tokens?: number; + completion_tokens?: number; + total_tokens?: number; + prompt_tokens_details?: { + text_tokens?: number; + audio_tokens?: number; + image_tokens?: number; + cached_read_tokens?: number; + cached_write_tokens?: number; + cached_write_token_details?: { + cached_write_tokens_5m?: number; + cached_write_tokens_1h?: number; + }; + }; + completion_tokens_details?: { + reasoning_tokens?: number; + accepted_prediction_tokens?: number; + }; + cost?: unknown; +} + +/** Semantic cache verdict. Source: `schemas.BifrostCacheDebug`. + * Distinct from prompt-cache token splits (those live in + * TokenUsage above). Present only when a semantic cache plugin + * is configured. */ +export interface CacheDebug { + cache_hit: boolean; + cache_id?: string; + hit_type?: string; + requested_provider?: string; + requested_model?: string; + provider_used?: string; + model_used?: string; + input_tokens?: number; + threshold?: number; + similarity?: number; +} diff --git a/gateway/internal/adminapi/ui/src/api/queries.ts b/gateway/internal/adminapi/ui/src/api/queries.ts index cd87946f0..42e77361f 100644 --- a/gateway/internal/adminapi/ui/src/api/queries.ts +++ b/gateway/internal/adminapi/ui/src/api/queries.ts @@ -25,13 +25,15 @@ import type { SpendByAgentResponse, SpendByAgentUserResponse, SpendByUserResponse, - TrustOrg, - TrustStatus, UserDetailResponse, - Window, +} from "./types"; +import type { Bucket, Dimension, -} from "./types"; + TrustOrg, + TrustStatus, + Window, +} from "./manual"; // ─── /me ───────────────────────────────────────────────────────────── // Fires once at boot, plus on tab refocus (Tanstack default). Cheap diff --git a/gateway/internal/adminapi/ui/src/api/types.ts b/gateway/internal/adminapi/ui/src/api/types.ts index f7bd8e94a..5997805ec 100644 --- a/gateway/internal/adminapi/ui/src/api/types.ts +++ b/gateway/internal/adminapi/ui/src/api/types.ts @@ -1,216 +1,394 @@ // Code generated by tygo. DO NOT EDIT. // -// To regenerate from the Go source of truth: -// make tygo (or: tygo generate from gateway/) -// // Source: github.com/stakwork/stakgraph/gateway/internal/adminapi -// -// Phase 8 hand-maintains this file as a stop-gap until the tygo -// codegen runs in CI. The shapes below MUST stay byte-equivalent to -// the Go structs they mirror (LoginResponse, MeResponse, -// SpendByAgentResponse, SpendByUserResponse, HistogramCostResponse, -// RunDetailResponse and friends). Any drift fails the matching -// adminapi Go tests on next run. - +// Regenerate with `make tygo`; CI enforces via `make tygo-check`. +// Hand-maintained companions (unions, trust mirrors) live in +// manual.ts. /* eslint-disable */ +////////// +// source: budgets.go + +/** + * AgentBudgetResponse is the wire shape for /_plugin/agents/:name/budget. + * Phase-8.5 read-only view — phase 9 grows the matching PUT/DELETE + * mutations on `/_plugin/config/agent_budgets/:name`. + * `CapUSD` and `Window` come from the plugin config (YAML / Redis + * override merged); `SpentUSD` comes from the live phase-6 Redis + * accumulator `bifrost:cost:agent::`. Either source + * being absent is fine — the response surfaces nil so the UI can + * render "no budget" or "no spend yet" rather than misleading zeros. + */ +export interface AgentBudgetResponse { + agent_name: string; + /** + * Cap is the configured maximum spend for the current window. + * Null when the agent has no entry in `agent_budgets`. + */ + cap_usd?: number /* float64 */; + /** + * Window is the Bifrost duration string the cap applies to + * (e.g. "1d", "1h"). Empty when no cap is set. + */ + window: string; + /** + * PeriodStart / PeriodEnd bracket the active bucket. Both are + * RFC3339 strings, UTC. Phase 8 only renders the start; future + * "resets in: 23h" badges can read end. + */ + period_start?: string; + period_end?: string; + /** + * SpentUSD is the running total against the cap for the + * current bucket. 0 (not null) when the bucket key simply + * doesn't exist yet — that's a real-world "fresh window, no + * calls yet" state, not a missing-data state. + */ + spent_usd: number /* float64 */; + /** + * RemainingUSD = max(cap - spent, 0). Null when cap is null. + */ + remaining_usd?: number /* float64 */; + /** + * Ratio = spent / cap, clamped to [0, 1+] (we don't clamp the + * upper bound so a runaway "150%" reads honestly). Null when + * cap is null. + */ + ratio?: number /* float64 */; +} + +////////// +// source: catalog.go + +/** + * AgentCatalogResponse is the merged view across all sources for one + * agent — what the agent is _made of_ (prompts/tools/skills), as + * opposed to the budget view's what it's _allowed to spend_. + */ +export interface AgentCatalogResponse { + name: string; + display_name?: string; + description?: string; + default_model?: string; + sources: string[]; + prompts: CatalogPrompt[]; + tools: CatalogTool[]; + skills: CatalogSkill[]; +} +/** + * CatalogAgentSummary is one row of the catalog list — enough to merge + * the registry into the spend-derived /agents table without pulling + * every agent's full prompt/tool/skill bodies. Counts let the list + * render "📄 2 · 🔧 5 · ✦ 1" badges cheaply. + */ +export interface CatalogAgentSummary { + name: string; + display_name?: string; + description?: string; + default_model?: string; + sources: string[]; + prompts: number /* int */; + tools: number /* int */; + skills: number /* int */; + updated_at: string; +} +/** + * CatalogListResponse is the whole registry — every HiveAgent node, + * traffic or not. The UI unions this with spend-by-agent so seeded + * agents that have never been invoked still appear. + */ +export interface CatalogListResponse { + agents: CatalogAgentSummary[]; +} +/** + * CatalogPrompt is one prompt linked to an agent. name/body come from + * the shared `:Prompt` node the agent links to; source/updated_at come + * from the HAS_PROMPT relationship (which system wired it, and when). + */ +export interface CatalogPrompt { + name: string; + body: string; + /** + * Role is the prompt's slot for this agent — "SYSTEM" or "USER" + * (the main/task prompt). Stored on the HAS_PROMPT relationship; + * empty when the wiring source didn't classify it. + */ + role?: string; + source: string; + updated_at: string; +} +export interface CatalogTool { + name: string; + description: string; + schema?: any /* json.RawMessage */; + source: string; + version?: string; + /** + * Enabled is the per-swarm operator toggle, mirroring skills: + * seeded enabled, flipped in the dashboard, preserved across + * Hive re-seeds. Legacy nodes with no value read back as true. + */ + enabled: boolean; + updated_at: string; +} +export interface CatalogSkill { + name: string; + description: string; + source: string; + version?: string; + /** + * Enabled is the per-swarm operator toggle. Skills seed enabled by + * default; an operator flips this in the dashboard and the gateway + * preserves it across Hive re-seeds (a push only refreshes the + * palette + metadata, never the toggle). Legacy nodes with no + * stored value read back as true (coalesce on the read query). + */ + enabled: boolean; + updated_at: string; +} + +////////// +// source: evals.go + +/** + * EvalSetSummary is one row of an agent's eval-set list. + */ +export interface EvalSetSummary { + ref_id: string; + name?: string; + description?: string; + requirements: number /* int */; +} +/** + * AgentEvalsResponse is the agent-detail Evals tab payload — the sets + * linked to this agent via HAS_EVAL_SET. + */ +export interface AgentEvalsResponse { + agent: string; + sets: EvalSetSummary[]; +} +/** + * EvalTriggerSummary is one captured trigger under a requirement, plus + * the outcome of its most-recent run (nil-ish fields when never run). + */ +export interface EvalTriggerSummary { + ref_id: string; + agent?: string; + source?: string; + environment?: string; + change_type?: string; + last_result?: string; // "pass" / "fail" / "" + last_score?: number /* float64 */; + last_notes?: string; + last_attempt?: number /* int */; +} +/** + * EvalRequirementDetail is a requirement with its triggers. + */ +export interface EvalRequirementDetail { + ref_id: string; + name?: string; + description?: string; + prompt_snippet?: string; + order: number /* int */; + triggers: EvalTriggerSummary[]; +} +/** + * EvalSetDetailResponse is the expanded view of one set. + */ +export interface EvalSetDetailResponse { + ref_id: string; + name?: string; + description?: string; + requirements: EvalRequirementDetail[]; +} +/** + * EvalRefResponse is the create/link acknowledgement. Exported so + * tygo emits the TS binding the SPA's eval mutations decode. + */ +export interface EvalRefResponse { + ref_id: string; + linked?: boolean; +} + +////////// +// source: hivecallback.go + + +////////// +// source: login.go + +/** + * LoginResponse is the JSON body returned by `POST /_plugin/login` + * on success. The SPA stashes the username so the topbar can render + * it without an extra `/me` round-trip. + * Exported because tygo emits TS bindings from this declaration + * (see gateway/tygo.yaml); rename in lockstep with the frontend. + */ export interface LoginResponse { user: string; } - +/** + * MeResponse is `GET /_plugin/me` — the SPA's boot probe to decide + * "am I authenticated?". Unix timestamps (seconds) keep the wire + * format compact and language-agnostic; the frontend converts to + * Date once. + */ export interface MeResponse { user: string; - iat: number; - last_seen: number; + iat: number /* int64 */; + last_seen: number /* int64 */; } +////////// +// source: logstore_client.go + + +////////// +// source: observability.go + +/** + * AgentSpend is one row of /_plugin/spend/by-agent. + */ export interface AgentSpend { agent_name: string; - total_cost: number; - total_tokens: number; - request_count: number; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; + request_count: number /* int64 */; } - +/** + * SpendByAgentResponse is the envelope for /_plugin/spend/by-agent. + */ export interface SpendByAgentResponse { window: string; results: AgentSpend[]; } - +/** + * UserSpend is one row of /_plugin/spend/by-user. + */ export interface UserSpend { user_id: string; user_name: string; - total_cost: number; - total_tokens: number; - request_count: number; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; + request_count: number /* int64 */; } - +/** + * SpendByUserResponse is the envelope for /_plugin/spend/by-user. + */ export interface SpendByUserResponse { window: string; results: UserSpend[]; } - -// Per-provider slice inside an AgentUserSpend row. Lets the canvas -// drive gateway→provider edge widths from real spend; same Bifrost -// call that computes the (agent × user) rollup also fills these, -// so no extra round-trips. +/** + * ProviderSpend is the per-provider slice carried inside an + * AgentUserSpend row. Lets the canvas drive gateway→provider edge + * widths (and the provider drawer) from real spend without a second + * round-trip — the data is already on every Bifrost log row, so + * surfacing it costs only a second bucket in the same pass. + */ export interface ProviderSpend { provider: string; - total_cost: number; - request_count: number; -} - -// One row of /_plugin/spend/by-agent-user — the (agent × user) fan-out -// the Canvas page uses to render one box per pairing without N round -// trips. Rows missing either dim are excluded server-side. -// -// `providers` sums to `total_cost` / `request_count` and is sorted by -// cost desc, with provider name as a stable tiebreaker. + total_cost: number /* float64 */; + request_count: number /* int64 */; +} +/** + * AgentUserSpend is one row of /_plugin/spend/by-agent-user — the + * fan-out crossing of (agent-name × user-id) so the flowchart UI can + * render one box per pairing in a single round-trip. Rows missing + * either dim are excluded (same policy as by-agent / by-user). + * `Providers` is the breakdown across providers for this pairing. + * Sums of `Providers[*].TotalCost` and `Providers[*].RequestCount` + * equal `TotalCost` and `RequestCount` — the breakdown is additive + * to the top-line totals, not a replacement. + */ export interface AgentUserSpend { agent_name: string; user_id: string; user_name: string; - total_cost: number; - total_tokens: number; - request_count: number; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; + request_count: number /* int64 */; providers: ProviderSpend[]; } - +/** + * SpendByAgentUserResponse is the envelope for + * /_plugin/spend/by-agent-user. + */ export interface SpendByAgentUserResponse { window: string; results: AgentUserSpend[]; } - +/** + * HistogramPoint is one (timestamp, cost) datum in a per-dimension + * series. Timestamp is the bucket's start time in RFC3339. + */ export interface HistogramPoint { ts: string; - cost: number; + cost: number /* float64 */; } - +/** + * HistogramSeries is one line on a stacked-area chart. + */ export interface HistogramSeries { dimension_value: string; points: HistogramPoint[]; } - +/** + * HistogramCostResponse is the envelope for + * /_plugin/histogram/cost. + */ export interface HistogramCostResponse { - bucket_size_seconds: number; + bucket_size_seconds: number /* int64 */; dimension: string; series: HistogramSeries[]; } - +/** + * RunLogEntry is one row inside a RunDetailResponse's `logs` field. + * A trimmed view of Bifrost's Log — phase 8 only renders the columns + * the call-log table actually shows. + */ export interface RunLogEntry { id: string; timestamp: string; provider: string; model: string; status: string; - cost: number; - latency: number; - metadata: Record; + cost: number /* float64 */; + latency: number /* float64 */; + metadata: { [key: string]: string}; } - +/** + * RunStats is the aggregate-card summary at the top of /runs/:id. + */ export interface RunStats { - total_requests: number; - total_cost: number; - total_tokens: number; + total_requests: number /* int64 */; + total_cost: number /* float64 */; + total_tokens: number /* int64 */; } - +/** + * RunDetailResponse is the envelope for /_plugin/runs/:run_id. + */ export interface RunDetailResponse { run_id: string; logs: RunLogEntry[]; stats: RunStats; } - -// One message in a chat-style input_history / output_message. -// Mirrors Bifrost's `schemas.ChatMessage` to the depth the drawer -// renders: role, content (string OR an array of content blocks), -// optional tool-call list (assistant) and tool_call_id (tool reply). -// Everything is optional because providers vary in which fields -// they populate, and the drawer falls back to JSON for anything it -// doesn't recognize. -export interface ChatMessage { - role?: string; - name?: string; - /** OpenAI/Anthropic-style: either a plain string or an array of - * typed content blocks. */ - content?: string | ChatContentBlock[] | null; - /** Tool messages: which prior tool_call this is the result for. */ - tool_call_id?: string; - /** Assistant tool calls. */ - tool_calls?: ChatToolCall[]; - /** Anthropic / OpenAI reasoning summaries. */ - reasoning?: string; - refusal?: string; -} - -export interface ChatContentBlock { - type: string; - text?: string; - refusal?: string; - /** Anthropic-style cache marker. When present on a block the - * provider charged this block as a cache write (or read on a - * subsequent call). */ - cache_control?: { type?: string } | null; - cachePoint?: { type?: string } | null; - image_url?: unknown; - input_audio?: unknown; - file?: unknown; -} - -export interface ChatToolCall { - id?: string; - type?: string; - function?: { name?: string; arguments?: string }; -} - -/** Provider-reported usage breakdown. Source: Bifrost - * `schemas.BifrostLLMUsage`. The `prompt_tokens_details` sub- - * object is where the cache split lives — Anthropic populates - * `cached_write_tokens` (prompt cache writes) and - * `cached_read_tokens`; OpenAI uses `cached_read_tokens` only. */ -export interface TokenUsage { - prompt_tokens?: number; - completion_tokens?: number; - total_tokens?: number; - prompt_tokens_details?: { - text_tokens?: number; - audio_tokens?: number; - image_tokens?: number; - cached_read_tokens?: number; - cached_write_tokens?: number; - cached_write_token_details?: { - cached_write_tokens_5m?: number; - cached_write_tokens_1h?: number; - }; - }; - completion_tokens_details?: { - reasoning_tokens?: number; - accepted_prediction_tokens?: number; - }; - cost?: unknown; -} - -/** Semantic cache verdict. Source: `schemas.BifrostCacheDebug`. - * Distinct from prompt-cache token splits (those live in - * TokenUsage above). Present only when a semantic cache plugin - * is configured. */ -export interface CacheDebug { - cache_hit: boolean; - cache_id?: string; - hit_type?: string; - requested_provider?: string; - requested_model?: string; - provider_used?: string; - model_used?: string; - input_tokens?: number; - threshold?: number; - similarity?: number; -} - -// CallDetailResponse — /_plugin/runs/:run_id/calls/:call_id. -// -// The full body of one LLM call: same fields as a list row, plus -// the heavy JSON columns Bifrost strips from /api/logs. Chat-shaped -// fields (input_history / output_message) are typed so the drawer -// can render bubble UIs; everything else stays opaque. +/** + * CallDetailResponse is the envelope for + * /_plugin/runs/:run_id/calls/:call_id — the full request/response + * content for a single LLM call, fetched on-demand when the + * operator clicks a row in the RunDetail call log. + * Run-scoping rationale: we verify that the fetched log's + * metadata.run-id matches the URL's run_id before returning, so a + * caller can't enumerate other workspaces' logs by guessing IDs. + * Bifrost's /api/logs/{id} doesn't do this check itself (it just + * looks up by primary key), so the plugin enforces it. + * Body fields are pass-through json.RawMessage from Bifrost — the + * SPA pretty-prints them; the plugin doesn't introspect them. This + * keeps the schema coupling minimal: new fields upstream surface in + * the UI without code changes here. + */ export interface CallDetailResponse { id: string; run_id: string; @@ -218,251 +396,154 @@ export interface CallDetailResponse { provider: string; model: string; status: string; - cost: number; - latency: number; + cost: number /* float64 */; + latency: number /* float64 */; customer_id: string; - metadata: Record; - + metadata: { [key: string]: string}; + /** + * Per-request descriptors stamped by Bifrost. `stop_reason` + * tells the operator why the model stopped (stop, length, + * content_filter, tool_calls, refusal). `stream` flags + * streaming responses (which lack a single output_message and + * surface their content via Bifrost's stream chunk replay). + * Retries / fallback_index are zero on the happy path; non-zero + * means Bifrost had to retry the call or fall back to a + * different provider, which is useful provenance. + */ stop_reason?: string; stream: boolean; - number_of_retries: number; - fallback_index: number; - - token_usage?: TokenUsage; - cache_debug?: CacheDebug; - - input_history?: ChatMessage[]; - output_message?: ChatMessage; - params?: unknown; - tools?: unknown; - error_details?: unknown; + number_of_retries: number /* int */; + fallback_index: number /* int */; + /** + * TokenUsage is the provider-reported usage breakdown + * (BifrostLLMUsage). Includes prompt/completion/total totals + * plus cached read/write splits (Anthropic prompt-cache, + * OpenAI cached_tokens), audio/image token counts, and a + * per-call cost record. Pass-through JSON — the SPA introspects + * it. Bifrost's row-level prompt_tokens / completion_tokens / + * total_tokens columns are denormalized helpers tagged + * `json:"-"`, so this is the only place token data is on the + * wire. + */ + token_usage?: any /* json.RawMessage */; + /** + * CacheDebug carries Bifrost's *semantic* cache verdict for + * this call (hit/miss + similarity score). Distinct from + * prompt-cache tokens, which live in TokenUsage above. Absent + * when no semantic cache is configured for this swarm. + */ + cache_debug?: any /* json.RawMessage */; + /** + * All optional; missing on failures, realtime turns, or rows + * recorded before a given column existed. + */ + input_history?: any /* json.RawMessage */; + output_message?: any /* json.RawMessage */; + params?: any /* json.RawMessage */; + tools?: any /* json.RawMessage */; + error_details?: any /* json.RawMessage */; raw_request?: string; raw_response?: string; content_summary?: string; } -// One row of UserDetailResponse.AgentsUsed — which agents the user -// invoked in the window, and how much each cost. -export interface UserAgentUsage { - agent_name: string; - total_cost: number; - request_count: number; - last_seen?: string; -} +////////// +// source: ratelimit.go -// One row of UserDetailResponse.RecentRuns — links into RunDetail. -export interface UserRunSummary { - run_id: string; - agent_name: string; - total_cost: number; - request_count: number; - first_seen?: string; - last_seen?: string; -} -// /_plugin/users/:id response. -export interface UserDetailResponse { - user_id: string; - window: string; - total_cost: number; - request_count: number; - agents_used: UserAgentUsage[]; - recent_runs: UserRunSummary[]; - first_seen?: string; - last_seen?: string; -} +////////// +// source: server.go +/* +Package adminapi is the gateway plugin's in-process HTTP server, +hosting the `/_plugin/*` route namespace. -// Trust-registry Org entry — mirrors gateway/internal/trust.Org. -// Surfaced on the dashboard's Provenance card so an operator can -// see which org's signature authorized a run, plus the pubkey / -// issuer URL the plugin would verify against. -export interface TrustOrg { - org_id: string; - pubkey: string; - issuer_url: string; - revocation_poll_seconds: number; - grace_pubkeys?: string[]; - grace_until?: string; -} +The wrapper binary (gateway/wrapper) reverse-proxies traffic on +`/_plugin/*` to this server (loopback only). Why a separate server +instead of routes registered through Bifrost's router: Bifrost +plugins (.so) cannot register arbitrary HTTP routes through +Bifrost's own router, and we also want routes that aren't behind +Bifrost's AuthMiddleware so Hive can bootstrap on a fresh swarm. -// Trust-registry status — mirrors gateway/internal/trust.StatusResponse. -// The Provenance card on RunDetail uses `realm_id` to show the -// swarm's self-identity ("this run was processed by swarm w1"), -// since phase 11 dropped the per-row realm-id metadata column. -export interface TrustStatus { - claimed: boolean; - org_count: number; - orgs: string[]; - seed_source: "" | "env" | "api"; - last_modified: string; - /** Set on multi-swarm deployments; absent / empty on single-swarm. */ - realm_id?: string; -} +Lifecycle +--------- + - Start() is called from the plugin's Init(). + - Stop() is called from the plugin's Cleanup() with a small grace + period. -// Per-agent budget (phase-8.5). Cap and the derived fields can be -// null when no budget is configured for the agent — the UI renders -// "no budget" rather than "$0". -export interface AgentBudgetResponse { - agent_name: string; - cap_usd: number | null; - window: string; - period_start?: string; - period_end?: string; - spent_usd: number; - remaining_usd: number | null; - ratio: number | null; -} +One server per process. Re-calling Start() is a no-op. +*/ -// ─── agent catalog ────────────────────────────────────────────────── -// Mirrors gateway/internal/adminapi/catalog.go. The catalog answers -// "what is this agent _made of_?" (prompts/tools/skills), as opposed to -// the budget view's "what is it _allowed to spend_?". Sourced from the -// neo4j Hive* catalog subgraph via GET /_plugin/agents/:name/catalog. -// A prompt linked to an agent. name/body come from the shared `:Prompt` -// node the agent links to (authored by the Stakwork prompt workflow); -// source/updated_at come from the HAS_PROMPT relationship. -export interface CatalogPrompt { - name: string; - body: string; - /** Prompt slot for this agent: "SYSTEM" or "USER" (the main/task - * prompt). Absent when the wiring source didn't classify it. */ - role?: string; - source: string; - updated_at: string; -} - -export interface CatalogTool { - name: string; - description: string; - /** JSON parameter schema, passed through opaque — the UI renders it - * as pretty-printed JSON. Absent when the source didn't supply one. */ - schema?: unknown; - source: string; - version?: string; - /** Per-swarm operator toggle. Seeded enabled; preserved across - * Hive re-seeds. Flip via PATCH /_plugin/agents/:name/tools. */ - enabled: boolean; - updated_at: string; -} - -export interface CatalogSkill { - name: string; - description: string; - source: string; - version?: string; - /** Per-swarm operator toggle. Seeded enabled; preserved across - * Hive re-seeds. Flip via PATCH /_plugin/agents/:name/skills. */ - enabled: boolean; - updated_at: string; -} - -// One row of the catalog list (GET /_plugin/agents/catalog) — identity -// + child counts, enough to merge the registry into the spend-derived -// /agents table without pulling every prompt/tool/skill body. -export interface CatalogAgentSummary { - name: string; - display_name?: string; - description?: string; - default_model?: string; - sources: string[]; - prompts: number; - tools: number; - skills: number; - updated_at: string; -} - -// The whole registry — every catalog agent, traffic or not. -export interface CatalogListResponse { - agents: CatalogAgentSummary[]; -} +////////// +// source: session.go -// Merged catalog view across all contributing sources for one agent. -export interface AgentCatalogResponse { - name: string; - display_name?: string; - description?: string; - /** Default LLM used for this agent (model shortcut or full id). */ - default_model?: string; - sources: string[]; - prompts: CatalogPrompt[]; - tools: CatalogTool[]; - skills: CatalogSkill[]; -} -// ─── Evals (agent-detail tab) ─────────────────────────────────────── -// Mirror of the Go structs in internal/adminapi/evals.go. Eval sets are -// Jarvis-authored nodes surfaced under an agent via HAS_EVAL_SET; the -// gateway reads them from neo4j and delegates writes/runs to Hive. +////////// +// source: tickets.go -export interface EvalSetSummary { - ref_id: string; - name?: string; - description?: string; - requirements: number; +/** + * TicketResponse is the JSON body returned by POST /_plugin/auth/ticket. + * Hive embeds the ticket in the iframe src as `?ticket=`. + */ +export interface TicketResponse { + ticket: string; + expires_in: number /* int */; // seconds; mirrors ticketTTL } -export interface AgentEvalsResponse { - agent: string; - sets: EvalSetSummary[]; -} +////////// +// source: trust.go -export interface EvalTriggerSummary { - ref_id: string; - agent?: string; - source?: string; - environment?: string; - change_type?: string; - last_result?: string; // "pass" | "fail" | "" - last_score?: number; - last_notes?: string; - last_attempt?: number; -} -export interface EvalRequirementDetail { - ref_id: string; - name?: string; - description?: string; - prompt_snippet?: string; - order: number; - triggers: EvalTriggerSummary[]; -} +////////// +// source: users.go -export interface EvalSetDetailResponse { - ref_id: string; - name?: string; - description?: string; - requirements: EvalRequirementDetail[]; +/** + * UserAgentUsage is one row in `UserDetailResponse.AgentsUsed` — + * a per-agent rollup scoped to this user's traffic in the window. + */ +export interface UserAgentUsage { + agent_name: string; + total_cost: number /* float64 */; + request_count: number /* int64 */; + last_seen?: string; } - -// Acknowledgement for create/link (ref_id of the set/requirement). -export interface EvalRefResponse { - ref_id: string; - linked?: boolean; +/** + * UserRunSummary is one row in `UserDetailResponse.RecentRuns`. + * Phase 8 keeps this lightweight — the dashboard renders cost + + * agent + first/last-seen and links into RunDetail for the full + * call log. + */ +export interface UserRunSummary { + run_id: string; + agent_name: string; + total_cost: number /* float64 */; + request_count: number /* int64 */; + first_seen?: string; + last_seen?: string; } - -// Phase-7 error envelope (returned on 4xx/5xx). -export interface ApiError { - error: { - code: string; - message: string; - }; +/** + * UserDetailResponse is the wire shape for /_plugin/users/:id. + * Composition + * ----------- + * Everything is derived from a single paged scan of `logs.db` + * filtered by `metadata.user-id = ` (with a fallback to the + * indexed `customer_id` column on Bifrost's logs table, which + * equals the user-id per the v2 invariant). One round-trip to + * Bifrost, one in-memory aggregation pass; the dashboard renders + * the result without further fan-out. + * Once phase 6's PostLLMHook fills Redis cost accumulators, the + * `total_cost` field could be sourced from the Redis hash instead + * of summing logs — same number, less work. Phase 8 doesn't take + * that shortcut yet because the Redis bucket is per-(agent, day) + * not per-user. + */ +export interface UserDetailResponse { + user_id: string; + window: string; + total_cost: number /* float64 */; + request_count: number /* int64 */; + agents_used: UserAgentUsage[]; + recent_runs: UserRunSummary[]; + first_seen?: string; + last_seen?: string; } - -// Window options the SPA exposes to the operator. Kept in lockstep -// with the Go-side validation in observability.go > parseWindow. -export type Window = "1h" | "6h" | "24h" | "7d" | "30d"; - -// Bucket options for the histogram endpoints — same source-of-truth -// note as Window. -export type Bucket = "1m" | "5m" | "10m" | "1h" | "6h" | "1d"; - -// Dimension values the histogram endpoint accepts. Phase 11 removed -// `realm-id` — every row in a swarm's logs.db is implicitly for -// that swarm's realm, and the realm is surfaced on the trust-status -// card instead of as a per-row column. -export type Dimension = - | "agent-name" - | "run-id" - | "session-id" - | "user-id"; diff --git a/gateway/internal/adminapi/ui/src/api/window.ts b/gateway/internal/adminapi/ui/src/api/window.ts index 57e45d414..cff5b3979 100644 --- a/gateway/internal/adminapi/ui/src/api/window.ts +++ b/gateway/internal/adminapi/ui/src/api/window.ts @@ -2,7 +2,7 @@ // because the backend's parseWindow is the authority on this set; if // it grows a new option, this mapping needs to grow with it. -import type { Window } from "./types"; +import type { Window } from "./manual"; export function windowToSeconds(w: Window): number { switch (w) { diff --git a/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx b/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx index 7f0d26435..1d5aca09f 100644 --- a/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx +++ b/gateway/internal/adminapi/ui/src/components/controls/WindowPicker.tsx @@ -3,7 +3,7 @@ // set stays consistent (and stays in lockstep with the Go-side // parseWindow whitelist). -import type { Window } from "../../api/types"; +import type { Window } from "../../api/manual"; const OPTIONS: Window[] = ["1h", "24h", "7d", "30d"]; diff --git a/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx b/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx index 20120ed98..1d6817927 100644 --- a/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx +++ b/gateway/internal/adminapi/ui/src/pages/AgentDetail.tsx @@ -31,7 +31,8 @@ import { useToggleSkill, useToggleTool, } from "../api/queries"; -import type { HistogramCostResponse, Window } from "../api/types"; +import type { HistogramCostResponse } from "../api/types"; +import type { Window } from "../api/manual"; import { windowToSeconds } from "../api/window"; import { EvalsView } from "./EvalsView"; diff --git a/gateway/internal/adminapi/ui/src/pages/Agents.tsx b/gateway/internal/adminapi/ui/src/pages/Agents.tsx index cb0c4e044..834365041 100644 --- a/gateway/internal/adminapi/ui/src/pages/Agents.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Agents.tsx @@ -15,7 +15,7 @@ import { useAgentCatalogList, useSpendByAgent, } from "../api/queries"; -import type { Window } from "../api/types"; +import type { Window } from "../api/manual"; // A merged agent row: spend metrics (zeroed when registry-only) plus // catalog identity/counts (absent when traffic-only). diff --git a/gateway/internal/adminapi/ui/src/pages/Canvas.tsx b/gateway/internal/adminapi/ui/src/pages/Canvas.tsx index e40b4d839..99adb7c48 100644 --- a/gateway/internal/adminapi/ui/src/pages/Canvas.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Canvas.tsx @@ -35,7 +35,8 @@ import { useTrustOrg, useTrustStatus, } from "../api/queries"; -import type { AgentUserSpend, TrustOrg, Window } from "../api/types"; +import type { AgentUserSpend } from "../api/types"; +import type { TrustOrg, Window } from "../api/manual"; import { WindowPicker } from "../components/controls/WindowPicker"; import { canvasTheme, PROVIDER_DISPLAY, providerIcon } from "./canvasTheme"; diff --git a/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx b/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx index 94be8c10c..6e970a5bb 100644 --- a/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Dashboard.tsx @@ -17,7 +17,8 @@ import { useSpendByAgent, useSpendByUser, } from "../api/queries"; -import type { AgentSpend, UserSpend, Window } from "../api/types"; +import type { AgentSpend, UserSpend } from "../api/types"; +import type { Window } from "../api/manual"; import { windowToSeconds } from "../api/window"; // LLM call costs can be fractions of a cent; clamping to 2 decimals diff --git a/gateway/internal/adminapi/ui/src/pages/People.tsx b/gateway/internal/adminapi/ui/src/pages/People.tsx index 091cdb976..7173254c0 100644 --- a/gateway/internal/adminapi/ui/src/pages/People.tsx +++ b/gateway/internal/adminapi/ui/src/pages/People.tsx @@ -14,7 +14,8 @@ import { WindowPicker } from "../components/controls/WindowPicker"; import { UserIcon } from "../components/icons"; import { getErrorMessage } from "../api/client"; import { useSpendByUser } from "../api/queries"; -import type { UserSpend, Window } from "../api/types"; +import type { UserSpend } from "../api/types"; +import type { Window } from "../api/manual"; const fmtUSD = (v: number) => { if (v === 0) return "$0.00"; diff --git a/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx b/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx index 4a4a92853..4f9e7820a 100644 --- a/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx +++ b/gateway/internal/adminapi/ui/src/pages/RunDetail.tsx @@ -12,16 +12,15 @@ import { DataTable } from "../components/tables/DataTable"; import { BotIcon, UserIcon } from "../components/icons"; import { getErrorMessage } from "../api/client"; import { useRunCall, useRunDetail, useTrustOrg, useTrustStatus } from "../api/queries"; +import type { CallDetailResponse, RunLogEntry } from "../api/types"; import type { CacheDebug, - CallDetailResponse, ChatContentBlock, ChatMessage, ChatToolCall, - RunLogEntry, TokenUsage, TrustOrg, -} from "../api/types"; +} from "../api/manual"; interface Props { runID: string; diff --git a/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx b/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx index d271ebf68..f026177a8 100644 --- a/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx +++ b/gateway/internal/adminapi/ui/src/pages/UserDetail.tsx @@ -22,7 +22,8 @@ import { WindowPicker } from "../components/controls/WindowPicker"; import { BotIcon, UserIcon } from "../components/icons"; import { getErrorMessage } from "../api/client"; import { useHistogramCost, useUserDetail } from "../api/queries"; -import type { UserAgentUsage, UserRunSummary, Window } from "../api/types"; +import type { UserAgentUsage, UserRunSummary } from "../api/types"; +import type { Window } from "../api/manual"; import { windowToSeconds } from "../api/window"; interface Props { diff --git a/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo b/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo index be7c070c3..d42130d52 100644 --- a/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo +++ b/gateway/internal/adminapi/ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/api/queries.ts","./src/api/types.ts","./src/api/window.ts","./src/components/emptystate.tsx","./src/components/errorboundary.tsx","./src/components/icons.tsx","./src/components/charts/costhistogram.tsx","./src/components/charts/uplotchart.tsx","./src/components/controls/windowpicker.tsx","./src/components/layout/shell.tsx","./src/components/layout/sidebar.tsx","./src/components/layout/topbar.tsx","./src/components/tables/datatable.tsx","./src/pages/agentdetail.tsx","./src/pages/agents.tsx","./src/pages/canvas.tsx","./src/pages/dashboard.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/people.tsx","./src/pages/rundetail.tsx","./src/pages/userdetail.tsx","./src/pages/canvastheme.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/api/manual.ts","./src/api/queries.ts","./src/api/types.ts","./src/api/window.ts","./src/components/emptystate.tsx","./src/components/errorboundary.tsx","./src/components/icons.tsx","./src/components/charts/costhistogram.tsx","./src/components/charts/uplotchart.tsx","./src/components/controls/windowpicker.tsx","./src/components/layout/shell.tsx","./src/components/layout/sidebar.tsx","./src/components/layout/topbar.tsx","./src/components/tables/datatable.tsx","./src/pages/agentdetail.tsx","./src/pages/agents.tsx","./src/pages/canvas.tsx","./src/pages/dashboard.tsx","./src/pages/evalsview.tsx","./src/pages/login.tsx","./src/pages/notfound.tsx","./src/pages/people.tsx","./src/pages/rundetail.tsx","./src/pages/userdetail.tsx","./src/pages/canvastheme.ts"],"version":"5.6.3"} \ No newline at end of file diff --git a/gateway/tygo.yaml b/gateway/tygo.yaml index 6e104565c..7ac359e51 100644 --- a/gateway/tygo.yaml +++ b/gateway/tygo.yaml @@ -11,7 +11,9 @@ # Source-of-truth: every type the SPA decodes is declared in # gateway/internal/adminapi/ as an exported Go struct with `json:` # tags. tygo translates those into TS interfaces; no hand-editing -# of types.ts. +# of types.ts. Types with no adminapi Go struct (string-literal +# unions, the trust-package mirrors, Bifrost pass-through shapes) +# are hand-maintained in ui/src/api/manual.ts instead. # # Install tygo (once, per dev machine): # go install github.com/gzuidhof/tygo@latest @@ -19,13 +21,14 @@ packages: - path: "github.com/stakwork/stakgraph/gateway/internal/adminapi" output_path: "internal/adminapi/ui/src/api/types.ts" - # frontmatter is prepended to the generated file. We mark it - # generated so editors / reviewers know not to hand-edit. + # frontmatter is prepended after tygo's own "Code generated" + # banner, so editors / reviewers know not to hand-edit. frontmatter: | - // Code generated by tygo. DO NOT EDIT. // // Source: github.com/stakwork/stakgraph/gateway/internal/adminapi - // Regenerate with `make tygo`. + // Regenerate with `make tygo`; CI enforces via `make tygo-check`. + // Hand-maintained companions (unions, trust mirrors) live in + // manual.ts. /* eslint-disable */ type_mappings: "time.Time": "string"