mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-01-17 05:02:43 +00:00
The Gitea API expects workflow_id as a string (filename like 'my-workflow.yml' or numeric ID as string), not as a number. This was causing 404 errors when trying to get or dispatch workflows. Affected tools: - get_repo_action_workflow - dispatch_repo_action_workflow Co-authored-by: runixer <runixer@yandex.ru> Co-authored-by: hiifong <f@f.style> Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/114 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: runixer <runixer@noreply.gitea.com> Co-committed-by: runixer <runixer@noreply.gitea.com>
469 lines
17 KiB
Go
469 lines
17 KiB
Go
package actions
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
|
"gitea.com/gitea/gitea-mcp/pkg/log"
|
|
"gitea.com/gitea/gitea-mcp/pkg/to"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
)
|
|
|
|
const (
|
|
ListRepoActionWorkflowsToolName = "list_repo_action_workflows"
|
|
GetRepoActionWorkflowToolName = "get_repo_action_workflow"
|
|
DispatchRepoActionWorkflowToolName = "dispatch_repo_action_workflow"
|
|
|
|
ListRepoActionRunsToolName = "list_repo_action_runs"
|
|
GetRepoActionRunToolName = "get_repo_action_run"
|
|
CancelRepoActionRunToolName = "cancel_repo_action_run"
|
|
RerunRepoActionRunToolName = "rerun_repo_action_run"
|
|
|
|
ListRepoActionJobsToolName = "list_repo_action_jobs"
|
|
ListRepoActionRunJobsToolName = "list_repo_action_run_jobs"
|
|
)
|
|
|
|
var (
|
|
ListRepoActionWorkflowsTool = mcp.NewTool(
|
|
ListRepoActionWorkflowsToolName,
|
|
mcp.WithDescription("List repository Actions workflows"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
|
|
)
|
|
|
|
GetRepoActionWorkflowTool = mcp.NewTool(
|
|
GetRepoActionWorkflowToolName,
|
|
mcp.WithDescription("Get a repository Actions workflow by ID"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("workflow ID or filename (e.g. 'my-workflow.yml')")),
|
|
)
|
|
|
|
DispatchRepoActionWorkflowTool = mcp.NewTool(
|
|
DispatchRepoActionWorkflowToolName,
|
|
mcp.WithDescription("Trigger (dispatch) a repository Actions workflow"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithString("workflow_id", mcp.Required(), mcp.Description("workflow ID or filename (e.g. 'my-workflow.yml')")),
|
|
mcp.WithString("ref", mcp.Required(), mcp.Description("git ref (branch or tag)")),
|
|
mcp.WithObject("inputs", mcp.Description("workflow inputs object")),
|
|
)
|
|
|
|
ListRepoActionRunsTool = mcp.NewTool(
|
|
ListRepoActionRunsToolName,
|
|
mcp.WithDescription("List repository Actions workflow runs"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
|
|
mcp.WithString("status", mcp.Description("optional status filter")),
|
|
)
|
|
|
|
GetRepoActionRunTool = mcp.NewTool(
|
|
GetRepoActionRunToolName,
|
|
mcp.WithDescription("Get a repository Actions run by ID"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("run_id", mcp.Required(), mcp.Description("run ID")),
|
|
)
|
|
|
|
CancelRepoActionRunTool = mcp.NewTool(
|
|
CancelRepoActionRunToolName,
|
|
mcp.WithDescription("Cancel a repository Actions run"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("run_id", mcp.Required(), mcp.Description("run ID")),
|
|
)
|
|
|
|
RerunRepoActionRunTool = mcp.NewTool(
|
|
RerunRepoActionRunToolName,
|
|
mcp.WithDescription("Rerun a repository Actions run"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("run_id", mcp.Required(), mcp.Description("run ID")),
|
|
)
|
|
|
|
ListRepoActionJobsTool = mcp.NewTool(
|
|
ListRepoActionJobsToolName,
|
|
mcp.WithDescription("List repository Actions jobs"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
|
|
mcp.WithString("status", mcp.Description("optional status filter")),
|
|
)
|
|
|
|
ListRepoActionRunJobsTool = mcp.NewTool(
|
|
ListRepoActionRunJobsToolName,
|
|
mcp.WithDescription("List Actions jobs for a specific run"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
mcp.WithNumber("run_id", mcp.Required(), mcp.Description("run ID")),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
|
|
)
|
|
)
|
|
|
|
func init() {
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListRepoActionWorkflowsTool, Handler: ListRepoActionWorkflowsFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: GetRepoActionWorkflowTool, Handler: GetRepoActionWorkflowFn})
|
|
Tool.RegisterWrite(server.ServerTool{Tool: DispatchRepoActionWorkflowTool, Handler: DispatchRepoActionWorkflowFn})
|
|
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListRepoActionRunsTool, Handler: ListRepoActionRunsFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: GetRepoActionRunTool, Handler: GetRepoActionRunFn})
|
|
Tool.RegisterWrite(server.ServerTool{Tool: CancelRepoActionRunTool, Handler: CancelRepoActionRunFn})
|
|
Tool.RegisterWrite(server.ServerTool{Tool: RerunRepoActionRunTool, Handler: RerunRepoActionRunFn})
|
|
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListRepoActionJobsTool, Handler: ListRepoActionJobsFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListRepoActionRunJobsTool, Handler: ListRepoActionRunJobsFn})
|
|
}
|
|
|
|
func doJSONWithFallback(ctx context.Context, method string, paths []string, query url.Values, body any, respOut any) (string, int, error) {
|
|
var lastErr error
|
|
for _, p := range paths {
|
|
status, err := gitea.DoJSON(ctx, method, p, query, body, respOut)
|
|
if err == nil {
|
|
return p, status, nil
|
|
}
|
|
lastErr = err
|
|
var httpErr *gitea.HTTPError
|
|
if errors.As(err, &httpErr) && (httpErr.StatusCode == 404 || httpErr.StatusCode == 405) {
|
|
continue
|
|
}
|
|
return p, status, err
|
|
}
|
|
return "", 0, lastErr
|
|
}
|
|
|
|
func ListRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListRepoActionWorkflowsFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
page, _ := req.GetArguments()["page"].(float64)
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
pageSize, _ := req.GetArguments()["pageSize"].(float64)
|
|
if pageSize <= 0 {
|
|
pageSize = 50
|
|
}
|
|
query := url.Values{}
|
|
query.Set("page", fmt.Sprintf("%d", int(page)))
|
|
query.Set("limit", fmt.Sprintf("%d", int(pageSize)))
|
|
|
|
var result any
|
|
_, _, err := doJSONWithFallback(ctx, "GET",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/workflows", url.PathEscape(owner), url.PathEscape(repo)),
|
|
},
|
|
query, nil, &result,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list action workflows err: %v", err))
|
|
}
|
|
return to.TextResult(result)
|
|
}
|
|
|
|
func GetRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called GetRepoActionWorkflowFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
workflowID, ok := req.GetArguments()["workflow_id"].(string)
|
|
if !ok || workflowID == "" {
|
|
return to.ErrorResult(fmt.Errorf("workflow_id is required"))
|
|
}
|
|
|
|
var result any
|
|
_, _, err := doJSONWithFallback(ctx, "GET",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/workflows/%s", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)),
|
|
},
|
|
nil, nil, &result,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get action workflow err: %v", err))
|
|
}
|
|
return to.TextResult(result)
|
|
}
|
|
|
|
func DispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called DispatchRepoActionWorkflowFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
workflowID, ok := req.GetArguments()["workflow_id"].(string)
|
|
if !ok || workflowID == "" {
|
|
return to.ErrorResult(fmt.Errorf("workflow_id is required"))
|
|
}
|
|
ref, ok := req.GetArguments()["ref"].(string)
|
|
if !ok || ref == "" {
|
|
return to.ErrorResult(fmt.Errorf("ref is required"))
|
|
}
|
|
|
|
var inputs map[string]any
|
|
if raw, exists := req.GetArguments()["inputs"]; exists {
|
|
if m, ok := raw.(map[string]any); ok {
|
|
inputs = m
|
|
} else if m, ok := raw.(map[string]interface{}); ok {
|
|
inputs = make(map[string]any, len(m))
|
|
for k, v := range m {
|
|
inputs[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
body := map[string]any{
|
|
"ref": ref,
|
|
}
|
|
if inputs != nil {
|
|
body["inputs"] = inputs
|
|
}
|
|
|
|
_, _, err := doJSONWithFallback(ctx, "POST",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/workflows/%s/dispatches", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)),
|
|
fmt.Sprintf("repos/%s/%s/actions/workflows/%s/dispatch", url.PathEscape(owner), url.PathEscape(repo), url.PathEscape(workflowID)),
|
|
},
|
|
nil, body, nil,
|
|
)
|
|
if err != nil {
|
|
var httpErr *gitea.HTTPError
|
|
if errors.As(err, &httpErr) && (httpErr.StatusCode == 404 || httpErr.StatusCode == 405) {
|
|
return to.ErrorResult(fmt.Errorf("workflow dispatch not supported on this Gitea version (endpoint returned %d). Check https://docs.gitea.com/api/1.24/ for available Actions endpoints", httpErr.StatusCode))
|
|
}
|
|
return to.ErrorResult(fmt.Errorf("dispatch action workflow err: %v", err))
|
|
}
|
|
return to.TextResult(map[string]any{"message": "workflow dispatched"})
|
|
}
|
|
|
|
func ListRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListRepoActionRunsFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
page, _ := req.GetArguments()["page"].(float64)
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
pageSize, _ := req.GetArguments()["pageSize"].(float64)
|
|
if pageSize <= 0 {
|
|
pageSize = 50
|
|
}
|
|
statusFilter, _ := req.GetArguments()["status"].(string)
|
|
|
|
query := url.Values{}
|
|
query.Set("page", fmt.Sprintf("%d", int(page)))
|
|
query.Set("limit", fmt.Sprintf("%d", int(pageSize)))
|
|
if statusFilter != "" {
|
|
query.Set("status", statusFilter)
|
|
}
|
|
|
|
var result any
|
|
_, _, err := doJSONWithFallback(ctx, "GET",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/runs", url.PathEscape(owner), url.PathEscape(repo)),
|
|
},
|
|
query, nil, &result,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list action runs err: %v", err))
|
|
}
|
|
return to.TextResult(result)
|
|
}
|
|
|
|
func GetRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called GetRepoActionRunFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
runID, ok := req.GetArguments()["run_id"].(float64)
|
|
if !ok || runID <= 0 {
|
|
return to.ErrorResult(fmt.Errorf("run_id is required"))
|
|
}
|
|
|
|
var result any
|
|
_, _, err := doJSONWithFallback(ctx, "GET",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/runs/%d", url.PathEscape(owner), url.PathEscape(repo), int64(runID)),
|
|
},
|
|
nil, nil, &result,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get action run err: %v", err))
|
|
}
|
|
return to.TextResult(result)
|
|
}
|
|
|
|
func CancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called CancelRepoActionRunFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
runID, ok := req.GetArguments()["run_id"].(float64)
|
|
if !ok || runID <= 0 {
|
|
return to.ErrorResult(fmt.Errorf("run_id is required"))
|
|
}
|
|
|
|
_, _, err := doJSONWithFallback(ctx, "POST",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/runs/%d/cancel", url.PathEscape(owner), url.PathEscape(repo), int64(runID)),
|
|
},
|
|
nil, nil, nil,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("cancel action run err: %v", err))
|
|
}
|
|
return to.TextResult(map[string]any{"message": "run cancellation requested"})
|
|
}
|
|
|
|
func RerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called RerunRepoActionRunFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
runID, ok := req.GetArguments()["run_id"].(float64)
|
|
if !ok || runID <= 0 {
|
|
return to.ErrorResult(fmt.Errorf("run_id is required"))
|
|
}
|
|
|
|
_, _, err := doJSONWithFallback(ctx, "POST",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/runs/%d/rerun", url.PathEscape(owner), url.PathEscape(repo), int64(runID)),
|
|
fmt.Sprintf("repos/%s/%s/actions/runs/%d/rerun-failed-jobs", url.PathEscape(owner), url.PathEscape(repo), int64(runID)),
|
|
},
|
|
nil, nil, nil,
|
|
)
|
|
if err != nil {
|
|
var httpErr *gitea.HTTPError
|
|
if errors.As(err, &httpErr) && (httpErr.StatusCode == 404 || httpErr.StatusCode == 405) {
|
|
return to.ErrorResult(fmt.Errorf("workflow rerun not supported on this Gitea version (endpoint returned %d). Check https://docs.gitea.com/api/1.24/ for available Actions endpoints", httpErr.StatusCode))
|
|
}
|
|
return to.ErrorResult(fmt.Errorf("rerun action run err: %v", err))
|
|
}
|
|
return to.TextResult(map[string]any{"message": "run rerun requested"})
|
|
}
|
|
|
|
func ListRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListRepoActionJobsFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
page, _ := req.GetArguments()["page"].(float64)
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
pageSize, _ := req.GetArguments()["pageSize"].(float64)
|
|
if pageSize <= 0 {
|
|
pageSize = 50
|
|
}
|
|
statusFilter, _ := req.GetArguments()["status"].(string)
|
|
|
|
query := url.Values{}
|
|
query.Set("page", fmt.Sprintf("%d", int(page)))
|
|
query.Set("limit", fmt.Sprintf("%d", int(pageSize)))
|
|
if statusFilter != "" {
|
|
query.Set("status", statusFilter)
|
|
}
|
|
|
|
var result any
|
|
_, _, err := doJSONWithFallback(ctx, "GET",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/jobs", url.PathEscape(owner), url.PathEscape(repo)),
|
|
},
|
|
query, nil, &result,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list action jobs err: %v", err))
|
|
}
|
|
return to.TextResult(result)
|
|
}
|
|
|
|
func ListRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListRepoActionRunJobsFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok || owner == "" {
|
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok || repo == "" {
|
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
|
}
|
|
runID, ok := req.GetArguments()["run_id"].(float64)
|
|
if !ok || runID <= 0 {
|
|
return to.ErrorResult(fmt.Errorf("run_id is required"))
|
|
}
|
|
page, _ := req.GetArguments()["page"].(float64)
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
pageSize, _ := req.GetArguments()["pageSize"].(float64)
|
|
if pageSize <= 0 {
|
|
pageSize = 50
|
|
}
|
|
|
|
query := url.Values{}
|
|
query.Set("page", fmt.Sprintf("%d", int(page)))
|
|
query.Set("limit", fmt.Sprintf("%d", int(pageSize)))
|
|
|
|
var result any
|
|
_, _, err := doJSONWithFallback(ctx, "GET",
|
|
[]string{
|
|
fmt.Sprintf("repos/%s/%s/actions/runs/%d/jobs", url.PathEscape(owner), url.PathEscape(repo), int64(runID)),
|
|
},
|
|
query, nil, &result,
|
|
)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list action run jobs err: %v", err))
|
|
}
|
|
return to.TextResult(result)
|
|
}
|