From 1ca104b3404374837b5fe3a68fd996db2bebced7 Mon Sep 17 00:00:00 2001 From: debidong <1953531014@qq.com> Date: Mon, 29 Jun 2026 13:23:26 +0800 Subject: [PATCH 1/2] feat: add automation api sdk --- automations.go | 106 +++ automations_fire.go | 40 ++ automations_fire_test.go | 94 +++ automations_models.go | 29 + flashduty.go | 23 +- internal/cmd/gen/main.go | 37 +- models_gen.go | 199 ++++++ openapi/openapi.en.json | 1390 ++++++++++++++++++++++++++++++++++++++ openapi/openapi.zh.json | 1390 ++++++++++++++++++++++++++++++++++++++ roundtrip_gen_test.go | 7 + services_gen.go | 2 + 11 files changed, 3310 insertions(+), 7 deletions(-) create mode 100644 automations.go create mode 100644 automations_fire.go create mode 100644 automations_fire_test.go create mode 100644 automations_models.go diff --git a/automations.go b/automations.go new file mode 100644 index 0000000..aa13a5e --- /dev/null +++ b/automations.go @@ -0,0 +1,106 @@ +// Code generated by internal/cmd/gen; DO NOT EDIT. + +package flashduty + +import "context" + +// AutomationsService handles the "AI SRE/Automations" API resource. +type AutomationsService service + +// Get Automation rule. +// +// Get one Automation rule by ID. +// +// API: POST /safari/automation/rule/get (automation-rule-read-get). +func (s *AutomationsService) RuleReadGet(ctx context.Context, req *AutomationRuleIDRequest) (*AutomationRuleItem, *Response, error) { + out := new(AutomationRuleItem) + resp, err := s.client.do(ctx, "/safari/automation/rule/get", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} + +// List Automation rules. +// +// List Automation rules visible to the caller. +// +// API: POST /safari/automation/rule/list (automation-rule-read-list). +func (s *AutomationsService) RuleReadList(ctx context.Context, req *AutomationRuleListRequest) (*AutomationRuleListResponse, *Response, error) { + out := new(AutomationRuleListResponse) + resp, err := s.client.do(ctx, "/safari/automation/rule/list", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} + +// Create Automation rule. +// +// Create an Automation rule with a schedule trigger and, optionally, an HTTP POST trigger. +// +// API: POST /safari/automation/rule/create (automation-rule-write-create). +func (s *AutomationsService) RuleWriteCreate(ctx context.Context, req *AutomationRuleCreateRequest) (*AutomationRuleItem, *Response, error) { + out := new(AutomationRuleItem) + resp, err := s.client.do(ctx, "/safari/automation/rule/create", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} + +// Delete Automation rule. +// +// Delete an Automation rule. +// +// API: POST /safari/automation/rule/delete (automation-rule-write-delete). +func (s *AutomationsService) RuleWriteDelete(ctx context.Context, req *AutomationRuleIDRequest) (*any, *Response, error) { + out := new(any) + resp, err := s.client.do(ctx, "/safari/automation/rule/delete", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} + +// Update Automation rule. +// +// Update mutable fields on an Automation rule. The personal/team scope is immutable. +// +// API: POST /safari/automation/rule/update (automation-rule-write-update). +func (s *AutomationsService) RuleWriteUpdate(ctx context.Context, req *AutomationRuleUpdateRequest) (*AutomationRuleItem, *Response, error) { + out := new(AutomationRuleItem) + resp, err := s.client.do(ctx, "/safari/automation/rule/update", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} + +// List Automation runs. +// +// List run history for a rule the caller can manage. +// +// API: POST /safari/automation/run/list (automation-run-read-list). +func (s *AutomationsService) RunReadList(ctx context.Context, req *AutomationRunListRequest) (*AutomationRunListResponse, *Response, error) { + out := new(AutomationRunListResponse) + resp, err := s.client.do(ctx, "/safari/automation/run/list", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} + +// List Automation templates. +// +// List preset Automation templates for the requested locale. +// +// API: POST /safari/automation/template/list (automation-template-read-list). +func (s *AutomationsService) TemplateReadList(ctx context.Context, req *AutomationTemplateListRequest) (*AutomationTemplateListResponse, *Response, error) { + out := new(AutomationTemplateListResponse) + resp, err := s.client.do(ctx, "/safari/automation/template/list", req, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} diff --git a/automations_fire.go b/automations_fire.go new file mode 100644 index 0000000..ba4b55d --- /dev/null +++ b/automations_fire.go @@ -0,0 +1,40 @@ +package flashduty + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" +) + +// TriggerWriteFire triggers an Automation run through its HTTP POST trigger URL. +// +// This endpoint authenticates with the trigger's one-time bearer token rather +// than the account app_key used by the generated API methods. +// +// API: POST /safari/automation/triggers/{trigger_id}/fire (automation-trigger-write-fire). +func (s *AutomationsService) TriggerWriteFire(ctx context.Context, triggerID, token string, req *AutomationFireAPITriggerRequest) (*AutomationFireAPITriggerResponse, *Response, error) { + triggerID = strings.TrimSpace(triggerID) + if triggerID == "" { + return nil, nil, fmt.Errorf("flashduty: automation trigger_id is required") + } + token = strings.TrimSpace(token) + if token == "" { + return nil, nil, fmt.Errorf("flashduty: automation trigger token is required") + } + + path := "/safari/automation/triggers/" + url.PathEscape(triggerID) + "/fire" + httpReq, err := s.client.newRequestWithoutAppKey(ctx, http.MethodPost, path, req) + if err != nil { + return nil, nil, err + } + httpReq.Header.Set("Authorization", "Bearer "+token) + + out := new(AutomationFireAPITriggerResponse) + resp, err := s.client.doRequest(httpReq, out) + if err != nil { + return nil, resp, err + } + return out, resp, nil +} diff --git a/automations_fire_test.go b/automations_fire_test.go new file mode 100644 index 0000000..0d674c9 --- /dev/null +++ b/automations_fire_test.go @@ -0,0 +1,94 @@ +package flashduty + +import ( + "context" + "encoding/json" + "io" + "net/http" + "strings" + "testing" +) + +func TestAutomationTriggerWriteFireUsesBearerTokenAndDecodesAccepted(t *testing.T) { + c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/safari/automation/triggers/trig_abc/fire" { + t.Errorf("path = %s", r.URL.Path) + } + if got := r.URL.Query().Get("app_key"); got != "" { + t.Errorf("app_key must not be sent for trigger fire, got %q", got) + } + if got := r.Header.Get("Authorization"); got != "Bearer tok_secret" { + t.Errorf("Authorization = %q", got) + } + if got := r.Header.Get("Content-Type"); got != "application/json" { + t.Errorf("Content-Type = %q", got) + } + body, _ := io.ReadAll(r.Body) + if !strings.Contains(string(body), `"text":"deployment finished"`) || + !strings.Contains(string(body), `"dedup_key":"deploy-1"`) { + t.Errorf("body = %s", body) + } + + w.Header().Set("Flashcat-Request-Id", "RIDF") + w.WriteHeader(http.StatusAccepted) + _, _ = io.WriteString(w, `{"request_id":"RIDF","data":{"run_id":"taskrun_1","rule_id":"auto_1","trigger_kind":"http_post","status":"running"}}`) + }) + + out, resp, err := c.Automations.TriggerWriteFire(context.Background(), "trig_abc", "tok_secret", &AutomationFireAPITriggerRequest{ + Text: "deployment finished", + DedupKey: "deploy-1", + }) + if err != nil { + t.Fatalf("TriggerWriteFire error: %v", err) + } + if resp == nil || resp.StatusCode != http.StatusAccepted || resp.RequestID != "RIDF" { + t.Fatalf("response meta = %+v", resp) + } + if out == nil || out.RunID != "taskrun_1" || out.RuleID != "auto_1" || out.TriggerKind != "http_post" || out.Status != "running" { + t.Fatalf("decoded response = %+v", out) + } +} + +func TestAutomationTriggerWriteFireValidatesInputs(t *testing.T) { + c := newTestClient(t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("request should not be sent") + }) + + if _, _, err := c.Automations.TriggerWriteFire(context.Background(), "", "tok", nil); err == nil { + t.Fatal("expected empty trigger_id error") + } + if _, _, err := c.Automations.TriggerWriteFire(context.Background(), "trig_abc", "", nil); err == nil { + t.Fatal("expected empty token error") + } +} + +func TestAutomationRuleUpdateRequestCanSendFalseValues(t *testing.T) { + payload, err := json.Marshal(&AutomationRuleUpdateRequest{ + RuleID: "auto_1", + Enabled: Bool(false), + ScheduleTriggerEnabled: Bool(false), + HTTPPostTriggerEnabled: Bool(false), + RotateHTTPPostTriggerToken: false, + TeamID: Int64(0), + EnvironmentID: String(""), + }) + if err != nil { + t.Fatal(err) + } + got := string(payload) + for _, want := range []string{ + `"rule_id":"auto_1"`, + `"enabled":false`, + `"schedule_trigger_enabled":false`, + `"http_post_trigger_enabled":false`, + `"team_id":0`, + `"environment_id":""`, + } { + if !strings.Contains(got, want) { + t.Fatalf("payload %s missing %s", got, want) + } + } + if strings.Contains(got, "rotate_http_post_trigger_token") { + t.Fatalf("zero rotate flag should stay omitted: %s", got) + } +} diff --git a/automations_models.go b/automations_models.go new file mode 100644 index 0000000..ff8b883 --- /dev/null +++ b/automations_models.go @@ -0,0 +1,29 @@ +package flashduty + +// AutomationRuleUpdateRequest updates mutable fields on an Automation rule. +// Pointer fields preserve partial-update semantics: nil means leave unchanged, +// while a non-nil zero value is sent to the API. +type AutomationRuleUpdateRequest struct { + // Target rule ID. + RuleID string `json:"rule_id,omitempty" toon:"rule_id,omitempty"` + // New rule name. + Name *string `json:"name,omitempty" toon:"name,omitempty"` + // Only the current value is accepted; personal/team scope is immutable after creation. + TeamID *int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` + // Whether the rule is enabled. + Enabled *bool `json:"enabled,omitempty" toon:"enabled,omitempty"` + // Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). + CronExpr *string `json:"cron_expr,omitempty" toon:"cron_expr,omitempty"` + // Whether the schedule trigger is enabled. + ScheduleTriggerEnabled *bool `json:"schedule_trigger_enabled,omitempty" toon:"schedule_trigger_enabled,omitempty"` + // New task prompt. + Prompt *string `json:"prompt,omitempty" toon:"prompt,omitempty"` + // Runtime environment kind. Omit or send an empty value for automatic selection. + EnvironmentKind *string `json:"environment_kind,omitempty" toon:"environment_kind,omitempty"` + // BYOC Runner ID. + EnvironmentID *string `json:"environment_id,omitempty" toon:"environment_id,omitempty"` + // Whether the HTTP POST trigger is enabled. Sending true creates one when missing. + HTTPPostTriggerEnabled *bool `json:"http_post_trigger_enabled,omitempty" toon:"http_post_trigger_enabled,omitempty"` + // Whether to rotate the HTTP POST trigger token. The new token is returned only in this response. + RotateHTTPPostTriggerToken bool `json:"rotate_http_post_trigger_token,omitempty" toon:"rotate_http_post_trigger_token,omitempty"` +} diff --git a/flashduty.go b/flashduty.go index fe6061c..5608c2a 100644 --- a/flashduty.go +++ b/flashduty.go @@ -97,14 +97,27 @@ type pageMeta struct { // parameter and JSON-encoding body when non-nil. Most Flashduty endpoints are // POST actions; a handful are GET with query parameters. func (c *Client) newRequest(ctx context.Context, method, path string, body any) (*http.Request, error) { + return c.newRequestWithAppKey(ctx, method, path, body, true) +} + +// newRequestWithoutAppKey builds a request for endpoints that use their own +// authentication scheme instead of the account app_key, such as Automation HTTP +// POST trigger bearer tokens. +func (c *Client) newRequestWithoutAppKey(ctx context.Context, method, path string, body any) (*http.Request, error) { + return c.newRequestWithAppKey(ctx, method, path, body, false) +} + +func (c *Client) newRequestWithAppKey(ctx context.Context, method, path string, body any, withAppKey bool) (*http.Request, error) { rel, err := url.Parse(strings.TrimPrefix(path, "/")) if err != nil { return nil, fmt.Errorf("flashduty: invalid path %q: %w", path, err) } u := c.BaseURL.ResolveReference(rel) - q := u.Query() - q.Set("app_key", c.appKey) - u.RawQuery = q.Encode() + if withAppKey { + q := u.Query() + q.Set("app_key", c.appKey) + u.RawQuery = q.Encode() + } var buf io.Reader var rawBody []byte @@ -168,6 +181,10 @@ func (c *Client) doMethod(ctx context.Context, method, path string, body, out an if err != nil { return nil, err } + return c.doRequest(req, out) +} + +func (c *Client) doRequest(req *http.Request, out any) (*Response, error) { httpResp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("flashduty: request to %s failed: %v", sanitizeURL(req.URL), sanitizeError(err)) diff --git a/internal/cmd/gen/main.go b/internal/cmd/gen/main.go index 4f77526..45d64f2 100644 --- a/internal/cmd/gen/main.go +++ b/internal/cmd/gen/main.go @@ -93,10 +93,15 @@ func run() error { root: root, schemas: asMap(asMap(spec["components"])["schemas"]), emptyTypes: map[string]bool{}, - skip: map[string]bool{"SuccessEnvelope": true, "ErrorResponse": true, "DutyError": true}, - queued: map[string]bool{}, - synth: map[string]any{}, - reqSynth: map[string]bool{}, + skip: map[string]bool{ + "SuccessEnvelope": true, + "ErrorResponse": true, + "DutyError": true, + "AutomationRuleUpdateRequest": true, // hand-written to preserve partial-update pointer semantics. + }, + queued: map[string]bool{}, + synth: map[string]any{}, + reqSynth: map[string]bool{}, } g.detectEmpty() g.reqGoNames = g.computeRequestReachable(asMap(spec["paths"])) @@ -169,6 +174,12 @@ func (g *Gen) collectServices(paths map[string]any) []service { if isStreamingOp(o) { continue } + // The generated client is an app_key client and does not template path + // parameters. Endpoints with operation-level non-AppKey auth or path + // params need hand-written methods. + if needsHandWrittenOperation(o) { + continue + } tag, _ := tags[0].(string) byTag[tag] = append(byTag[tag], opEntry{p, m, o}) } @@ -231,6 +242,24 @@ func isStreamingOp(o map[string]any) bool { return !hasJSON } +func needsHandWrittenOperation(o map[string]any) bool { + for _, raw := range asSlice(o["parameters"]) { + if str(asMap(raw), "in") == "path" { + return true + } + } + rawSecurity, ok := o["security"] + if !ok { + return false + } + for _, raw := range asSlice(rawSecurity) { + if _, ok := asMap(raw)["AppKeyAuth"]; ok { + return false + } + } + return true +} + // getRequestType synthesizes a request struct from a GET op's query parameters // and records it for emission. Returns "" when the op has no query parameters. func (g *Gen) getRequestType(o map[string]any, hint string) string { diff --git a/models_gen.go b/models_gen.go index 3bc2ff8..2d5142b 100644 --- a/models_gen.go +++ b/models_gen.go @@ -1065,6 +1065,205 @@ type AuditSearchResponse struct { Total int64 `json:"total" toon:"total"` } +// AutomationFireAPITriggerRequest is generated from the Flashduty OpenAPI schema. +type AutomationFireAPITriggerRequest struct { + // Optional idempotency key; the same trigger + dedup_key reuses the same run. + DedupKey string `json:"dedup_key,omitempty" toon:"dedup_key,omitempty"` + // Context text passed to this Automation run. + Text string `json:"text,omitempty" toon:"text,omitempty"` +} + +// AutomationFireAPITriggerResponse is generated from the Flashduty OpenAPI schema. +type AutomationFireAPITriggerResponse struct { + // Rule ID. + RuleID string `json:"rule_id" toon:"rule_id"` + // Created or reused run ID. + RunID string `json:"run_id" toon:"run_id"` + // Current run status. + Status string `json:"status" toon:"status"` + // Trigger kind. + TriggerKind string `json:"trigger_kind" toon:"trigger_kind"` +} + +// AutomationRuleCreateRequest is generated from the Flashduty OpenAPI schema. +type AutomationRuleCreateRequest struct { + // Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). The minute must be one fixed integer; 6-field seconds are not supported. The create API currently requires this field even for HTTP-POST-only rules; send a valid cron and set `schedule_trigger_enabled=false`. + CronExpr string `json:"cron_expr,omitempty" toon:"cron_expr,omitempty"` + // Whether the rule is enabled after creation. Omitted API value is false; Chat/CLI create sends true by default unless the user asks for disabled. + Enabled bool `json:"enabled,omitempty" toon:"enabled,omitempty"` + // BYOC Runner ID. Used only when `environment_kind=byoc`. + EnvironmentID string `json:"environment_id,omitempty" toon:"environment_id,omitempty"` + // Runtime environment kind. Omit or send an empty value for automatic selection. + EnvironmentKind string `json:"environment_kind,omitempty" toon:"environment_kind,omitempty"` + // Whether to create and enable an HTTP POST trigger. When enabled, the response includes a one-time token. + HTTPPostTriggerEnabled bool `json:"http_post_trigger_enabled,omitempty" toon:"http_post_trigger_enabled,omitempty"` + // Rule name. + Name string `json:"name,omitempty" toon:"name,omitempty"` + // Task prompt sent to the AI SRE agent on each run. + Prompt string `json:"prompt,omitempty" toon:"prompt,omitempty"` + // Whether the schedule trigger is enabled. Defaults to true when omitted; HTTP-POST-only rules should send false. + ScheduleTriggerEnabled *bool `json:"schedule_trigger_enabled,omitempty" toon:"schedule_trigger_enabled,omitempty"` + // Scope team ID. 0 or omitted means a personal rule; >0 means a team in the account. Immutable after creation. + TeamID int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` +} + +// AutomationRuleIDRequest is generated from the Flashduty OpenAPI schema. +type AutomationRuleIDRequest struct { + // Rule ID. + RuleID string `json:"rule_id,omitempty" toon:"rule_id,omitempty"` +} + +// AutomationRuleItem is generated from the Flashduty OpenAPI schema. +type AutomationRuleItem struct { + // Account ID. + AccountID int64 `json:"account_id" toon:"account_id"` + // Whether the caller can manage this rule. + CanEdit bool `json:"can_edit" toon:"can_edit"` + // Creation time, Unix milliseconds. + CreatedAt TimestampMilli `json:"created_at" toon:"created_at"` + // Normalized 5-field cron expression. + CronExpr string `json:"cron_expr" toon:"cron_expr"` + // Whether the rule is enabled. + Enabled bool `json:"enabled" toon:"enabled"` + // BYOC Runner ID. + EnvironmentID string `json:"environment_id" toon:"environment_id"` + // Runtime environment kind. Omit or send an empty value for automatic selection. + EnvironmentKind string `json:"environment_kind" toon:"environment_kind"` + // HTTP POST trigger token. Returned only on create or token rotation; save it immediately. + HTTPPostToken string `json:"http_post_token" toon:"http_post_token"` + // Whether the HTTP POST trigger is enabled. + HTTPPostTriggerEnabled bool `json:"http_post_trigger_enabled" toon:"http_post_trigger_enabled"` + // HTTP POST trigger ID. + HTTPPostTriggerID string `json:"http_post_trigger_id" toon:"http_post_trigger_id"` + // HTTP POST trigger path. + HTTPPostTriggerURL string `json:"http_post_trigger_url" toon:"http_post_trigger_url"` + // Rule name. + Name string `json:"name" toon:"name"` + // Creator person ID. + OwnerID int64 `json:"owner_id" toon:"owner_id"` + // Task prompt. + Prompt string `json:"prompt" toon:"prompt"` + // Rule ID. + RuleID string `json:"rule_id" toon:"rule_id"` + // Hidden session run scope. + RunScope string `json:"run_scope" toon:"run_scope"` + // Whether the schedule trigger is enabled. + ScheduleTriggerEnabled bool `json:"schedule_trigger_enabled" toon:"schedule_trigger_enabled"` + // Schedule trigger ID. + ScheduleTriggerID string `json:"schedule_trigger_id" toon:"schedule_trigger_id"` + // Scope team ID; 0 means personal rule. + TeamID int64 `json:"team_id" toon:"team_id"` + // Last update time, Unix milliseconds. + UpdatedAt TimestampMilli `json:"updated_at" toon:"updated_at"` +} + +// AutomationRuleListRequest is generated from the Flashduty OpenAPI schema. +type AutomationRuleListRequest struct { + ListOptions + // Filter by enabled status. + Enabled *bool `json:"enabled,omitempty" toon:"enabled,omitempty"` + // Compatibility field; when scope is empty and this is false, behaves like team scope. + IncludePerson *bool `json:"include_person,omitempty" toon:"include_person,omitempty"` + // Filter by name keyword. + Keyword string `json:"keyword,omitempty" toon:"keyword,omitempty"` + // Scope filter. Defaults to all. + Scope string `json:"scope,omitempty" toon:"scope,omitempty"` + // Filter to these team IDs; this filters results and does not expand access. + TeamIDs []int64 `json:"team_ids,omitempty" toon:"team_ids,omitempty"` +} + +// AutomationRuleListResponse is generated from the Flashduty OpenAPI schema. +type AutomationRuleListResponse struct { + Rules []AutomationRuleItem `json:"rules" toon:"rules"` + // Total count. + Total int64 `json:"total" toon:"total"` +} + +// AutomationRunItem is generated from the Flashduty OpenAPI schema. +type AutomationRunItem struct { + // Account ID. + AccountID int64 `json:"account_id" toon:"account_id"` + // Attempt count. + Attempts int64 `json:"attempts" toon:"attempts"` + // Completion time, Unix milliseconds. 0 means not completed. + CompletedAt TimestampMilli `json:"completed_at" toon:"completed_at"` + // Creation time, Unix milliseconds. + CreatedAt TimestampMilli `json:"created_at" toon:"created_at"` + // Duration in milliseconds. + DurationMs int64 `json:"duration_ms" toon:"duration_ms"` + // Error code. + ErrorCode string `json:"error_code" toon:"error_code"` + // Error message. + ErrorMessage string `json:"error_message" toon:"error_message"` + // Run kind. + Kind string `json:"kind" toon:"kind"` + // Idempotency key for this occurrence. + OccurrenceKey string `json:"occurrence_key" toon:"occurrence_key"` + // Run result JSON. + ResultJSON any `json:"result_json" toon:"result_json"` + // Rule ID. + RuleID string `json:"rule_id" toon:"rule_id"` + // Run ID. + RunID string `json:"run_id" toon:"run_id"` + // Start time, Unix milliseconds. + StartedAt TimestampMilli `json:"started_at" toon:"started_at"` + // Run stats JSON. + StatsJSON any `json:"stats_json" toon:"stats_json"` + // Run status. + Status string `json:"status" toon:"status"` + // Trigger kind. + TriggerKind string `json:"trigger_kind" toon:"trigger_kind"` + // Last update time, Unix milliseconds. + UpdatedAt TimestampMilli `json:"updated_at" toon:"updated_at"` +} + +// AutomationRunListRequest is generated from the Flashduty OpenAPI schema. +type AutomationRunListRequest struct { + ListOptions + // Target rule ID. + RuleID string `json:"rule_id,omitempty" toon:"rule_id,omitempty"` + // Start-time lower bound, Unix milliseconds. + StartedAfterMs int64 `json:"started_after_ms,omitempty" toon:"started_after_ms,omitempty"` + // Start-time upper bound, Unix milliseconds. + StartedBeforeMs int64 `json:"started_before_ms,omitempty" toon:"started_before_ms,omitempty"` + // Run status filter. + Status string `json:"status,omitempty" toon:"status,omitempty"` + // Trigger kind filter. + TriggerKind string `json:"trigger_kind,omitempty" toon:"trigger_kind,omitempty"` +} + +// AutomationRunListResponse is generated from the Flashduty OpenAPI schema. +type AutomationRunListResponse struct { + Runs []AutomationRunItem `json:"runs" toon:"runs"` + // Total count. + Total int64 `json:"total" toon:"total"` +} + +// AutomationTemplateItem is generated from the Flashduty OpenAPI schema. +type AutomationTemplateItem struct { + // Template description. + Description string `json:"description" toon:"description"` + // Whether the template is enabled. + Enabled bool `json:"enabled" toon:"enabled"` + // Icon identifier. + Icon string `json:"icon" toon:"icon"` + // Template name. + Name string `json:"name" toon:"name"` + // Template prompt. + Prompt string `json:"prompt" toon:"prompt"` +} + +// AutomationTemplateListRequest is generated from the Flashduty OpenAPI schema. +type AutomationTemplateListRequest struct { + // Template locale such as zh-CN or en-US. Omit to detect from the request locale. + Locale string `json:"locale,omitempty" toon:"locale,omitempty"` +} + +// AutomationTemplateListResponse is generated from the Flashduty OpenAPI schema. +type AutomationTemplateListResponse struct { + Templates []AutomationTemplateItem `json:"templates" toon:"templates"` +} + // CalEventIDRequest is generated from the Flashduty OpenAPI schema. type CalEventIDRequest struct { // Calendar ID. diff --git a/openapi/openapi.en.json b/openapi/openapi.en.json index 8dbead4..d32cb4d 100644 --- a/openapi/openapi.en.json +++ b/openapi/openapi.en.json @@ -132,6 +132,9 @@ { "name": "Monitors/Monitor utilities", "description": "Monitors service activation and data preview utilities." + }, + { + "name": "AI SRE/Automations" } ], "paths": { @@ -24503,6 +24506,779 @@ } ] } + }, + "/safari/automation/rule/create": { + "post": { + "operationId": "automation-rule-write-create", + "summary": "Create Automation rule", + "description": "Create an Automation rule with a schedule trigger and, optionally, an HTTP POST trigger.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; management operations require the caller to manage the target rule |\n\n## Usage\n\n- A caller may create personal rules and rules for any team in the current account; `team_id` is immutable after creation.\n- List visibility follows session-list visibility: owners/admins see all rules; ordinary members see rules they created and rules for their teams.\n- `http_post_token` is returned only when creating or rotating the token. Save it immediately.\n", + "href": "/en/api-reference/ai-sre/automations/automation-rule-write-create", + "metadata": { + "sidebarTitle": "Create Automation rule" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228, + "http_post_token": "sat_yQ9p8V7n6M5k4J3h2G1f0E9d8C7b6A5z4Y3x2W1v0U" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleCreateRequest" + }, + "example": { + "name": "Weekly on-call review", + "team_id": 123, + "enabled": true, + "cron_expr": "0 9 * * 1", + "schedule_trigger_enabled": true, + "prompt": "Summarize last week's alert noise and escalation load.", + "http_post_trigger_enabled": true + } + } + } + } + } + }, + "/safari/automation/rule/list": { + "post": { + "operationId": "automation-rule-read-list", + "summary": "List Automation rules", + "description": "List Automation rules visible to the caller.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; results are filtered to the caller's visible scope |\n", + "href": "/en/api-reference/ai-sre/automations/automation-rule-read-list", + "metadata": { + "sidebarTitle": "List Automation rules" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleListResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "total": 1, + "rules": [ + { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228 + } + ] + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleListRequest" + }, + "example": { + "scope": "all", + "limit": 20 + } + } + } + } + } + }, + "/safari/automation/rule/get": { + "post": { + "operationId": "automation-rule-read-get", + "summary": "Get Automation rule", + "description": "Get one Automation rule by ID.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; results are filtered to the caller's visible scope |\n", + "href": "/en/api-reference/ai-sre/automations/automation-rule-read-get", + "metadata": { + "sidebarTitle": "Get Automation rule" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228 + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleIDRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b" + } + } + } + } + } + }, + "/safari/automation/rule/update": { + "post": { + "operationId": "automation-rule-write-update", + "summary": "Update Automation rule", + "description": "Update mutable fields on an Automation rule. The personal/team scope is immutable.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; management operations require the caller to manage the target rule |\n\n## Usage\n\n- A caller may create personal rules and rules for any team in the current account; `team_id` is immutable after creation.\n- List visibility follows session-list visibility: owners/admins see all rules; ordinary members see rules they created and rules for their teams.\n- `http_post_token` is returned only when creating or rotating the token. Save it immediately.\n", + "href": "/en/api-reference/ai-sre/automations/automation-rule-write-update", + "metadata": { + "sidebarTitle": "Update Automation rule" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228, + "http_post_token": "sat_yQ9p8V7n6M5k4J3h2G1f0E9d8C7b6A5z4Y3x2W1v0U" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleUpdateRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "enabled": true, + "cron_expr": "15 9 * * 1", + "rotate_http_post_trigger_token": true + } + } + } + } + } + }, + "/safari/automation/rule/delete": { + "post": { + "operationId": "automation-rule-write-delete", + "summary": "Delete Automation rule", + "description": "Delete an Automation rule.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; management operations require the caller to manage the target rule |\n\n## Usage\n\n- A caller may create personal rules and rules for any team in the current account; `team_id` is immutable after creation.\n- List visibility follows session-list visibility: owners/admins see all rules; ordinary members see rules they created and rules for their teams.\n- `http_post_token` is returned only when creating or rotating the token. Save it immediately.\n", + "href": "/en/api-reference/ai-sre/automations/automation-rule-write-delete", + "metadata": { + "sidebarTitle": "Delete Automation rule" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "type": "null", + "description": "Always null on success." + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": null + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleIDRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b" + } + } + } + } + } + }, + "/safari/automation/template/list": { + "post": { + "operationId": "automation-template-read-list", + "summary": "List Automation templates", + "description": "List preset Automation templates for the requested locale.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; results are filtered to the caller's visible scope |\n", + "href": "/en/api-reference/ai-sre/automations/automation-template-read-list", + "metadata": { + "sidebarTitle": "List Automation templates" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationTemplateListResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "templates": [ + { + "name": "Noise reduction", + "description": "Analyze recent alert noise and recommend cleanup actions.", + "icon": "bell-off", + "enabled": true, + "prompt": "Inspect alert noise, escalation load, and on-call handling in the last 24 hours." + } + ] + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationTemplateListRequest" + }, + "example": { + "locale": "en-US" + } + } + } + } + } + }, + "/safari/automation/run/list": { + "post": { + "operationId": "automation-run-read-list", + "summary": "List Automation runs", + "description": "List run history for a rule the caller can manage.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | Valid `app_key`; results are filtered to the caller's visible scope |\n", + "href": "/en/api-reference/ai-sre/automations/automation-run-read-list", + "metadata": { + "sidebarTitle": "List Automation runs" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRunListResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "total": 1, + "runs": [ + { + "run_id": "taskrun_5oDvqiG64uur6sBNsTc4u", + "kind": "automation_rule", + "account_id": 10023, + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "trigger_kind": "schedule", + "occurrence_key": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z:1780630800000", + "status": "succeeded", + "attempts": 1, + "started_at": 1780630800000, + "completed_at": 1780630923456, + "duration_ms": 123456, + "error_code": "", + "error_message": "", + "stats_json": {}, + "result_json": { + "session_id": "sess_f8oDvqiG64uur6sBNsTc4u" + }, + "created_at": 1780630800000, + "updated_at": 1780630923456 + } + ] + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRunListRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "limit": 20, + "trigger_kind": "schedule" + } + } + } + } + } + }, + "/safari/automation/triggers/{trigger_id}/fire": { + "post": { + "operationId": "automation-trigger-write-fire", + "summary": "Fire Automation HTTP POST trigger", + "description": "Trigger an Automation run through its HTTP POST trigger URL.", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AutomationTriggerBearerAuth": [] + } + ], + "x-mint": { + "content": "## Restrictions\n\n| Aspect | Value |\n| ------ | ----- |\n| Rate limits | **1,000 requests/minute**; **50 requests/second** per account |\n| Permissions | HTTP POST trigger Bearer token |\n\n## Usage\n\n- This endpoint does not use `app_key`. Put the token returned on Automation creation or token rotation in `Authorization: Bearer `.\n- Request body max size is 256 KiB and may be empty; `text` is passed to the agent as this run's context, and `dedup_key` provides idempotency.\n- A successful call returns `202 Accepted` with a run ID; the hidden session continues in the background.\n", + "href": "/en/api-reference/ai-sre/automations/automation-trigger-write-fire", + "metadata": { + "sidebarTitle": "Fire Automation HTTP POST trigger" + } + }, + "responses": { + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationFireAPITriggerResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "run_id": "taskrun_5oDvqiG64uur6sBNsTc4u", + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "trigger_kind": "http_post", + "status": "running" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "parameters": [ + { + "name": "trigger_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "HTTP POST trigger ID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationFireAPITriggerRequest" + }, + "example": { + "text": "A deployment finished for checkout-api. Check whether related alerts increased.", + "dedup_key": "deploy-2026-06-29-001" + } + } + } + } + } } }, "components": { @@ -24512,6 +25288,11 @@ "in": "query", "name": "app_key", "description": "App key issued from the Flashduty console under Account → APP Keys. Required on every public API call. Keep it secret — it grants the same access as the owning account." + }, + "AutomationTriggerBearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "Bearer token generated for one Automation HTTP POST trigger. This is not an app_key." } }, "responses": { @@ -44052,6 +44833,615 @@ "description": "ID of the created or updated template." } } + }, + "AutomationRuleCreateRequest": { + "type": "object", + "description": "Create an Automation rule.", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Rule name." + }, + "team_id": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "Scope team ID. 0 or omitted means a personal rule; >0 means a team in the account. Immutable after creation." + }, + "enabled": { + "type": "boolean", + "description": "Whether the rule is enabled after creation. Omitted API value is false; Chat/CLI create sends true by default unless the user asks for disabled." + }, + "cron_expr": { + "type": "string", + "description": "Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). The minute must be one fixed integer; 6-field seconds are not supported. The create API currently requires this field even for HTTP-POST-only rules; send a valid cron and set `schedule_trigger_enabled=false`.", + "example": "15 9 * * *" + }, + "schedule_trigger_enabled": { + "type": [ + "boolean", + "null" + ], + "description": "Whether the schedule trigger is enabled. Defaults to true when omitted; HTTP-POST-only rules should send false." + }, + "prompt": { + "type": "string", + "minLength": 1, + "description": "Task prompt sent to the AI SRE agent on each run." + }, + "environment_kind": { + "type": "string", + "description": "Runtime environment kind. Omit or send an empty value for automatic selection.", + "enum": [ + "", + "cloud", + "byoc" + ] + }, + "environment_id": { + "type": "string", + "description": "BYOC Runner ID. Used only when `environment_kind=byoc`." + }, + "http_post_trigger_enabled": { + "type": "boolean", + "description": "Whether to create and enable an HTTP POST trigger. When enabled, the response includes a one-time token." + } + }, + "required": [ + "name", + "cron_expr", + "prompt" + ] + }, + "AutomationRuleUpdateRequest": { + "type": "object", + "description": "Update an Automation rule. Omit fields to leave them unchanged.", + "properties": { + "rule_id": { + "type": "string", + "description": "Target rule ID." + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "New rule name." + }, + "team_id": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "Only the current value is accepted; personal/team scope is immutable after creation." + }, + "enabled": { + "type": "boolean", + "description": "Whether the rule is enabled." + }, + "cron_expr": { + "type": "string", + "description": "Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). The minute must be one fixed integer; 6-field seconds are not supported. The create API currently requires this field even for HTTP-POST-only rules; send a valid cron and set `schedule_trigger_enabled=false`.", + "example": "15 9 * * *" + }, + "schedule_trigger_enabled": { + "type": "boolean", + "description": "Whether the schedule trigger is enabled." + }, + "prompt": { + "type": "string", + "description": "New task prompt." + }, + "environment_kind": { + "type": "string", + "description": "Runtime environment kind. Omit or send an empty value for automatic selection.", + "enum": [ + "", + "cloud", + "byoc" + ] + }, + "environment_id": { + "type": "string", + "description": "BYOC Runner ID." + }, + "http_post_trigger_enabled": { + "type": "boolean", + "description": "Whether the HTTP POST trigger is enabled. Sending true creates one when missing." + }, + "rotate_http_post_trigger_token": { + "type": "boolean", + "description": "Whether to rotate the HTTP POST trigger token. The new token is returned only in this response." + } + }, + "required": [ + "rule_id" + ] + }, + "AutomationRuleIDRequest": { + "type": "object", + "properties": { + "rule_id": { + "type": "string", + "description": "Rule ID." + } + }, + "required": [ + "rule_id" + ] + }, + "AutomationRuleListRequest": { + "type": "object", + "description": "List Automation rules visible to the caller.", + "properties": { + "p": { + "type": "integer", + "default": 1, + "description": "Page number, 1-based." + }, + "limit": { + "type": "integer", + "default": 20, + "maximum": 100, + "description": "Page size." + }, + "scope": { + "type": "string", + "enum": [ + "all", + "personal", + "team" + ], + "description": "Scope filter. Defaults to all." + }, + "team_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Filter to these team IDs; this filters results and does not expand access." + }, + "include_person": { + "type": [ + "boolean", + "null" + ], + "description": "Compatibility field; when scope is empty and this is false, behaves like team scope." + }, + "enabled": { + "type": [ + "boolean", + "null" + ], + "description": "Filter by enabled status." + }, + "keyword": { + "type": "string", + "maxLength": 64, + "description": "Filter by name keyword." + } + } + }, + "AutomationRuleListResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64", + "description": "Total count." + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + }, + "required": [ + "total", + "rules" + ] + }, + "AutomationRuleItem": { + "type": "object", + "description": "Automation rule.", + "properties": { + "rule_id": { + "type": "string", + "description": "Rule ID." + }, + "account_id": { + "type": "integer", + "format": "int64", + "description": "Account ID." + }, + "team_id": { + "type": "integer", + "format": "int64", + "description": "Scope team ID; 0 means personal rule." + }, + "owner_id": { + "type": "integer", + "format": "int64", + "description": "Creator person ID." + }, + "name": { + "type": "string", + "description": "Rule name." + }, + "enabled": { + "type": "boolean", + "description": "Whether the rule is enabled." + }, + "run_scope": { + "type": "string", + "enum": [ + "person", + "team" + ], + "description": "Hidden session run scope." + }, + "cron_expr": { + "type": "string", + "description": "Normalized 5-field cron expression." + }, + "prompt": { + "type": "string", + "description": "Task prompt." + }, + "environment_kind": { + "type": "string", + "description": "Runtime environment kind. Omit or send an empty value for automatic selection.", + "enum": [ + "", + "cloud", + "byoc" + ] + }, + "environment_id": { + "type": "string", + "description": "BYOC Runner ID." + }, + "schedule_trigger_id": { + "type": "string", + "description": "Schedule trigger ID." + }, + "schedule_trigger_enabled": { + "type": "boolean", + "description": "Whether the schedule trigger is enabled." + }, + "http_post_trigger_id": { + "type": "string", + "description": "HTTP POST trigger ID." + }, + "http_post_trigger_url": { + "type": "string", + "description": "HTTP POST trigger path." + }, + "http_post_trigger_enabled": { + "type": "boolean", + "description": "Whether the HTTP POST trigger is enabled." + }, + "http_post_token": { + "type": "string", + "description": "HTTP POST trigger token. Returned only on create or token rotation; save it immediately." + }, + "can_edit": { + "type": "boolean", + "description": "Whether the caller can manage this rule." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "Creation time, Unix milliseconds." + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "Last update time, Unix milliseconds." + } + }, + "required": [ + "rule_id", + "account_id", + "team_id", + "owner_id", + "name", + "enabled", + "run_scope", + "cron_expr", + "prompt", + "environment_kind", + "environment_id", + "schedule_trigger_enabled", + "http_post_trigger_enabled", + "can_edit", + "created_at", + "updated_at" + ] + }, + "AutomationTemplateListRequest": { + "type": "object", + "properties": { + "locale": { + "type": "string", + "maxLength": 16, + "description": "Template locale such as zh-CN or en-US. Omit to detect from the request locale." + } + } + }, + "AutomationTemplateListResponse": { + "type": "object", + "properties": { + "templates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationTemplateItem" + } + } + }, + "required": [ + "templates" + ] + }, + "AutomationTemplateItem": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Template name." + }, + "description": { + "type": "string", + "description": "Template description." + }, + "icon": { + "type": "string", + "description": "Icon identifier." + }, + "enabled": { + "type": "boolean", + "description": "Whether the template is enabled." + }, + "prompt": { + "type": "string", + "description": "Template prompt." + } + }, + "required": [ + "name", + "description", + "icon", + "enabled", + "prompt" + ] + }, + "AutomationRunListRequest": { + "type": "object", + "properties": { + "rule_id": { + "type": "string", + "description": "Target rule ID." + }, + "p": { + "type": "integer", + "default": 1, + "description": "Page number, 1-based." + }, + "limit": { + "type": "integer", + "default": 20, + "maximum": 100, + "description": "Page size." + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "retrying", + "succeeded", + "partial", + "failed", + "skipped", + "abandoned" + ], + "description": "Run status filter." + }, + "trigger_kind": { + "type": "string", + "enum": [ + "schedule", + "debug", + "http_post" + ], + "description": "Trigger kind filter." + }, + "started_after_ms": { + "type": "integer", + "format": "int64", + "description": "Start-time lower bound, Unix milliseconds." + }, + "started_before_ms": { + "type": "integer", + "format": "int64", + "description": "Start-time upper bound, Unix milliseconds." + } + }, + "required": [ + "rule_id" + ] + }, + "AutomationRunListResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64", + "description": "Total count." + }, + "runs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationRunItem" + } + } + }, + "required": [ + "total", + "runs" + ] + }, + "AutomationRunItem": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "description": "Run ID." + }, + "kind": { + "type": "string", + "description": "Run kind." + }, + "account_id": { + "type": "integer", + "format": "int64", + "description": "Account ID." + }, + "rule_id": { + "type": "string", + "description": "Rule ID." + }, + "trigger_kind": { + "type": "string", + "enum": [ + "schedule", + "debug", + "http_post" + ], + "description": "Trigger kind." + }, + "occurrence_key": { + "type": "string", + "description": "Idempotency key for this occurrence." + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "retrying", + "succeeded", + "partial", + "failed", + "skipped", + "abandoned" + ], + "description": "Run status." + }, + "attempts": { + "type": "integer", + "description": "Attempt count." + }, + "started_at": { + "type": "integer", + "format": "int64", + "description": "Start time, Unix milliseconds." + }, + "completed_at": { + "type": "integer", + "format": "int64", + "description": "Completion time, Unix milliseconds. 0 means not completed." + }, + "duration_ms": { + "type": "integer", + "format": "int64", + "description": "Duration in milliseconds." + }, + "error_code": { + "type": "string", + "description": "Error code." + }, + "error_message": { + "type": "string", + "description": "Error message." + }, + "stats_json": { + "description": "Run stats JSON." + }, + "result_json": { + "description": "Run result JSON." + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "Creation time, Unix milliseconds." + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "Last update time, Unix milliseconds." + } + }, + "required": [ + "run_id", + "kind", + "account_id", + "rule_id", + "trigger_kind", + "occurrence_key", + "status", + "attempts", + "started_at", + "completed_at", + "duration_ms", + "created_at", + "updated_at" + ] + }, + "AutomationFireAPITriggerRequest": { + "type": "object", + "description": "HTTP POST trigger body. The body may be empty; when present, fields must be strings.", + "properties": { + "text": { + "type": "string", + "description": "Context text passed to this Automation run." + }, + "dedup_key": { + "type": "string", + "description": "Optional idempotency key; the same trigger + dedup_key reuses the same run." + } + } + }, + "AutomationFireAPITriggerResponse": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "description": "Created or reused run ID." + }, + "rule_id": { + "type": "string", + "description": "Rule ID." + }, + "trigger_kind": { + "type": "string", + "enum": [ + "http_post" + ], + "description": "Trigger kind." + }, + "status": { + "type": "string", + "description": "Current run status." + } + }, + "required": [ + "run_id", + "rule_id", + "trigger_kind", + "status" + ] } } } diff --git a/openapi/openapi.zh.json b/openapi/openapi.zh.json index a033db6..5a26bdf 100644 --- a/openapi/openapi.zh.json +++ b/openapi/openapi.zh.json @@ -132,6 +132,9 @@ { "name": "Monitors/通用工具", "description": "监控服务开通及数据预览工具。" + }, + { + "name": "AI SRE/Automations" } ], "paths": { @@ -24495,6 +24498,779 @@ } ] } + }, + "/safari/automation/rule/create": { + "post": { + "operationId": "automation-rule-write-create", + "summary": "创建自动化规则", + "description": "创建自动化规则,包含 schedule trigger,并可选启用 HTTP POST trigger。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;管理操作要求调用者可管理目标规则 |\n\n## 使用说明\n\n- 可以创建个人规则,也可以创建当前 account 下任意团队规则;`team_id` 创建后不可修改。\n- 列表可见性与 Session 列表对齐:Owner / 管理员可见全部;普通成员可见自己创建的规则和自己团队的规则。\n- `http_post_token` 只在创建或轮换 token 的响应中返回,请立即保存。\n", + "href": "/zh/api-reference/ai-sre/automations/automation-rule-write-create", + "metadata": { + "sidebarTitle": "创建自动化规则" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228, + "http_post_token": "sat_yQ9p8V7n6M5k4J3h2G1f0E9d8C7b6A5z4Y3x2W1v0U" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleCreateRequest" + }, + "example": { + "name": "Weekly on-call review", + "team_id": 123, + "enabled": true, + "cron_expr": "0 9 * * 1", + "schedule_trigger_enabled": true, + "prompt": "Summarize last week's alert noise and escalation load.", + "http_post_trigger_enabled": true + } + } + } + } + } + }, + "/safari/automation/rule/list": { + "post": { + "operationId": "automation-rule-read-list", + "summary": "列出自动化规则", + "description": "列出当前调用者可见的自动化规则。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;结果按调用者可见范围过滤 |\n", + "href": "/zh/api-reference/ai-sre/automations/automation-rule-read-list", + "metadata": { + "sidebarTitle": "列出自动化规则" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleListResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "total": 1, + "rules": [ + { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228 + } + ] + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleListRequest" + }, + "example": { + "scope": "all", + "limit": 20 + } + } + } + } + } + }, + "/safari/automation/rule/get": { + "post": { + "operationId": "automation-rule-read-get", + "summary": "查看自动化规则", + "description": "按 ID 查看一条自动化规则。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;结果按调用者可见范围过滤 |\n", + "href": "/zh/api-reference/ai-sre/automations/automation-rule-read-get", + "metadata": { + "sidebarTitle": "查看自动化规则" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228 + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleIDRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b" + } + } + } + } + } + }, + "/safari/automation/rule/update": { + "post": { + "operationId": "automation-rule-write-update", + "summary": "更新自动化规则", + "description": "更新自动化规则的可变字段。personal / team scope 创建后不可修改。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;管理操作要求调用者可管理目标规则 |\n\n## 使用说明\n\n- 可以创建个人规则,也可以创建当前 account 下任意团队规则;`team_id` 创建后不可修改。\n- 列表可见性与 Session 列表对齐:Owner / 管理员可见全部;普通成员可见自己创建的规则和自己团队的规则。\n- `http_post_token` 只在创建或轮换 token 的响应中返回,请立即保存。\n", + "href": "/zh/api-reference/ai-sre/automations/automation-rule-write-update", + "metadata": { + "sidebarTitle": "更新自动化规则" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "account_id": 10023, + "team_id": 123, + "owner_id": 80011, + "name": "Weekly on-call review", + "enabled": true, + "run_scope": "team", + "cron_expr": "0 9 * * 1", + "prompt": "Summarize last week's alert noise and escalation load.", + "environment_kind": "", + "environment_id": "", + "schedule_trigger_id": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z", + "schedule_trigger_enabled": true, + "http_post_trigger_id": "autotrg_2bLq4xT8mP1sWd9cN3rF6y", + "http_post_trigger_url": "/safari/automation/triggers/autotrg_2bLq4xT8mP1sWd9cN3rF6y/fire", + "http_post_trigger_enabled": true, + "can_edit": true, + "created_at": 1780367971228, + "updated_at": 1780367971228, + "http_post_token": "sat_yQ9p8V7n6M5k4J3h2G1f0E9d8C7b6A5z4Y3x2W1v0U" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleUpdateRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "enabled": true, + "cron_expr": "15 9 * * 1", + "rotate_http_post_trigger_token": true + } + } + } + } + } + }, + "/safari/automation/rule/delete": { + "post": { + "operationId": "automation-rule-write-delete", + "summary": "删除自动化规则", + "description": "删除一条自动化规则。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;管理操作要求调用者可管理目标规则 |\n\n## 使用说明\n\n- 可以创建个人规则,也可以创建当前 account 下任意团队规则;`team_id` 创建后不可修改。\n- 列表可见性与 Session 列表对齐:Owner / 管理员可见全部;普通成员可见自己创建的规则和自己团队的规则。\n- `http_post_token` 只在创建或轮换 token 的响应中返回,请立即保存。\n", + "href": "/zh/api-reference/ai-sre/automations/automation-rule-write-delete", + "metadata": { + "sidebarTitle": "删除自动化规则" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "type": "null", + "description": "成功时固定为 null。" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": null + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRuleIDRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b" + } + } + } + } + } + }, + "/safari/automation/template/list": { + "post": { + "operationId": "automation-template-read-list", + "summary": "列出自动化模板", + "description": "按语言列出自动化预设模板。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;结果按调用者可见范围过滤 |\n", + "href": "/zh/api-reference/ai-sre/automations/automation-template-read-list", + "metadata": { + "sidebarTitle": "列出自动化模板" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationTemplateListResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "templates": [ + { + "name": "噪音治理", + "description": "分析近期告警噪音并给出治理建议。", + "icon": "bell-off", + "enabled": true, + "prompt": "检查过去 24 小时告警噪音、升级负载和值班处理情况。" + } + ] + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationTemplateListRequest" + }, + "example": { + "locale": "en-US" + } + } + } + } + } + }, + "/safari/automation/run/list": { + "post": { + "operationId": "automation-run-read-list", + "summary": "列出自动化运行历史", + "description": "列出调用者可管理规则的运行历史。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AppKeyAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | 有效 `app_key`;结果按调用者可见范围过滤 |\n", + "href": "/zh/api-reference/ai-sre/automations/automation-run-read-list", + "metadata": { + "sidebarTitle": "列出自动化运行历史" + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationRunListResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "total": 1, + "runs": [ + { + "run_id": "taskrun_5oDvqiG64uur6sBNsTc4u", + "kind": "automation_rule", + "account_id": 10023, + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "trigger_kind": "schedule", + "occurrence_key": "autotrg_6aKp3wT9mQ2xVc8bR1nY7z:1780630800000", + "status": "succeeded", + "attempts": 1, + "started_at": 1780630800000, + "completed_at": 1780630923456, + "duration_ms": 123456, + "error_code": "", + "error_message": "", + "stats_json": {}, + "result_json": { + "session_id": "sess_f8oDvqiG64uur6sBNsTc4u" + }, + "created_at": 1780630800000, + "updated_at": 1780630923456 + } + ] + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationRunListRequest" + }, + "example": { + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "limit": 20, + "trigger_kind": "schedule" + } + } + } + } + } + }, + "/safari/automation/triggers/{trigger_id}/fire": { + "post": { + "operationId": "automation-trigger-write-fire", + "summary": "触发自动化 HTTP POST trigger", + "description": "通过 HTTP POST trigger URL 触发一次自动化运行。", + "tags": [ + "AI SRE/Automations" + ], + "security": [ + { + "AutomationTriggerBearerAuth": [] + } + ], + "x-mint": { + "content": "## 调用限制\n\n| 项 | 值 |\n| ------ | ----- |\n| 速率限制 | **1,000 次/分钟**;**50 次/秒** 每账户 |\n| 权限 | HTTP POST trigger Bearer Token |\n\n## 使用说明\n\n- 此接口不使用 `app_key`。将自动化创建或 token 轮换时返回的 token 放在 `Authorization: Bearer ` 请求头中。\n- 请求体最大 256 KiB,可为空;`text` 会作为本次运行上下文传给 Agent,`dedup_key` 用于幂等。\n- 成功后立即返回 `202 Accepted` 和 run ID,实际会话在后台运行。\n", + "href": "/zh/api-reference/ai-sre/automations/automation-trigger-write-fire", + "metadata": { + "sidebarTitle": "触发自动化 HTTP POST trigger" + } + }, + "responses": { + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseEnvelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/AutomationFireAPITriggerResponse" + } + } + } + ] + }, + "example": { + "request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4", + "data": { + "run_id": "taskrun_5oDvqiG64uur6sBNsTc4u", + "rule_id": "auto_7NnLzY2Qp8xS4kUaV3mR6b", + "trigger_kind": "http_post", + "status": "running" + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests" + }, + "500": { + "$ref": "#/components/responses/ServerError" + } + }, + "parameters": [ + { + "name": "trigger_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "HTTP POST trigger ID。" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AutomationFireAPITriggerRequest" + }, + "example": { + "text": "A deployment finished for checkout-api. Check whether related alerts increased.", + "dedup_key": "deploy-2026-06-29-001" + } + } + } + } + } } }, "components": { @@ -24504,6 +25280,11 @@ "in": "query", "name": "app_key", "description": "在 Flashduty 控制台 账户 → APP Key 中签发的 app_key。调用任何公开 API 时都必须携带。它等同于所属账户的身份凭证,请妥善保管。" + }, + "AutomationTriggerBearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "自动化 HTTP POST 触发器生成的一次性 Bearer Token。不要把它当作 app_key 使用。" } }, "responses": { @@ -44043,6 +44824,615 @@ "description": "创建或更新的模板 ID。" } } + }, + "AutomationRuleCreateRequest": { + "type": "object", + "description": "创建自动化规则。", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "规则名称。" + }, + "team_id": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "作用域团队 ID。0 或省略表示个人规则;>0 表示账户下某团队。创建后不可修改。" + }, + "enabled": { + "type": "boolean", + "description": "规则创建后是否启用。API 省略时为 false;Chat/CLI 入口会默认发送 true,除非用户要求禁用。" + }, + "cron_expr": { + "type": "string", + "description": "运行周期。支持 4 段 `hour day month weekday`,会补 `minute=0`;也支持 5 段 `minute hour day month weekday`。分钟必须是固定整数,秒级 6 段不支持。创建 API 当前要求该字段,即使只启用 HTTP POST trigger,也要提供一个有效 cron 并把 `schedule_trigger_enabled` 设为 false。", + "example": "15 9 * * *" + }, + "schedule_trigger_enabled": { + "type": [ + "boolean", + "null" + ], + "description": "是否启用 schedule trigger。省略时为 true;HTTP-POST-only 规则应传 false。" + }, + "prompt": { + "type": "string", + "minLength": 1, + "description": "每次运行发给 AI SRE Agent 的任务提示词。" + }, + "environment_kind": { + "type": "string", + "description": "运行环境类型。省略或空字符串表示自动选择。", + "enum": [ + "", + "cloud", + "byoc" + ] + }, + "environment_id": { + "type": "string", + "description": "BYOC Runner ID。仅 `environment_kind=byoc` 时使用。" + }, + "http_post_trigger_enabled": { + "type": "boolean", + "description": "是否创建并启用 HTTP POST trigger。启用时响应里会返回一次性 token。" + } + }, + "required": [ + "name", + "cron_expr", + "prompt" + ] + }, + "AutomationRuleUpdateRequest": { + "type": "object", + "description": "更新自动化规则。省略字段表示不修改。", + "properties": { + "rule_id": { + "type": "string", + "description": "目标规则 ID。" + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "新规则名称。" + }, + "team_id": { + "type": "integer", + "format": "int64", + "minimum": 0, + "description": "只允许传当前值;创建后 personal / team scope 不可修改。" + }, + "enabled": { + "type": "boolean", + "description": "是否启用规则。" + }, + "cron_expr": { + "type": "string", + "description": "运行周期。支持 4 段 `hour day month weekday`,会补 `minute=0`;也支持 5 段 `minute hour day month weekday`。分钟必须是固定整数,秒级 6 段不支持。创建 API 当前要求该字段,即使只启用 HTTP POST trigger,也要提供一个有效 cron 并把 `schedule_trigger_enabled` 设为 false。", + "example": "15 9 * * *" + }, + "schedule_trigger_enabled": { + "type": "boolean", + "description": "是否启用 schedule trigger。" + }, + "prompt": { + "type": "string", + "description": "新的任务提示词。" + }, + "environment_kind": { + "type": "string", + "description": "运行环境类型。省略或空字符串表示自动选择。", + "enum": [ + "", + "cloud", + "byoc" + ] + }, + "environment_id": { + "type": "string", + "description": "BYOC Runner ID。" + }, + "http_post_trigger_enabled": { + "type": "boolean", + "description": "是否启用 HTTP POST trigger。不存在时设为 true 会创建。" + }, + "rotate_http_post_trigger_token": { + "type": "boolean", + "description": "是否轮换 HTTP POST trigger token。新 token 只会在本次响应中返回。" + } + }, + "required": [ + "rule_id" + ] + }, + "AutomationRuleIDRequest": { + "type": "object", + "properties": { + "rule_id": { + "type": "string", + "description": "规则 ID。" + } + }, + "required": [ + "rule_id" + ] + }, + "AutomationRuleListRequest": { + "type": "object", + "description": "列出当前调用者可见的自动化规则。", + "properties": { + "p": { + "type": "integer", + "default": 1, + "description": "页码,从 1 开始。" + }, + "limit": { + "type": "integer", + "default": 20, + "maximum": 100, + "description": "每页数量。" + }, + "scope": { + "type": "string", + "enum": [ + "all", + "personal", + "team" + ], + "description": "作用域过滤。默认 all。" + }, + "team_ids": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "额外过滤到这些团队 ID;这是过滤器,不是扩权。" + }, + "include_person": { + "type": [ + "boolean", + "null" + ], + "description": "兼容字段;scope 为空且为 false 时等同于 team。" + }, + "enabled": { + "type": [ + "boolean", + "null" + ], + "description": "按启用状态过滤。" + }, + "keyword": { + "type": "string", + "maxLength": 64, + "description": "按名称关键字过滤。" + } + } + }, + "AutomationRuleListResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64", + "description": "总数。" + }, + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationRuleItem" + } + } + }, + "required": [ + "total", + "rules" + ] + }, + "AutomationRuleItem": { + "type": "object", + "description": "自动化规则。", + "properties": { + "rule_id": { + "type": "string", + "description": "规则 ID。" + }, + "account_id": { + "type": "integer", + "format": "int64", + "description": "账户 ID。" + }, + "team_id": { + "type": "integer", + "format": "int64", + "description": "作用域团队 ID;0 表示个人规则。" + }, + "owner_id": { + "type": "integer", + "format": "int64", + "description": "创建者 person ID。" + }, + "name": { + "type": "string", + "description": "规则名称。" + }, + "enabled": { + "type": "boolean", + "description": "规则是否启用。" + }, + "run_scope": { + "type": "string", + "enum": [ + "person", + "team" + ], + "description": "运行会话作用域。" + }, + "cron_expr": { + "type": "string", + "description": "规范化后的 5 段 cron 表达式。" + }, + "prompt": { + "type": "string", + "description": "任务提示词。" + }, + "environment_kind": { + "type": "string", + "description": "运行环境类型。省略或空字符串表示自动选择。", + "enum": [ + "", + "cloud", + "byoc" + ] + }, + "environment_id": { + "type": "string", + "description": "BYOC Runner ID。" + }, + "schedule_trigger_id": { + "type": "string", + "description": "Schedule trigger ID。" + }, + "schedule_trigger_enabled": { + "type": "boolean", + "description": "Schedule trigger 是否启用。" + }, + "http_post_trigger_id": { + "type": "string", + "description": "HTTP POST trigger ID。" + }, + "http_post_trigger_url": { + "type": "string", + "description": "HTTP POST 触发路径。" + }, + "http_post_trigger_enabled": { + "type": "boolean", + "description": "HTTP POST trigger 是否启用。" + }, + "http_post_token": { + "type": "string", + "description": "HTTP POST trigger token。只在创建或轮换 token 的响应中返回;请立即保存。" + }, + "can_edit": { + "type": "boolean", + "description": "当前调用者是否可管理该规则。" + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "创建时间,Unix 毫秒。" + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "更新时间,Unix 毫秒。" + } + }, + "required": [ + "rule_id", + "account_id", + "team_id", + "owner_id", + "name", + "enabled", + "run_scope", + "cron_expr", + "prompt", + "environment_kind", + "environment_id", + "schedule_trigger_enabled", + "http_post_trigger_enabled", + "can_edit", + "created_at", + "updated_at" + ] + }, + "AutomationTemplateListRequest": { + "type": "object", + "properties": { + "locale": { + "type": "string", + "maxLength": 16, + "description": "模板语言,例如 zh-CN 或 en-US。省略时按请求语言自动选择。" + } + } + }, + "AutomationTemplateListResponse": { + "type": "object", + "properties": { + "templates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationTemplateItem" + } + } + }, + "required": [ + "templates" + ] + }, + "AutomationTemplateItem": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "模板名称。" + }, + "description": { + "type": "string", + "description": "模板说明。" + }, + "icon": { + "type": "string", + "description": "图标标识。" + }, + "enabled": { + "type": "boolean", + "description": "模板是否可用。" + }, + "prompt": { + "type": "string", + "description": "模板提示词。" + } + }, + "required": [ + "name", + "description", + "icon", + "enabled", + "prompt" + ] + }, + "AutomationRunListRequest": { + "type": "object", + "properties": { + "rule_id": { + "type": "string", + "description": "目标规则 ID。" + }, + "p": { + "type": "integer", + "default": 1, + "description": "页码,从 1 开始。" + }, + "limit": { + "type": "integer", + "default": 20, + "maximum": 100, + "description": "每页数量。" + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "retrying", + "succeeded", + "partial", + "failed", + "skipped", + "abandoned" + ], + "description": "运行状态过滤。" + }, + "trigger_kind": { + "type": "string", + "enum": [ + "schedule", + "debug", + "http_post" + ], + "description": "触发方式过滤。" + }, + "started_after_ms": { + "type": "integer", + "format": "int64", + "description": "开始时间下界,Unix 毫秒。" + }, + "started_before_ms": { + "type": "integer", + "format": "int64", + "description": "开始时间上界,Unix 毫秒。" + } + }, + "required": [ + "rule_id" + ] + }, + "AutomationRunListResponse": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64", + "description": "总数。" + }, + "runs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AutomationRunItem" + } + } + }, + "required": [ + "total", + "runs" + ] + }, + "AutomationRunItem": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "description": "运行 ID。" + }, + "kind": { + "type": "string", + "description": "运行类型。" + }, + "account_id": { + "type": "integer", + "format": "int64", + "description": "账户 ID。" + }, + "rule_id": { + "type": "string", + "description": "规则 ID。" + }, + "trigger_kind": { + "type": "string", + "enum": [ + "schedule", + "debug", + "http_post" + ], + "description": "触发方式。" + }, + "occurrence_key": { + "type": "string", + "description": "幂等键。" + }, + "status": { + "type": "string", + "enum": [ + "queued", + "running", + "retrying", + "succeeded", + "partial", + "failed", + "skipped", + "abandoned" + ], + "description": "运行状态。" + }, + "attempts": { + "type": "integer", + "description": "尝试次数。" + }, + "started_at": { + "type": "integer", + "format": "int64", + "description": "开始时间,Unix 毫秒。" + }, + "completed_at": { + "type": "integer", + "format": "int64", + "description": "完成时间,Unix 毫秒。0 表示尚未完成。" + }, + "duration_ms": { + "type": "integer", + "format": "int64", + "description": "运行耗时,毫秒。" + }, + "error_code": { + "type": "string", + "description": "错误码。" + }, + "error_message": { + "type": "string", + "description": "错误消息。" + }, + "stats_json": { + "description": "统计 JSON。" + }, + "result_json": { + "description": "结果 JSON。" + }, + "created_at": { + "type": "integer", + "format": "int64", + "description": "创建时间,Unix 毫秒。" + }, + "updated_at": { + "type": "integer", + "format": "int64", + "description": "更新时间,Unix 毫秒。" + } + }, + "required": [ + "run_id", + "kind", + "account_id", + "rule_id", + "trigger_kind", + "occurrence_key", + "status", + "attempts", + "started_at", + "completed_at", + "duration_ms", + "created_at", + "updated_at" + ] + }, + "AutomationFireAPITriggerRequest": { + "type": "object", + "description": "HTTP POST trigger 请求体。请求体可为空;字段存在时必须是字符串。", + "properties": { + "text": { + "type": "string", + "description": "传给本次自动化运行的上下文文本。" + }, + "dedup_key": { + "type": "string", + "description": "可选幂等键;相同 trigger + dedup_key 会复用同一次运行。" + } + } + }, + "AutomationFireAPITriggerResponse": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "description": "已创建或复用的运行 ID。" + }, + "rule_id": { + "type": "string", + "description": "规则 ID。" + }, + "trigger_kind": { + "type": "string", + "enum": [ + "http_post" + ], + "description": "触发方式。" + }, + "status": { + "type": "string", + "description": "运行当前状态。" + } + }, + "required": [ + "run_id", + "rule_id", + "trigger_kind", + "status" + ] } } } diff --git a/roundtrip_gen_test.go b/roundtrip_gen_test.go index 781eae4..c9341c0 100644 --- a/roundtrip_gen_test.go +++ b/roundtrip_gen_test.go @@ -146,6 +146,13 @@ var exampleDataDecoders = map[string]func(json.RawMessage) error{ "POST /safari/a2a-agent/get": func(d json.RawMessage) error { var v A2aAgentItem; return json.Unmarshal(d, &v) }, "POST /safari/a2a-agent/list": func(d json.RawMessage) error { var v A2aAgentListResponse; return json.Unmarshal(d, &v) }, "POST /safari/a2a-agent/update": func(d json.RawMessage) error { var v any; return json.Unmarshal(d, &v) }, + "POST /safari/automation/rule/create": func(d json.RawMessage) error { var v AutomationRuleItem; return json.Unmarshal(d, &v) }, + "POST /safari/automation/rule/delete": func(d json.RawMessage) error { var v any; return json.Unmarshal(d, &v) }, + "POST /safari/automation/rule/get": func(d json.RawMessage) error { var v AutomationRuleItem; return json.Unmarshal(d, &v) }, + "POST /safari/automation/rule/list": func(d json.RawMessage) error { var v AutomationRuleListResponse; return json.Unmarshal(d, &v) }, + "POST /safari/automation/rule/update": func(d json.RawMessage) error { var v AutomationRuleItem; return json.Unmarshal(d, &v) }, + "POST /safari/automation/run/list": func(d json.RawMessage) error { var v AutomationRunListResponse; return json.Unmarshal(d, &v) }, + "POST /safari/automation/template/list": func(d json.RawMessage) error { var v AutomationTemplateListResponse; return json.Unmarshal(d, &v) }, "POST /safari/mcp/server/create": func(d json.RawMessage) error { var v McpServerItem; return json.Unmarshal(d, &v) }, "POST /safari/mcp/server/delete": func(d json.RawMessage) error { var v any; return json.Unmarshal(d, &v) }, "POST /safari/mcp/server/disable": func(d json.RawMessage) error { var v any; return json.Unmarshal(d, &v) }, diff --git a/services_gen.go b/services_gen.go index 6cd83ee..caa7f27 100644 --- a/services_gen.go +++ b/services_gen.go @@ -9,6 +9,7 @@ type service struct{ client *Client } // genServices is embedded in Client to expose the generated service handles. type genServices struct { A2aAgents *A2aAgentsService + Automations *AutomationsService McpServers *McpServersService Sessions *SessionsService Skills *SkillsService @@ -43,6 +44,7 @@ type genServices struct { func (c *Client) initServices() { c.common.client = c c.A2aAgents = (*A2aAgentsService)(&c.common) + c.Automations = (*AutomationsService)(&c.common) c.McpServers = (*McpServersService)(&c.common) c.Sessions = (*SessionsService)(&c.common) c.Skills = (*SkillsService)(&c.common) From 22c4ff1157e59736372b2e2055a4814c50235276 Mon Sep 17 00:00:00 2001 From: debidong <1953531014@qq.com> Date: Mon, 29 Jun 2026 13:29:02 +0800 Subject: [PATCH 2/2] fix: keep automation fire request scoped --- automations_fire.go | 10 +++------- flashduty.go | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/automations_fire.go b/automations_fire.go index ba4b55d..906ba9f 100644 --- a/automations_fire.go +++ b/automations_fire.go @@ -25,14 +25,10 @@ func (s *AutomationsService) TriggerWriteFire(ctx context.Context, triggerID, to } path := "/safari/automation/triggers/" + url.PathEscape(triggerID) + "/fire" - httpReq, err := s.client.newRequestWithoutAppKey(ctx, http.MethodPost, path, req) - if err != nil { - return nil, nil, err - } - httpReq.Header.Set("Authorization", "Bearer "+token) - out := new(AutomationFireAPITriggerResponse) - resp, err := s.client.doRequest(httpReq, out) + resp, err := s.client.doMethodWithoutAppKey(ctx, http.MethodPost, path, req, out, func(httpReq *http.Request) { + httpReq.Header.Set("Authorization", "Bearer "+token) + }) if err != nil { return nil, resp, err } diff --git a/flashduty.go b/flashduty.go index 5608c2a..4fc625b 100644 --- a/flashduty.go +++ b/flashduty.go @@ -100,13 +100,6 @@ func (c *Client) newRequest(ctx context.Context, method, path string, body any) return c.newRequestWithAppKey(ctx, method, path, body, true) } -// newRequestWithoutAppKey builds a request for endpoints that use their own -// authentication scheme instead of the account app_key, such as Automation HTTP -// POST trigger bearer tokens. -func (c *Client) newRequestWithoutAppKey(ctx context.Context, method, path string, body any) (*http.Request, error) { - return c.newRequestWithAppKey(ctx, method, path, body, false) -} - func (c *Client) newRequestWithAppKey(ctx context.Context, method, path string, body any, withAppKey bool) (*http.Request, error) { rel, err := url.Parse(strings.TrimPrefix(path, "/")) if err != nil { @@ -177,14 +170,21 @@ func (c *Client) doGet(ctx context.Context, path string, opt, out any) (*Respons // (when non-nil), and returns a Response. A non-nil envelope error or a non-2xx // status yields an *ErrorResponse (or *RateLimitError on 429). func (c *Client) doMethod(ctx context.Context, method, path string, body, out any) (*Response, error) { - req, err := c.newRequest(ctx, method, path, body) + return c.doMethodWithAppKey(ctx, method, path, body, out, true, nil) +} + +func (c *Client) doMethodWithoutAppKey(ctx context.Context, method, path string, body, out any, configure func(*http.Request)) (*Response, error) { + return c.doMethodWithAppKey(ctx, method, path, body, out, false, configure) +} + +func (c *Client) doMethodWithAppKey(ctx context.Context, method, path string, body, out any, withAppKey bool, configure func(*http.Request)) (*Response, error) { + req, err := c.newRequestWithAppKey(ctx, method, path, body, withAppKey) if err != nil { return nil, err } - return c.doRequest(req, out) -} - -func (c *Client) doRequest(req *http.Request, out any) (*Response, error) { + if configure != nil { + configure(req) + } httpResp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("flashduty: request to %s failed: %v", sanitizeURL(req.URL), sanitizeError(err))