package sub2api import ( "bufio" "bytes" "context" "encoding/json" "fmt" "net/http" "strings" ) func (c *Client) CreateAccount(ctx context.Context, req CreateAccountRequest) (AccountRef, error) { var ref AccountRef if err := c.postJSON(ctx, "/api/v1/admin/accounts", req, &ref); err != nil { return AccountRef{}, err } return ref, nil } func (c *Client) BatchCreateAccounts(ctx context.Context, req BatchCreateAccountsRequest) ([]AccountRef, error) { statusCode, _, body, err := c.perform(ctx, http.MethodPost, "/api/v1/admin/accounts/batch", req) if err != nil { return nil, err } if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices { return nil, newHTTPError(http.MethodPost, "/api/v1/admin/accounts/batch", statusCode, body) } models, err := decodeAccountRefs(body) if err != nil { return nil, fmt.Errorf("decode /api/v1/admin/accounts/batch response: %w", err) } return models, nil } func (c *Client) TestAccount(ctx context.Context, accountID, modelID string) (ProbeResult, error) { path := "/api/v1/admin/accounts/" + accountID + "/test" req := map[string]any{} if strings.TrimSpace(modelID) != "" { req["model_id"] = strings.TrimSpace(modelID) } statusCode, _, body, err := c.perform(ctx, http.MethodPost, path, req) if err != nil { return ProbeResult{}, err } if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices { return ProbeResult{}, newHTTPError(http.MethodPost, path, statusCode, body) } result, err := parseProbeResult(body) if err != nil { return ProbeResult{}, fmt.Errorf("parse %s sse: %w", path, err) } return result, nil } func (c *Client) GetAccountModels(ctx context.Context, accountID string) ([]AccountModel, error) { path := "/api/v1/admin/accounts/" + accountID + "/models" statusCode, _, body, err := c.perform(ctx, http.MethodGet, path, nil) if err != nil { return nil, err } if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices { return nil, newHTTPError(http.MethodGet, path, statusCode, body) } models, err := decodeAccountModels(body) if err != nil { return nil, fmt.Errorf("decode %s response: %w", path, err) } return models, nil } func decodeAccountRefs(body []byte) ([]AccountRef, error) { var refs []AccountRef if err := decodeEnvelopeObject(body, &refs); err == nil { return refs, nil } var wrapper struct { Data struct { Items []AccountRef `json:"items"` Results []struct { ID json.RawMessage `json:"id"` Name string `json:"name"` Success bool `json:"success"` } `json:"results"` } `json:"data"` } if err := json.Unmarshal(body, &wrapper); err == nil { if len(wrapper.Data.Items) > 0 { return wrapper.Data.Items, nil } if len(wrapper.Data.Results) > 0 { return decodeBatchAccountResults(wrapper.Data.Results) } } var batch struct { Results []struct { ID json.RawMessage `json:"id"` Name string `json:"name"` Success bool `json:"success"` } `json:"results"` } if err := json.Unmarshal(body, &batch); err != nil { return nil, err } return decodeBatchAccountResults(batch.Results) } func decodeBatchAccountResults(results []struct { ID json.RawMessage `json:"id"` Name string `json:"name"` Success bool `json:"success"` }) ([]AccountRef, error) { refs := make([]AccountRef, 0, len(results)) for _, item := range results { if !item.Success { continue } id, err := decodeFlexibleID(item.ID) if err != nil { return nil, err } refs = append(refs, AccountRef{ID: id, Name: item.Name}) } return refs, nil } func decodeAccountModels(body []byte) ([]AccountModel, error) { var models []AccountModel if err := decodeEnvelopeObject(body, &models); err == nil { return models, nil } var wrapper struct { Data struct { Items []AccountModel `json:"items"` } `json:"data"` } if err := json.Unmarshal(body, &wrapper); err != nil { return nil, err } return wrapper.Data.Items, nil } func parseProbeResult(body []byte) (ProbeResult, error) { scanner := bufio.NewScanner(bytes.NewReader(body)) var payloads []string for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, "data:") { payloads = append(payloads, strings.TrimSpace(strings.TrimPrefix(line, "data:"))) } } if err := scanner.Err(); err != nil { return ProbeResult{}, err } if len(payloads) == 0 { return ProbeResult{}, fmt.Errorf("missing data event") } type probeEvent struct { Type string `json:"type"` Status string `json:"status"` Message string `json:"message"` Error string `json:"error"` OK *bool `json:"ok"` Success *bool `json:"success"` } var latest ProbeResult sawProbeState := false for _, payload := range payloads { var event probeEvent if err := json.Unmarshal([]byte(payload), &event); err != nil { return ProbeResult{}, err } message := strings.TrimSpace(event.Message) if message == "" { message = strings.TrimSpace(event.Error) } eventType := strings.ToLower(strings.TrimSpace(event.Type)) if eventType == "error" || strings.TrimSpace(event.Error) != "" { if message == "" { message = "account probe returned an error event" } return ProbeResult{ OK: false, Status: "failed", Message: message, }, nil } ok := false switch { case event.OK != nil: ok = *event.OK case event.Success != nil: ok = *event.Success default: switch strings.ToLower(strings.TrimSpace(event.Status)) { case "ok", "pass", "passed", "success", "succeeded": ok = true } } if eventType == "test_complete" { return ProbeResult{ OK: ok, Status: normalizeProbeStatus(event.Status, ok), Message: message, }, nil } if event.Status != "" || event.OK != nil || event.Success != nil { latest = ProbeResult{ OK: ok, Status: normalizeProbeStatus(event.Status, ok), Message: message, } sawProbeState = true } } if sawProbeState { return latest, nil } return ProbeResult{}, fmt.Errorf("missing probe status event") } func normalizeProbeStatus(status string, ok bool) string { switch strings.ToLower(strings.TrimSpace(status)) { case "pass", "passed", "ok", "success", "succeeded": return "passed" case "fail", "failed", "error": return "failed" } if ok { return "passed" } return "failed" }