package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" ) // Client wraps HTTP calls to the Control Plane API type Client struct { baseURL string adminToken string dryRun bool verbose bool httpClient *http.Client } // NewClient creates a new API client func NewClient(baseURL, adminToken string, dryRun, verbose bool) *Client { return &Client{ baseURL: baseURL, adminToken: adminToken, dryRun: dryRun, verbose: verbose, httpClient: &http.Client{Timeout: 30 * time.Second}, } } // APIError represents an error response from the API type APIError struct { StatusCode int Body string } func (e *APIError) Error() string { return fmt.Sprintf("API error %d: %s", e.StatusCode, e.Body) } // IsNotFound returns true if the error is a 404 func (e *APIError) IsNotFound() bool { return e.StatusCode == http.StatusNotFound } // IsConflict returns true if the error is a 409 func (e *APIError) IsConflict() bool { return e.StatusCode == http.StatusConflict } // doRequest executes an HTTP request func (c *Client) doRequest(method, path string, body any) ([]byte, error) { var reqBody io.Reader if body != nil { data, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("marshal request: %w", err) } reqBody = bytes.NewReader(data) if c.verbose { fmt.Printf(" Request: %s %s\n", method, path) fmt.Printf(" Body: %s\n", string(data)) } } req, err := http.NewRequest(method, c.baseURL+path, reqBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+c.adminToken) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("execute request: %w", err) } defer resp.Body.Close() respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } if c.verbose { fmt.Printf(" Response: %d %s\n", resp.StatusCode, string(respBody)) } if resp.StatusCode >= 400 { return nil, &APIError{StatusCode: resp.StatusCode, Body: string(respBody)} } return respBody, nil } // get performs a GET request func (c *Client) get(path string) ([]byte, error) { return c.doRequest(http.MethodGet, path, nil) } // post performs a POST request (respects dry-run) func (c *Client) post(path string, body any) ([]byte, error) { if c.dryRun { data, _ := json.Marshal(body) fmt.Printf(" [DRY-RUN] POST %s: %s\n", path, string(data)) return []byte("{}"), nil } return c.doRequest(http.MethodPost, path, body) } // put performs a PUT request (respects dry-run) func (c *Client) put(path string, body any) ([]byte, error) { if c.dryRun { data, _ := json.Marshal(body) fmt.Printf(" [DRY-RUN] PUT %s: %s\n", path, string(data)) return []byte("{}"), nil } return c.doRequest(http.MethodPut, path, body) } // delete performs a DELETE request (respects dry-run) func (c *Client) delete(path string) error { if c.dryRun { fmt.Printf(" [DRY-RUN] DELETE %s\n", path) return nil } _, err := c.doRequest(http.MethodDelete, path, nil) return err } // --- Namespace API --- type NamespaceRequest struct { Name string `json:"name"` Status string `json:"status,omitempty"` Description string `json:"description,omitempty"` } type NamespaceResponse struct { ID uint `json:"id"` Name string `json:"name"` Status string `json:"status"` Description string `json:"description"` } func (c *Client) ListNamespaces() ([]NamespaceResponse, error) { data, err := c.get("/admin/namespaces") if err != nil { return nil, err } var result []NamespaceResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal namespaces: %w", err) } return result, nil } func (c *Client) CreateNamespace(req NamespaceRequest) (*NamespaceResponse, error) { data, err := c.post("/admin/namespaces", req) if err != nil { return nil, err } var result NamespaceResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal namespace: %w", err) } return &result, nil } func (c *Client) DeleteNamespace(id uint) error { return c.delete(fmt.Sprintf("/admin/namespaces/%d", id)) } // --- ProviderGroup API --- type ProviderGroupRequest struct { Name string `json:"name"` Type string `json:"type"` BaseURL string `json:"base_url,omitempty"` GoogleProject string `json:"google_project,omitempty"` GoogleLocation string `json:"google_location,omitempty"` StaticHeaders string `json:"static_headers,omitempty"` HeadersProfile string `json:"headers_profile,omitempty"` Models []string `json:"models,omitempty"` Status string `json:"status,omitempty"` } type ProviderGroupResponse struct { ID uint `json:"id"` Name string `json:"name"` Type string `json:"type"` BaseURL string `json:"base_url"` GoogleProject string `json:"google_project"` GoogleLocation string `json:"google_location"` Models []string `json:"models"` Status string `json:"status"` } func (c *Client) ListProviderGroups() ([]ProviderGroupResponse, error) { data, err := c.get("/admin/provider-groups") if err != nil { return nil, err } var result []ProviderGroupResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal provider groups: %w", err) } return result, nil } func (c *Client) CreateProviderGroup(req ProviderGroupRequest) (*ProviderGroupResponse, error) { data, err := c.post("/admin/provider-groups", req) if err != nil { return nil, err } var result ProviderGroupResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal provider group: %w", err) } return &result, nil } func (c *Client) DeleteProviderGroup(id uint) error { return c.delete(fmt.Sprintf("/admin/provider-groups/%d", id)) } // --- APIKey (Provider) API --- type APIKeyRequest struct { GroupID uint `json:"group_id"` APIKey string `json:"api_key"` AccessToken string `json:"access_token,omitempty"` RefreshToken string `json:"refresh_token,omitempty"` ExpiresAt *string `json:"expires_at,omitempty"` AccountID string `json:"account_id,omitempty"` ProjectID string `json:"project_id,omitempty"` Weight int `json:"weight,omitempty"` Status string `json:"status,omitempty"` AutoBan *bool `json:"auto_ban,omitempty"` } type APIKeyResponse struct { ID uint `json:"id"` GroupID uint `json:"group_id"` APIKey string `json:"api_key"` Weight int `json:"weight"` Status string `json:"status"` AutoBan bool `json:"auto_ban"` } func (c *Client) ListAPIKeys() ([]APIKeyResponse, error) { data, err := c.get("/admin/api-keys") if err != nil { return nil, err } var result []APIKeyResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal api keys: %w", err) } return result, nil } func (c *Client) CreateAPIKey(req APIKeyRequest) (*APIKeyResponse, error) { data, err := c.post("/admin/api-keys", req) if err != nil { return nil, err } var result APIKeyResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal api key: %w", err) } return &result, nil } func (c *Client) DeleteAPIKey(id uint) error { return c.delete(fmt.Sprintf("/admin/api-keys/%d", id)) } // --- Model API --- type ModelRequest struct { Name string `json:"name"` Kind string `json:"kind,omitempty"` ContextWindow int `json:"context_window,omitempty"` CostPerToken float64 `json:"cost_per_token,omitempty"` SupportsVision bool `json:"supports_vision,omitempty"` SupportsFunctions bool `json:"supports_functions,omitempty"` SupportsToolChoice bool `json:"supports_tool_choice,omitempty"` SupportsFIM bool `json:"supports_fim,omitempty"` MaxOutputTokens int `json:"max_output_tokens,omitempty"` } type ModelResponse struct { ID uint `json:"id"` Name string `json:"name"` Kind string `json:"kind"` ContextWindow int `json:"context_window"` CostPerToken float64 `json:"cost_per_token"` SupportsVision bool `json:"supports_vision"` SupportsFunctions bool `json:"supports_functions"` SupportsToolChoice bool `json:"supports_tool_choice"` SupportsFIM bool `json:"supports_fim"` MaxOutputTokens int `json:"max_output_tokens"` } func (c *Client) ListModels() ([]ModelResponse, error) { data, err := c.get("/admin/models") if err != nil { return nil, err } var result []ModelResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal models: %w", err) } return result, nil } func (c *Client) CreateModel(req ModelRequest) (*ModelResponse, error) { data, err := c.post("/admin/models", req) if err != nil { return nil, err } var result ModelResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal model: %w", err) } return &result, nil } func (c *Client) DeleteModel(id uint) error { return c.delete(fmt.Sprintf("/admin/models/%d", id)) } // --- Binding API --- type BindingRequest struct { Namespace string `json:"namespace"` PublicModel string `json:"public_model"` GroupID uint `json:"group_id"` Weight int `json:"weight,omitempty"` SelectorType string `json:"selector_type,omitempty"` SelectorValue string `json:"selector_value,omitempty"` Status string `json:"status,omitempty"` } type BindingResponse struct { ID uint `json:"id"` Namespace string `json:"namespace"` PublicModel string `json:"public_model"` GroupID uint `json:"group_id"` Weight int `json:"weight"` SelectorType string `json:"selector_type"` SelectorValue string `json:"selector_value"` Status string `json:"status"` } func (c *Client) ListBindings() ([]BindingResponse, error) { data, err := c.get("/admin/bindings") if err != nil { return nil, err } var result []BindingResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal bindings: %w", err) } return result, nil } func (c *Client) CreateBinding(req BindingRequest) (*BindingResponse, error) { data, err := c.post("/admin/bindings", req) if err != nil { return nil, err } var result BindingResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal binding: %w", err) } return &result, nil } func (c *Client) DeleteBinding(id uint) error { return c.delete(fmt.Sprintf("/admin/bindings/%d", id)) } // --- Master API --- type MasterRequest struct { Name string `json:"name"` Group string `json:"group"` MaxChildKeys int `json:"max_child_keys,omitempty"` GlobalQPS int `json:"global_qps,omitempty"` } type MasterResponse struct { ID uint `json:"id"` Name string `json:"name"` Group string `json:"group"` MasterKey string `json:"master_key,omitempty"` // Only returned on create MaxChildKeys int `json:"max_child_keys"` GlobalQPS int `json:"global_qps"` Status string `json:"status"` } func (c *Client) ListMasters() ([]MasterResponse, error) { data, err := c.get("/admin/masters") if err != nil { return nil, err } var result []MasterResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal masters: %w", err) } return result, nil } func (c *Client) CreateMaster(req MasterRequest) (*MasterResponse, error) { data, err := c.post("/admin/masters", req) if err != nil { return nil, err } var result MasterResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal master: %w", err) } return &result, nil } func (c *Client) DeleteMaster(id uint) error { return c.delete(fmt.Sprintf("/admin/masters/%d", id)) } // --- Key API --- type KeyRequest struct { Group string `json:"group,omitempty"` Scopes string `json:"scopes,omitempty"` ModelLimits string `json:"model_limits,omitempty"` ModelLimitsEnabled *bool `json:"model_limits_enabled,omitempty"` ExpiresAt *string `json:"expires_at,omitempty"` AllowIPs string `json:"allow_ips,omitempty"` DenyIPs string `json:"deny_ips,omitempty"` } type KeyResponse struct { ID uint `json:"id"` KeySecret string `json:"key_secret,omitempty"` // Only returned on create Group string `json:"group"` Scopes string `json:"scopes"` IssuedBy string `json:"issued_by"` Status string `json:"status"` } func (c *Client) CreateKey(masterID uint, req KeyRequest) (*KeyResponse, error) { data, err := c.post(fmt.Sprintf("/admin/masters/%d/keys", masterID), req) if err != nil { return nil, err } var result KeyResponse if err := json.Unmarshal(data, &result); err != nil { return nil, fmt.Errorf("unmarshal key: %w", err) } return &result, nil } // --- Log API --- type LogRequest struct { Group string `json:"group,omitempty"` MasterID uint `json:"master_id,omitempty"` KeyID uint `json:"key_id,omitempty"` ModelName string `json:"model_name,omitempty"` ProviderID uint `json:"provider_id,omitempty"` ProviderType string `json:"provider_type,omitempty"` ProviderName string `json:"provider_name,omitempty"` StatusCode int `json:"status_code,omitempty"` LatencyMs int64 `json:"latency_ms,omitempty"` TokensIn int64 `json:"tokens_in,omitempty"` TokensOut int64 `json:"tokens_out,omitempty"` ErrorMessage string `json:"error_message,omitempty"` ClientIP string `json:"client_ip,omitempty"` RequestSize int64 `json:"request_size,omitempty"` ResponseSize int64 `json:"response_size,omitempty"` } func (c *Client) CreateLog(req LogRequest) error { _, err := c.post("/logs", req) return err } func (c *Client) CreateLogsBatch(logs []LogRequest) error { for _, log := range logs { if err := c.CreateLog(log); err != nil { return err } } return nil }