From 99b0948da981f6217027e3b485123873c1150a49 Mon Sep 17 00:00:00 2001 From: Zachary Date: Wed, 25 Mar 2026 00:48:39 -0400 Subject: [PATCH] Deprecate legacy Login/Register, add admin events, members, webhooks - Mark Register() and Login() as Deprecated (use Better Auth directly) - Add admin_events.go, members.go, webhooks.go to match backend API - Sync types with backend changes Co-Authored-By: Claude Opus 4.6 (1M context) --- admin_events.go | 17 ++++++ agents.go | 31 ++++++++-- auth.go | 4 ++ health.go | 54 +++++++++++++++++ members.go | 10 ++++ types.go | 153 ++++++++++++++++++++++++++++++++++++++++++++---- webhooks.go | 29 +++++++++ 7 files changed, 280 insertions(+), 18 deletions(-) create mode 100644 admin_events.go create mode 100644 members.go create mode 100644 webhooks.go diff --git a/admin_events.go b/admin_events.go new file mode 100644 index 0000000..adcb1a3 --- /dev/null +++ b/admin_events.go @@ -0,0 +1,17 @@ +package elydora + +import "fmt" + +// ListAdminEvents retrieves recent administrative events for the organization. +// An optional limit (1–100) may be provided; pass 0 to use the server default (20). +func (c *Client) ListAdminEvents(limit int) (*ListAdminEventsResponse, error) { + path := "/v1/admin/events" + if limit > 0 { + path = fmt.Sprintf("%s?limit=%d", path, limit) + } + var result ListAdminEventsResponse + if err := c.doGet(path, &result); err != nil { + return nil, err + } + return &result, nil +} diff --git a/agents.go b/agents.go index 37719b1..68e0609 100644 --- a/agents.go +++ b/agents.go @@ -24,10 +24,14 @@ func (c *Client) GetAgent(agentID string) (*GetAgentResponse, error) { } // FreezeAgent freezes an agent, preventing it from submitting operations. -func (c *Client) FreezeAgent(agentID, reason string) error { - return c.doPost(fmt.Sprintf("/v1/agents/%s/freeze", agentID), &FreezeAgentRequest{ +func (c *Client) FreezeAgent(agentID, reason string) (*FreezeAgentResponse, error) { + var result FreezeAgentResponse + if err := c.doPost(fmt.Sprintf("/v1/agents/%s/freeze", agentID), &FreezeAgentRequest{ Reason: reason, - }, nil) + }, &result); err != nil { + return nil, err + } + return &result, nil } // RevokeKey revokes a specific key for an agent. @@ -48,10 +52,25 @@ func (c *Client) ListAgents() (*ListAgentsResponse, error) { } // UnfreezeAgent unfreezes a previously frozen agent. -func (c *Client) UnfreezeAgent(agentID, reason string) error { - return c.doPost(fmt.Sprintf("/v1/agents/%s/unfreeze", agentID), &UnfreezeAgentRequest{ +func (c *Client) UnfreezeAgent(agentID, reason string) (*UnfreezeAgentResponse, error) { + var result UnfreezeAgentResponse + if err := c.doPost(fmt.Sprintf("/v1/agents/%s/unfreeze", agentID), &UnfreezeAgentRequest{ Reason: reason, - }, nil) + }, &result); err != nil { + return nil, err + } + return &result, nil +} + +// UpdateAgent updates an agent's integration type. +func (c *Client) UpdateAgent(agentID string, integrationType IntegrationType) (*UpdateAgentResponse, error) { + var result UpdateAgentResponse + if err := c.doRequest(http.MethodPatch, fmt.Sprintf("/v1/agents/%s", agentID), &UpdateAgentRequest{ + IntegrationType: integrationType, + }, &result); err != nil { + return nil, err + } + return &result, nil } // DeleteAgent permanently deletes an agent and all associated data. diff --git a/auth.go b/auth.go index 1a6cb9e..44921ab 100644 --- a/auth.go +++ b/auth.go @@ -28,6 +28,8 @@ func WithOrgName(name string) RegisterOption { } // Register creates a new user and organization. +// +// Deprecated: Use Better Auth endpoints directly. See docs. func Register(baseURL, email, password string, opts ...RegisterOption) (*AuthRegisterResponse, error) { if baseURL == "" { baseURL = defaultBaseURL @@ -50,6 +52,8 @@ func Register(baseURL, email, password string, opts ...RegisterOption) (*AuthReg } // Login authenticates a user and returns a session token. +// +// Deprecated: Use Better Auth endpoints directly. See docs. func Login(baseURL, email, password string) (*AuthLoginResponse, error) { if baseURL == "" { baseURL = defaultBaseURL diff --git a/health.go b/health.go index 102dd6a..cf78961 100644 --- a/health.go +++ b/health.go @@ -60,3 +60,57 @@ func Health(baseURL string) (*HealthResponse, error) { Message: string(respBody), } } + +// DeepHealth checks the deep health of the Elydora API, including its backend +// dependencies (D1, R2, KV). This is a public endpoint that does not require +// authentication. +func DeepHealth(baseURL string) (*DeepHealthResponse, error) { + if baseURL == "" { + baseURL = defaultBaseURL + } + baseURL = strings.TrimRight(baseURL, "/") + + client := &http.Client{Timeout: 30 * time.Second} + req, err := http.NewRequest(http.MethodGet, baseURL+"/v1/health/deep", nil) + if err != nil { + return nil, fmt.Errorf("elydora: create request: %w", err) + } + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("elydora: http request: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20)) + if err != nil { + return nil, fmt.Errorf("elydora: read response body: %w", err) + } + + // Accept both 200 (healthy) and 503 (degraded) as valid deep-health responses. + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusServiceUnavailable { + var result DeepHealthResponse + if err := json.Unmarshal(respBody, &result); err != nil { + return nil, fmt.Errorf("elydora: unmarshal response: %w", err) + } + return &result, nil + } + + var errResp errorResponse + if err := json.Unmarshal(respBody, &errResp); err == nil && errResp.Error.Code != "" { + return nil, &ElydoraError{ + StatusCode: resp.StatusCode, + Code: errResp.Error.Code, + Message: errResp.Error.Message, + RequestID: errResp.Error.RequestID, + Details: errResp.Error.Details, + } + } + + return nil, &ElydoraError{ + StatusCode: resp.StatusCode, + Code: ErrorCodeInternalError, + Message: string(respBody), + } +} diff --git a/members.go b/members.go new file mode 100644 index 0000000..2690728 --- /dev/null +++ b/members.go @@ -0,0 +1,10 @@ +package elydora + +// ListMembers retrieves all members of the organization. +func (c *Client) ListMembers() (*ListMembersResponse, error) { + var result ListMembersResponse + if err := c.doGet("/v1/members", &result); err != nil { + return nil, err + } + return &result, nil +} diff --git a/types.go b/types.go index 1f7e32c..66c7944 100644 --- a/types.go +++ b/types.go @@ -20,6 +20,25 @@ const ( KeyStatusRevoked KeyStatus = "revoked" ) +type IntegrationType string + +const ( + IntegrationTypeClaudecode IntegrationType = "claudecode" + IntegrationTypeCursor IntegrationType = "cursor" + IntegrationTypeGemini IntegrationType = "gemini" + IntegrationTypeKiroCLI IntegrationType = "kirocli" + IntegrationTypeKiroIDE IntegrationType = "kiroide" + IntegrationTypeOpenCode IntegrationType = "opencode" + IntegrationTypeCopilot IntegrationType = "copilot" + IntegrationTypeLetta IntegrationType = "letta" + IntegrationTypeCodex IntegrationType = "codex" + IntegrationTypeKimi IntegrationType = "kimi" + IntegrationTypeEnterprise IntegrationType = "enterprise" + IntegrationTypeGUI IntegrationType = "gui" + IntegrationTypeSDK IntegrationType = "sdk" + IntegrationTypeOther IntegrationType = "other" +) + type ExportStatus string const ( @@ -70,13 +89,14 @@ const ( // --------------------------------------------------------------------------- type Agent struct { - AgentID string `json:"agent_id"` - OrgID string `json:"org_id"` - DisplayName string `json:"display_name"` - ResponsibleEntity string `json:"responsible_entity"` - Status AgentStatus `json:"status"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + AgentID string `json:"agent_id"` + OrgID string `json:"org_id"` + DisplayName string `json:"display_name"` + ResponsibleEntity string `json:"responsible_entity"` + IntegrationType IntegrationType `json:"integration_type"` + Status AgentStatus `json:"status"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } type AgentKey struct { @@ -128,10 +148,12 @@ type Epoch struct { } type Organization struct { - OrgID string `json:"org_id"` - Name string `json:"name"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + OrgID string `json:"org_id"` + Name string `json:"name"` + Description string `json:"description"` + BAOrgID *string `json:"ba_org_id"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` } type User struct { @@ -225,6 +247,11 @@ type FreezeAgentRequest struct { Reason string `json:"reason"` } +type FreezeAgentResponse struct { + Agent Agent `json:"agent"` + PreviousStatus AgentStatus `json:"previous_status"` +} + type RevokeAgentRequest struct { KID string `json:"kid"` Reason string `json:"reason"` @@ -234,6 +261,19 @@ type UnfreezeAgentRequest struct { Reason string `json:"reason"` } +type UnfreezeAgentResponse struct { + Agent Agent `json:"agent"` + PreviousStatus AgentStatus `json:"previous_status"` +} + +type UpdateAgentRequest struct { + IntegrationType IntegrationType `json:"integration_type"` +} + +type UpdateAgentResponse struct { + Agent Agent `json:"agent"` +} + type ListAgentsResponse struct { Agents []Agent `json:"agents"` } @@ -313,7 +353,9 @@ type GetEpochResponse struct { } type EpochAnchor struct { - TSAToken string `json:"tsa_token,omitempty"` + TSAToken string `json:"tsa_token,omitempty"` + TSAUrl string `json:"tsa_url,omitempty"` + AnchoredAt *int64 `json:"anchored_at,omitempty"` } type ListEpochsResponse struct { @@ -377,6 +419,93 @@ type AuthLoginResponse struct { Token string `json:"token"` } +// AdminEvent represents an administrative event log entry. +type AdminEvent struct { + EventID string `json:"event_id"` + OrgID string `json:"org_id"` + Actor string `json:"actor"` + Action string `json:"action"` + TargetType string `json:"target_type"` + TargetID string `json:"target_id"` + Details *string `json:"details"` + CreatedAt int64 `json:"created_at"` +} + +// AgentAssignment represents a many-to-many agent-to-user assignment. +type AgentAssignment struct { + ID string `json:"id"` + AgentID string `json:"agent_id"` + UserID string `json:"user_id"` + OrgID string `json:"org_id"` + AssignedBy string `json:"assigned_by"` + CreatedAt int64 `json:"created_at"` +} + +// Webhook represents a registered webhook endpoint. +type Webhook struct { + WebhookID string `json:"webhook_id"` + OrgID string `json:"org_id"` + EndpointURL string `json:"endpoint_url"` + Events []string `json:"events"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +type ListWebhooksResponse struct { + Webhooks []Webhook `json:"webhooks"` +} + +type RegisterWebhookRequest struct { + EndpointURL string `json:"endpoint_url"` + Events []string `json:"events"` + Secret string `json:"secret"` +} + +type RegisterWebhookResponse struct { + Webhook Webhook `json:"webhook"` +} + +// Member represents a console user returned from the members list endpoint. +type Member struct { + MemberID string `json:"member_id"` + UserID string `json:"user_id"` + Role string `json:"role"` + JoinedAt string `json:"joined_at"` + Email string `json:"email"` + DisplayName string `json:"display_name"` +} + +type ListMembersResponse struct { + Members []Member `json:"members"` +} + +type ListAdminEventsResponse struct { + Events []AdminEvent `json:"events"` +} + +// DependencyHealth holds the health status of a single backend dependency. +type DependencyHealth struct { + Status string `json:"status"` + LatencyMs int64 `json:"latency_ms"` + Error *string `json:"error,omitempty"` +} + +// DeepHealthDependencies lists the health of all backend dependencies. +type DeepHealthDependencies struct { + D1 DependencyHealth `json:"d1"` + R2 DependencyHealth `json:"r2"` + KV DependencyHealth `json:"kv"` +} + +// DeepHealthResponse is returned by GET /v1/health/deep. +type DeepHealthResponse struct { + Status string `json:"status"` + Version string `json:"version"` + Timestamp int64 `json:"timestamp"` + Dependencies DeepHealthDependencies `json:"dependencies"` +} + // --------------------------------------------------------------------------- // CreateOperation params (SDK-specific) // --------------------------------------------------------------------------- diff --git a/webhooks.go b/webhooks.go new file mode 100644 index 0000000..fa1acd0 --- /dev/null +++ b/webhooks.go @@ -0,0 +1,29 @@ +package elydora + +import ( + "fmt" + "net/http" +) + +// ListWebhooks retrieves all registered webhooks for the organization. +func (c *Client) ListWebhooks() (*ListWebhooksResponse, error) { + var result ListWebhooksResponse + if err := c.doGet("/v1/webhooks", &result); err != nil { + return nil, err + } + return &result, nil +} + +// RegisterWebhook registers a new webhook endpoint for the organization. +func (c *Client) RegisterWebhook(req *RegisterWebhookRequest) (*RegisterWebhookResponse, error) { + var result RegisterWebhookResponse + if err := c.doPost("/v1/webhooks", req, &result); err != nil { + return nil, err + } + return &result, nil +} + +// DeleteWebhook deletes a registered webhook by ID. +func (c *Client) DeleteWebhook(webhookID string) error { + return c.doRequest(http.MethodDelete, fmt.Sprintf("/v1/webhooks/%s", webhookID), nil, nil) +}