From 32fc1af7658a543f28b3bbb7e380c65f9174db30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 14:22:46 +0000 Subject: [PATCH] build(deps): bump github.com/digitalocean/godo from 1.208.0 to 1.217.0 Bumps [github.com/digitalocean/godo](https://github.com/digitalocean/godo) from 1.208.0 to 1.217.0. - [Release notes](https://github.com/digitalocean/godo/releases) - [Changelog](https://github.com/digitalocean/godo/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalocean/godo/compare/v1.208.0...v1.217.0) --- updated-dependencies: - dependency-name: github.com/digitalocean/godo dependency-version: 1.217.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- .../github.com/digitalocean/godo/CHANGELOG.md | 41 + .../digitalocean/godo/CONTRIBUTING.md | 11 + vendor/github.com/digitalocean/godo/README.md | 45 +- .../digitalocean/godo/action_gateway.go | 85 + .../godo/action_gateway_connections.go | 148 ++ .../godo/action_gateway_inference.go | 407 ++++ .../godo/action_gateway_resources.go | 278 +++ .../godo/action_gateway_runtime.go | 500 +++++ .../godo/action_gateway_sessions.go | 178 ++ .../godo/action_gateway_toolbelts.go | 223 +++ .../digitalocean/godo/action_gateway_tools.go | 405 ++++ .../digitalocean/godo/action_gateway_users.go | 146 ++ .../godo/{gradientai.go => agentplatform.go} | 485 +++-- ...ations.go => agentplatform_simulations.go} | 44 +- vendor/github.com/digitalocean/godo/godo.go | 39 +- .../godo/hosted_agent_checkpoints.go | 224 +++ .../digitalocean/godo/hosted_agent_configs.go | 228 +++ .../digitalocean/godo/hosted_agent_policy.go | 95 + .../godo/hosted_agent_templates.go | 310 +++ .../godo/hosted_agent_triggers.go | 581 ++++++ .../digitalocean/godo/hosted_agents.go | 1780 +++++++++++++++++ .../digitalocean/godo/kubernetes.go | 8 + .../digitalocean/godo/micro_droplets.go | 551 ----- .../github.com/digitalocean/godo/microvms.go | 744 +++++++ vendor/github.com/digitalocean/godo/nfs.go | 1 + .../digitalocean/godo/prepayment.go | 96 + .../github.com/digitalocean/godo/streaming.go | 14 + vendor/modules.txt | 2 +- 30 files changed, 6917 insertions(+), 758 deletions(-) create mode 100644 vendor/github.com/digitalocean/godo/action_gateway.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_connections.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_inference.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_resources.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_runtime.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_sessions.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_toolbelts.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_tools.go create mode 100644 vendor/github.com/digitalocean/godo/action_gateway_users.go rename vendor/github.com/digitalocean/godo/{gradientai.go => agentplatform.go} (82%) rename vendor/github.com/digitalocean/godo/{gradientai_simulations.go => agentplatform_simulations.go} (92%) create mode 100644 vendor/github.com/digitalocean/godo/hosted_agent_checkpoints.go create mode 100644 vendor/github.com/digitalocean/godo/hosted_agent_configs.go create mode 100644 vendor/github.com/digitalocean/godo/hosted_agent_policy.go create mode 100644 vendor/github.com/digitalocean/godo/hosted_agent_templates.go create mode 100644 vendor/github.com/digitalocean/godo/hosted_agent_triggers.go create mode 100644 vendor/github.com/digitalocean/godo/hosted_agents.go delete mode 100644 vendor/github.com/digitalocean/godo/micro_droplets.go create mode 100644 vendor/github.com/digitalocean/godo/microvms.go create mode 100644 vendor/github.com/digitalocean/godo/prepayment.go diff --git a/go.mod b/go.mod index ff9b5259e..e722dab12 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.27.0 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/digitalocean/godo v1.208.0 + github.com/digitalocean/godo v1.217.0 github.com/go-logr/logr v1.4.4 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index baafc8f0a..26a36383f 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/digitalocean/godo v1.208.0 h1:J1+thyQyuU5OXRvifI3NKQZNirNiY6jzEPbvqOQ8yoI= -github.com/digitalocean/godo v1.208.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU= +github.com/digitalocean/godo v1.217.0 h1:yMFsrwEAsAbztsCq8bKoBoZdmIs3xTR7la9p0AjqSkY= +github.com/digitalocean/godo v1.217.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md index 66f4b96d1..16d43c596 100644 --- a/vendor/github.com/digitalocean/godo/CHANGELOG.md +++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md @@ -1,5 +1,46 @@ # Change Log +## [1.217.0] - 2026-10-01 + +- #1130 - @aguan-hue - Syncing Model Evaluation fields from gen-ai Public API + +## [1.216.0] - 2026-09-30 + +- #1134 - @kishlay-singh-DO - Rename the GradientAI client to Agent Platform. +- #1133 - @tgillam-do - Add Action Gateway SDK +- #1129 - @sanpj2292 - Add Cancel to HostedAgentTriggersService +- #1132 - @fumblehool - Update HostedAgentSandboxSize struct to include PricePerHourUSD and PricePerMonthUSD + +## [1.215.0] - 2026-09-24 + +- #1127 - @vrindavinod-do - hosted_agents: add ZeroBalance pause reason, deprecate LowBalance + +## [1.214.0] - 2026-09-24 + +- #1125 - @SSharma-10 - Add Environment aliases for Managed Agents +- #1123 - @SSharma-10 - Add post-release checksum verification steps to CONTRIBUTING.md + +## [1.213.0] - 2026-09-23 + +- #1102 - @kgautam676 - Adding nfs_csi_plugin to kubernetes cluster types +- #1120 - @sgupta832 - microvm: decode http_protocol, checkpoint size, and combined list filters + +## [1.212.0] - 2026-09-22 + +- #1117 - @sgupta832 - MicroVM URN flip + Exec/ConsoleURL (MDROP-392, MDROP-427) + +## [1.211.0] - 2026-09-22 + +- #1021 - @SSharma-10 - Add OHS endpoints + +## [1.210.0] - 2026-09-18 + +- #1110 - @kwadhwa-source - Mdrop 391/create options sizes + +## [1.209.0] - 2026-09-17 + +- #1107 - @rodrigoea - billing(prepayment): add public config and status client methods + ## [1.208.0] - 2026-09-15 - #1105 - @AKatruwar - registry: expose registry UUID and add ListByUUID diff --git a/vendor/github.com/digitalocean/godo/CONTRIBUTING.md b/vendor/github.com/digitalocean/godo/CONTRIBUTING.md index 4eb32251c..f17e6c772 100644 --- a/vendor/github.com/digitalocean/godo/CONTRIBUTING.md +++ b/vendor/github.com/digitalocean/godo/CONTRIBUTING.md @@ -59,6 +59,17 @@ number for the tag. 5. Once the release process completes, review the draft release for correctness and publish the release. Ensure the release has been marked `Latest`. +6. Verify the module checksum against the Go checksum database (to catch retagging issues): + + 1. Run `go mod tidy` inside doctl (or any other repo that uses godo) so that the corresponding godo hash in the `go.sum` file gets updated. + 2. Hit the curl command in the terminal for the recent release version, for example: + + ```bash + curl https://sum.golang.org/lookup/github.com/digitalocean/godo@v1.165.1 + ``` + + 3. Both hashes should match for that specific godo version. + ## Go Version Support This project follows the support [policy of Go](https://go.dev/doc/devel/release#policy) diff --git a/vendor/github.com/digitalocean/godo/README.md b/vendor/github.com/digitalocean/godo/README.md index 470e12558..4ff229021 100644 --- a/vendor/github.com/digitalocean/godo/README.md +++ b/vendor/github.com/digitalocean/godo/README.md @@ -12,7 +12,7 @@ You can view DigitalOcean API docs here: [https://docs.digitalocean.com/referenc > **🚀 New in v1.191.0 — AI & Inference support** > > `godo` now ships first-class support for DigitalOcean's -> [Gradient AI Platform](https://www.digitalocean.com/products/gradient): chat +> [DigitalOcean Inference](https://docs.digitalocean.com/products/inference/): chat > completions (with streaming), image generation, embeddings, batch inference, > model listing, and more — all from the same `Client`. Jump to > [**AI & Inference**](#ai--inference) to get started. @@ -64,7 +64,7 @@ func main() { > | What you're calling | What you need | > | --- | --- | > | Infrastructure APIs (`Droplets`, `Kubernetes`, `Volumes`, …) | A DigitalOcean API token (PAT). | -> | Inference APIs (`Chat`, `Models`, `Embeddings`, `ImageGenerations`, `Messages`, `Responses`, `BatchInference`, …) | A PAT created with **full access** scope, **or** a Gradient **Model Access Key**. | +> | Inference APIs (`Chat`, `Models`, `Embeddings`, `ImageGenerations`, `Messages`, `Responses`, `BatchInference`, …) | A PAT created with **full access** scope, **or** a **model access key**. | > > If you only have a limited-scope PAT, infrastructure calls will work but > inference calls will fail with `401`. Create a new PAT with full access, or use @@ -73,16 +73,51 @@ func main() { > ```go > // Either credential works with godo.NewFromToken: > client := godo.NewFromToken(os.Getenv("DIGITALOCEAN_TOKEN")) // full-access PAT -> client := godo.NewFromToken(os.Getenv("MODEL_ACCESS_KEY")) // Gradient model access key +> client := godo.NewFromToken(os.Getenv("MODEL_ACCESS_KEY")) // model access key > ``` If you need to provide a `context.Context` to your new client, you should use [`godo.NewClient`](https://godoc.org/github.com/digitalocean/godo#NewClient) to manually construct a client instead. +## Action Gateway + +`client.ActionGateway` exposes the Action Gateway control-plane resources: tools, +toolbelts, output views, MCP servers, connections, sessions, users, and end-user +limits. Use a PAT with the corresponding `action_gateway` scopes. For example: + +```go +toolbelts, _, err := client.ActionGateway.Toolbelts.List(ctx, &godo.ActionGatewayToolbeltsListOptions{Status: "active"}) +if err != nil { return err } +fmt.Println(toolbelts.Toolbelts) + +session, _, err := client.ActionGateway.Sessions.CreateRuntime(ctx, &godo.ActionGatewaySessionCreateRequest{ + ActorID: "alice", +}) +if err != nil { return err } +tools, _, err := session.ChatTools(ctx, nil) // action_search, action_invoke, action_code +if err != nil { return err } +completion, _, err := client.Chat.Completions.New(ctx, &godo.ChatCompletionNewParams{ + Model: "llama3.3-70b-instruct", Messages: []godo.ChatCompletionMessage{godo.UserMessage("Find recent news")}, Tools: tools, +}) +if err != nil { return err } +toolMessages, _, err := session.HandleChatToolCalls(ctx, completion) +if err != nil { return err } +fmt.Println(toolMessages) +``` + +`session.Tools` supports direct discovery and invocation, while `session.Code` +runs sandboxed code. `MessageTools`/`HandleMessageToolCalls` and +`ResponseTools`/`HandleResponseToolCalls` integrate with the other inference +formats. `CreateRuntime` defaults to an ask-before-executing policy; provide an +explicit policy to change it. Omitted `Tools` enables all tools, while an empty +non-nil slice enables none. MCP calls accept only HTTPS session URLs on +`actions.do-ai.run`; `WithActionGatewayMCPBaseURL` overrides the origin for +development or tests. + ## AI & Inference -> Talk to models on DigitalOcean's [Gradient AI Platform](https://www.digitalocean.com/products/gradient) with the same `godo.Client`. +> Talk to models on [DigitalOcean Inference](https://docs.digitalocean.com/products/inference/) with the same `godo.Client`. -The [Serverless Inference API](https://docs.digitalocean.com/reference/api/reference/serverless-inference/) is available at `https://inference.do-ai.run/`. Use a **DigitalOcean PAT with full access scope** or a Gradient **Model Access Key** — see the [credentials note](#authentication) above. +The [Serverless Inference API](https://docs.digitalocean.com/reference/api/reference/serverless-inference/) is available at `https://inference.do-ai.run/`. Use a **DigitalOcean PAT with full access scope** or a **model access key** — see the [credentials note](#authentication) above. #### Chat completion diff --git a/vendor/github.com/digitalocean/godo/action_gateway.go b/vendor/github.com/digitalocean/godo/action_gateway.go new file mode 100644 index 000000000..99a420035 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway.go @@ -0,0 +1,85 @@ +package godo + +import ( + "context" + "net/url" + "strings" +) + +const actionGatewayPath = "/v2/action-gateway" + +// ActionGatewayService groups the public Action Gateway control-plane resources. +type ActionGatewayService struct { + Tools *ActionGatewayToolsService + Toolbelts *ActionGatewayToolbeltsService + OutputViews *ActionGatewayOutputViewsService + MCPServers *ActionGatewayMCPServersService + Connections *ActionGatewayConnectionsService + Sessions *ActionGatewaySessionsService + Users *ActionGatewayUsersService + ActorLimits *ActionGatewayActorLimitsService +} + +func newActionGatewayService(client *Client) *ActionGatewayService { + return &ActionGatewayService{ + Tools: &ActionGatewayToolsService{client}, + Toolbelts: &ActionGatewayToolbeltsService{client}, + OutputViews: &ActionGatewayOutputViewsService{client}, + MCPServers: &ActionGatewayMCPServersService{client}, + Connections: &ActionGatewayConnectionsService{client}, + Sessions: &ActionGatewaySessionsService{client}, + Users: &ActionGatewayUsersService{client}, + ActorLimits: &ActionGatewayActorLimitsService{client}, + } +} + +func actionGatewayRequest[T any](ctx context.Context, client *Client, method, path string, options, body interface{}) (*T, *Response, error) { + if options != nil { + var err error + path, err = addOptions(path, options) + if err != nil { + return nil, nil, err + } + } + request, err := client.NewRequest(ctx, method, path, body) + if err != nil { + return nil, nil, err + } + result := new(T) + response, err := client.Do(ctx, request, result) + if err != nil { + return nil, response, err + } + return result, response, nil +} + +type actionGatewayPathPart struct { + name string + value string +} + +func actionGatewayItem(path string, part actionGatewayPathPart) (string, error) { + if strings.TrimSpace(part.value) == "" { + return "", NewArgError(part.name, "cannot be empty") + } + return path + "/" + url.PathEscape(part.value), nil +} + +// ActionGatewayPagination describes offset-based list results. +type ActionGatewayPagination struct { + Page int `json:"page"` + PerPage int `json:"per_page"` + Total int `json:"total"` +} + +// ActionGatewayListOptions selects an offset-based page. +type ActionGatewayListOptions struct { + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// ActionGatewayCursorOptions selects an opaque cursor-based page. +type ActionGatewayCursorOptions struct { + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_connections.go b/vendor/github.com/digitalocean/godo/action_gateway_connections.go new file mode 100644 index 000000000..39f8b9b2f --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_connections.go @@ -0,0 +1,148 @@ +package godo + +import ( + "context" + "encoding/json" + "net/http" +) + +// ActionGatewayConnectionsService manages provider connections for end users. +type ActionGatewayConnectionsService struct{ client *Client } + +// ActionGatewayConnectionDestination permits a private endpoint in a VPC. +type ActionGatewayConnectionDestination struct { + Host string `json:"host"` + Port uint32 `json:"port"` + AllowedIPCIDRs []string `json:"allowed_ip_cidrs"` +} + +// ActionGatewayConnectionVPC describes an existing VPC connection. +type ActionGatewayConnectionVPC struct { + VPCUUID string `json:"vpc_uuid"` + Region string `json:"region,omitempty"` + Destinations []ActionGatewayConnectionDestination `json:"destinations,omitempty"` +} + +// ActionGatewayConnectionNetwork identifies the VPC and destinations. +type ActionGatewayConnectionNetwork struct { + VPC *ActionGatewayConnectionVPC `json:"vpc"` +} + +// ActionGatewayConnectionCreateVPC selects a VPC on create. +type ActionGatewayConnectionCreateVPC struct { + VPCUUID string `json:"vpc_uuid"` + Destinations []ActionGatewayConnectionDestination `json:"destinations,omitempty"` +} + +// ActionGatewayConnectionCreateNetwork selects a VPC without output-only region. +type ActionGatewayConnectionCreateNetwork struct { + VPC *ActionGatewayConnectionCreateVPC `json:"vpc"` +} + +// ActionGatewayTeamCredential references a previously registered team credential. +type ActionGatewayTeamCredential struct { + CredentialID string `json:"credential_id"` +} + +// ActionGatewayConnectionCredential selects exactly one authorization mode. +type ActionGatewayConnectionCredential struct { + DigitalOceanOAuth *struct{} `json:"digitalocean_oauth,omitempty"` + TeamCredential *ActionGatewayTeamCredential `json:"team_credential,omitempty"` +} + +// ActionGatewayConnectionAuthorization describes an incomplete authorization. +type ActionGatewayConnectionAuthorization struct { + Status string `json:"status"` + ConnectURL string `json:"connect_url"` + VerificationCode string `json:"verification_code"` + ExpiresAt *Timestamp `json:"expires_at"` +} + +// ActionGatewayConnection describes a user's provider authorization. +type ActionGatewayConnection struct { + ID string `json:"id"` + Provider string `json:"provider"` + ProviderDisplayName string `json:"provider_display_name"` + UserID string `json:"user_id"` + Status string `json:"status"` + CreatedAt *Timestamp `json:"created_at"` + UpdatedAt *Timestamp `json:"updated_at"` + RevokedAt *Timestamp `json:"revoked_at"` + ConnectionParameters map[string]interface{} `json:"connection_parameters"` + CredentialKind string `json:"credential_kind"` + CredentialID string `json:"credential_id"` + Network *ActionGatewayConnectionNetwork `json:"network"` + OAuth *struct { + Scopes []string `json:"scopes"` + GrantedAt *Timestamp `json:"granted_at"` + } `json:"oauth"` + APIKey json.RawMessage `json:"api_key"` + Scopes []string `json:"scopes"` + GrantedAt *Timestamp `json:"granted_at"` + OwningUserID string `json:"owning_user_id"` + OwningUserNumericID string `json:"owning_user_numeric_id"` +} + +// ActionGatewayConnectionCreateRequest creates a provider connection. +type ActionGatewayConnectionCreateRequest struct { + Provider string `json:"provider"` + UserID string `json:"user_id"` + Scopes []string `json:"scopes,omitempty"` + ConnectionParameters map[string]interface{} `json:"connection_parameters,omitempty"` + Credential *ActionGatewayConnectionCredential `json:"credential,omitempty"` + Network *ActionGatewayConnectionCreateNetwork `json:"network,omitempty"` +} + +// ActionGatewayConnectionResponse contains one connection and any pending authorization. +type ActionGatewayConnectionResponse struct { + Connection *ActionGatewayConnection `json:"connection"` + Authorization *ActionGatewayConnectionAuthorization `json:"authorization"` +} + +// ActionGatewayConnectionsResponse contains offset-paginated connections. +type ActionGatewayConnectionsResponse struct { + Connections []ActionGatewayConnection `json:"connections"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayConnectionListOptions filters connections. +type ActionGatewayConnectionListOptions struct { + Provider string `url:"provider,omitempty"` + UserID string `url:"user_id,omitempty"` + Status string `url:"status,omitempty"` + Sort string `url:"sort,omitempty"` + SortDirection string `url:"sort_direction,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// Create creates a connection. +func (s *ActionGatewayConnectionsService) Create(ctx context.Context, body *ActionGatewayConnectionCreateRequest) (*ActionGatewayConnectionResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + return actionGatewayRequest[ActionGatewayConnectionResponse](ctx, s.client, http.MethodPost, actionGatewayPath+"/connections", nil, body) +} + +// List lists connections. +func (s *ActionGatewayConnectionsService) List(ctx context.Context, opt *ActionGatewayConnectionListOptions) (*ActionGatewayConnectionsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayConnectionsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/connections", opt, nil) +} + +// Get retrieves one connection. +func (s *ActionGatewayConnectionsService) Get(ctx context.Context, id string) (*ActionGatewayConnectionResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/connections", actionGatewayPathPart{"id", id}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayConnectionResponse](ctx, s.client, http.MethodGet, path, nil, nil) +} + +// Delete revokes and deletes a connection. +func (s *ActionGatewayConnectionsService) Delete(ctx context.Context, id string) (*ActionGatewayConnectionResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/connections", actionGatewayPathPart{"id", id}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayConnectionResponse](ctx, s.client, http.MethodDelete, path, nil, nil) +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_inference.go b/vendor/github.com/digitalocean/godo/action_gateway_inference.go new file mode 100644 index 000000000..d2a9d7da8 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_inference.go @@ -0,0 +1,407 @@ +package godo + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" +) + +// ActionGatewayInferenceToolsOptions selects session tools for an inference request. +// By default it selects the three meta-tools. +type ActionGatewayInferenceToolsOptions struct { + IncludeAll bool + Names []string + Search *ActionGatewayToolSearchRequest +} + +func (session *ActionGatewaySession) inferenceTools(ctx context.Context, opt *ActionGatewayInferenceToolsOptions) ([]ActionGatewayMCPTool, *Response, error) { + if opt != nil && opt.Search != nil { + result, response, err := session.Tools.Search(ctx, opt.Search) + if err != nil { + return nil, response, err + } + var found struct { + Results []struct { + Results []ActionGatewayMCPTool `json:"results"` + } `json:"results"` + } + if err = json.Unmarshal(result, &found); err != nil { + return nil, response, err + } + seen := make(map[string]bool) + var tools []ActionGatewayMCPTool + for _, group := range found.Results { + for _, tool := range group.Results { + if tool.Name != "" && !seen[tool.Name] { + tools = append(tools, tool) + seen[tool.Name] = true + } + } + } + return tools, response, nil + } + includeAll := opt != nil && (opt.IncludeAll || len(opt.Names) > 0) + tools, response, err := session.Tools.List(ctx, includeAll) + if err != nil { + return nil, response, err + } + if opt == nil || len(opt.Names) == 0 { + return tools, response, nil + } + wanted := make(map[string]bool, len(opt.Names)) + for _, name := range opt.Names { + wanted[name] = true + } + selected := make([]ActionGatewayMCPTool, 0, len(wanted)) + for _, tool := range tools { + if wanted[tool.Name] { + selected = append(selected, tool) + delete(wanted, tool.Name) + } + } + if len(wanted) > 0 { + return nil, response, fmt.Errorf("action gateway: requested tools not in session catalog: %v", wanted) + } + return selected, response, nil +} + +func actionGatewayToolSchema(schema json.RawMessage) map[string]interface{} { + result := make(map[string]interface{}) + if len(schema) > 0 { + _ = json.Unmarshal(schema, &result) + } + for _, key := range []string{"oneOf", "allOf", "anyOf", "enum", "const", "not"} { + delete(result, key) + } + if result["type"] == nil { + result["type"] = "object" + } + if result["type"] == "object" && result["properties"] == nil { + result["properties"] = map[string]interface{}{} + } + return result +} + +// ChatTools returns tools ready for ChatCompletionNewParams.Tools. +func (session *ActionGatewaySession) ChatTools(ctx context.Context, opt *ActionGatewayInferenceToolsOptions) ([]ChatCompletionTool, *Response, error) { + tools, response, err := session.inferenceTools(ctx, opt) + if err != nil { + return nil, response, err + } + result := make([]ChatCompletionTool, 0, len(tools)) + for _, tool := range tools { + result = append(result, ChatCompletionTool{Type: "function", Function: ChatCompletionToolFunction{Name: tool.Name, Description: tool.Description, Parameters: actionGatewayToolSchema(tool.InputSchema)}}) + } + return result, response, nil +} + +// MessageTools returns tools ready for MessageNewParams.Tools. +func (session *ActionGatewaySession) MessageTools(ctx context.Context, opt *ActionGatewayInferenceToolsOptions) ([]MessageNewParamsTool, *Response, error) { + tools, response, err := session.inferenceTools(ctx, opt) + if err != nil { + return nil, response, err + } + result := make([]MessageNewParamsTool, 0, len(tools)) + for _, tool := range tools { + result = append(result, MessageNewParamsTool{Name: tool.Name, Description: tool.Description, InputSchema: actionGatewayToolSchema(tool.InputSchema)}) + } + return result, response, nil +} + +// ResponseTools returns tools ready for ResponseNewParams.Tools. +func (session *ActionGatewaySession) ResponseTools(ctx context.Context, opt *ActionGatewayInferenceToolsOptions) ([]ResponseTool, *Response, error) { + tools, response, err := session.inferenceTools(ctx, opt) + if err != nil { + return nil, response, err + } + result := make([]ResponseTool, 0, len(tools)) + for _, tool := range tools { + result = append(result, ResponseTool{Type: "function", Name: tool.Name, Description: tool.Description, Parameters: actionGatewayToolSchema(tool.InputSchema)}) + } + return result, response, nil +} + +// ActionGatewayToolCall is a normalized model-requested tool invocation. +type ActionGatewayToolCall struct { + ID string + Name string + Arguments json.RawMessage +} + +func actionGatewayArguments(arguments json.RawMessage) json.RawMessage { + if len(arguments) == 0 { + return json.RawMessage(`{}`) + } + return arguments +} + +func actionGatewayFailure(err error) json.RawMessage { + failure := map[string]interface{}{"message": err.Error()} + payload := map[string]interface{}{"error": failure} + var toolError *ActionGatewayToolError + if errors.As(err, &toolError) { + failure["message"] = toolError.Message + failure["class"] = toolError.Class + failure["retriable"] = toolError.Retriable + failure["recovery_hint"] = toolError.RecoveryHint + if len(toolError.Meta) > 0 { + payload["_meta"] = toolError.Meta + } + } + result, _ := json.Marshal(payload) + return result +} + +func actionGatewayNormalizeInvoke(arguments interface{}) (interface{}, error) { + object, ok := arguments.(map[string]interface{}) + if !ok { + return arguments, nil + } + entries, present := object["tools"] + if !present { + return arguments, nil + } + tools, ok := entries.([]interface{}) + if !ok { + tools = []interface{}{entries} + } + normalized := make([]map[string]interface{}, 0, len(tools)) + for _, entry := range tools { + spec, ok := entry.(map[string]interface{}) + if !ok { + return nil, errors.New("invoke entry must be an object") + } + name, _ := spec["tool"].(string) + if name == "" { + name, _ = spec["tool_slug"].(string) + } + if name == "" { + name, _ = spec["name"].(string) + } + value := spec["arguments"] + if function, ok := spec["function"].(map[string]interface{}); ok { + if functionName, ok := function["name"].(string); ok && functionName != "" { + name = functionName + } + if function["arguments"] != nil { + value = function["arguments"] + } + } + if name == "" { + return nil, errors.New("invoke entry requires a tool name") + } + if value == nil { + hoisted := make(map[string]interface{}) + for key, field := range spec { + switch key { + case "tool", "tool_slug", "name", "function", "type", "id", "arguments": + default: + hoisted[key] = field + } + } + value = hoisted + } + if text, ok := value.(string); ok { + if strings.TrimSpace(text) == "" { + value = map[string]interface{}{} + } else if err := json.Unmarshal([]byte(text), &value); err != nil { + return nil, err + } + } + normalized = append(normalized, map[string]interface{}{"tool": name, "arguments": value}) + } + copy := make(map[string]interface{}, len(object)) + for key, value := range object { + copy[key] = value + } + copy["tools"] = normalized + return copy, nil +} + +// ExecuteToolCalls executes meta-tools and batches concrete tool calls. Individual +// failures are returned as error payloads for the model; transport failures return an error. +func (session *ActionGatewaySession) ExecuteToolCalls(ctx context.Context, calls []ActionGatewayToolCall, rationale string) ([]json.RawMessage, *Response, error) { + results := make([]json.RawMessage, len(calls)) + var response *Response + var indices []int + for index, call := range calls { + if !json.Valid(actionGatewayArguments(call.Arguments)) { + results[index] = actionGatewayFailure(errors.New("invalid tool arguments JSON")) + continue + } + switch call.Name { + case actionGatewaySearchTool, actionGatewayInvokeTool, actionGatewayCodeTool: + var arguments interface{} + _ = json.Unmarshal(actionGatewayArguments(call.Arguments), &arguments) + if call.Name == actionGatewayInvokeTool { + var normalizeError error + arguments, normalizeError = actionGatewayNormalizeInvoke(arguments) + if normalizeError != nil { + results[index] = actionGatewayFailure(normalizeError) + continue + } + } + output, current, err := session.Tools.Call(ctx, call.Name, arguments) + response = current + if err != nil { + var toolError *ActionGatewayToolError + if !errors.As(err, &toolError) { + return nil, response, err + } + output = actionGatewayFailure(err) + } + results[index] = output + default: + indices = append(indices, index) + } + } + for start := 0; start < len(indices); start += 10 { + end := start + 10 + if end > len(indices) { + end = len(indices) + } + entries := make([]ActionGatewayInvokeTool, 0, end-start) + for _, index := range indices[start:end] { + entries = append(entries, ActionGatewayInvokeTool{Tool: calls[index].Name, Arguments: actionGatewayArguments(calls[index].Arguments)}) + } + batch, current, err := session.Tools.Invoke(ctx, entries, rationale) + response = current + if err != nil { + return nil, response, err + } + for position, index := range indices[start:end] { + if position >= len(batch.Results) { + results[index] = actionGatewayFailure(errors.New("no result returned for tool call")) + continue + } + item := batch.Results[position].Result + if item.Status != "" && item.Status != "succeeded" { + if item.Error != nil { + item.Error.Meta = item.Meta + results[index] = actionGatewayFailure(item.Error) + } else { + results[index] = actionGatewayFailure(errors.New("tool invocation failed")) + } + } else { + results[index] = item.Output + } + } + } + return results, response, nil +} + +func actionGatewayContent(output json.RawMessage) string { + var text string + if json.Unmarshal(output, &text) == nil { + return text + } + if len(output) == 0 { + return "null" + } + return string(output) +} + +// HandleChatToolCalls returns Chat Completions tool messages for the first choice. +func (session *ActionGatewaySession) HandleChatToolCalls(ctx context.Context, completion *ChatCompletion) ([]ChatCompletionMessage, *Response, error) { + if completion == nil || len(completion.Choices) == 0 { + return nil, nil, nil + } + var calls []ActionGatewayToolCall + for _, call := range completion.Choices[0].Message.ToolCalls { + calls = append(calls, ActionGatewayToolCall{ID: call.ID, Name: call.Function.Name, Arguments: json.RawMessage(call.Function.Arguments)}) + } + if len(calls) == 0 { + return nil, nil, nil + } + results, response, err := session.ExecuteToolCalls(ctx, calls, "") + if err != nil { + return nil, response, err + } + messages := make([]ChatCompletionMessage, len(calls)) + for index, call := range calls { + text := actionGatewayContent(results[index]) + messages[index] = ChatCompletionMessage{Role: "tool", ToolCallID: call.ID, Content: &text} + } + return messages, response, nil +} + +// HandleMessageToolCalls returns a single Messages user turn of tool results. +func (session *ActionGatewaySession) HandleMessageToolCalls(ctx context.Context, message *Message) ([]MessageParam, *Response, error) { + if message == nil { + return nil, nil, nil + } + var calls []ActionGatewayToolCall + for _, block := range message.Content { + if block.Type == "tool_use" { + arguments := json.RawMessage(`{}`) + if block.Input != nil { + var err error + arguments, err = json.Marshal(block.Input) + if err != nil { + return nil, nil, fmt.Errorf("action gateway: marshal tool input: %w", err) + } + } + calls = append(calls, ActionGatewayToolCall{ID: block.ID, Name: block.Name, Arguments: arguments}) + } + } + if len(calls) == 0 { + return nil, nil, nil + } + results, response, err := session.ExecuteToolCalls(ctx, calls, "") + if err != nil { + return nil, response, err + } + blocks := make([]map[string]string, len(calls)) + for index, call := range calls { + blocks[index] = map[string]string{"type": "tool_result", "tool_use_id": call.ID, "content": actionGatewayContent(results[index])} + } + content, err := json.Marshal(blocks) + if err != nil { + return nil, response, err + } + return []MessageParam{{Role: "user", Content: content}}, response, nil +} + +// ActionGatewayResponseToolOutput is a function_call_output item for Responses input. +type ActionGatewayResponseToolOutput struct { + Type string `json:"type"` + CallID string `json:"call_id"` + Output string `json:"output"` +} + +// HandleResponseToolCalls returns Responses function-call output items. +func (session *ActionGatewaySession) HandleResponseToolCalls(ctx context.Context, responseBody *ResponsesResponse) ([]ActionGatewayResponseToolOutput, *Response, error) { + if responseBody == nil { + return nil, nil, nil + } + var calls []ActionGatewayToolCall + for _, item := range responseBody.Output { + if item.Type == "function_call" { + call := ActionGatewayToolCall{} + if item.CallID != nil { + call.ID = *item.CallID + } + if item.Name != nil { + call.Name = *item.Name + } + if item.Arguments != nil { + call.Arguments = json.RawMessage(*item.Arguments) + } + calls = append(calls, call) + } + } + if len(calls) == 0 { + return nil, nil, nil + } + results, response, err := session.ExecuteToolCalls(ctx, calls, "") + if err != nil { + return nil, response, err + } + items := make([]ActionGatewayResponseToolOutput, len(calls)) + for index, call := range calls { + items[index] = ActionGatewayResponseToolOutput{Type: "function_call_output", CallID: call.ID, Output: actionGatewayContent(results[index])} + } + return items, response, nil +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_resources.go b/vendor/github.com/digitalocean/godo/action_gateway_resources.go new file mode 100644 index 000000000..35ce3d191 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_resources.go @@ -0,0 +1,278 @@ +package godo + +import ( + "context" + "encoding/json" + "net/http" +) + +// ActionGatewayOutputViewsService manages tool output projections. +type ActionGatewayOutputViewsService struct{ client *Client } + +// ActionGatewayOutputView is a saved or previewed output projection. +type ActionGatewayOutputView struct { + ViewID string `json:"view_id"` + ToolID string `json:"tool_id"` + Tool string `json:"tool"` + Version string `json:"version"` + TeamID string `json:"team_id"` + Name string `json:"name"` + Description string `json:"description"` + Kind string `json:"kind"` + Fields []string `json:"fields"` + Transform json.RawMessage `json:"transform"` + OutputSchema json.RawMessage `json:"output_schema"` + Audit *struct { + CreatedAt *Timestamp `json:"createdAt"` + UpdatedAt *Timestamp `json:"updatedAt"` + DeletedAt *Timestamp `json:"deletedAt"` + CreatedBy string `json:"createdBy"` + UpdatedBy string `json:"updatedBy"` + } `json:"audit"` + DeletedBy string `json:"deleted_by"` +} + +// ActionGatewayOutputViewCreateRequest creates an output projection. +type ActionGatewayOutputViewCreateRequest struct { + Tool string `json:"tool,omitempty"` + ToolID string `json:"tool_id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Fields []string `json:"fields"` +} + +// ActionGatewayOutputViewPreviewRequest previews an output projection without saving it. +type ActionGatewayOutputViewPreviewRequest struct { + Tool string `json:"tool,omitempty"` + ToolID string `json:"tool_id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Fields []string `json:"fields"` +} + +// ActionGatewayOutputViewResponse contains one view. +type ActionGatewayOutputViewResponse struct { + View *ActionGatewayOutputView `json:"view"` +} + +// ActionGatewayOutputViewsResponse contains cursor-paginated views. +type ActionGatewayOutputViewsResponse struct { + Views []ActionGatewayOutputView `json:"views"` + ToolID string `json:"tool_id"` + Tool string `json:"tool"` + Version string `json:"version"` + NextPageToken string `json:"next_page_token"` +} + +// ActionGatewayOutputViewListOptions filters and pages output views. +type ActionGatewayOutputViewListOptions struct { + Tool string `url:"tool,omitempty"` + ToolID string `url:"tool_id,omitempty"` + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} + +// Create saves an output view. +func (s *ActionGatewayOutputViewsService) Create(ctx context.Context, body *ActionGatewayOutputViewCreateRequest) (*ActionGatewayOutputViewResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + return actionGatewayRequest[ActionGatewayOutputViewResponse](ctx, s.client, http.MethodPost, actionGatewayPath+"/output-views", nil, body) +} + +// Preview validates an output view without saving it. +func (s *ActionGatewayOutputViewsService) Preview(ctx context.Context, body *ActionGatewayOutputViewPreviewRequest) (*ActionGatewayOutputViewResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + return actionGatewayRequest[ActionGatewayOutputViewResponse](ctx, s.client, http.MethodPost, actionGatewayPath+"/output-views/preview", nil, body) +} + +// List retrieves cursor-paginated output views. +func (s *ActionGatewayOutputViewsService) List(ctx context.Context, opt *ActionGatewayOutputViewListOptions) (*ActionGatewayOutputViewsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayOutputViewsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/output-views", opt, nil) +} + +// Get retrieves an output view. +func (s *ActionGatewayOutputViewsService) Get(ctx context.Context, viewID string) (*ActionGatewayOutputViewResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/output-views", actionGatewayPathPart{"viewID", viewID}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayOutputViewResponse](ctx, s.client, http.MethodGet, path, nil, nil) +} + +// Delete deletes an output view. +func (s *ActionGatewayOutputViewsService) Delete(ctx context.Context, viewID string) (*ActionGatewayOutputViewResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/output-views", actionGatewayPathPart{"viewID", viewID}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayOutputViewResponse](ctx, s.client, http.MethodDelete, path, nil, nil) +} + +// ActionGatewayMCPServersService manages registered MCP servers. +type ActionGatewayMCPServersService struct{ client *Client } + +// ActionGatewayMCPServer describes a registered MCP server. +type ActionGatewayMCPServer struct { + ServerRef string `json:"serverRef"` + Endpoint string `json:"endpoint"` + Transport string `json:"transport"` + CredentialRefSource string `json:"credentialRefSource"` + CredentialRef string `json:"credentialRef"` + SyncStatus string `json:"syncStatus"` + SyncError string `json:"syncError"` + LastSyncedAt string `json:"lastSyncedAt"` + ProtocolVersion string `json:"protocolVersion"` + ToolCount int `json:"toolCount"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + Description string `json:"description"` + OAuthAuthorizeURL string `json:"oauth_authorize_url"` + OAuthScopes []string `json:"oauth_scopes"` + OAuthAuthorizationTTLSeconds string `json:"oauth_authorization_ttl_seconds"` +} + +// ActionGatewayMCPServerCreateRequest registers an MCP server. +type ActionGatewayMCPServerCreateRequest struct { + ServerRef string `json:"serverRef"` + Endpoint string `json:"endpoint"` + Transport string `json:"transport,omitempty"` + CredentialRefSource string `json:"credentialRefSource,omitempty"` + Description string `json:"description,omitempty"` + CredentialRef string `json:"credentialRef,omitempty"` + APIKey string `json:"api_key,omitempty"` + OAuthClientID string `json:"oauth_client_id,omitempty"` + OAuthClientSecret string `json:"oauth_client_secret,omitempty"` + OAuthAuthorizeURL string `json:"oauth_authorize_url,omitempty"` + OAuthTokenURL string `json:"oauth_token_url,omitempty"` + OAuthScopes []string `json:"oauth_scopes,omitempty"` + OAuthAuthorizationTTLSeconds string `json:"oauth_authorization_ttl_seconds,omitempty"` +} + +// ActionGatewayMCPServerUpdateRequest updates a server's description. +type ActionGatewayMCPServerUpdateRequest struct { + Description string `json:"description"` +} + +// ActionGatewayMCPServerResyncRequest optionally selects a user for resync. +type ActionGatewayMCPServerResyncRequest struct { + UserID string `json:"user_id,omitempty"` +} + +// ActionGatewayMCPServerToolsUpdateRequest selects the enabled server tools. +type ActionGatewayMCPServerToolsUpdateRequest struct { + EnabledToolSlugs []string `json:"enabledToolSlugs"` + UserID string `json:"user_id,omitempty"` +} + +// ActionGatewayMCPServerTool is a discovered MCP tool. +type ActionGatewayMCPServerTool struct { + Name string `json:"name"` + ToolSlug string `json:"toolSlug"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + Quarantined bool `json:"quarantined"` + QuarantineReason string `json:"quarantineReason"` + CatalogSlug string `json:"catalogSlug"` +} + +// ActionGatewayMCPServerResponse contains one MCP server. +type ActionGatewayMCPServerResponse struct { + MCPServer *ActionGatewayMCPServer `json:"mcpServer"` +} + +// ActionGatewayMCPServersResponse contains registered servers. +type ActionGatewayMCPServersResponse struct { + MCPServers []ActionGatewayMCPServer `json:"mcpServers"` +} + +// ActionGatewayMCPServerToolsResponse contains discovered tools. +type ActionGatewayMCPServerToolsResponse struct { + Tools []ActionGatewayMCPServerTool `json:"tools"` +} + +// ActionGatewayMCPServerResyncResponse reports an immediate or pending resync. +type ActionGatewayMCPServerResyncResponse struct { + MCPServer *ActionGatewayMCPServer `json:"mcpServer"` + Tools []ActionGatewayMCPServerTool `json:"tools"` + Pending bool `json:"pending"` + Authorization *ActionGatewayConnectionAuthorization `json:"authorization"` +} + +// Create registers an MCP server. +func (s *ActionGatewayMCPServersService) Create(ctx context.Context, body *ActionGatewayMCPServerCreateRequest) (*ActionGatewayMCPServerResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + return actionGatewayRequest[ActionGatewayMCPServerResponse](ctx, s.client, http.MethodPost, actionGatewayPath+"/mcp-servers", nil, body) +} + +// List retrieves all registered MCP servers. +func (s *ActionGatewayMCPServersService) List(ctx context.Context) (*ActionGatewayMCPServersResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayMCPServersResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/mcp-servers", nil, nil) +} + +// Get retrieves one MCP server. +func (s *ActionGatewayMCPServersService) Get(ctx context.Context, ref string) (*ActionGatewayMCPServerResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/mcp-servers", actionGatewayPathPart{"ref", ref}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayMCPServerResponse](ctx, s.client, http.MethodGet, path, nil, nil) +} + +// Update changes an MCP server's description. +func (s *ActionGatewayMCPServersService) Update(ctx context.Context, ref string, body *ActionGatewayMCPServerUpdateRequest) (*ActionGatewayMCPServerResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/mcp-servers", actionGatewayPathPart{"ref", ref}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayMCPServerResponse](ctx, s.client, http.MethodPatch, path, nil, body) +} + +// Delete unregisters an MCP server. +func (s *ActionGatewayMCPServersService) Delete(ctx context.Context, ref string) (*ActionGatewayMCPServerResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/mcp-servers", actionGatewayPathPart{"ref", ref}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayMCPServerResponse](ctx, s.client, http.MethodDelete, path, nil, nil) +} + +// Resync refreshes a server's discovered tools; the response may be pending. +func (s *ActionGatewayMCPServersService) Resync(ctx context.Context, ref string, body *ActionGatewayMCPServerResyncRequest) (*ActionGatewayMCPServerResyncResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/mcp-servers", actionGatewayPathPart{"ref", ref}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayMCPServerResyncResponse](ctx, s.client, http.MethodPost, path+"/resync", nil, body) +} + +// ListTools retrieves the discovered server tools. +func (s *ActionGatewayMCPServersService) ListTools(ctx context.Context, ref string) (*ActionGatewayMCPServerToolsResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/mcp-servers", actionGatewayPathPart{"ref", ref}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayMCPServerToolsResponse](ctx, s.client, http.MethodGet, path+"/tools", nil, nil) +} + +// UpdateTools changes the enabled server tools. +func (s *ActionGatewayMCPServersService) UpdateTools(ctx context.Context, ref string, body *ActionGatewayMCPServerToolsUpdateRequest) (*ActionGatewayMCPServerToolsResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/mcp-servers", actionGatewayPathPart{"ref", ref}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayMCPServerToolsResponse](ctx, s.client, http.MethodPut, path+"/tools", nil, body) +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_runtime.go b/vendor/github.com/digitalocean/godo/action_gateway_runtime.go new file mode 100644 index 000000000..8de02e728 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_runtime.go @@ -0,0 +1,500 @@ +package godo + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "sync/atomic" +) + +const ( + actionGatewaySearchTool = "action_search" + actionGatewayInvokeTool = "action_invoke" + actionGatewayCodeTool = "action_code" + actionGatewayMCPVersion = "2025-06-18" + actionGatewayMaxRPCResponseBytes = 32 << 20 +) + +// WithActionGatewayMCPBaseURL replaces only the origin of returned session MCP +// URLs. HTTP is permitted for loopback development servers only. +func WithActionGatewayMCPBaseURL(rawURL string) ClientOpt { + return func(client *Client) error { + endpoint, err := url.Parse(rawURL) + if err != nil { + return err + } + if err = validateActionGatewayURL(endpoint, true); err != nil { + return err + } + client.actionGatewayMCPBaseURL = endpoint + return nil + } +} + +func validateActionGatewayURL(endpoint *url.URL, allowLoopback bool) error { + if endpoint == nil || endpoint.Host == "" || endpoint.User != nil || endpoint.Fragment != "" || endpoint.Opaque != "" { + return errors.New("action gateway: invalid MCP URL") + } + if endpoint.Scheme == "https" { + return nil + } + if allowLoopback && endpoint.Scheme == "http" { + host := endpoint.Hostname() + if host == "localhost" || (net.ParseIP(host) != nil && net.ParseIP(host).IsLoopback()) { + return nil + } + } + return errors.New("action gateway: MCP URL must use HTTPS") +} + +// ActionGatewaySession is a control-plane session bound to its MCP endpoint. +type ActionGatewaySession struct { + Record *ActionGatewaySessionRecord + MCPURL string + ActorID string + Tools *ActionGatewaySessionTools + Code *ActionGatewaySessionCode + client *Client + endpoint *url.URL + actorID string + sessionID string + requestID atomic.Int64 +} + +func newActionGatewaySession(client *Client, created *ActionGatewaySessionCreateResponse, actorID string) (*ActionGatewaySession, error) { + if created == nil || created.Session == nil || created.Session.SessionURN == "" || created.MCPURL == "" { + return nil, &ActionGatewayProtocolError{Message: "session create response lacks a session URN or MCP URL"} + } + endpoint, err := url.Parse(created.MCPURL) + if err != nil { + return nil, err + } + if err = validateActionGatewayURL(endpoint, false); err != nil { + return nil, err + } + if client.actionGatewayMCPBaseURL == nil && (!strings.EqualFold(endpoint.Hostname(), "actions.do-ai.run") || (endpoint.Port() != "" && endpoint.Port() != "443")) { + return nil, &ActionGatewayProtocolError{Message: "untrusted MCP URL origin"} + } + if client.actionGatewayMCPBaseURL != nil { + endpoint.Scheme = client.actionGatewayMCPBaseURL.Scheme + endpoint.Host = client.actionGatewayMCPBaseURL.Host + } + if err = validateActionGatewayURL(endpoint, client.actionGatewayMCPBaseURL != nil); err != nil { + return nil, err + } + parts := strings.Split(created.Session.SessionURN, ":") + session := &ActionGatewaySession{Record: created.Session, MCPURL: endpoint.String(), ActorID: actorID, client: client, endpoint: endpoint, actorID: actorID, sessionID: parts[len(parts)-1]} + session.Tools = &ActionGatewaySessionTools{session: session} + session.Code = &ActionGatewaySessionCode{session: session} + return session, nil +} + +// ActionGatewayProtocolError reports a malformed or failed MCP JSON-RPC response. +type ActionGatewayProtocolError struct { + Message string + Code int + Data json.RawMessage +} + +func (err *ActionGatewayProtocolError) Error() string { + return "action gateway protocol: " + err.Message +} + +// ActionGatewayToolError reports an individual failed tool invocation. +type ActionGatewayToolError struct { + Message string `json:"message"` + Class string `json:"class,omitempty"` + Retriable bool `json:"retriable,omitempty"` + RecoveryHint string `json:"recovery_hint,omitempty"` + InvocationID string `json:"invocation_id,omitempty"` + Meta json.RawMessage `json:"_meta,omitempty"` +} + +func (err *ActionGatewayToolError) Error() string { return "action gateway tool: " + err.Message } + +func (session *ActionGatewaySession) headers(request *http.Request) { + request.Header.Set("MCP-Protocol-Version", actionGatewayMCPVersion) + request.Header.Set("X-Actor-Id", session.actorID) + request.Header.Set("X-Session-Id", session.sessionID) +} + +func (session *ActionGatewaySession) do(ctx context.Context, endpoint string, body interface{}, accept string, readBody func(*http.Response) ([]byte, error)) ([]byte, *Response, error) { + request, err := session.client.NewRequest(ctx, http.MethodPost, endpoint, body) + if err != nil { + return nil, nil, err + } + session.headers(request) + request.Header.Set("Accept", accept) + client := *session.client.HTTPClient + previousRedirect := client.CheckRedirect + client.CheckRedirect = func(redirect *http.Request, via []*http.Request) error { + if redirect.URL.Scheme != request.URL.Scheme || redirect.URL.Host != request.URL.Host { + return errors.New("action gateway: cross-origin redirect refused") + } + if previousRedirect != nil { + return previousRedirect(redirect, via) + } + if len(via) >= 10 { + return errors.New("action gateway: too many redirects") + } + return nil + } + if session.client.rateLimiter != nil { + if err = session.client.rateLimiter.Wait(ctx); err != nil { + return nil, nil, err + } + } + actual, err := DoRequestWithClient(ctx, &client, request) + if err != nil { + return nil, nil, err + } + defer actual.Body.Close() + if session.client.onRequestCompleted != nil { + session.client.onRequestCompleted(request, actual) + } + response := newResponse(actual) + session.client.ratemtx.Lock() + session.client.Rate = response.Rate + session.client.ratemtx.Unlock() + if err = CheckResponse(actual); err != nil { + return nil, response, err + } + if readBody == nil { + readBody = func(response *http.Response) ([]byte, error) { return io.ReadAll(response.Body) } + } + data, err := readBody(actual) + return data, response, err +} + +func (session *ActionGatewaySession) rpc(ctx context.Context, method string, params interface{}) (json.RawMessage, *Response, error) { + requestID := session.requestID.Add(1) + message := map[string]interface{}{"jsonrpc": "2.0", "id": requestID, "method": method} + if params != nil { + message["params"] = params + } + data, response, err := session.do(ctx, session.endpoint.String(), message, "application/json, text/event-stream", func(actual *http.Response) ([]byte, error) { + mediaType, _, _ := mime.ParseMediaType(actual.Header.Get("Content-Type")) + if mediaType == "text/event-stream" { + limited := &io.LimitedReader{R: actual.Body, N: actionGatewayMaxRPCResponseBytes + 1} + reader := NewSSEReader(limited) + for { + event, err := reader.Next() + if limited.N == 0 { + return nil, &ActionGatewayProtocolError{Message: "SSE response exceeds size limit"} + } + if errors.Is(err, io.EOF) { + return nil, &ActionGatewayProtocolError{Message: "SSE stream ended without a matching JSON-RPC response"} + } + if err != nil { + return nil, err + } + var candidate struct { + ID json.RawMessage `json:"id"` + Result json.RawMessage `json:"result"` + Error json.RawMessage `json:"error"` + } + if json.Unmarshal(event.Data, &candidate) != nil || (len(candidate.Result) == 0 && len(candidate.Error) == 0) { + continue + } + var eventID int64 + if json.Unmarshal(candidate.ID, &eventID) != nil { + return nil, &ActionGatewayProtocolError{Message: "missing or invalid JSON-RPC response ID"} + } + if eventID != requestID { + return nil, &ActionGatewayProtocolError{Message: "mismatched JSON-RPC response ID"} + } + return append([]byte(nil), event.Data...), nil + } + } + data, err := io.ReadAll(io.LimitReader(actual.Body, actionGatewayMaxRPCResponseBytes+1)) + if len(data) > actionGatewayMaxRPCResponseBytes { + return nil, &ActionGatewayProtocolError{Message: "JSON-RPC response exceeds size limit"} + } + return data, err + }) + if err != nil { + return nil, response, err + } + var envelope struct { + JSONRPC string `json:"jsonrpc"` + ID json.RawMessage `json:"id"` + Result json.RawMessage `json:"result"` + Error *struct { + Code int `json:"code"` + Message string `json:"message"` + Data json.RawMessage `json:"data"` + } `json:"error"` + } + if err = json.Unmarshal(data, &envelope); err != nil { + return nil, response, &ActionGatewayProtocolError{Message: "malformed JSON-RPC response"} + } + if envelope.JSONRPC != "2.0" { + return nil, response, &ActionGatewayProtocolError{Message: "missing JSON-RPC version"} + } + if !bytes.Equal(bytes.TrimSpace(envelope.ID), []byte(strconv.FormatInt(requestID, 10))) { + return nil, response, &ActionGatewayProtocolError{Message: "mismatched JSON-RPC response ID"} + } + if envelope.Error != nil { + return nil, response, &ActionGatewayProtocolError{Message: envelope.Error.Message, Code: envelope.Error.Code, Data: envelope.Error.Data} + } + if len(envelope.Result) == 0 || string(envelope.Result) == "null" { + return nil, response, &ActionGatewayProtocolError{Message: "missing JSON-RPC result"} + } + return envelope.Result, response, nil +} + +// ActionGatewayMCPTool is a session-visible MCP tool definition. +type ActionGatewayMCPTool struct { + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + InputSchema json.RawMessage `json:"inputSchema"` + OutputSchema json.RawMessage `json:"outputSchema"` +} + +// ActionGatewaySessionTools discovers and invokes bound session tools. +type ActionGatewaySessionTools struct{ session *ActionGatewaySession } + +// List retrieves the three meta-tools by default; includeAll also retrieves concrete tools. +func (tools *ActionGatewaySessionTools) List(ctx context.Context, includeAll bool) ([]ActionGatewayMCPTool, *Response, error) { + result, response, err := tools.session.rpc(ctx, "tools/list", nil) + if err != nil { + return nil, response, err + } + var catalog struct { + Tools []ActionGatewayMCPTool `json:"tools"` + } + if err = json.Unmarshal(result, &catalog); err != nil { + return nil, response, err + } + if includeAll { + return catalog.Tools, response, nil + } + meta := make([]ActionGatewayMCPTool, 0, 3) + for _, tool := range catalog.Tools { + if tool.Name == actionGatewaySearchTool || tool.Name == actionGatewayInvokeTool || tool.Name == actionGatewayCodeTool { + meta = append(meta, tool) + } + } + return meta, response, nil +} + +// ActionGatewayToolQuery describes a use case to search for tools. +type ActionGatewayToolQuery struct { + UseCase string `json:"use_case"` + KnownFields string `json:"known_fields,omitempty"` +} + +// ActionGatewayToolSearchRequest specifies use cases and optional filters. +type ActionGatewayToolSearchRequest struct { + Queries []ActionGatewayToolQuery `json:"queries"` + Providers []string `json:"providers,omitempty"` + Tags []string `json:"tags,omitempty"` + Limit *int `json:"limit,omitempty"` +} + +// Search discovers catalog tools by use case. +func (tools *ActionGatewaySessionTools) Search(ctx context.Context, query *ActionGatewayToolSearchRequest) (json.RawMessage, *Response, error) { + if query == nil || len(query.Queries) < 1 || len(query.Queries) > 5 { + return nil, nil, NewArgError("queries", "must contain 1 to 5 use cases") + } + for _, entry := range query.Queries { + if strings.TrimSpace(entry.UseCase) == "" { + return nil, nil, NewArgError("useCase", "cannot be empty") + } + } + return tools.Call(ctx, actionGatewaySearchTool, query) +} + +// ActionGatewayInvokeTool describes one invocation and its JSON arguments. +type ActionGatewayInvokeTool struct { + Tool string `json:"tool"` + Arguments json.RawMessage `json:"arguments"` +} + +// ActionGatewayInvokeResult contains one item in a batch invocation. +type ActionGatewayInvokeResult struct { + Tool string `json:"tool"` + Result struct { + Status string `json:"status"` + Output json.RawMessage `json:"output"` + Error *ActionGatewayToolError `json:"error"` + Meta json.RawMessage `json:"_meta"` + } `json:"result"` +} + +// ActionGatewayInvokeResponse contains the full invocation batch envelope. +type ActionGatewayInvokeResponse struct { + TotalCount int `json:"total_count"` + SuccessCount int `json:"success_count"` + ErrorCount int `json:"error_count"` + Results []ActionGatewayInvokeResult `json:"results"` +} + +// Invoke runs one to ten tools and retains individual failures in the envelope. +func (tools *ActionGatewaySessionTools) Invoke(ctx context.Context, entries []ActionGatewayInvokeTool, rationale string) (*ActionGatewayInvokeResponse, *Response, error) { + if len(entries) < 1 || len(entries) > 10 { + return nil, nil, NewArgError("tools", "must contain 1 to 10 tools") + } + for _, entry := range entries { + if entry.Tool == "" { + return nil, nil, NewArgError("tool", "cannot be empty") + } + } + entries = append([]ActionGatewayInvokeTool(nil), entries...) + for index := range entries { + entries[index].Arguments = actionGatewayArguments(entries[index].Arguments) + } + arguments := map[string]interface{}{"tools": entries} + if rationale != "" { + arguments["rationale"] = rationale + } + result, response, err := tools.Call(ctx, actionGatewayInvokeTool, arguments) + if err != nil { + return nil, response, err + } + var batch ActionGatewayInvokeResponse + if err = json.Unmarshal(result, &batch); err != nil { + return nil, response, err + } + return &batch, response, nil +} + +// InvokeOne invokes a single tool and returns its output or individual failure. +func (tools *ActionGatewaySessionTools) InvokeOne(ctx context.Context, name string, arguments json.RawMessage, rationale string) (json.RawMessage, *Response, error) { + batch, response, err := tools.Invoke(ctx, []ActionGatewayInvokeTool{{Tool: name, Arguments: arguments}}, rationale) + if err != nil { + return nil, response, err + } + if len(batch.Results) == 0 { + return nil, response, &ActionGatewayProtocolError{Message: "invocation returned no results"} + } + first := batch.Results[0].Result + if first.Status != "" && first.Status != "succeeded" { + if first.Error != nil { + first.Error.Meta = first.Meta + return nil, response, first.Error + } + return nil, response, &ActionGatewayToolError{Message: "tool invocation failed"} + } + return first.Output, response, nil +} + +// Call invokes one MCP tool directly, including the gateway meta-tools. +func (tools *ActionGatewaySessionTools) Call(ctx context.Context, name string, arguments interface{}) (json.RawMessage, *Response, error) { + if strings.TrimSpace(name) == "" { + return nil, nil, NewArgError("name", "cannot be empty") + } + if arguments == nil { + arguments = map[string]interface{}{} + } + result, response, err := tools.session.rpc(ctx, "tools/call", map[string]interface{}{"name": name, "arguments": arguments}) + if err != nil { + return nil, response, err + } + var call struct { + IsError bool `json:"isError"` + Structured json.RawMessage `json:"structuredContent"` + Content []struct { + Type string `json:"type"` + Text string `json:"text"` + } `json:"content"` + Meta json.RawMessage `json:"_meta"` + } + if err = json.Unmarshal(result, &call); err != nil { + return nil, response, err + } + output := call.Structured + if len(output) == 0 || string(output) == "null" { + var text strings.Builder + for _, block := range call.Content { + if block.Type == "text" { + if text.Len() != 0 { + text.WriteByte('\n') + } + text.WriteString(block.Text) + } + } + output = []byte(text.String()) + if !json.Valid(output) { + output, _ = json.Marshal(text.String()) + } + } + if call.IsError { + failure := &ActionGatewayToolError{Message: "tool call failed", Meta: call.Meta} + var details struct { + Error *ActionGatewayToolError `json:"error"` + InvocationID string `json:"invocation_id"` + Message string `json:"message"` + } + if json.Unmarshal(output, &details) == nil { + if details.Error != nil { + failure = details.Error + failure.Meta = call.Meta + } + if details.Message != "" { + failure.Message = details.Message + } + if details.InvocationID != "" { + failure.InvocationID = details.InvocationID + } + } + return nil, response, failure + } + return output, response, nil +} + +// ActionGatewaySessionCode executes sandboxed Python through action_code. +type ActionGatewaySessionCode struct{ session *ActionGatewaySession } + +// Execute runs code in the gateway sandbox. +func (code *ActionGatewaySessionCode) Execute(ctx context.Context, source, thought string) (json.RawMessage, *Response, error) { + if source == "" { + return nil, nil, NewArgError("source", "cannot be empty") + } + arguments := map[string]string{"code": source} + if thought != "" { + arguments["thought"] = thought + } + return code.session.Tools.Call(ctx, actionGatewayCodeTool, arguments) +} + +// Approve approves a pending tool invocation. +func (session *ActionGatewaySession) Approve(ctx context.Context, approvalID string) (json.RawMessage, *Response, error) { + return session.decide(ctx, approvalID, "approve") +} + +// Deny denies a pending tool invocation. +func (session *ActionGatewaySession) Deny(ctx context.Context, approvalID string) (json.RawMessage, *Response, error) { + return session.decide(ctx, approvalID, "deny") +} + +func (session *ActionGatewaySession) decide(ctx context.Context, approvalID, decision string) (json.RawMessage, *Response, error) { + if strings.TrimSpace(approvalID) == "" { + return nil, nil, NewArgError("approvalID", "cannot be empty") + } + endpoint := *session.endpoint + endpoint.Path = "/approvals/" + approvalID + endpoint.RawPath = "/approvals/" + url.PathEscape(approvalID) + endpoint.RawQuery = "" + data, response, err := session.do(ctx, endpoint.String(), map[string]string{"decision": decision}, "application/json", nil) + if err != nil { + return nil, response, err + } + if len(bytes.TrimSpace(data)) == 0 { + return nil, response, nil + } + if !json.Valid(data) { + return nil, response, &ActionGatewayProtocolError{Message: fmt.Sprintf("invalid approval response: %q", data)} + } + return data, response, nil +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_sessions.go b/vendor/github.com/digitalocean/godo/action_gateway_sessions.go new file mode 100644 index 000000000..cf8916eb4 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_sessions.go @@ -0,0 +1,178 @@ +package godo + +import ( + "context" + "encoding/json" + "net/http" + "strings" + "time" +) + +// ActionGatewaySessionsService manages control-plane session records. +type ActionGatewaySessionsService struct{ client *Client } + +// ActionGatewaySessionPolicy controls tool permissions. +type ActionGatewaySessionPolicy struct { + DefaultAction string `json:"defaultAction,omitempty"` + Rules []ActionGatewaySessionPolicyRule `json:"rules,omitempty"` +} + +// ActionGatewaySessionPolicyRule selects a tool and allowed argument matches. +type ActionGatewaySessionPolicyRule struct { + Tool string `json:"tool"` + Match map[string]string `json:"match,omitempty"` + Action string `json:"action"` +} + +// ActionGatewaySessionNetwork places a session on a VPC. +type ActionGatewaySessionNetwork struct { + VPCUUID string `json:"vpcUuid"` +} + +// ActionGatewaySessionInsights controls session telemetry. +type ActionGatewaySessionInsights struct { + Metrics *bool `json:"metrics,omitempty"` + Logs *bool `json:"logs,omitempty"` + Traces *bool `json:"traces,omitempty"` +} + +// ActionGatewaySessionToolReference is a pinned tool or toolbelt reference. +type ActionGatewaySessionToolReference struct { + Kind string `json:"kind"` + Name string `json:"name"` + Version string `json:"version"` +} + +// ActionGatewaySessionRecord is the public control-plane session representation. +type ActionGatewaySessionRecord struct { + SessionURN string `json:"sessionUrn"` + Name string `json:"name"` + ActorID string `json:"actorId"` + Policy *ActionGatewaySessionPolicy `json:"policy"` + Tools *struct { + References []ActionGatewaySessionToolReference `json:"references"` + } `json:"tools"` + Config json.RawMessage `json:"config"` + Network *ActionGatewaySessionNetwork `json:"network"` + Insights *ActionGatewaySessionInsights `json:"insights"` + AgentURN string `json:"agentUrn"` + AgentName string `json:"agentName"` + CreatedAt *Timestamp `json:"createdAt"` + UpdatedAt *Timestamp `json:"updatedAt"` + OwningUserID string `json:"owning_user_id"` + OwningUserNumericID string `json:"owning_user_numeric_id"` +} + +// ActionGatewaySessionCreateRequest is the wire request for creating a session. +// Nil Tools enables all tools; a non-nil empty slice enables none. +type ActionGatewaySessionCreateRequest struct { + Name string `json:"name"` + ActorID string `json:"actorId,omitempty"` + Policy *ActionGatewaySessionPolicy `json:"policy,omitempty"` + Tools []string `json:"tools,omitempty"` + Config json.RawMessage `json:"config,omitempty"` + Network *ActionGatewaySessionNetwork `json:"network,omitempty"` + Insights *ActionGatewaySessionInsights `json:"insights,omitempty"` +} + +// MarshalJSON preserves an explicitly empty tools selection on create. +func (body ActionGatewaySessionCreateRequest) MarshalJSON() ([]byte, error) { + type wire ActionGatewaySessionCreateRequest + encoded, err := json.Marshal(wire(body)) + if err != nil || body.Tools == nil { + return encoded, err + } + var fields map[string]json.RawMessage + if err = json.Unmarshal(encoded, &fields); err != nil { + return nil, err + } + fields["tools"], err = json.Marshal(body.Tools) + if err != nil { + return nil, err + } + return json.Marshal(fields) +} + +// ActionGatewaySessionCreateResponse contains the new session and pinned MCP URL. +type ActionGatewaySessionCreateResponse struct { + Session *ActionGatewaySessionRecord `json:"session"` + MCPURL string `json:"mcpUrl"` + Tools []string `json:"tools"` +} + +// ActionGatewaySessionsResponse contains offset-paginated sessions. +type ActionGatewaySessionsResponse struct { + Sessions []ActionGatewaySessionRecord `json:"sessions"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewaySearchSessionsResponse contains cursor-paginated sessions. +type ActionGatewaySearchSessionsResponse struct { + Sessions []ActionGatewaySessionRecord `json:"sessions"` + NextPageToken string `json:"next_page_token"` +} + +// ActionGatewaySessionListOptions filters sessions by end user. +type ActionGatewaySessionListOptions struct { + EndUserID string `url:"end_user_id,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// ActionGatewaySessionSearchOptions searches sessions by query and user. +type ActionGatewaySessionSearchOptions struct { + Query string `url:"query,omitempty"` + EndUserID string `url:"end_user_id,omitempty"` + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} + +// Create creates a control-plane session. +func (s *ActionGatewaySessionsService) Create(ctx context.Context, body *ActionGatewaySessionCreateRequest) (*ActionGatewaySessionCreateResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + return actionGatewayRequest[ActionGatewaySessionCreateResponse](ctx, s.client, http.MethodPost, actionGatewayPath+"/sessions", nil, body) +} + +// List lists sessions. +func (s *ActionGatewaySessionsService) List(ctx context.Context, opt *ActionGatewaySessionListOptions) (*ActionGatewaySessionsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewaySessionsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/sessions", opt, nil) +} + +// Search searches sessions using cursor pagination. +func (s *ActionGatewaySessionsService) Search(ctx context.Context, opt *ActionGatewaySessionSearchOptions) (*ActionGatewaySearchSessionsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewaySearchSessionsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/sessions/search", opt, nil) +} + +// Delete deletes a session by URN. +func (s *ActionGatewaySessionsService) Delete(ctx context.Context, urn string) (*Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/sessions", actionGatewayPathPart{"urn", urn}) + if err != nil { + return nil, err + } + _, response, err := actionGatewayRequest[struct{}](ctx, s.client, http.MethodDelete, path, nil, nil) + return response, err +} + +// CreateRuntime creates and binds a session to its MCP endpoint. It supplies a +// generated name and an ask-by-default policy when those are omitted. +func (s *ActionGatewaySessionsService) CreateRuntime(ctx context.Context, body *ActionGatewaySessionCreateRequest) (*ActionGatewaySession, *Response, error) { + if body == nil || strings.TrimSpace(body.ActorID) == "" { + return nil, nil, NewArgError("actorID", "cannot be empty") + } + copy := *body + copy.ActorID = strings.TrimSpace(body.ActorID) + if copy.Name == "" { + copy.Name = "godo-session-" + time.Now().UTC().Format("20060102-150405.000000000") + } + if copy.Policy == nil { + copy.Policy = &ActionGatewaySessionPolicy{DefaultAction: "ask"} + } + created, response, err := s.Create(ctx, ©) + if err != nil { + return nil, response, err + } + session, err := newActionGatewaySession(s.client, created, copy.ActorID) + return session, response, err +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_toolbelts.go b/vendor/github.com/digitalocean/godo/action_gateway_toolbelts.go new file mode 100644 index 000000000..9f39fb843 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_toolbelts.go @@ -0,0 +1,223 @@ +package godo + +import ( + "context" + "net/http" +) + +// ActionGatewayToolbeltsService manages versioned toolbelts. +type ActionGatewayToolbeltsService struct{ client *Client } + +// ActionGatewayToolbelt is an immutable version of a toolbelt. +type ActionGatewayToolbelt struct { + Name string `json:"name"` + Version string `json:"version"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + Tools []string `json:"tools"` + Status string `json:"status"` + Reference string `json:"reference"` + ReferenceLatest string `json:"reference_latest"` + ToolCount int `json:"tool_count"` + CreatedAt *Timestamp `json:"created_at"` + UpdatedAt *Timestamp `json:"updated_at"` +} + +// ActionGatewayToolbeltSummary summarizes the latest version. +type ActionGatewayToolbeltSummary struct { + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + LatestVersion string `json:"latest_version"` + VersionCount int `json:"version_count"` + ToolCount int `json:"tool_count"` + Status string `json:"status"` + ReferenceLatest string `json:"reference_latest"` + UpdatedAt *Timestamp `json:"updated_at"` +} + +// ActionGatewayToolbeltTool describes one pinned tool. +type ActionGatewayToolbeltTool struct { + ToolSlug string `json:"tool_slug"` + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + Provider string `json:"provider"` + Category string `json:"category"` + Version int `json:"version"` +} + +// ActionGatewayToolbeltProvider describes a provider in a toolbelt. +type ActionGatewayToolbeltProvider struct { + Provider string `json:"provider"` + ToolCount int `json:"tool_count"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Categories []string `json:"categories"` + CreatedAt *Timestamp `json:"created_at"` +} + +// ActionGatewayToolbeltCreateRequest creates a toolbelt. +type ActionGatewayToolbeltCreateRequest struct { + Name string `json:"name"` + Version string `json:"version,omitempty"` + DisplayName string `json:"display_name,omitempty"` + Description string `json:"description,omitempty"` + Tools []string `json:"tools,omitempty"` +} + +// ActionGatewayToolbeltToolsRequest modifies a toolbelt's members. +type ActionGatewayToolbeltToolsRequest struct { + Tools []string `json:"tools"` +} + +// ActionGatewayToolbeltResponse contains the resulting toolbelt. +type ActionGatewayToolbeltResponse struct { + Toolbelt *ActionGatewayToolbelt `json:"toolbelt"` +} + +// ActionGatewayGetToolbeltResponse contains one toolbelt and cursor-paginated member details. +type ActionGatewayGetToolbeltResponse struct { + Toolbelt *ActionGatewayToolbelt `json:"toolbelt"` + ToolDetails []ActionGatewayToolbeltTool `json:"tool_details"` + NextPageToken string `json:"next_page_token"` +} + +// ActionGatewayToolbeltsResponse contains an offset-paginated list. +type ActionGatewayToolbeltsResponse struct { + Toolbelts []ActionGatewayToolbeltSummary `json:"toolbelts"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewaySearchToolbeltsResponse contains cursor-paginated search results. +type ActionGatewaySearchToolbeltsResponse struct { + Toolbelts []ActionGatewayToolbeltSummary `json:"toolbelts"` + NextPageToken string `json:"next_page_token"` +} + +// ActionGatewayToolbeltProvidersResponse contains a toolbelt's providers. +type ActionGatewayToolbeltProvidersResponse struct { + Toolbelt *ActionGatewayToolbelt `json:"toolbelt"` + Providers []ActionGatewayToolbeltProvider `json:"providers"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayToolbeltProviderToolsResponse contains tools for a provider. +type ActionGatewayToolbeltProviderToolsResponse struct { + Tools []ActionGatewayToolbeltTool `json:"tools"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayToolbeltsListOptions filters an offset-based toolbelt list. +type ActionGatewayToolbeltsListOptions struct { + Status string `url:"status,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// ActionGatewayToolbeltsSearchOptions filters cursor-based toolbelt search. +type ActionGatewayToolbeltsSearchOptions struct { + Query string `url:"query,omitempty"` + Status string `url:"status,omitempty"` + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} + +// ActionGatewayGetToolbeltOptions selects a version and pages member details. +type ActionGatewayGetToolbeltOptions struct { + Version string `url:"version,omitempty"` + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` + Search string `url:"search,omitempty"` +} + +// ActionGatewayToolbeltProviderOptions selects a version and offset page. +type ActionGatewayToolbeltProviderOptions struct { + Version string `url:"version,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` + Search string `url:"search,omitempty"` +} + +// Create creates a toolbelt. +func (s *ActionGatewayToolbeltsService) Create(ctx context.Context, body *ActionGatewayToolbeltCreateRequest) (*ActionGatewayToolbeltResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + return actionGatewayRequest[ActionGatewayToolbeltResponse](ctx, s.client, http.MethodPost, actionGatewayPath+"/toolbelts", nil, body) +} + +// List lists toolbelts. +func (s *ActionGatewayToolbeltsService) List(ctx context.Context, opt *ActionGatewayToolbeltsListOptions) (*ActionGatewayToolbeltsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayToolbeltsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/toolbelts", opt, nil) +} + +// Search searches toolbelts using cursor pagination. +func (s *ActionGatewayToolbeltsService) Search(ctx context.Context, opt *ActionGatewayToolbeltsSearchOptions) (*ActionGatewaySearchToolbeltsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewaySearchToolbeltsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/toolbelts/search", opt, nil) +} + +// Get retrieves one toolbelt. +func (s *ActionGatewayToolbeltsService) Get(ctx context.Context, name string, opt *ActionGatewayGetToolbeltOptions) (*ActionGatewayGetToolbeltResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/toolbelts", actionGatewayPathPart{"name", name}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayGetToolbeltResponse](ctx, s.client, http.MethodGet, path, opt, nil) +} + +// Delete deletes a toolbelt. +func (s *ActionGatewayToolbeltsService) Delete(ctx context.Context, name string) (*ActionGatewayToolbeltResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/toolbelts", actionGatewayPathPart{"name", name}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayToolbeltResponse](ctx, s.client, http.MethodDelete, path, nil, nil) +} + +// AddTools creates a new version containing the additional tools. +func (s *ActionGatewayToolbeltsService) AddTools(ctx context.Context, name string, body *ActionGatewayToolbeltToolsRequest) (*ActionGatewayToolbeltResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/toolbelts", actionGatewayPathPart{"name", name}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayToolbeltResponse](ctx, s.client, http.MethodPost, path+"/tools/add", nil, body) +} + +// RemoveTools creates a new version without the specified tools. +func (s *ActionGatewayToolbeltsService) RemoveTools(ctx context.Context, name string, body *ActionGatewayToolbeltToolsRequest) (*ActionGatewayToolbeltResponse, *Response, error) { + if body == nil { + return nil, nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/toolbelts", actionGatewayPathPart{"name", name}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayToolbeltResponse](ctx, s.client, http.MethodPost, path+"/tools/remove", nil, body) +} + +// ListProviders lists providers in a toolbelt. +func (s *ActionGatewayToolbeltsService) ListProviders(ctx context.Context, name string, opt *ActionGatewayToolbeltProviderOptions) (*ActionGatewayToolbeltProvidersResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/toolbelts", actionGatewayPathPart{"name", name}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayToolbeltProvidersResponse](ctx, s.client, http.MethodGet, path+"/providers", opt, nil) +} + +// ListProviderTools lists a provider's tools in a toolbelt. +func (s *ActionGatewayToolbeltsService) ListProviderTools(ctx context.Context, name, provider string, opt *ActionGatewayToolbeltProviderOptions) (*ActionGatewayToolbeltProviderToolsResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/toolbelts", actionGatewayPathPart{"name", name}) + if err != nil { + return nil, nil, err + } + path, err = actionGatewayItem(path+"/providers", actionGatewayPathPart{"provider", provider}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayToolbeltProviderToolsResponse](ctx, s.client, http.MethodGet, path+"/tools", opt, nil) +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_tools.go b/vendor/github.com/digitalocean/godo/action_gateway_tools.go new file mode 100644 index 000000000..fcc117c93 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_tools.go @@ -0,0 +1,405 @@ +package godo + +import ( + "context" + "encoding/json" + "net/http" +) + +// ActionGatewayToolsService exposes catalog and health operations. +type ActionGatewayToolsService struct{ client *Client } + +// ActionGatewayToolkit describes a catalog toolkit. +type ActionGatewayToolkit struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Categories []string `json:"categories"` + CreatedAt *Timestamp `json:"created_at"` +} + +// ActionGatewayToolAnnotations describes a tool's behavioral hints. +type ActionGatewayToolAnnotations struct { + Title string `json:"title"` + ReadOnlyHint bool `json:"readOnlyHint"` + IdempotentHint bool `json:"idempotentHint"` + OpenWorldHint bool `json:"openWorldHint"` + DestructiveHint bool `json:"destructiveHint"` +} + +// ActionGatewayTool describes a catalog tool. +type ActionGatewayTool struct { + Name string `json:"name"` + ToolkitID string `json:"toolkitId"` + ToolSlug string `json:"toolSlug"` + Title string `json:"title"` + Description string `json:"description"` + Version string `json:"version"` + InputSchema json.RawMessage `json:"inputSchema"` + OutputSchema json.RawMessage `json:"outputSchema"` + Annotations *ActionGatewayToolAnnotations `json:"annotations"` + Parallelizable bool `json:"parallelizable"` + StreamingSafe bool `json:"streamingSafe"` +} + +// ActionGatewayHTTPLookup describes HTTP-based credential resolution. +type ActionGatewayHTTPLookup struct { + Method string `json:"method"` + URL string `json:"url"` + MatchField string `json:"matchField"` + MatchValue string `json:"matchValue"` + MatchValueParameter string `json:"match_value_parameter"` + CaseInsensitive bool `json:"caseInsensitive"` + TrimTrailingSlash bool `json:"trimTrailingSlash"` + ExtractField string `json:"extractField"` + BaseURLTemplate string `json:"baseUrlTemplate"` + RequiredScopes []string `json:"requiredScopes"` +} + +// ActionGatewayToolDefinition is the full public catalog definition. +type ActionGatewayToolDefinition struct { + ToolID string `json:"toolId"` + Name string `json:"name"` + ToolkitID string `json:"toolkitId"` + ToolSlug string `json:"toolSlug"` + Title string `json:"title"` + Description string `json:"description"` + InputSchema json.RawMessage `json:"inputSchema"` + OutputSchema json.RawMessage `json:"outputSchema"` + Annotations *ActionGatewayToolAnnotations `json:"annotations"` + Version string `json:"version"` + SchemaVersion string `json:"schemaVersion"` + Status string `json:"status"` + Parallelizable bool `json:"parallelizable"` + StreamingSafe bool `json:"streamingSafe"` + Auth *ActionGatewayToolAuth `json:"auth"` + Execution *ActionGatewayToolExecution `json:"execution"` + Transform *ActionGatewayToolTransform `json:"transform"` + Policy *ActionGatewayToolPolicy `json:"policy"` + Reliability *ActionGatewayToolReliability `json:"reliability"` + Hooks *ActionGatewayToolHooks `json:"hooks"` + Classification *ActionGatewayToolClassification `json:"classification"` + FlipperName string `json:"flipperName"` + Tags []string `json:"tags"` + ProviderKind string `json:"providerKind"` +} + +// ActionGatewayToolAuth describes a definition's authentication requirements. +type ActionGatewayToolAuth struct { + Modes []string `json:"modes"` + Provider string `json:"provider"` + Scopes []string `json:"scopes"` + CredentialBinding string `json:"credentialBinding"` + CredentialRefSource string `json:"credentialRefSource"` + DOManagedCredentialRef string `json:"doManagedCredentialRef"` + Injection *struct { + Location string `json:"location"` + Name string `json:"name"` + Scheme string `json:"scheme"` + } `json:"injection"` + BaseURLResolution *struct { + HTTPLookup *ActionGatewayHTTPLookup `json:"httpLookup"` + } `json:"baseUrlResolution"` + HeaderResolution []struct { + HeaderName string `json:"headerName"` + HTTPLookup *ActionGatewayHTTPLookup `json:"httpLookup"` + } `json:"headerResolution"` + TeamAPIKey *struct { + TokenExchange *struct { + Method string `json:"method"` + URLTemplate string `json:"urlTemplate"` + ExtractField string `json:"extractField"` + BodyParameter string `json:"bodyParameter"` + } `json:"tokenExchange"` + } `json:"teamApiKey"` +} + +// ActionGatewayToolExecution describes a tool execution adapter. +type ActionGatewayToolExecution struct { + Type string `json:"type"` + AdapterVersion string `json:"adapterVersion"` + ConfigRef string `json:"configRef"` + HTTP *struct { + BaseURL string `json:"baseUrl"` + Path string `json:"path"` + Method string `json:"method"` + AllowedHosts []string `json:"allowedHosts"` + RequestEncoding string `json:"requestEncoding"` + ResponseFormat string `json:"responseFormat"` + AllowedHostSuffixes []string `json:"allowed_host_suffixes"` + } `json:"http"` + MCP *struct { + Endpoint string `json:"endpoint"` + ToolName string `json:"toolName"` + Transport string `json:"transport"` + ServerRef string `json:"serverRef"` + AllowedHosts []string `json:"allowedHosts"` + AllowedHostSuffixes []string `json:"allowed_host_suffixes"` + } `json:"mcp"` +} + +// ActionGatewayToolTransform describes public input and output mappings. +type ActionGatewayToolTransform struct { + Language string `json:"language"` + Input json.RawMessage `json:"input"` + Output json.RawMessage `json:"output"` +} + +// ActionGatewayToolPolicy describes a tool's permission classification. +type ActionGatewayToolPolicy struct { + Permission string `json:"permission"` +} + +// ActionGatewayToolReliability describes timeouts and retry settings. +type ActionGatewayToolReliability struct { + TimeoutMS int `json:"timeoutMs"` + MaxOutputBytes string `json:"maxOutputBytes"` + Retry *struct { + MaxAttempts int `json:"maxAttempts"` + Backoff string `json:"backoff"` + RetryOn []string `json:"retryOn"` + HonorRetryAfter bool `json:"honorRetryAfter"` + BackoffPolicy *struct { + InitialMS string `json:"initialMs"` + Multiplier float64 `json:"multiplier"` + MaxMS string `json:"maxMs"` + Jitter string `json:"jitter"` + } `json:"backoffPolicy"` + Replay *struct { + Mode string `json:"mode"` + } `json:"replay"` + Billable *struct { + Enabled bool `json:"enabled"` + } `json:"billable"` + } `json:"retry"` + ProviderRateLimit *struct { + Scope string `json:"scope"` + } `json:"provider_rate_limit"` +} + +// ActionGatewayToolHooks describes billing hooks. +type ActionGatewayToolHooks struct { + Usage *struct { + Billable bool `json:"billable"` + Meters []struct { + SKU string `json:"sku"` + Unit string `json:"unit"` + QuantitySource string `json:"quantitySource"` + } `json:"meters"` + } `json:"usage"` +} + +// ActionGatewayToolClassification describes a tool's risk and data classes. +type ActionGatewayToolClassification struct { + Operation string `json:"operation"` + Risk string `json:"risk"` + DataClasses []string `json:"dataClasses"` +} + +// ActionGatewayProvider describes a tool provider and its connection requirements. +type ActionGatewayProvider struct { + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + AuthTypes []string `json:"auth_types"` + AuthType string `json:"auth_type"` + Scopes []string `json:"scopes"` + ConnectionParameters []ActionGatewayConnectionParameter `json:"connection_parameters"` + CredentialParameters []ActionGatewayConnectionParameter `json:"credential_parameters"` +} + +// ActionGatewayConnectionParameter describes one required provider setting. +type ActionGatewayConnectionParameter struct { + Key string `json:"key"` + Label string `json:"label"` + Description string `json:"description"` + InputKind string `json:"input_kind"` + Required bool `json:"required"` + Normalization string `json:"normalization"` + AllowedValues []string `json:"allowed_values"` + AllowedHostSuffixes []string `json:"allowed_host_suffixes"` + MaxLength int `json:"max_length"` + Pattern string `json:"pattern"` +} + +// ActionGatewayToolSearchResult is a compact search hit. +type ActionGatewayToolSearchResult struct { + ToolSlug string `json:"tool_slug"` + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + Provider string `json:"provider"` + Category string `json:"category"` + Version int `json:"version"` +} + +// ActionGatewayHealthSnapshot describes a health sampling window. +type ActionGatewayHealthSnapshot struct { + MeasuredAt *Timestamp `json:"measured_at"` + Window string `json:"window"` + Stale bool `json:"stale"` +} + +// ActionGatewayHealthMetrics describes measured tool or provider health. +type ActionGatewayHealthMetrics struct { + DataStatus string `json:"data_status"` + UptimePercentage float64 `json:"uptime_percentage"` + LatencyP50MS float64 `json:"latency_p50_ms"` + LatencyP95MS float64 `json:"latency_p95_ms"` +} + +// ActionGatewayProviderHealth is a provider's health summary. +type ActionGatewayProviderHealth struct { + Provider string `json:"provider"` + Health *ActionGatewayHealthMetrics `json:"health"` +} + +// ActionGatewayToolHealth is a tool's health summary. +type ActionGatewayToolHealth struct { + ToolSlug string `json:"tool_slug"` + Provider string `json:"provider"` + Health *ActionGatewayHealthMetrics `json:"health"` +} + +// ActionGatewayHealthHistory contains per-bucket measurements. +type ActionGatewayHealthHistory struct { + Resolution string `json:"resolution"` + Stale bool `json:"stale"` + Points []struct { + BucketStart *Timestamp `json:"bucket_start"` + Health *ActionGatewayHealthMetrics `json:"health"` + } `json:"points"` +} + +// ActionGatewayToolListOptions filters the tool catalog. +type ActionGatewayToolListOptions struct { + ToolkitID string `url:"toolkit_id,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// ActionGatewayToolSearchOptions filters the cursor-based tool search. +type ActionGatewayToolSearchOptions struct { + Query string `url:"query,omitempty"` + Providers []string `url:"provider,omitempty"` + Toolbelt string `url:"toolbelt,omitempty"` + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} + +// ActionGatewayProviderSearchOptions filters provider search. +type ActionGatewayProviderSearchOptions struct { + Query string `url:"query,omitempty"` + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} + +// ActionGatewayHealthOptions filters a paginated health listing. +type ActionGatewayHealthOptions struct { + Provider string `url:"provider,omitempty"` + Window string `url:"window,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// ActionGatewayToolHealthOptions selects a tool's health window and history. +type ActionGatewayToolHealthOptions struct { + Window string `url:"window,omitempty"` + IncludeHistory bool `url:"include_history,omitempty"` +} + +// ActionGatewayToolkitsResponse contains toolkits and their catalog version. +type ActionGatewayToolkitsResponse struct { + Version string `json:"version"` + Toolkits []ActionGatewayToolkit `json:"toolkits"` +} + +// ActionGatewayToolsResponse contains a page of tools and definitions. +type ActionGatewayToolsResponse struct { + Version string `json:"version"` + Tools []ActionGatewayTool `json:"tools"` + Definitions []ActionGatewayToolDefinition `json:"definitions"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayProvidersResponse contains providers. +type ActionGatewayProvidersResponse struct { + Providers []ActionGatewayProvider `json:"providers"` +} + +// ActionGatewaySearchToolsResponse contains cursor-paginated tool hits. +type ActionGatewaySearchToolsResponse struct { + Tools []ActionGatewayToolSearchResult `json:"tools"` + NextPageToken string `json:"next_page_token"` +} + +// ActionGatewaySearchProvidersResponse contains cursor-paginated provider hits. +type ActionGatewaySearchProvidersResponse struct { + Providers []ActionGatewayProvider `json:"providers"` + NextPageToken string `json:"next_page_token"` +} + +// ActionGatewayProviderHealthResponse contains paginated provider health. +type ActionGatewayProviderHealthResponse struct { + Snapshot *ActionGatewayHealthSnapshot `json:"snapshot"` + Providers []ActionGatewayProviderHealth `json:"providers"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayToolHealthResponse contains paginated tool health. +type ActionGatewayToolHealthResponse struct { + Snapshot *ActionGatewayHealthSnapshot `json:"snapshot"` + Tools []ActionGatewayToolHealth `json:"tools"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayGetToolHealthResponse contains one tool's health and history. +type ActionGatewayGetToolHealthResponse struct { + Snapshot *ActionGatewayHealthSnapshot `json:"snapshot"` + Tool *ActionGatewayToolHealth `json:"tool"` + History *ActionGatewayHealthHistory `json:"history"` +} + +// List retrieves a page of catalog tools. +func (s *ActionGatewayToolsService) List(ctx context.Context, opt *ActionGatewayToolListOptions) (*ActionGatewayToolsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayToolsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools", opt, nil) +} + +// ListToolkits retrieves catalog toolkits. +func (s *ActionGatewayToolsService) ListToolkits(ctx context.Context) (*ActionGatewayToolkitsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayToolkitsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools/toolkits", nil, nil) +} + +// ListProviders retrieves tool providers. +func (s *ActionGatewayToolsService) ListProviders(ctx context.Context) (*ActionGatewayProvidersResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayProvidersResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools/providers", nil, nil) +} + +// Search retrieves cursor-paginated tool search results. +func (s *ActionGatewayToolsService) Search(ctx context.Context, opt *ActionGatewayToolSearchOptions) (*ActionGatewaySearchToolsResponse, *Response, error) { + return actionGatewayRequest[ActionGatewaySearchToolsResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools/search", opt, nil) +} + +// SearchProviders retrieves cursor-paginated provider search results. +func (s *ActionGatewayToolsService) SearchProviders(ctx context.Context, opt *ActionGatewayProviderSearchOptions) (*ActionGatewaySearchProvidersResponse, *Response, error) { + return actionGatewayRequest[ActionGatewaySearchProvidersResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools/providers/search", opt, nil) +} + +// ListProviderHealth retrieves provider health snapshots. +func (s *ActionGatewayToolsService) ListProviderHealth(ctx context.Context, opt *ActionGatewayHealthOptions) (*ActionGatewayProviderHealthResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayProviderHealthResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools/health/providers", opt, nil) +} + +// ListHealth retrieves tool health snapshots. +func (s *ActionGatewayToolsService) ListHealth(ctx context.Context, opt *ActionGatewayHealthOptions) (*ActionGatewayToolHealthResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayToolHealthResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/tools/health", opt, nil) +} + +// GetHealth retrieves health and optionally history for one tool. +func (s *ActionGatewayToolsService) GetHealth(ctx context.Context, slug string, opt *ActionGatewayToolHealthOptions) (*ActionGatewayGetToolHealthResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/tools/health/tools", actionGatewayPathPart{"slug", slug}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayGetToolHealthResponse](ctx, s.client, http.MethodGet, path, opt, nil) +} diff --git a/vendor/github.com/digitalocean/godo/action_gateway_users.go b/vendor/github.com/digitalocean/godo/action_gateway_users.go new file mode 100644 index 000000000..99fa147dd --- /dev/null +++ b/vendor/github.com/digitalocean/godo/action_gateway_users.go @@ -0,0 +1,146 @@ +package godo + +import ( + "context" + "net/http" +) + +// ActionGatewayUsersService exposes end-user summaries. +type ActionGatewayUsersService struct{ client *Client } + +// ActionGatewayUserSession is a session associated with an end user. +type ActionGatewayUserSession struct { + SessionURN string `json:"session_urn"` + Name string `json:"name"` + CreatedAt *Timestamp `json:"created_at"` + UpdatedAt *Timestamp `json:"updated_at"` +} + +// ActionGatewayUserConnection is a connection associated with an end user. +type ActionGatewayUserConnection struct { + ID string `json:"id"` + Provider string `json:"provider"` + ProviderDisplayName string `json:"provider_display_name"` + UserID string `json:"user_id"` + Scopes []string `json:"scopes"` + Status string `json:"status"` + GrantedAt *Timestamp `json:"granted_at"` + RevokedAt *Timestamp `json:"revoked_at"` + CreatedAt *Timestamp `json:"created_at"` + UpdatedAt *Timestamp `json:"updated_at"` + ConnectionParameters map[string]interface{} `json:"connection_parameters"` + CredentialKind string `json:"credential_kind"` + CredentialID string `json:"credential_id"` + Network *ActionGatewayConnectionNetwork `json:"network"` + OwningUserID string `json:"owning_user_id"` + OwningUserNumericID string `json:"owning_user_numeric_id"` +} + +// ActionGatewayUser is a derived end-user summary. +type ActionGatewayUser struct { + UserID string `json:"user_id"` + Sessions []ActionGatewayUserSession `json:"sessions"` + Connections []ActionGatewayUserConnection `json:"connections"` +} + +// ActionGatewayUsersResponse contains offset-paginated end-user IDs. +type ActionGatewayUsersResponse struct { + UserIDs []string `json:"user_ids"` + Pagination *ActionGatewayPagination `json:"pagination"` +} + +// ActionGatewayUserResponse contains one end-user summary. +type ActionGatewayUserResponse struct { + User *ActionGatewayUser `json:"user"` +} + +// ActionGatewayUsersListOptions filters end users. +type ActionGatewayUsersListOptions struct { + UserID string `url:"user_id,omitempty"` + Sort string `url:"sort,omitempty"` + SortDirection string `url:"sort_direction,omitempty"` + Page int `url:"page,omitempty"` + PerPage int `url:"per_page,omitempty"` +} + +// List lists end-user IDs. +func (s *ActionGatewayUsersService) List(ctx context.Context, opt *ActionGatewayUsersListOptions) (*ActionGatewayUsersResponse, *Response, error) { + return actionGatewayRequest[ActionGatewayUsersResponse](ctx, s.client, http.MethodGet, actionGatewayPath+"/users", opt, nil) +} + +// Get retrieves an end-user summary. +func (s *ActionGatewayUsersService) Get(ctx context.Context, userID string) (*ActionGatewayUserResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/users", actionGatewayPathPart{"userID", userID}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayUserResponse](ctx, s.client, http.MethodGet, path, nil, nil) +} + +// ActionGatewayActorLimitsService manages end-user rate-limit overrides. +type ActionGatewayActorLimitsService struct{ client *Client } + +// ActionGatewayLimitOverride changes one quota category. +type ActionGatewayLimitOverride struct { + Category string `json:"category"` + RequestsPerMinute string `json:"requests_per_minute"` +} + +// ActionGatewayToolQuotas lists effective per-minute limits. +type ActionGatewayToolQuotas struct { + WebSearchRequestsPerMinute string `json:"web_search_requests_per_minute"` + WebFetchRequestsPerMinute string `json:"web_fetch_requests_per_minute"` + NativeToolCallsRequestsPerMinute string `json:"native_tool_calls_requests_per_minute"` + NonNativeToolCallsRequestsPerMinute string `json:"non_native_tool_calls_requests_per_minute"` +} + +// ActionGatewayActorLimitsResponse contains overrides and effective quotas. +type ActionGatewayActorLimitsResponse struct { + ConfiguredLimits []ActionGatewayLimitOverride `json:"configured_limits"` + EffectiveLimits *ActionGatewayToolQuotas `json:"effective_limits"` +} + +// ActionGatewaySetLimitsRequest supplies one or more overrides. +type ActionGatewaySetLimitsRequest struct { + Overrides []ActionGatewayLimitOverride `json:"overrides"` +} + +// ActionGatewayClearLimitsRequest selects categories to clear. +type ActionGatewayClearLimitsRequest struct { + Categories []string `json:"categories"` +} + +// Get retrieves an actor's configured and effective limits. +func (s *ActionGatewayActorLimitsService) Get(ctx context.Context, actorID string) (*ActionGatewayActorLimitsResponse, *Response, error) { + path, err := actionGatewayItem(actionGatewayPath+"/actors", actionGatewayPathPart{"actorID", actorID}) + if err != nil { + return nil, nil, err + } + return actionGatewayRequest[ActionGatewayActorLimitsResponse](ctx, s.client, http.MethodGet, path+"/limits", nil, nil) +} + +// Set changes selected limit categories using the public limits:set action. +func (s *ActionGatewayActorLimitsService) Set(ctx context.Context, actorID string, body *ActionGatewaySetLimitsRequest) (*Response, error) { + if body == nil { + return nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/actors", actionGatewayPathPart{"actorID", actorID}) + if err != nil { + return nil, err + } + _, response, err := actionGatewayRequest[struct{}](ctx, s.client, http.MethodPost, path+"/limits:set", nil, body) + return response, err +} + +// Clear removes selected overrides using the public limits:clear action. +func (s *ActionGatewayActorLimitsService) Clear(ctx context.Context, actorID string, body *ActionGatewayClearLimitsRequest) (*Response, error) { + if body == nil { + return nil, NewArgError("body", "cannot be nil") + } + path, err := actionGatewayItem(actionGatewayPath+"/actors", actionGatewayPathPart{"actorID", actorID}) + if err != nil { + return nil, err + } + _, response, err := actionGatewayRequest[struct{}](ctx, s.client, http.MethodPost, path+"/limits:clear", nil, body) + return response, err +} diff --git a/vendor/github.com/digitalocean/godo/gradientai.go b/vendor/github.com/digitalocean/godo/agentplatform.go similarity index 82% rename from vendor/github.com/digitalocean/godo/gradientai.go rename to vendor/github.com/digitalocean/godo/agentplatform.go index 6b65aa331..68417150c 100644 --- a/vendor/github.com/digitalocean/godo/gradientai.go +++ b/vendor/github.com/digitalocean/godo/agentplatform.go @@ -116,14 +116,15 @@ const ( ModelEvaluationRunFailed ModelEvaluationRunStatus = "MODEL_EVALUATION_RUN_FAILED" ) -// CandidateModelSource indicates whether evaluation inference runs against the -// serverless platform, a dedicated deployment, or a model router. +// CandidateModelSource indicates whether the evaluation candidate is a served +// model (serverless, dedicated, or router) or an OHS-hosted agent config. type CandidateModelSource string const ( CandidateModelSourceServerless CandidateModelSource = "CANDIDATE_MODEL_SOURCE_SERVERLESS" CandidateModelSourceDedicated CandidateModelSource = "CANDIDATE_MODEL_SOURCE_DEDICATED" CandidateModelSourceRouter CandidateModelSource = "CANDIDATE_MODEL_SOURCE_ROUTER" + CandidateModelSourceAgent CandidateModelSource = "CANDIDATE_MODEL_SOURCE_AGENT" ) // ModelEvaluationRunSortField is the field used to sort model evaluation run @@ -156,10 +157,34 @@ const ( EvaluationDatasetTypeModel EvaluationDatasetType = "EVALUATION_DATASET_TYPE_MODEL" ) -// GradientAIService is an interface for interfacing with the Gradient AI Agent endpoints +// EvaluationDatasetParadigm is the row/content shape of a dataset, orthogonal +// to EvaluationDatasetType (e.g. a model dataset can be single- or multi-turn). +type EvaluationDatasetParadigm string + +const ( + EvaluationDatasetParadigmSingleTurn EvaluationDatasetParadigm = "EVALUATION_DATASET_PARADIGM_SINGLE_TURN" + EvaluationDatasetParadigmMultiTurn EvaluationDatasetParadigm = "EVALUATION_DATASET_PARADIGM_MULTI_TURN" + EvaluationDatasetParadigmCoding EvaluationDatasetParadigm = "EVALUATION_DATASET_PARADIGM_CODING" + EvaluationDatasetParadigmNPlus1 EvaluationDatasetParadigm = "EVALUATION_DATASET_PARADIGM_N_PLUS_1" +) + +// PresetSaveSection names a self-contained group of fields that can be +// persisted when creating a model evaluation run as a reusable preset. +type PresetSaveSection string + +const ( + PresetSaveSectionUnspecified PresetSaveSection = "PRESET_SAVE_SECTION_UNSPECIFIED" + PresetSaveSectionCandidate PresetSaveSection = "PRESET_SAVE_SECTION_CANDIDATE" + PresetSaveSectionMetrics PresetSaveSection = "PRESET_SAVE_SECTION_METRICS" + PresetSaveSectionJudge PresetSaveSection = "PRESET_SAVE_SECTION_JUDGE" + PresetSaveSectionDataset PresetSaveSection = "PRESET_SAVE_SECTION_DATASET" + PresetSaveSectionSystemPrompt PresetSaveSection = "PRESET_SAVE_SECTION_SYSTEM_PROMPT" +) + +// AgentPlatformService is an interface for interfacing with the Agent Platform Agent endpoints // of the DigitalOcean API. -// See https://docs.digitalocean.com/reference/api/digitalocean/#tag/GradientAI-Platform for more details. -type GradientAIService interface { +// See https://docs.digitalocean.com/reference/api/digitalocean/#tag/Agent-Platform-API for more details. +type AgentPlatformService interface { ListAgents(context.Context, *ListOptions) ([]*Agent, *Response, error) CreateAgent(context.Context, *AgentCreateRequest) (*Agent, *Response, error) ListAgentAPIKeys(context.Context, string, *ListOptions) ([]*ApiKeyInfo, *Response, error) @@ -236,6 +261,7 @@ type GradientAIService interface { ListModelEvaluationPresets(ctx context.Context) (*ModelEvaluationPresetListResponse, *Response, error) ListModelEvaluationMetrics(ctx context.Context) (*ModelEvaluationMetricListResponse, *Response, error) ListEvaluationDatasets(ctx context.Context, opt *EvaluationDatasetListOptions) (*EvaluationDatasetListResponse, *Response, error) + CreateEvaluationDataset(ctx context.Context, createRequest *CreateEvaluationDatasetRequest) (*CreateEvaluationDatasetResponse, *Response, error) DeleteEvaluationDataset(ctx context.Context, datasetUUID string) (*EvaluationDatasetDeleteResponse, *Response, error) CreateScenarioSetUploadPresignedURLs(ctx context.Context, createRequest *CreateScenarioSetUploadPresignedURLsRequest) (*CreateScenarioSetUploadPresignedURLsResponse, *Response, error) CreateScenarioSet(ctx context.Context, createRequest *CreateScenarioSetRequest) (*ScenarioSet, *Response, error) @@ -261,10 +287,10 @@ type GradientAIService interface { GetSimulationJourneyTrajectory(ctx context.Context, runUUID, journeyUUID string) (*SimulationTrajectory, *Response, error) } -var _ GradientAIService = &GradientAIServiceOp{} +var _ AgentPlatformService = &AgentPlatformServiceOp{} -// GradientAIServiceOp interfaces with the Gradient AI Service endpoints in the DigitalOcean API. -type GradientAIServiceOp struct { +// AgentPlatformServiceOp interfaces with the Agent Platform Service endpoints in the DigitalOcean API. +type AgentPlatformServiceOp struct { client *Client } @@ -319,7 +345,7 @@ type openaiAPIKeyRoot struct { OpenAIAPIKey *OpenAiApiKey `json:"api_key_info,omitempty"` } -// Agent represents a Gradient AI Agent +// Agent represents an Agent Platform Agent type Agent struct { AnthropicApiKey *AnthropicApiKeyInfo `json:"anthropic_api_key,omitempty"` ApiKeyInfos []*ApiKeyInfo `json:"api_key_infos,omitempty"` @@ -364,7 +390,7 @@ type Agent struct { Workspace Workspace `json:"workspace,omitempty"` } -// AgentVersion represents a version of a Gradient AI Agent +// AgentVersion represents a version of an Agent Platform Agent type AgentVersion struct { AgentUuid string `json:"agent_uuid,omitempty"` AttachedChildAgents []*AttachedChildAgent `json:"attached_child_agents,omitempty"` @@ -415,13 +441,13 @@ type AuditHeader struct { UserUUID string `json:"user_uuid,omitempty"` } -// RollbackVersionRequest represents the request to rollback a Gradient AI Agent to a previous version +// RollbackVersionRequest represents the request to rollback an Agent Platform Agent to a previous version type RollbackVersionRequest struct { AgentUuid string `json:"uuid,omitempty"` VersionHash string `json:"version_hash,omitempty"` } -// AgentFunction represents a Gradient AI Agent Function +// AgentFunction represents an Agent Platform Agent Function type AgentFunction struct { ApiKey string `json:"api_key,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` @@ -436,7 +462,7 @@ type AgentFunction struct { IsDeleted bool `json:"is_deleted,omitempty"` } -// AgentGuardrail represents a Guardrail attached to Gradient AI Agent +// AgentGuardrail represents a Guardrail attached to Agent Platform Agent type AgentGuardrail struct { AgentUuid string `json:"agent_uuid,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` @@ -494,7 +520,7 @@ type AgentVisibilityUpdateRequest struct { Visibility string `json:"visibility,omitempty"` } -// AgentTemplate represents the template of a Gradient AI Agent +// AgentTemplate represents the template of an Agent Platform Agent type AgentTemplate struct { CreatedAt *Timestamp `json:"created_at,omitempty"` Instruction string `json:"instruction,omitempty"` @@ -517,6 +543,8 @@ const ( MetricTypeUnspecified EvaluationMetricType = "METRIC_TYPE_UNSPECIFIED" MetricTypeGeneralQuality EvaluationMetricType = "METRIC_TYPE_GENERAL_QUALITY" MetricTypeRAGAndTool EvaluationMetricType = "METRIC_TYPE_RAG_AND_TOOL" + MetricTypeModelQuality EvaluationMetricType = "METRIC_TYPE_MODEL_QUALITY" + MetricTypeModelSafety EvaluationMetricType = "METRIC_TYPE_MODEL_SAFETY" ) // EvaluationMetricValueType represents the value type of an evaluation metric. @@ -539,6 +567,7 @@ const ( MetricCategorySafetyAndSecurity EvaluationMetricCategory = "METRIC_CATEGORY_SAFETY_AND_SECURITY" MetricCategoryContextQuality EvaluationMetricCategory = "METRIC_CATEGORY_CONTEXT_QUALITY" MetricCategoryModelFit EvaluationMetricCategory = "METRIC_CATEGORY_MODEL_FIT" + MetricCategoryConversational EvaluationMetricCategory = "METRIC_CATEGORY_CONVERSATIONAL" ) // EvaluationMetricSource distinguishes platform catalog metrics from user-defined LLM-as-judge metrics. @@ -636,12 +665,14 @@ type UpdateCustomEvaluationMetricRequest struct { // EvaluationDataset represents the dataset information for an evaluation. type EvaluationDataset struct { - DatasetUUID string `json:"dataset_uuid,omitempty"` - DatasetName string `json:"dataset_name,omitempty"` - RowCount uint32 `json:"row_count,omitempty"` - HasGroundTruth bool `json:"has_ground_truth,omitempty"` - FileSize uint64 `json:"file_size,omitempty"` - CreatedAt *Timestamp `json:"created_at,omitempty"` + DatasetUUID string `json:"dataset_uuid,omitempty"` + DatasetName string `json:"dataset_name,omitempty"` + RowCount uint32 `json:"row_count,omitempty"` + HasGroundTruth bool `json:"has_ground_truth,omitempty"` + FileSize uint64 `json:"file_size,omitempty"` + DatasetType EvaluationDatasetType `json:"dataset_type,omitempty"` + DatasetParadigm EvaluationDatasetParadigm `json:"dataset_paradigm,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` } // StarMetric represents a star metric configuration. @@ -652,7 +683,7 @@ type StarMetric struct { SuccessThreshold *float32 `json:"success_threshold,omitempty"` } -// KnowledgeBase represents a Gradient AI Knowledge Base +// KnowledgeBase represents an Agent Platform Knowledge Base type KnowledgeBase struct { AddedToAgentAt *Timestamp `json:"added_to_agent_at,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` @@ -670,7 +701,7 @@ type KnowledgeBase struct { IsDeleted bool `json:"is_deleted,omitempty"` } -// LastIndexingJob represents the last indexing job description of a Gradient AI Knowledge Base +// LastIndexingJob represents the last indexing job description of an Agent Platform Knowledge Base type LastIndexingJob struct { CompletedDatasources int `json:"completed_datasources,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` @@ -733,7 +764,7 @@ type AgentChatbotIdentifier struct { AgentChatbotIdentifier string `json:"agent_chatbot_identifier,omitempty"` } -// AgentDeployment represents the deployment information of a Gradient AI Agent +// AgentDeployment represents the deployment information of an Agent Platform Agent type AgentDeployment struct { CreatedAt *Timestamp `json:"created_at,omitempty"` Name string `json:"name,omitempty"` @@ -744,7 +775,7 @@ type AgentDeployment struct { Visibility string `json:"visibility,omitempty"` } -// ChatBot represents the chatbot information of a Gradient AI Agent +// ChatBot represents the chatbot information of an Agent Platform Agent type ChatBot struct { ButtonBackgroundColor string `json:"button_background_color,omitempty"` Logo string `json:"logo,omitempty"` @@ -754,7 +785,7 @@ type ChatBot struct { StartingMessage string `json:"starting_message,omitempty"` } -// Model represents a Gradient AI Model +// Model represents an Agent Platform Model type Model struct { Agreement *Agreement `json:"agreement,omitempty"` BenchmarkScore json.RawMessage `json:"benchmark_score,omitempty"` @@ -787,7 +818,7 @@ type ModelModalities struct { Output []string `json:"output,omitempty"` } -// Agreement represents the agreement information of a Gradient AI Model +// Agreement represents the agreement information of an Agent Platform Model type Agreement struct { Description string `json:"description,omitempty"` Name string `json:"name,omitempty"` @@ -819,7 +850,7 @@ type ModelPricing struct { ImageCacheReadInputPricePerMillion float64 `json:"image_cache_read_input_price_per_million,omitempty"` } -// AgentCreateRequest represents the request to create a new Gradient AI Agent +// AgentCreateRequest represents the request to create a new Agent Platform Agent type AgentCreateRequest struct { AnthropicKeyUuid string `json:"anthropic_key_uuid,omitempty"` Description string `json:"description,omitempty"` @@ -835,13 +866,13 @@ type AgentCreateRequest struct { WorkspaceUuid string `json:"workspace_uuid,omitempty"` } -// AgentAPIKeyCreateRequest represents the request to create a new Gradient AI Agent API Key +// AgentAPIKeyCreateRequest represents the request to create a new Agent Platform Agent API Key type AgentAPIKeyCreateRequest struct { AgentUuid string `json:"agent_uuid,omitempty"` Name string `json:"name,omitempty"` } -// AgentUpdateRequest represents the request to update an existing Gradient AI Agent +// AgentUpdateRequest represents the request to update an existing Agent Platform Agent type AgentUpdateRequest struct { AnthropicKeyUuid string `json:"anthropic_key_uuid,omitempty"` Description string `json:"description,omitempty"` @@ -861,7 +892,7 @@ type AgentUpdateRequest struct { ProvideCitations bool `json:"provide_citations,omitempty"` } -// AgentAPIKeyUpdateRequest represents the request to update an existing Gradient AI Agent API Key +// AgentAPIKeyUpdateRequest represents the request to update an existing Agent Platform Agent API Key type AgentAPIKeyUpdateRequest struct { AgentUuid string `json:"agent_uuid,omitempty"` APIKeyUuid string `json:"api_key_uuid,omitempty"` @@ -901,7 +932,7 @@ type KnowledgeBaseCreateRequest struct { VPCUuid string `json:"vpc_uuid"` } -// KnowledgeBaseDataSource represents a Gradient AI Knowledge Base Data Source +// KnowledgeBaseDataSource represents an Agent Platform Knowledge Base Data Source type KnowledgeBaseDataSource struct { CreatedAt *Timestamp `json:"created_at,omitempty"` FileUploadDataSource *FileUploadDataSource `json:"file_upload_data_source,omitempty"` @@ -1143,8 +1174,8 @@ type gradientAgentKBRoot struct { Agent *Agent `json:"agent"` } -// ListAgents returns a list of Gradient AI Agents -func (s *GradientAIServiceOp) ListAgents(ctx context.Context, opt *ListOptions) ([]*Agent, *Response, error) { +// ListAgents returns a list of Agent Platform Agents +func (s *AgentPlatformServiceOp) ListAgents(ctx context.Context, opt *ListOptions) ([]*Agent, *Response, error) { path, err := addOptions(gradientBasePath, opt) if err != nil { return nil, nil, err @@ -1169,8 +1200,8 @@ func (s *GradientAIServiceOp) ListAgents(ctx context.Context, opt *ListOptions) return root.Agents, resp, nil } -// CreateAgent creates a new Gradient AI Agent by providing the AgentCreateRequest object -func (s *GradientAIServiceOp) CreateAgent(ctx context.Context, create *AgentCreateRequest) (*Agent, *Response, error) { +// CreateAgent creates a new Agent Platform Agent by providing the AgentCreateRequest object +func (s *AgentPlatformServiceOp) CreateAgent(ctx context.Context, create *AgentCreateRequest) (*Agent, *Response, error) { path := gradientBasePath if create.ProjectId == "" { return nil, nil, fmt.Errorf("Project ID is required") @@ -1202,8 +1233,8 @@ func (s *GradientAIServiceOp) CreateAgent(ctx context.Context, create *AgentCrea return root.Agent, resp, nil } -// ListAgentAPIKeys retrieves list of API Keys associated with the specified Gradient AI agent -func (s *GradientAIServiceOp) ListAgentAPIKeys(ctx context.Context, agentId string, opt *ListOptions) ([]*ApiKeyInfo, *Response, error) { +// ListAgentAPIKeys retrieves list of API Keys associated with the specified Agent Platform agent +func (s *AgentPlatformServiceOp) ListAgentAPIKeys(ctx context.Context, agentId string, opt *ListOptions) ([]*ApiKeyInfo, *Response, error) { path := fmt.Sprintf("%s/%s/api_keys", gradientBasePath, agentId) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { @@ -1226,8 +1257,8 @@ func (s *GradientAIServiceOp) ListAgentAPIKeys(ctx context.Context, agentId stri return root.ApiKeys, resp, nil } -// CreateAgentAPIKey creates a new API key for the specified Gradient AI agent -func (s *GradientAIServiceOp) CreateAgentAPIKey(ctx context.Context, agentId string, createRequest *AgentAPIKeyCreateRequest) (*ApiKeyInfo, *Response, error) { +// CreateAgentAPIKey creates a new API key for the specified Agent Platform agent +func (s *AgentPlatformServiceOp) CreateAgentAPIKey(ctx context.Context, agentId string, createRequest *AgentAPIKeyCreateRequest) (*ApiKeyInfo, *Response, error) { path := fmt.Sprintf("%s/%s/api_keys", gradientBasePath, agentId) createRequest.AgentUuid = agentId @@ -1246,8 +1277,8 @@ func (s *GradientAIServiceOp) CreateAgentAPIKey(ctx context.Context, agentId str return root.ApiKey, resp, err } -// UpdateAgentAPIKey updates an existing API key for the specified Gradient AI agent -func (s *GradientAIServiceOp) UpdateAgentAPIKey(ctx context.Context, agentId, apiKeyId string, updateRequest *AgentAPIKeyUpdateRequest) (*ApiKeyInfo, *Response, error) { +// UpdateAgentAPIKey updates an existing API key for the specified Agent Platform agent +func (s *AgentPlatformServiceOp) UpdateAgentAPIKey(ctx context.Context, agentId, apiKeyId string, updateRequest *AgentAPIKeyUpdateRequest) (*ApiKeyInfo, *Response, error) { path := fmt.Sprintf("%s/%s/api_keys/%s", gradientBasePath, agentId, apiKeyId) updateRequest.AgentUuid = agentId @@ -1266,8 +1297,8 @@ func (s *GradientAIServiceOp) UpdateAgentAPIKey(ctx context.Context, agentId, ap return root.ApiKey, resp, nil } -// DeleteAgentAPIKey deletes an existing API key for the specified Gradient AI agent -func (s *GradientAIServiceOp) DeleteAgentAPIKey(ctx context.Context, agentId, apiKeyId string) (*ApiKeyInfo, *Response, error) { +// DeleteAgentAPIKey deletes an existing API key for the specified Agent Platform agent +func (s *AgentPlatformServiceOp) DeleteAgentAPIKey(ctx context.Context, agentId, apiKeyId string) (*ApiKeyInfo, *Response, error) { path := fmt.Sprintf("%s/%s/api_keys/%s", gradientBasePath, agentId, apiKeyId) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) @@ -1284,8 +1315,8 @@ func (s *GradientAIServiceOp) DeleteAgentAPIKey(ctx context.Context, agentId, ap return root.ApiKey, resp, nil } -// RegenerateAgentAPIKey regenerates an API key for the specified Gradient AI agent -func (s *GradientAIServiceOp) RegenerateAgentAPIKey(ctx context.Context, agentId, apiKeyId string) (*ApiKeyInfo, *Response, error) { +// RegenerateAgentAPIKey regenerates an API key for the specified Agent Platform agent +func (s *AgentPlatformServiceOp) RegenerateAgentAPIKey(ctx context.Context, agentId, apiKeyId string) (*ApiKeyInfo, *Response, error) { path := fmt.Sprintf("%s/%s/api_keys/%s/regenerate", gradientBasePath, agentId, apiKeyId) req, err := s.client.NewRequest(ctx, http.MethodPut, path, nil) @@ -1302,8 +1333,8 @@ func (s *GradientAIServiceOp) RegenerateAgentAPIKey(ctx context.Context, agentId return root.ApiKey, resp, nil } -// GetAgent returns the details of a Gradient AI Agent based on the Agent UUID -func (s *GradientAIServiceOp) GetAgent(ctx context.Context, id string) (*Agent, *Response, error) { +// GetAgent returns the details of an Agent Platform Agent based on the Agent UUID +func (s *AgentPlatformServiceOp) GetAgent(ctx context.Context, id string) (*Agent, *Response, error) { path := fmt.Sprintf("%s/%s", gradientBasePath, id) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) @@ -1320,8 +1351,8 @@ func (s *GradientAIServiceOp) GetAgent(ctx context.Context, id string) (*Agent, return root.Agent, resp, nil } -// UpdateAgent function updates a Gradient AI Agent properties for the given UUID -func (s *GradientAIServiceOp) UpdateAgent(ctx context.Context, id string, update *AgentUpdateRequest) (*Agent, *Response, error) { +// UpdateAgent function updates an Agent Platform Agent properties for the given UUID +func (s *AgentPlatformServiceOp) UpdateAgent(ctx context.Context, id string, update *AgentUpdateRequest) (*Agent, *Response, error) { path := fmt.Sprintf("%s/%s", gradientBasePath, id) req, err := s.client.NewRequest(ctx, http.MethodPut, path, update) if err != nil { @@ -1337,8 +1368,8 @@ func (s *GradientAIServiceOp) UpdateAgent(ctx context.Context, id string, update return root.Agent, resp, nil } -// DeleteAgent function deletes a Gradient AI Agent by its corresponding UUID -func (s *GradientAIServiceOp) DeleteAgent(ctx context.Context, id string) (*Agent, *Response, error) { +// DeleteAgent function deletes an Agent Platform Agent by its corresponding UUID +func (s *AgentPlatformServiceOp) DeleteAgent(ctx context.Context, id string) (*Agent, *Response, error) { path := fmt.Sprintf("%s/%s", gradientBasePath, id) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { @@ -1354,8 +1385,8 @@ func (s *GradientAIServiceOp) DeleteAgent(ctx context.Context, id string) (*Agen return root.Agent, resp, nil } -// UpdateAgentVisibility function updates a Gradient AI Agent status by changing visibility to public or private. -func (s *GradientAIServiceOp) UpdateAgentVisibility(ctx context.Context, id string, update *AgentVisibilityUpdateRequest) (*Agent, *Response, error) { +// UpdateAgentVisibility function updates an Agent Platform Agent status by changing visibility to public or private. +func (s *AgentPlatformServiceOp) UpdateAgentVisibility(ctx context.Context, id string, update *AgentVisibilityUpdateRequest) (*Agent, *Response, error) { path := fmt.Sprintf("%s/%s/deployment_visibility", gradientBasePath, id) req, err := s.client.NewRequest(ctx, http.MethodPut, path, update) if err != nil { @@ -1372,7 +1403,7 @@ func (s *GradientAIServiceOp) UpdateAgentVisibility(ctx context.Context, id stri } // List all knowledge bases -func (s *GradientAIServiceOp) ListKnowledgeBases(ctx context.Context, opt *ListOptions) ([]KnowledgeBase, *Response, error) { +func (s *AgentPlatformServiceOp) ListKnowledgeBases(ctx context.Context, opt *ListOptions) ([]KnowledgeBase, *Response, error) { path := KnowledgeBasePath path, err := addOptions(path, opt) @@ -1400,7 +1431,7 @@ func (s *GradientAIServiceOp) ListKnowledgeBases(ctx context.Context, opt *ListO } // ListIndexingJobs returns a list of all indexing jobs for knowledge bases -func (s *GradientAIServiceOp) ListIndexingJobs(ctx context.Context, opt *ListOptions) (*IndexingJobsResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListIndexingJobs(ctx context.Context, opt *ListOptions) (*IndexingJobsResponse, *Response, error) { path := IndexingJobsPath path, err := addOptions(path, opt) if err != nil { @@ -1434,7 +1465,7 @@ func (s *GradientAIServiceOp) ListIndexingJobs(ctx context.Context, opt *ListOpt } // ListIndexingJobDataSources returns the data sources for a specific indexing job -func (s *GradientAIServiceOp) ListIndexingJobDataSources(ctx context.Context, indexingJobUUID string) (*IndexingJobDataSourcesResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListIndexingJobDataSources(ctx context.Context, indexingJobUUID string) (*IndexingJobDataSourcesResponse, *Response, error) { path := fmt.Sprintf(IndexingJobDataSourcesPath, indexingJobUUID) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { @@ -1451,7 +1482,7 @@ func (s *GradientAIServiceOp) ListIndexingJobDataSources(ctx context.Context, in } // GetIndexingJob retrieves the status of a specific indexing job for a knowledge base -func (s *GradientAIServiceOp) GetIndexingJob(ctx context.Context, indexingJobUUID string) (*IndexingJobResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetIndexingJob(ctx context.Context, indexingJobUUID string) (*IndexingJobResponse, *Response, error) { path := fmt.Sprintf(IndexingJobByIDPath, indexingJobUUID) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { @@ -1468,7 +1499,7 @@ func (s *GradientAIServiceOp) GetIndexingJob(ctx context.Context, indexingJobUUI } // CancelIndexingJob cancels a specific indexing job for a knowledge base -func (s *GradientAIServiceOp) CancelIndexingJob(ctx context.Context, indexingJobUUID string) (*IndexingJobResponse, *Response, error) { +func (s *AgentPlatformServiceOp) CancelIndexingJob(ctx context.Context, indexingJobUUID string) (*IndexingJobResponse, *Response, error) { path := fmt.Sprintf(IndexingJobCancelPath, indexingJobUUID) // Create the request payload @@ -1491,7 +1522,7 @@ func (s *GradientAIServiceOp) CancelIndexingJob(ctx context.Context, indexingJob } // Create a knowledge base -func (s *GradientAIServiceOp) CreateKnowledgeBase(ctx context.Context, knowledgeBaseCreate *KnowledgeBaseCreateRequest) (*KnowledgeBase, *Response, error) { +func (s *AgentPlatformServiceOp) CreateKnowledgeBase(ctx context.Context, knowledgeBaseCreate *KnowledgeBaseCreateRequest) (*KnowledgeBase, *Response, error) { path := KnowledgeBasePath @@ -1533,7 +1564,7 @@ func (s *GradientAIServiceOp) CreateKnowledgeBase(ctx context.Context, knowledge } // List Data Sources for a Knowledge Base -func (s *GradientAIServiceOp) ListKnowledgeBaseDataSources(ctx context.Context, knowledgeBaseID string, opt *ListOptions) ([]KnowledgeBaseDataSource, *Response, error) { +func (s *AgentPlatformServiceOp) ListKnowledgeBaseDataSources(ctx context.Context, knowledgeBaseID string, opt *ListOptions) ([]KnowledgeBaseDataSource, *Response, error) { path := fmt.Sprintf(KnowledgeBaseDataSourcesPath, knowledgeBaseID) path, err := addOptions(path, opt) @@ -1559,7 +1590,7 @@ func (s *GradientAIServiceOp) ListKnowledgeBaseDataSources(ctx context.Context, } // Add Data Source to a Knowledge Base -func (s *GradientAIServiceOp) AddKnowledgeBaseDataSource(ctx context.Context, knowledgeBaseID string, addDataSource *AddKnowledgeBaseDataSourceRequest) (*KnowledgeBaseDataSource, *Response, error) { +func (s *AgentPlatformServiceOp) AddKnowledgeBaseDataSource(ctx context.Context, knowledgeBaseID string, addDataSource *AddKnowledgeBaseDataSourceRequest) (*KnowledgeBaseDataSource, *Response, error) { path := fmt.Sprintf(KnowledgeBaseDataSourcesPath, knowledgeBaseID) req, err := s.client.NewRequest(ctx, http.MethodPost, path, addDataSource) if err != nil { @@ -1574,7 +1605,7 @@ func (s *GradientAIServiceOp) AddKnowledgeBaseDataSource(ctx context.Context, kn } // Deletes data source from a knowledge base -func (s *GradientAIServiceOp) DeleteKnowledgeBaseDataSource(ctx context.Context, knowledgeBaseID string, dataSourceID string) (string, string, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteKnowledgeBaseDataSource(ctx context.Context, knowledgeBaseID string, dataSourceID string) (string, string, *Response, error) { path := fmt.Sprintf(DeleteDataSourcePath, knowledgeBaseID, dataSourceID) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) @@ -1594,7 +1625,7 @@ func (s *GradientAIServiceOp) DeleteKnowledgeBaseDataSource(ctx context.Context, // Get information about a KnowledgeBase and its Database status // Database status can be "CREATING","ONLINE","POWEROFF","REBUILDING","REBALANCING","DECOMMISSIONED","FORKING","MIGRATING","RESIZING","RESTORING","POWERING_ON","UNHEALTHY" -func (s *GradientAIServiceOp) GetKnowledgeBase(ctx context.Context, knowledgeBaseID string) (*KnowledgeBase, string, *Response, error) { +func (s *AgentPlatformServiceOp) GetKnowledgeBase(ctx context.Context, knowledgeBaseID string) (*KnowledgeBase, string, *Response, error) { path := fmt.Sprintf(GetKnowledgeBaseByIDPath, knowledgeBaseID) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) @@ -1611,7 +1642,7 @@ func (s *GradientAIServiceOp) GetKnowledgeBase(ctx context.Context, knowledgeBas } // Update a knowledge base -func (s *GradientAIServiceOp) UpdateKnowledgeBase(ctx context.Context, knowledgeBaseID string, update *UpdateKnowledgeBaseRequest) (*KnowledgeBase, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateKnowledgeBase(ctx context.Context, knowledgeBaseID string, update *UpdateKnowledgeBaseRequest) (*KnowledgeBase, *Response, error) { path := fmt.Sprintf(UpdateKnowledgeBaseByIDPath, knowledgeBaseID) req, err := s.client.NewRequest(ctx, http.MethodPut, path, update) if err != nil { @@ -1628,7 +1659,7 @@ func (s *GradientAIServiceOp) UpdateKnowledgeBase(ctx context.Context, knowledge } // Deletes a knowledge base by its corresponding UUID and returns the UUID of the deleted knowledge base -func (s *GradientAIServiceOp) DeleteKnowledgeBase(ctx context.Context, knowledgeBaseID string) (string, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteKnowledgeBase(ctx context.Context, knowledgeBaseID string) (string, *Response, error) { path := fmt.Sprintf(DeleteKnowledgeBaseByIDPath, knowledgeBaseID) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) @@ -1646,7 +1677,7 @@ func (s *GradientAIServiceOp) DeleteKnowledgeBase(ctx context.Context, knowledge } // Attach a knowledge base to an agent -func (s *GradientAIServiceOp) AttachKnowledgeBaseToAgent(ctx context.Context, agentID string, knowledgeBaseID string) (*Agent, *Response, error) { +func (s *AgentPlatformServiceOp) AttachKnowledgeBaseToAgent(ctx context.Context, agentID string, knowledgeBaseID string) (*Agent, *Response, error) { path := fmt.Sprintf(AgentKnowledgeBasePath, agentID, knowledgeBaseID) req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) @@ -1664,7 +1695,7 @@ func (s *GradientAIServiceOp) AttachKnowledgeBaseToAgent(ctx context.Context, ag } // Detach a knowledge base from an agent -func (s *GradientAIServiceOp) DetachKnowledgeBaseToAgent(ctx context.Context, agentID string, knowledgeBaseID string) (*Agent, *Response, error) { +func (s *AgentPlatformServiceOp) DetachKnowledgeBaseToAgent(ctx context.Context, agentID string, knowledgeBaseID string) (*Agent, *Response, error) { path := fmt.Sprintf(AgentKnowledgeBasePath, agentID, knowledgeBaseID) @@ -1681,7 +1712,7 @@ func (s *GradientAIServiceOp) DetachKnowledgeBaseToAgent(ctx context.Context, ag } // AddAgentRoute function adds a route between a parent and child agent. -func (s *GradientAIServiceOp) AddAgentRoute(ctx context.Context, parentId string, childId string, route *AgentRouteCreateRequest) (*AgentRouteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) AddAgentRoute(ctx context.Context, parentId string, childId string, route *AgentRouteCreateRequest) (*AgentRouteResponse, *Response, error) { path := fmt.Sprintf(agentRouteBasePath, parentId, childId) req, err := s.client.NewRequest(ctx, http.MethodPost, path, route) if err != nil { @@ -1698,7 +1729,7 @@ func (s *GradientAIServiceOp) AddAgentRoute(ctx context.Context, parentId string } // UpdateAgentRoute function updates a route between a parent and child agent. -func (s *GradientAIServiceOp) UpdateAgentRoute(ctx context.Context, parentId string, childId string, route *AgentRouteUpdateRequest) (*AgentRouteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateAgentRoute(ctx context.Context, parentId string, childId string, route *AgentRouteUpdateRequest) (*AgentRouteResponse, *Response, error) { path := fmt.Sprintf(agentRouteBasePath, parentId, childId) req, err := s.client.NewRequest(ctx, http.MethodPut, path, route) if err != nil { @@ -1715,7 +1746,7 @@ func (s *GradientAIServiceOp) UpdateAgentRoute(ctx context.Context, parentId str } // DeleteAgentRoute function deletes a route between a parent and child agent. -func (s *GradientAIServiceOp) DeleteAgentRoute(ctx context.Context, parentId string, childId string) (*AgentRouteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteAgentRoute(ctx context.Context, parentId string, childId string) (*AgentRouteResponse, *Response, error) { path := fmt.Sprintf(agentRouteBasePath, parentId, childId) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { @@ -1731,8 +1762,8 @@ func (s *GradientAIServiceOp) DeleteAgentRoute(ctx context.Context, parentId str return root, resp, nil } -// ListAgentVersions retrieves a list of versions for the specified GradientAI agent -func (s *GradientAIServiceOp) ListAgentVersions(ctx context.Context, agentId string, opt *ListOptions) ([]*AgentVersion, *Response, error) { +// ListAgentVersions retrieves a list of versions for the specified Agent Platform agent +func (s *AgentPlatformServiceOp) ListAgentVersions(ctx context.Context, agentId string, opt *ListOptions) ([]*AgentVersion, *Response, error) { path := fmt.Sprintf("%s/%s/versions", gradientBasePath, agentId) path, err := addOptions(path, opt) if err != nil { @@ -1753,7 +1784,7 @@ func (s *GradientAIServiceOp) ListAgentVersions(ctx context.Context, agentId str return root.AgentVersions, resp, nil } -func (s *GradientAIServiceOp) RollbackAgentVersion(ctx context.Context, agentId string, versionId string) (string, *Response, error) { +func (s *AgentPlatformServiceOp) RollbackAgentVersion(ctx context.Context, agentId string, versionId string) (string, *Response, error) { path := fmt.Sprintf("%s/%s/versions", gradientBasePath, agentId) req, err := s.client.NewRequest(ctx, http.MethodPut, path, RollbackVersionRequest{ AgentUuid: agentId, @@ -1773,7 +1804,7 @@ func (s *GradientAIServiceOp) RollbackAgentVersion(ctx context.Context, agentId } // ListAnthropicAPIKeys retrieves a list of Anthropic API Keys -func (s *GradientAIServiceOp) ListAnthropicAPIKeys(ctx context.Context, opt *ListOptions) ([]*AnthropicApiKeyInfo, *Response, error) { +func (s *AgentPlatformServiceOp) ListAnthropicAPIKeys(ctx context.Context, opt *ListOptions) ([]*AnthropicApiKeyInfo, *Response, error) { path := AnthropicAPIKeysPath path, err := addOptions(path, opt) if err != nil { @@ -1798,7 +1829,7 @@ func (s *GradientAIServiceOp) ListAnthropicAPIKeys(ctx context.Context, opt *Lis return root.AnthropicApiKeys, resp, nil } -func (s *GradientAIServiceOp) CreateAnthropicAPIKey(ctx context.Context, anthropicAPIKeyCreate *AnthropicAPIKeyCreateRequest) (*AnthropicApiKeyInfo, *Response, error) { +func (s *AgentPlatformServiceOp) CreateAnthropicAPIKey(ctx context.Context, anthropicAPIKeyCreate *AnthropicAPIKeyCreateRequest) (*AnthropicApiKeyInfo, *Response, error) { path := AnthropicAPIKeysPath if anthropicAPIKeyCreate.Name == "" { @@ -1822,7 +1853,7 @@ func (s *GradientAIServiceOp) CreateAnthropicAPIKey(ctx context.Context, anthrop return root.AnthropicApiKey, resp, nil } -func (s *GradientAIServiceOp) GetAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string) (*AnthropicApiKeyInfo, *Response, error) { +func (s *AgentPlatformServiceOp) GetAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string) (*AnthropicApiKeyInfo, *Response, error) { path := AnthropicAPIKeysPath + "/" + anthropicApiKeyId req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) @@ -1839,7 +1870,7 @@ func (s *GradientAIServiceOp) GetAnthropicAPIKey(ctx context.Context, anthropicA return root.AnthropicApiKey, resp, nil } -func (s *GradientAIServiceOp) UpdateAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string, anthropicAPIKeyUpdate *AnthropicAPIKeyUpdateRequest) (*AnthropicApiKeyInfo, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string, anthropicAPIKeyUpdate *AnthropicAPIKeyUpdateRequest) (*AnthropicApiKeyInfo, *Response, error) { path := AnthropicAPIKeysPath + "/" + anthropicApiKeyId if anthropicAPIKeyUpdate.ApiKeyUuid == "" { @@ -1863,7 +1894,7 @@ func (s *GradientAIServiceOp) UpdateAnthropicAPIKey(ctx context.Context, anthrop return root.AnthropicApiKey, resp, nil } -func (s *GradientAIServiceOp) DeleteAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string) (*AnthropicApiKeyInfo, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string) (*AnthropicApiKeyInfo, *Response, error) { path := AnthropicAPIKeysPath + "/" + anthropicApiKeyId req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) @@ -1880,7 +1911,7 @@ func (s *GradientAIServiceOp) DeleteAnthropicAPIKey(ctx context.Context, anthrop return root.AnthropicApiKey, resp, nil } -func (s *GradientAIServiceOp) ListAgentsByAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string, opt *ListOptions) ([]*Agent, *Response, error) { +func (s *AgentPlatformServiceOp) ListAgentsByAnthropicAPIKey(ctx context.Context, anthropicApiKeyId string, opt *ListOptions) ([]*Agent, *Response, error) { path := fmt.Sprintf("%s/%s/agents", AnthropicAPIKeysPath, anthropicApiKeyId) path, err := addOptions(path, opt) if err != nil { @@ -1905,7 +1936,7 @@ func (s *GradientAIServiceOp) ListAgentsByAnthropicAPIKey(ctx context.Context, a return root.Agents, resp, nil } -func (s *GradientAIServiceOp) ListOpenAIAPIKeys(ctx context.Context, opt *ListOptions) ([]*OpenAiApiKey, *Response, error) { +func (s *AgentPlatformServiceOp) ListOpenAIAPIKeys(ctx context.Context, opt *ListOptions) ([]*OpenAiApiKey, *Response, error) { path := OpenAIAPIKeysPath path, err := addOptions(path, opt) if err != nil { @@ -1931,7 +1962,7 @@ func (s *GradientAIServiceOp) ListOpenAIAPIKeys(ctx context.Context, opt *ListOp return root.OpenAIApiKeys, resp, nil } -func (s *GradientAIServiceOp) CreateOpenAIAPIKey(ctx context.Context, openaiAPIKeyCreate *OpenAIAPIKeyCreateRequest) (*OpenAiApiKey, *Response, error) { +func (s *AgentPlatformServiceOp) CreateOpenAIAPIKey(ctx context.Context, openaiAPIKeyCreate *OpenAIAPIKeyCreateRequest) (*OpenAiApiKey, *Response, error) { path := OpenAIAPIKeysPath if openaiAPIKeyCreate.Name == "" { @@ -1955,7 +1986,7 @@ func (s *GradientAIServiceOp) CreateOpenAIAPIKey(ctx context.Context, openaiAPIK return root.OpenAIAPIKey, resp, nil } -func (s *GradientAIServiceOp) GetOpenAIAPIKey(ctx context.Context, openaiApiKeyId string) (*OpenAiApiKey, *Response, error) { +func (s *AgentPlatformServiceOp) GetOpenAIAPIKey(ctx context.Context, openaiApiKeyId string) (*OpenAiApiKey, *Response, error) { path := OpenAIAPIKeysPath + "/" + openaiApiKeyId req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) @@ -1972,7 +2003,7 @@ func (s *GradientAIServiceOp) GetOpenAIAPIKey(ctx context.Context, openaiApiKeyI return root.OpenAIAPIKey, resp, nil } -func (s *GradientAIServiceOp) UpdateOpenAIAPIKey(ctx context.Context, openaiApiKeyId string, openaiAPIKeyUpdate *OpenAIAPIKeyUpdateRequest) (*OpenAiApiKey, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateOpenAIAPIKey(ctx context.Context, openaiApiKeyId string, openaiAPIKeyUpdate *OpenAIAPIKeyUpdateRequest) (*OpenAiApiKey, *Response, error) { path := OpenAIAPIKeysPath + "/" + openaiApiKeyId if openaiAPIKeyUpdate.ApiKeyUuid == "" { @@ -1996,7 +2027,7 @@ func (s *GradientAIServiceOp) UpdateOpenAIAPIKey(ctx context.Context, openaiApiK return root.OpenAIAPIKey, resp, nil } -func (s *GradientAIServiceOp) DeleteOpenAIAPIKey(ctx context.Context, openaiApiKeyId string) (*OpenAiApiKey, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteOpenAIAPIKey(ctx context.Context, openaiApiKeyId string) (*OpenAiApiKey, *Response, error) { path := OpenAIAPIKeysPath + "/" + openaiApiKeyId req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) @@ -2013,7 +2044,7 @@ func (s *GradientAIServiceOp) DeleteOpenAIAPIKey(ctx context.Context, openaiApiK return root.OpenAIAPIKey, resp, nil } -func (s *GradientAIServiceOp) ListAgentsByOpenAIAPIKey(ctx context.Context, openaiApiKeyId string, opt *ListOptions) ([]*Agent, *Response, error) { +func (s *AgentPlatformServiceOp) ListAgentsByOpenAIAPIKey(ctx context.Context, openaiApiKeyId string, opt *ListOptions) ([]*Agent, *Response, error) { path := fmt.Sprintf("%s/%s/agents", OpenAIAPIKeysPath, openaiApiKeyId) path, err := addOptions(path, opt) if err != nil { @@ -2039,7 +2070,7 @@ func (s *GradientAIServiceOp) ListAgentsByOpenAIAPIKey(ctx context.Context, open } // Attaches a functionroute to an agent. -func (g *GradientAIServiceOp) CreateFunctionRoute(ctx context.Context, id string, create *FunctionRouteCreateRequest) (*Agent, *Response, error) { +func (g *AgentPlatformServiceOp) CreateFunctionRoute(ctx context.Context, id string, create *FunctionRouteCreateRequest) (*Agent, *Response, error) { path := fmt.Sprintf(functionRouteBasePath, id) if create.AgentUuid == "" { @@ -2077,7 +2108,7 @@ func (g *GradientAIServiceOp) CreateFunctionRoute(ctx context.Context, id string } // Deletes a functionroute to an agent. -func (g *GradientAIServiceOp) DeleteFunctionRoute(ctx context.Context, agent_id string, function_id string) (*Agent, *Response, error) { +func (g *AgentPlatformServiceOp) DeleteFunctionRoute(ctx context.Context, agent_id string, function_id string) (*Agent, *Response, error) { path := fmt.Sprintf(UpdateFunctionRoutePath, agent_id, function_id) req, err := g.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { @@ -2095,7 +2126,7 @@ func (g *GradientAIServiceOp) DeleteFunctionRoute(ctx context.Context, agent_id } // Updates a functionroute to an agent. -func (g *GradientAIServiceOp) UpdateFunctionRoute(ctx context.Context, agent_id string, function_id string, update *FunctionRouteUpdateRequest) (*Agent, *Response, error) { +func (g *AgentPlatformServiceOp) UpdateFunctionRoute(ctx context.Context, agent_id string, function_id string, update *FunctionRouteUpdateRequest) (*Agent, *Response, error) { path := fmt.Sprintf(UpdateFunctionRoutePath, agent_id, function_id) req, err := g.client.NewRequest(ctx, http.MethodPut, path, update) if err != nil { @@ -2113,7 +2144,7 @@ func (g *GradientAIServiceOp) UpdateFunctionRoute(ctx context.Context, agent_id } // ListInferenceRouters returns a page of inference routers. -func (s *GradientAIServiceOp) ListInferenceRouters(ctx context.Context, opt *ListOptions) ([]*InferenceRouterSummary, *Response, error) { +func (s *AgentPlatformServiceOp) ListInferenceRouters(ctx context.Context, opt *ListOptions) ([]*InferenceRouterSummary, *Response, error) { path, err := addOptions(inferenceRoutersBasePath, opt) if err != nil { return nil, nil, err @@ -2140,7 +2171,7 @@ func (s *GradientAIServiceOp) ListInferenceRouters(ctx context.Context, opt *Lis } // ListInferenceRouterTaskPresets returns a page of preset tasks for building inference router policies. -func (s *GradientAIServiceOp) ListInferenceRouterTaskPresets(ctx context.Context, opt *ListOptions) ([]*InferenceRouterTaskPreset, *Response, error) { +func (s *AgentPlatformServiceOp) ListInferenceRouterTaskPresets(ctx context.Context, opt *ListOptions) ([]*InferenceRouterTaskPreset, *Response, error) { path, err := addOptions(inferenceRouterTaskPresetsPath, opt) if err != nil { return nil, nil, err @@ -2167,7 +2198,7 @@ func (s *GradientAIServiceOp) ListInferenceRouterTaskPresets(ctx context.Context } // GetInferenceRouter retrieves an inference router by UUID. -func (s *GradientAIServiceOp) GetInferenceRouter(ctx context.Context, uuid string) (*InferenceRouter, *Response, error) { +func (s *AgentPlatformServiceOp) GetInferenceRouter(ctx context.Context, uuid string) (*InferenceRouter, *Response, error) { if uuid == "" { return nil, nil, fmt.Errorf("uuid is required") } @@ -2188,7 +2219,7 @@ func (s *GradientAIServiceOp) GetInferenceRouter(ctx context.Context, uuid strin } // CreateInferenceRouter creates a new inference router. -func (s *GradientAIServiceOp) CreateInferenceRouter(ctx context.Context, create *InferenceRouterCreateRequest) (*InferenceRouter, *Response, error) { +func (s *AgentPlatformServiceOp) CreateInferenceRouter(ctx context.Context, create *InferenceRouterCreateRequest) (*InferenceRouter, *Response, error) { if create == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -2219,7 +2250,7 @@ func (s *GradientAIServiceOp) CreateInferenceRouter(ctx context.Context, create } // UpdateInferenceRouter updates an inference router. At least one field in the update request must be set. -func (s *GradientAIServiceOp) UpdateInferenceRouter(ctx context.Context, uuid string, update *InferenceRouterUpdateRequest) (*InferenceRouter, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateInferenceRouter(ctx context.Context, uuid string, update *InferenceRouterUpdateRequest) (*InferenceRouter, *Response, error) { if uuid == "" { return nil, nil, fmt.Errorf("uuid is required") } @@ -2246,7 +2277,7 @@ func (s *GradientAIServiceOp) UpdateInferenceRouter(ctx context.Context, uuid st } // DeleteInferenceRouter deletes an inference router by UUID. -func (s *GradientAIServiceOp) DeleteInferenceRouter(ctx context.Context, uuid string) (*InferenceRouterDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteInferenceRouter(ctx context.Context, uuid string) (*InferenceRouterDeleteResponse, *Response, error) { if uuid == "" { return nil, nil, fmt.Errorf("uuid is required") } @@ -2269,8 +2300,8 @@ func (s *GradientAIServiceOp) DeleteInferenceRouter(ctx context.Context, uuid st return out, resp, nil } -// ListAvailableModels returns a list of available Gradient AI models -func (g *GradientAIServiceOp) ListAvailableModels(ctx context.Context, opt *ListOptions) ([]*Model, *Response, error) { +// ListAvailableModels returns a list of available Agent Platform models +func (g *AgentPlatformServiceOp) ListAvailableModels(ctx context.Context, opt *ListOptions) ([]*Model, *Response, error) { path, err := addOptions(agentModelBasePath, opt) if err != nil { return nil, nil, err @@ -2294,7 +2325,7 @@ func (g *GradientAIServiceOp) ListAvailableModels(ctx context.Context, opt *List } // MCPSearchModels searches available models by name and returns the list of matching UUIDs. -func (g *GradientAIServiceOp) SearchModels(ctx context.Context, query string) ([]string, *Response, error) { +func (g *AgentPlatformServiceOp) SearchModels(ctx context.Context, query string) ([]string, *Response, error) { models, resp, err := g.ListAvailableModels(ctx, nil) if err != nil { return nil, resp, err @@ -2312,7 +2343,7 @@ func (g *GradientAIServiceOp) SearchModels(ctx context.Context, query string) ([ } // MCPSearchModelByUUID searches available models for a specific UUID and returns the model if it exists. -func (g *GradientAIServiceOp) GetModelByUUID(ctx context.Context, uuid string) (*Model, *Response, error) { +func (g *AgentPlatformServiceOp) GetModelByUUID(ctx context.Context, uuid string) (*Model, *Response, error) { models, resp, err := g.ListAvailableModels(ctx, nil) if err != nil { return nil, resp, err @@ -2327,8 +2358,8 @@ func (g *GradientAIServiceOp) GetModelByUUID(ctx context.Context, uuid string) ( return nil, resp, nil } -// ListDatacenterRegions returns a list of available datacenter regions for Gradient AI services -func (g *GradientAIServiceOp) ListDatacenterRegions(ctx context.Context, servesInference, servesBatch *bool) ([]*DatacenterRegions, *Response, error) { +// ListDatacenterRegions returns a list of available datacenter regions for Agent Platform services +func (g *AgentPlatformServiceOp) ListDatacenterRegions(ctx context.Context, servesInference, servesBatch *bool) ([]*DatacenterRegions, *Response, error) { path := datacenterRegionsPath var params []string @@ -2495,7 +2526,7 @@ type customEvaluationMetricRoot struct { } // ListCustomModels returns the list of custom models for the team. -func (s *GradientAIServiceOp) ListCustomModels(ctx context.Context, opt *CustomModelListOptions) (*CustomModelListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListCustomModels(ctx context.Context, opt *CustomModelListOptions) (*CustomModelListResponse, *Response, error) { path, err := addOptions(customModelsBasePath, opt) if err != nil { return nil, nil, err @@ -2521,7 +2552,7 @@ func (s *GradientAIServiceOp) ListCustomModels(ctx context.Context, opt *CustomM } // GetCustomModel retrieves a single custom model by UUID. -func (s *GradientAIServiceOp) GetCustomModel(ctx context.Context, uuid string) (*CustomModel, *Response, error) { +func (s *AgentPlatformServiceOp) GetCustomModel(ctx context.Context, uuid string) (*CustomModel, *Response, error) { if uuid == "" { return nil, nil, fmt.Errorf("uuid is required") } @@ -2541,7 +2572,7 @@ func (s *GradientAIServiceOp) GetCustomModel(ctx context.Context, uuid string) ( } // ImportCustomModel imports a new custom model from a supported source (HuggingFace, Spaces, etc.). -func (s *GradientAIServiceOp) ImportCustomModel(ctx context.Context, importRequest *CustomModelImportRequest) (*CustomModelImportResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ImportCustomModel(ctx context.Context, importRequest *CustomModelImportRequest) (*CustomModelImportResponse, *Response, error) { if importRequest == nil { return nil, nil, fmt.Errorf("import request is required") } @@ -2566,7 +2597,7 @@ func (s *GradientAIServiceOp) ImportCustomModel(ctx context.Context, importReque } // DeleteCustomModel deletes the custom model with the given UUID. -func (s *GradientAIServiceOp) DeleteCustomModel(ctx context.Context, uuid string) (*CustomModelDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteCustomModel(ctx context.Context, uuid string) (*CustomModelDeleteResponse, *Response, error) { if uuid == "" { return nil, nil, fmt.Errorf("uuid is required") } @@ -2640,7 +2671,7 @@ type ModelEvaluationPresetDeleteResponse struct{} // The run must be in a terminal status (successful, partially_successful, failed, // or cancelled). For runs still in progress, either wait for the run to finish or // cancel it, then retry the delete. -func (s *GradientAIServiceOp) DeleteModelEvaluationRun(ctx context.Context, evalRunUUID string) (*ModelEvaluationRunDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteModelEvaluationRun(ctx context.Context, evalRunUUID string) (*ModelEvaluationRunDeleteResponse, *Response, error) { if evalRunUUID == "" { return nil, nil, fmt.Errorf("eval run uuid is required") } @@ -2661,7 +2692,7 @@ func (s *GradientAIServiceOp) DeleteModelEvaluationRun(ctx context.Context, eval // DeleteModelEvaluationPreset deletes the saved model evaluation preset with // the given UUID. -func (s *GradientAIServiceOp) DeleteModelEvaluationPreset(ctx context.Context, evalPresetUUID string) (*ModelEvaluationPresetDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteModelEvaluationPreset(ctx context.Context, evalPresetUUID string) (*ModelEvaluationPresetDeleteResponse, *Response, error) { if evalPresetUUID == "" { return nil, nil, fmt.Errorf("eval preset uuid is required") } @@ -2685,7 +2716,7 @@ func (s *GradientAIServiceOp) DeleteModelEvaluationPreset(ctx context.Context, e // evaluating_results); already-terminal runs return an error. The returned // summary's status is `cancelling` while the underlying workflow is being torn // down and transitions to `cancelled` once cluster-side teardown completes. -func (s *GradientAIServiceOp) CancelModelEvaluationRun(ctx context.Context, evalRunUUID string) (*ModelEvaluationRunCancelResponse, *Response, error) { +func (s *AgentPlatformServiceOp) CancelModelEvaluationRun(ctx context.Context, evalRunUUID string) (*ModelEvaluationRunCancelResponse, *Response, error) { if evalRunUUID == "" { return nil, nil, fmt.Errorf("eval run uuid is required") } @@ -2711,7 +2742,7 @@ func (s *GradientAIServiceOp) CancelModelEvaluationRun(ctx context.Context, eval // UpdateModelEvaluationRun updates mutable fields on an existing model // evaluation run identified by its UUID. Currently only the run's display name // can be updated. -func (s *GradientAIServiceOp) UpdateModelEvaluationRun(ctx context.Context, evalRunUUID string, updateRequest *UpdateModelEvaluationRunRequest) (*ModelEvaluationRunUpdateResponse, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateModelEvaluationRun(ctx context.Context, evalRunUUID string, updateRequest *UpdateModelEvaluationRunRequest) (*ModelEvaluationRunUpdateResponse, *Response, error) { if evalRunUUID == "" { return nil, nil, fmt.Errorf("eval run uuid is required") } @@ -2736,10 +2767,11 @@ func (s *GradientAIServiceOp) UpdateModelEvaluationRun(ctx context.Context, eval // CandidateInferenceConfig is the inference configuration applied to the // candidate model when running a model evaluation run. type CandidateInferenceConfig struct { - MaxTokens int64 `json:"max_tokens,omitempty"` - StopToken string `json:"stop_token,omitempty"` - SystemPrompt string `json:"system_prompt,omitempty"` - Temperature float32 `json:"temperature,omitempty"` + MaxTokens int64 `json:"max_tokens,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` + StopToken string `json:"stop_token,omitempty"` + SystemPrompt string `json:"system_prompt,omitempty"` + Temperature float32 `json:"temperature,omitempty"` } // PresignedUrlFile describes a single file for which a presigned upload URL is @@ -2779,13 +2811,20 @@ type CreateModelEvaluationRunRequest struct { CandidateModelSource CandidateModelSource `json:"candidate_model_source,omitempty"` CandidateModelUUID string `json:"candidate_model_uuid,omitempty"` DatasetUUID string `json:"dataset_uuid,omitempty"` - EvalPresetUUID string `json:"eval_preset_uuid,omitempty"` - JudgeModelUUID string `json:"judge_model_uuid,omitempty"` - MetricUUIDs []string `json:"metric_uuids,omitempty"` - Name string `json:"name,omitempty"` - PresetName string `json:"preset_name,omitempty"` - Source string `json:"source,omitempty"` - StarMetric *StarMetric `json:"star_metric,omitempty"` + // Epochs is the number of times to evaluate each dataset row (n-pass). + // Defaults to 1 when unset. Capped at 3 by the API today. + Epochs uint32 `json:"epochs,omitempty"` + EvalPresetUUID string `json:"eval_preset_uuid,omitempty"` + JudgeModelUUID string `json:"judge_model_uuid,omitempty"` + MetricUUIDs []string `json:"metric_uuids,omitempty"` + Name string `json:"name,omitempty"` + PresetName string `json:"preset_name,omitempty"` + // PresetSaveSections controls which sections of the resolved configuration + // are persisted as a new reusable preset. Empty means do not save a preset. + // Ignored when EvalPresetUUID is set. + PresetSaveSections []PresetSaveSection `json:"preset_save_sections,omitempty"` + Source string `json:"source,omitempty"` + StarMetric *StarMetric `json:"star_metric,omitempty"` } // ModelEvaluationRunCreateResponse is the response returned by @@ -2797,25 +2836,38 @@ type ModelEvaluationRunCreateResponse struct { // ModelEvaluationPreset is a saved, reusable configuration for model // evaluation runs. type ModelEvaluationPreset struct { - CreatedAt *Timestamp `json:"created_at,omitempty"` - DatasetName string `json:"dataset_name,omitempty"` - DatasetUuid string `json:"dataset_uuid,omitempty"` - EvalPresetUuid string `json:"eval_preset_uuid,omitempty"` - JudgeModelName string `json:"judge_model_name,omitempty"` - JudgeModelUuid string `json:"judge_model_uuid,omitempty"` - Metrics []*EvaluationMetric `json:"metrics,omitempty"` - Name string `json:"name,omitempty"` - StarMetric *StarMetric `json:"star_metric,omitempty"` + CandidateInferenceConfig *CandidateInferenceConfig `json:"candidate_inference_config,omitempty"` + CandidateModelName string `json:"candidate_model_name,omitempty"` + CandidateModelSource CandidateModelSource `json:"candidate_model_source,omitempty"` + CandidateModelUuid string `json:"candidate_model_uuid,omitempty"` + CandidateSystemPrompt string `json:"candidate_system_prompt,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + DatasetName string `json:"dataset_name,omitempty"` + DatasetUuid string `json:"dataset_uuid,omitempty"` + EvalPresetUuid string `json:"eval_preset_uuid,omitempty"` + JudgeModelName string `json:"judge_model_name,omitempty"` + JudgeModelUuid string `json:"judge_model_uuid,omitempty"` + Metrics []*EvaluationMetric `json:"metrics,omitempty"` + Name string `json:"name,omitempty"` + SavedSections []PresetSaveSection `json:"saved_sections,omitempty"` + StarMetric *StarMetric `json:"star_metric,omitempty"` } // MetricResultSummary represents per-metric aggregated pass/fail statistics // across all prompts in an evaluation run. type MetricResultSummary struct { - Description string `json:"description,omitempty"` - FailPercent float64 `json:"fail_percent,omitempty"` - MetricName string `json:"metric_name,omitempty"` - MetricUuid string `json:"metric_uuid,omitempty"` - PassPercent float64 `json:"pass_percent,omitempty"` + AvgAtKPercent float64 `json:"avg_at_k_percent,omitempty"` + ConsAtKPercent float64 `json:"cons_at_k_percent,omitempty"` + Description string `json:"description,omitempty"` + FailCount uint32 `json:"fail_count,omitempty"` + FailPercent float64 `json:"fail_percent,omitempty"` + MetricName string `json:"metric_name,omitempty"` + MetricUuid string `json:"metric_uuid,omitempty"` + PassAtKPercent float64 `json:"pass_at_k_percent,omitempty"` + PassCount uint32 `json:"pass_count,omitempty"` + PassPercent float64 `json:"pass_percent,omitempty"` + SkipPercent float64 `json:"skip_percent,omitempty"` + SkippedCount uint32 `json:"skipped_count,omitempty"` } // LatencyMetrics contains latency metrics for candidate model invocations, @@ -2894,13 +2946,51 @@ type PerModelResultSummaries struct { Summaries []*PerModelResultSummary `json:"summaries,omitempty"` } +// PerTaskResultSummary is the aggregated evaluation results for a single +// routing task category in a router evaluation run. +type PerTaskResultSummary struct { + MetricSummaries []*MetricResultSummary `json:"metric_summaries,omitempty"` + PerformanceMetrics *PerformanceMetrics `json:"performance_metrics,omitempty"` + PromptCount int64 `json:"prompt_count,omitempty"` + TaskName string `json:"task_name,omitempty"` +} + +// PerTaskResultSummaries wraps the per-task summaries used inside a +// ModelEvaluationRunResultSummary. +type PerTaskResultSummaries struct { + Summaries []*PerTaskResultSummary `json:"summaries,omitempty"` +} + +// PerEpochResultSummary is one epoch's standalone score for a multi-epoch run. +type PerEpochResultSummary struct { + Epoch uint32 `json:"epoch,omitempty"` + OverallScorePercent float64 `json:"overall_score_percent,omitempty"` + RowsScored uint32 `json:"rows_scored,omitempty"` +} + +// EpochResultSummary aggregates avg@k/pass@k/cons@k across epochs when a run +// repeats each dataset row k times. Only populated when epochs > 1. +type EpochResultSummary struct { + AvgAtKPercent float64 `json:"avg_at_k_percent,omitempty"` + ConsAtKPercent float64 `json:"cons_at_k_percent,omitempty"` + Epochs uint32 `json:"epochs,omitempty"` + PassAtKPercent float64 `json:"pass_at_k_percent,omitempty"` + PerEpoch []*PerEpochResultSummary `json:"per_epoch,omitempty"` + RowsExcluded uint32 `json:"rows_excluded,omitempty"` + RowsScored uint32 `json:"rows_scored,omitempty"` + ScoreStddevPercent float64 `json:"score_stddev_percent,omitempty"` +} + // ModelEvaluationRunResultSummary contains the aggregated result summary for // a completed model evaluation run. type ModelEvaluationRunResultSummary struct { EndTime *Timestamp `json:"end_time,omitempty"` + EpochSummary *EpochResultSummary `json:"epoch_summary,omitempty"` + Epochs uint32 `json:"epochs,omitempty"` MetricSummaries []*MetricResultSummary `json:"metric_summaries,omitempty"` OverallScorePercent float64 `json:"overall_score_percent,omitempty"` PerModelSummaries *PerModelResultSummaries `json:"per_model_summaries,omitempty"` + PerTaskSummaries *PerTaskResultSummaries `json:"per_task_summaries,omitempty"` PerformanceMetrics *PerformanceMetrics `json:"performance_metrics,omitempty"` Pricing *EvaluationPricing `json:"pricing,omitempty"` StarMetricSummary *StarMetricSummary `json:"star_metric_summary,omitempty"` @@ -2937,6 +3027,7 @@ type ModelEvaluationRunDetail struct { CreatedAt *Timestamp `json:"created_at,omitempty"` DatasetName string `json:"dataset_name,omitempty"` DatasetUuid string `json:"dataset_uuid,omitempty"` + Epochs uint32 `json:"epochs,omitempty"` ErrorDescription string `json:"error_description,omitempty"` EvalPresetName string `json:"eval_preset_name,omitempty"` EvalPresetUuid string `json:"eval_preset_uuid,omitempty"` @@ -2955,23 +3046,37 @@ type ModelEvaluationRunDetail struct { // ModelEvaluationMetricResult represents the per-metric score and judge // reasoning for a single prompt in an evaluation run. type ModelEvaluationMetricResult struct { - ErrorDescription string `json:"error_description,omitempty"` - MetricName string `json:"metric_name,omitempty"` - MetricValueType EvaluationMetricValueType `json:"metric_value_type,omitempty"` - NumberValue float64 `json:"number_value,omitempty"` - Reasoning string `json:"reasoning,omitempty"` - StringValue string `json:"string_value,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` + MetricName string `json:"metric_name,omitempty"` + MetricUUID string `json:"metric_uuid,omitempty"` + MetricValueType EvaluationMetricValueType `json:"metric_value_type,omitempty"` + NumberValue float64 `json:"number_value,omitempty"` + Reasoning string `json:"reasoning,omitempty"` + Status EvaluationMetricResultStatus `json:"status,omitempty"` + StringValue string `json:"string_value,omitempty"` +} + +// ModelEvaluationEpochAttempt is one epoch's attempt at a dataset row, nested +// under ModelEvaluationResult.EpochResults for multi-epoch runs. +type ModelEvaluationEpochAttempt struct { + CandidateRoutedTask string `json:"candidate_routed_task,omitempty"` + MetricResults []*ModelEvaluationMetricResult `json:"metric_results,omitempty"` + Output string `json:"output,omitempty"` } // ModelEvaluationResult represents the per-prompt result for a model // evaluation run. type ModelEvaluationResult struct { - CandidateModelName string `json:"candidate_model_name,omitempty"` - CandidateModelUuid string `json:"candidate_model_uuid,omitempty"` - GroundTruth string `json:"ground_truth,omitempty"` - Input string `json:"input,omitempty"` - MetricResults []*ModelEvaluationMetricResult `json:"metric_results,omitempty"` - Output string `json:"output,omitempty"` + CandidateModelName string `json:"candidate_model_name,omitempty"` + CandidateModelUuid string `json:"candidate_model_uuid,omitempty"` + CandidateRoutedTask string `json:"candidate_routed_task,omitempty"` + Epoch uint32 `json:"epoch,omitempty"` + EpochResults map[string]*ModelEvaluationEpochAttempt `json:"epoch_results,omitempty"` + GroundTruth string `json:"ground_truth,omitempty"` + Input string `json:"input,omitempty"` + MetricResults []*ModelEvaluationMetricResult `json:"metric_results,omitempty"` + Output string `json:"output,omitempty"` + RowNumber uint32 `json:"row_number,omitempty"` } // ModelEvaluationRunGetOptions specifies optional pagination parameters for @@ -3021,9 +3126,11 @@ type ModelEvaluationRunListOptions struct { // ModelEvaluationRunListResponse is the response returned by // ListModelEvaluationRuns. type ModelEvaluationRunListResponse struct { - Runs []*ModelEvaluationRunSummary `json:"runs,omitempty"` - Links *Links `json:"links,omitempty"` - Meta *Meta `json:"meta,omitempty"` + AvailableCandidateTypes []CandidateModelSource `json:"available_candidate_types,omitempty"` + AvailableStatuses []ModelEvaluationRunStatus `json:"available_statuses,omitempty"` + Runs []*ModelEvaluationRunSummary `json:"runs,omitempty"` + Links *Links `json:"links,omitempty"` + Meta *Meta `json:"meta,omitempty"` } // ModelEvaluationPresetListResponse is the response returned by @@ -3043,18 +3150,23 @@ type ModelEvaluationMetricListResponse struct { type EvaluationDatasetListOptions struct { // DatasetType filters the results by evaluation dataset type. DatasetType EvaluationDatasetType `url:"dataset_type,omitempty"` + // DatasetParadigm filters by row/content shape. + DatasetParadigm EvaluationDatasetParadigm `url:"dataset_paradigm,omitempty"` + // HasGroundTruth filters by whether the dataset includes ground-truth values. + HasGroundTruth *bool `url:"has_ground_truth,omitempty"` } // EvaluationDatasetInfo represents an evaluation dataset returned by // ListEvaluationDatasets. type EvaluationDatasetInfo struct { - CreatedAt *Timestamp `json:"created_at,omitempty"` - DatasetName string `json:"dataset_name,omitempty"` - DatasetType EvaluationDatasetType `json:"dataset_type,omitempty"` - DatasetUUID string `json:"dataset_uuid,omitempty"` - FileSize string `json:"file_size,omitempty"` - HasGroundTruth bool `json:"has_ground_truth,omitempty"` - RowCount int64 `json:"row_count,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + DatasetName string `json:"dataset_name,omitempty"` + DatasetParadigm EvaluationDatasetParadigm `json:"dataset_paradigm,omitempty"` + DatasetType EvaluationDatasetType `json:"dataset_type,omitempty"` + DatasetUUID string `json:"dataset_uuid,omitempty"` + FileSize string `json:"file_size,omitempty"` + HasGroundTruth bool `json:"has_ground_truth,omitempty"` + RowCount int64 `json:"row_count,omitempty"` } // EvaluationDatasetListResponse is the response returned by @@ -3067,8 +3179,43 @@ type EvaluationDatasetListResponse struct { // DeleteEvaluationDataset. type EvaluationDatasetDeleteResponse struct{} +// CreateEvaluationDatasetRequest is the request body for creating an +// evaluation dataset from a previously uploaded file. +type CreateEvaluationDatasetRequest struct { + Name string `json:"name,omitempty"` + DatasetType EvaluationDatasetType `json:"dataset_type,omitempty"` + DatasetParadigm EvaluationDatasetParadigm `json:"dataset_paradigm,omitempty"` + FileUploadDataset *FileUploadDataSource `json:"file_upload_dataset,omitempty"` +} + +// CreateEvaluationDatasetResponse is the response returned by +// CreateEvaluationDataset. +type CreateEvaluationDatasetResponse struct { + EvaluationDatasetUUID string `json:"evaluation_dataset_uuid,omitempty"` +} + +// CreateEvaluationDataset registers an evaluation dataset from a file that was +// previously uploaded via CreateModelEvalDatasetUploadPresignedURLs. +func (s *AgentPlatformServiceOp) CreateEvaluationDataset(ctx context.Context, createRequest *CreateEvaluationDatasetRequest) (*CreateEvaluationDatasetResponse, *Response, error) { + if createRequest == nil { + return nil, nil, fmt.Errorf("create request is required") + } + + req, err := s.client.NewRequest(ctx, http.MethodPost, evaluationDatasetsBasePath, createRequest) + if err != nil { + return nil, nil, err + } + + root := new(CreateEvaluationDatasetResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + // CreateModelEvaluationRun creates a new model evaluation run. -func (s *GradientAIServiceOp) CreateModelEvaluationRun(ctx context.Context, createRequest *CreateModelEvaluationRunRequest) (*ModelEvaluationRunCreateResponse, *Response, error) { +func (s *AgentPlatformServiceOp) CreateModelEvaluationRun(ctx context.Context, createRequest *CreateModelEvaluationRunRequest) (*ModelEvaluationRunCreateResponse, *Response, error) { if createRequest == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -3088,7 +3235,7 @@ func (s *GradientAIServiceOp) CreateModelEvaluationRun(ctx context.Context, crea // CreateModelEvalDatasetUploadPresignedURLs creates presigned URLs that can be // used to upload model evaluation dataset files. -func (s *GradientAIServiceOp) CreateModelEvalDatasetUploadPresignedURLs(ctx context.Context, createRequest *CreateModelEvalDatasetUploadPresignedURLsRequest) (*CreateModelEvalDatasetUploadPresignedURLsResponse, *Response, error) { +func (s *AgentPlatformServiceOp) CreateModelEvalDatasetUploadPresignedURLs(ctx context.Context, createRequest *CreateModelEvalDatasetUploadPresignedURLsRequest) (*CreateModelEvalDatasetUploadPresignedURLsResponse, *Response, error) { if createRequest == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -3109,7 +3256,7 @@ func (s *GradientAIServiceOp) CreateModelEvalDatasetUploadPresignedURLs(ctx cont // GetModelEvaluationRun retrieves a model evaluation run by UUID. Optional // pagination options control the per-prompt results page returned alongside // the run detail. -func (s *GradientAIServiceOp) GetModelEvaluationRun(ctx context.Context, evalRunUUID string, opt *ModelEvaluationRunGetOptions) (*ModelEvaluationRunGetResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetModelEvaluationRun(ctx context.Context, evalRunUUID string, opt *ModelEvaluationRunGetOptions) (*ModelEvaluationRunGetResponse, *Response, error) { if evalRunUUID == "" { return nil, nil, fmt.Errorf("eval run uuid is required") } @@ -3139,7 +3286,7 @@ func (s *GradientAIServiceOp) GetModelEvaluationRun(ctx context.Context, evalRun } // GetModelEvaluationPreset retrieves a saved model evaluation preset by UUID. -func (s *GradientAIServiceOp) GetModelEvaluationPreset(ctx context.Context, evalPresetUUID string) (*ModelEvaluationPresetGetResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetModelEvaluationPreset(ctx context.Context, evalPresetUUID string) (*ModelEvaluationPresetGetResponse, *Response, error) { if evalPresetUUID == "" { return nil, nil, fmt.Errorf("eval preset uuid is required") } @@ -3160,7 +3307,7 @@ func (s *GradientAIServiceOp) GetModelEvaluationPreset(ctx context.Context, eval // GetModelEvaluationRunResultsDownloadURL returns a presigned download URL // (gzip-compressed JSON) for a model evaluation run's results. -func (s *GradientAIServiceOp) GetModelEvaluationRunResultsDownloadURL(ctx context.Context, evalRunUUID string) (*ModelEvaluationRunResultsDownloadURLResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetModelEvaluationRunResultsDownloadURL(ctx context.Context, evalRunUUID string) (*ModelEvaluationRunResultsDownloadURLResponse, *Response, error) { if evalRunUUID == "" { return nil, nil, fmt.Errorf("eval run uuid is required") } @@ -3183,7 +3330,7 @@ func (s *GradientAIServiceOp) GetModelEvaluationRunResultsDownloadURL(ctx contex // by preset UUID, status (single or multiple), candidate model source types, // and a free-text search across run, candidate model, and dataset names. The // result set can also be sorted via SortBy / SortDirection. -func (s *GradientAIServiceOp) ListModelEvaluationRuns(ctx context.Context, opt *ModelEvaluationRunListOptions) (*ModelEvaluationRunListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListModelEvaluationRuns(ctx context.Context, opt *ModelEvaluationRunListOptions) (*ModelEvaluationRunListResponse, *Response, error) { path, err := addOptions(modelEvaluationRunsBasePath, opt) if err != nil { return nil, nil, err @@ -3209,7 +3356,7 @@ func (s *GradientAIServiceOp) ListModelEvaluationRuns(ctx context.Context, opt * } // ListModelEvaluationPresets lists all saved model evaluation presets. -func (s *GradientAIServiceOp) ListModelEvaluationPresets(ctx context.Context) (*ModelEvaluationPresetListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListModelEvaluationPresets(ctx context.Context) (*ModelEvaluationPresetListResponse, *Response, error) { req, err := s.client.NewRequest(ctx, http.MethodGet, modelEvaluationPresetsBasePath, nil) if err != nil { return nil, nil, err @@ -3225,7 +3372,7 @@ func (s *GradientAIServiceOp) ListModelEvaluationPresets(ctx context.Context) (* // ListModelEvaluationMetrics lists all available metrics that can be selected // when creating a model evaluation run. -func (s *GradientAIServiceOp) ListModelEvaluationMetrics(ctx context.Context) (*ModelEvaluationMetricListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListModelEvaluationMetrics(ctx context.Context) (*ModelEvaluationMetricListResponse, *Response, error) { req, err := s.client.NewRequest(ctx, http.MethodGet, modelEvaluationMetricsBasePath, nil) if err != nil { return nil, nil, err @@ -3241,7 +3388,7 @@ func (s *GradientAIServiceOp) ListModelEvaluationMetrics(ctx context.Context) (* // ListEvaluationDatasets lists evaluation datasets. Results can be filtered by // dataset type using the provided options. -func (s *GradientAIServiceOp) ListEvaluationDatasets(ctx context.Context, opt *EvaluationDatasetListOptions) (*EvaluationDatasetListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListEvaluationDatasets(ctx context.Context, opt *EvaluationDatasetListOptions) (*EvaluationDatasetListResponse, *Response, error) { path, err := addOptions(evaluationDatasetsBasePath, opt) if err != nil { return nil, nil, err @@ -3261,7 +3408,7 @@ func (s *GradientAIServiceOp) ListEvaluationDatasets(ctx context.Context, opt *E } // DeleteEvaluationDataset deletes the evaluation dataset with the given UUID. -func (s *GradientAIServiceOp) DeleteEvaluationDataset(ctx context.Context, datasetUUID string) (*EvaluationDatasetDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteEvaluationDataset(ctx context.Context, datasetUUID string) (*EvaluationDatasetDeleteResponse, *Response, error) { if datasetUUID == "" { return nil, nil, fmt.Errorf("dataset uuid is required") } @@ -3281,7 +3428,7 @@ func (s *GradientAIServiceOp) DeleteEvaluationDataset(ctx context.Context, datas } // UpdateCustomModelMetadata updates the metadata of an existing custom model. -func (s *GradientAIServiceOp) UpdateCustomModelMetadata(ctx context.Context, uuid string, updateRequest *CustomModelMetadataUpdateRequest) (*CustomModel, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateCustomModelMetadata(ctx context.Context, uuid string, updateRequest *CustomModelMetadataUpdateRequest) (*CustomModel, *Response, error) { if uuid == "" { return nil, nil, fmt.Errorf("uuid is required") } @@ -3304,7 +3451,7 @@ func (s *GradientAIServiceOp) UpdateCustomModelMetadata(ctx context.Context, uui } // CreateCustomEvaluationMetric creates a custom model-evaluation metric. -func (s *GradientAIServiceOp) CreateCustomEvaluationMetric(ctx context.Context, createRequest *CreateCustomEvaluationMetricRequest) (*EvaluationMetric, *Response, error) { +func (s *AgentPlatformServiceOp) CreateCustomEvaluationMetric(ctx context.Context, createRequest *CreateCustomEvaluationMetricRequest) (*EvaluationMetric, *Response, error) { if createRequest == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -3323,7 +3470,7 @@ func (s *GradientAIServiceOp) CreateCustomEvaluationMetric(ctx context.Context, } // UpdateCustomEvaluationMetric updates an existing custom model-evaluation metric. -func (s *GradientAIServiceOp) UpdateCustomEvaluationMetric(ctx context.Context, metricUUID string, updateRequest *UpdateCustomEvaluationMetricRequest) (*EvaluationMetric, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateCustomEvaluationMetric(ctx context.Context, metricUUID string, updateRequest *UpdateCustomEvaluationMetricRequest) (*EvaluationMetric, *Response, error) { if metricUUID == "" { return nil, nil, fmt.Errorf("metricUUID is required") } @@ -3346,7 +3493,7 @@ func (s *GradientAIServiceOp) UpdateCustomEvaluationMetric(ctx context.Context, } // DeleteCustomEvaluationMetric soft-deletes a custom model-evaluation metric. -func (s *GradientAIServiceOp) DeleteCustomEvaluationMetric(ctx context.Context, metricUUID string) (*Response, error) { +func (s *AgentPlatformServiceOp) DeleteCustomEvaluationMetric(ctx context.Context, metricUUID string) (*Response, error) { if metricUUID == "" { return nil, fmt.Errorf("metricUUID is required") } diff --git a/vendor/github.com/digitalocean/godo/gradientai_simulations.go b/vendor/github.com/digitalocean/godo/agentplatform_simulations.go similarity index 92% rename from vendor/github.com/digitalocean/godo/gradientai_simulations.go rename to vendor/github.com/digitalocean/godo/agentplatform_simulations.go index 4a54561c6..28eea67eb 100644 --- a/vendor/github.com/digitalocean/godo/gradientai_simulations.go +++ b/vendor/github.com/digitalocean/godo/agentplatform_simulations.go @@ -593,7 +593,7 @@ type simulationTrajectoryRoot struct { // CreateScenarioSetUploadPresignedURLs creates presigned URLs for uploading // scenario set files. -func (s *GradientAIServiceOp) CreateScenarioSetUploadPresignedURLs(ctx context.Context, createRequest *CreateScenarioSetUploadPresignedURLsRequest) (*CreateScenarioSetUploadPresignedURLsResponse, *Response, error) { +func (s *AgentPlatformServiceOp) CreateScenarioSetUploadPresignedURLs(ctx context.Context, createRequest *CreateScenarioSetUploadPresignedURLsRequest) (*CreateScenarioSetUploadPresignedURLsResponse, *Response, error) { if createRequest == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -621,7 +621,7 @@ func (s *GradientAIServiceOp) CreateScenarioSetUploadPresignedURLs(ctx context.C // CreateScenarioSet creates a scenario set from inline scenarios or an uploaded // file. Exactly one of the two sources must be provided. -func (s *GradientAIServiceOp) CreateScenarioSet(ctx context.Context, createRequest *CreateScenarioSetRequest) (*ScenarioSet, *Response, error) { +func (s *AgentPlatformServiceOp) CreateScenarioSet(ctx context.Context, createRequest *CreateScenarioSetRequest) (*ScenarioSet, *Response, error) { if createRequest == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -648,7 +648,7 @@ func (s *GradientAIServiceOp) CreateScenarioSet(ctx context.Context, createReque } // GenerateScenarioSet dispatches goal-driven scenario generation. -func (s *GradientAIServiceOp) GenerateScenarioSet(ctx context.Context, generateRequest *GenerateScenarioSetRequest) (*ScenarioSet, *Response, error) { +func (s *AgentPlatformServiceOp) GenerateScenarioSet(ctx context.Context, generateRequest *GenerateScenarioSetRequest) (*ScenarioSet, *Response, error) { if generateRequest == nil { return nil, nil, fmt.Errorf("generate request is required") } @@ -673,7 +673,7 @@ func (s *GradientAIServiceOp) GenerateScenarioSet(ctx context.Context, generateR } // ListScenarioSets lists scenario sets for the team. -func (s *GradientAIServiceOp) ListScenarioSets(ctx context.Context, opt *ScenarioSetListOptions) (*ScenarioSetListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListScenarioSets(ctx context.Context, opt *ScenarioSetListOptions) (*ScenarioSetListResponse, *Response, error) { path, err := addOptions(scenarioSetsBasePath, opt) if err != nil { return nil, nil, err @@ -699,7 +699,7 @@ func (s *GradientAIServiceOp) ListScenarioSets(ctx context.Context, opt *Scenari } // GetScenarioSet retrieves a scenario set by UUID. -func (s *GradientAIServiceOp) GetScenarioSet(ctx context.Context, scenarioSetUUID string) (*ScenarioSet, *Response, error) { +func (s *AgentPlatformServiceOp) GetScenarioSet(ctx context.Context, scenarioSetUUID string) (*ScenarioSet, *Response, error) { if scenarioSetUUID == "" { return nil, nil, fmt.Errorf("scenario set uuid is required") } @@ -719,7 +719,7 @@ func (s *GradientAIServiceOp) GetScenarioSet(ctx context.Context, scenarioSetUUI } // ListScenarios lists scenarios within a scenario set. -func (s *GradientAIServiceOp) ListScenarios(ctx context.Context, scenarioSetUUID string, opt *ScenarioListOptions) (*ScenarioListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListScenarios(ctx context.Context, scenarioSetUUID string, opt *ScenarioListOptions) (*ScenarioListResponse, *Response, error) { if scenarioSetUUID == "" { return nil, nil, fmt.Errorf("scenario set uuid is required") } @@ -750,7 +750,7 @@ func (s *GradientAIServiceOp) ListScenarios(ctx context.Context, scenarioSetUUID // GetScenarioSetDownloadURL returns a presigned download URL for a scenario set's // canonical JSONL file. -func (s *GradientAIServiceOp) GetScenarioSetDownloadURL(ctx context.Context, scenarioSetUUID string) (*ScenarioSetDownloadURLResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetScenarioSetDownloadURL(ctx context.Context, scenarioSetUUID string) (*ScenarioSetDownloadURLResponse, *Response, error) { if scenarioSetUUID == "" { return nil, nil, fmt.Errorf("scenario set uuid is required") } @@ -770,7 +770,7 @@ func (s *GradientAIServiceOp) GetScenarioSetDownloadURL(ctx context.Context, sce } // UpdateScenarioSet updates a scenario set name and/or replaces its scenarios. -func (s *GradientAIServiceOp) UpdateScenarioSet(ctx context.Context, scenarioSetUUID string, updateRequest *UpdateScenarioSetRequest) (*ScenarioSet, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateScenarioSet(ctx context.Context, scenarioSetUUID string, updateRequest *UpdateScenarioSetRequest) (*ScenarioSet, *Response, error) { if scenarioSetUUID == "" { return nil, nil, fmt.Errorf("scenario set uuid is required") } @@ -796,7 +796,7 @@ func (s *GradientAIServiceOp) UpdateScenarioSet(ctx context.Context, scenarioSet } // DeleteScenarioSet deletes a scenario set by UUID. -func (s *GradientAIServiceOp) DeleteScenarioSet(ctx context.Context, scenarioSetUUID string) (*ScenarioSetDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteScenarioSet(ctx context.Context, scenarioSetUUID string) (*ScenarioSetDeleteResponse, *Response, error) { if scenarioSetUUID == "" { return nil, nil, fmt.Errorf("scenario set uuid is required") } @@ -816,7 +816,7 @@ func (s *GradientAIServiceOp) DeleteScenarioSet(ctx context.Context, scenarioSet } // ListScenarioLibrary lists platform-curated scenario library entries. -func (s *GradientAIServiceOp) ListScenarioLibrary(ctx context.Context, opt *ScenarioLibraryListOptions) (*ScenarioLibraryListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListScenarioLibrary(ctx context.Context, opt *ScenarioLibraryListOptions) (*ScenarioLibraryListResponse, *Response, error) { path, err := addOptions(scenarioLibraryBasePath, opt) if err != nil { return nil, nil, err @@ -842,7 +842,7 @@ func (s *GradientAIServiceOp) ListScenarioLibrary(ctx context.Context, opt *Scen } // ListScenarioLibraryScenarios lists scenarios within a scenario library entry. -func (s *GradientAIServiceOp) ListScenarioLibraryScenarios(ctx context.Context, libraryScenarioUUID string, opt *ScenarioListOptions) (*ScenarioListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListScenarioLibraryScenarios(ctx context.Context, libraryScenarioUUID string, opt *ScenarioListOptions) (*ScenarioListResponse, *Response, error) { if libraryScenarioUUID == "" { return nil, nil, fmt.Errorf("library scenario uuid is required") } @@ -873,7 +873,7 @@ func (s *GradientAIServiceOp) ListScenarioLibraryScenarios(ctx context.Context, // CreateScenarioSetFromLibrary materializes a library entry into a team-owned // scenario set. -func (s *GradientAIServiceOp) CreateScenarioSetFromLibrary(ctx context.Context, libraryScenarioUUID string, createRequest *CreateScenarioSetFromLibraryRequest) (*ScenarioSet, *Response, error) { +func (s *AgentPlatformServiceOp) CreateScenarioSetFromLibrary(ctx context.Context, libraryScenarioUUID string, createRequest *CreateScenarioSetFromLibraryRequest) (*ScenarioSet, *Response, error) { if libraryScenarioUUID == "" { return nil, nil, fmt.Errorf("library scenario uuid is required") } @@ -899,7 +899,7 @@ func (s *GradientAIServiceOp) CreateScenarioSetFromLibrary(ctx context.Context, } // CreateSimulationRun creates a simulation run. -func (s *GradientAIServiceOp) CreateSimulationRun(ctx context.Context, createRequest *CreateSimulationRunRequest) (*SimulationRun, *Response, error) { +func (s *AgentPlatformServiceOp) CreateSimulationRun(ctx context.Context, createRequest *CreateSimulationRunRequest) (*SimulationRun, *Response, error) { if createRequest == nil { return nil, nil, fmt.Errorf("create request is required") } @@ -924,7 +924,7 @@ func (s *GradientAIServiceOp) CreateSimulationRun(ctx context.Context, createReq } // ListSimulationRuns lists simulation runs for the team. -func (s *GradientAIServiceOp) ListSimulationRuns(ctx context.Context, opt *SimulationRunListOptions) (*SimulationRunListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListSimulationRuns(ctx context.Context, opt *SimulationRunListOptions) (*SimulationRunListResponse, *Response, error) { path, err := addOptions(simulationRunsBasePath, opt) if err != nil { return nil, nil, err @@ -951,7 +951,7 @@ func (s *GradientAIServiceOp) ListSimulationRuns(ctx context.Context, opt *Simul // GetSimulationRun retrieves a simulation run by UUID, including per-scenario // result rollups when available. -func (s *GradientAIServiceOp) GetSimulationRun(ctx context.Context, runUUID string) (*SimulationRunGetResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetSimulationRun(ctx context.Context, runUUID string) (*SimulationRunGetResponse, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -971,7 +971,7 @@ func (s *GradientAIServiceOp) GetSimulationRun(ctx context.Context, runUUID stri } // UpdateSimulationRun updates mutable fields on a simulation run. -func (s *GradientAIServiceOp) UpdateSimulationRun(ctx context.Context, runUUID string, updateRequest *UpdateSimulationRunRequest) (*SimulationRun, *Response, error) { +func (s *AgentPlatformServiceOp) UpdateSimulationRun(ctx context.Context, runUUID string, updateRequest *UpdateSimulationRunRequest) (*SimulationRun, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -997,7 +997,7 @@ func (s *GradientAIServiceOp) UpdateSimulationRun(ctx context.Context, runUUID s } // CancelSimulationRun cancels an in-progress simulation run. -func (s *GradientAIServiceOp) CancelSimulationRun(ctx context.Context, runUUID string) (*SimulationRun, *Response, error) { +func (s *AgentPlatformServiceOp) CancelSimulationRun(ctx context.Context, runUUID string) (*SimulationRun, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -1017,7 +1017,7 @@ func (s *GradientAIServiceOp) CancelSimulationRun(ctx context.Context, runUUID s } // DeleteSimulationRun deletes a simulation run by UUID. -func (s *GradientAIServiceOp) DeleteSimulationRun(ctx context.Context, runUUID string) (*SimulationRunDeleteResponse, *Response, error) { +func (s *AgentPlatformServiceOp) DeleteSimulationRun(ctx context.Context, runUUID string) (*SimulationRunDeleteResponse, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -1037,7 +1037,7 @@ func (s *GradientAIServiceOp) DeleteSimulationRun(ctx context.Context, runUUID s } // ListSimulationJourneys lists journeys for a simulation run. -func (s *GradientAIServiceOp) ListSimulationJourneys(ctx context.Context, runUUID string, opt *SimulationJourneyListOptions) (*SimulationJourneyListResponse, *Response, error) { +func (s *AgentPlatformServiceOp) ListSimulationJourneys(ctx context.Context, runUUID string, opt *SimulationJourneyListOptions) (*SimulationJourneyListResponse, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -1067,7 +1067,7 @@ func (s *GradientAIServiceOp) ListSimulationJourneys(ctx context.Context, runUUI } // GetSimulationJourney retrieves a single journey within a simulation run. -func (s *GradientAIServiceOp) GetSimulationJourney(ctx context.Context, runUUID, journeyUUID string) (*SimulationJourney, *Response, error) { +func (s *AgentPlatformServiceOp) GetSimulationJourney(ctx context.Context, runUUID, journeyUUID string) (*SimulationJourney, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -1091,7 +1091,7 @@ func (s *GradientAIServiceOp) GetSimulationJourney(ctx context.Context, runUUID, // GetSimulationJourneyTrajectoryURL returns a presigned download URL for a // journey's trajectory JSON. -func (s *GradientAIServiceOp) GetSimulationJourneyTrajectoryURL(ctx context.Context, runUUID, journeyUUID string) (*SimulationJourneyTrajectoryURLResponse, *Response, error) { +func (s *AgentPlatformServiceOp) GetSimulationJourneyTrajectoryURL(ctx context.Context, runUUID, journeyUUID string) (*SimulationJourneyTrajectoryURLResponse, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } @@ -1115,7 +1115,7 @@ func (s *GradientAIServiceOp) GetSimulationJourneyTrajectoryURL(ctx context.Cont // GetSimulationJourneyTrajectory retrieves the parsed trajectory JSON for a // journey. -func (s *GradientAIServiceOp) GetSimulationJourneyTrajectory(ctx context.Context, runUUID, journeyUUID string) (*SimulationTrajectory, *Response, error) { +func (s *AgentPlatformServiceOp) GetSimulationJourneyTrajectory(ctx context.Context, runUUID, journeyUUID string) (*SimulationTrajectory, *Response, error) { if runUUID == "" { return nil, nil, fmt.Errorf("run uuid is required") } diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go index 3e57c8f10..4f2c5159d 100644 --- a/vendor/github.com/digitalocean/godo/godo.go +++ b/vendor/github.com/digitalocean/godo/godo.go @@ -21,7 +21,7 @@ import ( ) const ( - libraryVersion = "1.208.0" + libraryVersion = "1.217.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" @@ -59,6 +59,7 @@ type Client struct { Apps AppsService Balance BalanceService BillingHistory BillingHistoryService + Prepayment PrepaymentService CDNs CDNService Certificates CertificatesService Databases DatabasesService @@ -77,7 +78,7 @@ type Client struct { Keys KeysService Kubernetes KubernetesService LoadBalancers LoadBalancersService - MicroDroplets MicroDropletsService + MicroVMs MicroVMsService Monitoring MonitoringService Security SecurityService Secrets SecretsService @@ -103,7 +104,10 @@ type Client struct { VPCs VPCsService Routes RoutesService PartnerAttachment PartnerAttachmentService - GradientAI GradientAIService + AgentPlatform AgentPlatformService + ActionGateway *ActionGatewayService + HostedAgents HostedAgentsService + HostedAgentTriggers HostedAgentTriggersService DedicatedInference DedicatedInferenceService BatchInference BatchInferenceService BYOIPPrefixes BYOIPPrefixesService @@ -124,6 +128,8 @@ type Client struct { // Optional rate limiter to ensure QoS. rateLimiter *rate.Limiter + // Optional Action Gateway MCP origin override for development and testing. + actionGatewayMCPBaseURL *url.URL // Optional retry values. Setting the RetryConfig.RetryMax value enables automatically retrying requests // that fail with 429 or 500-level response codes using the go-retryablehttp client @@ -221,6 +227,16 @@ type ErrorResponse struct { // Attempts is the number of times the request was attempted when retries are enabled. Attempts int + + // NestedError captures the alternate {"error":{"code":...,"message":...}} + // envelope returned by some DigitalOcean services (e.g. the hosted-agents + // harness-api) instead of the top-level {"message":...} shape. When the + // top-level Message is empty, Error() falls back to this message so callers + // see the server's reason rather than a bare status code. + NestedError *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error,omitempty"` } // Rate contains the rate limit for the current client. @@ -304,6 +320,7 @@ func NewClient(httpClient *http.Client) *Client { c.Apps = &AppsServiceOp{client: c} c.Balance = &BalanceServiceOp{client: c} c.BillingHistory = &BillingHistoryServiceOp{client: c} + c.Prepayment = &PrepaymentServiceOp{client: c} c.CDNs = &CDNServiceOp{client: c} c.Certificates = &CertificatesServiceOp{client: c} c.Databases = &DatabasesServiceOp{client: c} @@ -321,7 +338,7 @@ func NewClient(httpClient *http.Client) *Client { c.Keys = &KeysServiceOp{client: c} c.Kubernetes = &KubernetesServiceOp{client: c} c.LoadBalancers = &LoadBalancersServiceOp{client: c} - c.MicroDroplets = &MicroDropletsServiceOp{client: c} + c.MicroVMs = &MicroVMsServiceOp{client: c} c.Monitoring = &MonitoringServiceOp{client: c} c.Security = &SecurityServiceOp{client: c} c.Secrets = &SecretsServiceOp{client: c} @@ -349,7 +366,10 @@ func NewClient(httpClient *http.Client) *Client { c.VPCs = &VPCsServiceOp{client: c} c.Routes = &RoutesServiceOp{client: c} c.PartnerAttachment = &PartnerAttachmentServiceOp{client: c} - c.GradientAI = &GradientAIServiceOp{client: c} + c.AgentPlatform = &AgentPlatformServiceOp{client: c} + c.ActionGateway = newActionGatewayService(c) + c.HostedAgents = &HostedAgentsServiceOp{client: c} + c.HostedAgentTriggers = &HostedAgentTriggersServiceOp{client: c} c.DedicatedInference = &DedicatedInferenceServiceOp{client: c} batchInferenceURL, _ := url.Parse(defaultBatchInferenceBaseURL) c.BatchInference = &BatchInferenceServiceOp{client: c, baseURL: batchInferenceURL} @@ -684,12 +704,17 @@ func (r *ErrorResponse) Error() string { attempted = fmt.Sprintf("; giving up after %d attempt(s)", r.Attempts) } + message := r.Message + if message == "" && r.NestedError != nil { + message = r.NestedError.Message + } + if r.RequestID != "" { return fmt.Sprintf("%v %v: %d (request %q) %v%s", - r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.RequestID, r.Message, attempted) + r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.RequestID, message, attempted) } return fmt.Sprintf("%v %v: %d %v%s", - r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message, attempted) + r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, message, attempted) } // CheckResponse checks the API response for errors, and returns them if present. A response is considered an diff --git a/vendor/github.com/digitalocean/godo/hosted_agent_checkpoints.go b/vendor/github.com/digitalocean/godo/hosted_agent_checkpoints.go new file mode 100644 index 000000000..bb53c3f34 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/hosted_agent_checkpoints.go @@ -0,0 +1,224 @@ +package godo + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +// HostedAgentCheckpointStatus is the lifecycle status of a session checkpoint. +type HostedAgentCheckpointStatus string + +const ( + HostedAgentCheckpointStatusPending HostedAgentCheckpointStatus = "PENDING" + HostedAgentCheckpointStatusReady HostedAgentCheckpointStatus = "READY" + HostedAgentCheckpointStatusFailed HostedAgentCheckpointStatus = "FAILED" +) + +// HostedAgentCheckpointKind distinguishes user-requested vs automatic checkpoints. +type HostedAgentCheckpointKind string + +const ( + HostedAgentCheckpointKindExplicit HostedAgentCheckpointKind = "explicit" + HostedAgentCheckpointKindImplicit HostedAgentCheckpointKind = "implicit" +) + +// HostedAgentCheckpoint is an immutable save point of a session's microVM +// (files + live process memory) plus an event-log cursor. +type HostedAgentCheckpoint struct { + CheckpointID string `json:"checkpoint_id"` + SessionID string `json:"session_id"` + Status HostedAgentCheckpointStatus `json:"status"` + Kind HostedAgentCheckpointKind `json:"kind"` + Label string `json:"label,omitempty"` + EventID string `json:"event_id,omitempty"` + SizeBytes uint64 `json:"size_bytes,omitempty"` + CreatedAt Timestamp `json:"created_at"` + ErrorMessage string `json:"error_message,omitempty"` +} + +// HostedAgentCheckpointCreateRequest is the optional body for CreateCheckpoint. +type HostedAgentCheckpointCreateRequest struct { + Label string `json:"label,omitempty"` +} + +// HostedAgentCheckpointListOptions paginates ListCheckpoints (newest-first). +type HostedAgentCheckpointListOptions struct { + PageToken string `url:"page_token,omitempty"` + PageSize int `url:"page_size,omitempty"` +} + +// HostedAgentCheckpointsListResponse is returned by ListCheckpoints. +type HostedAgentCheckpointsListResponse struct { + Checkpoints []HostedAgentCheckpoint `json:"checkpoints"` + NextPageToken string `json:"next_page_token"` +} + +// HostedAgentCheckpointDeleteResponse is returned by DeleteCheckpoint. +type HostedAgentCheckpointDeleteResponse struct { + CheckpointID string `json:"checkpoint_id"` + Deleted bool `json:"deleted"` +} + +// HostedAgentForkSessionRequest is the body for ForkSession. +// Count defaults to 1 when zero; the server rejects values above HostedAgentForkMaxCount. +type HostedAgentForkSessionRequest struct { + FromCheckpointID string `json:"from_checkpoint_id,omitempty"` + Count int `json:"count,omitempty"` +} + +// HostedAgentForkSessionResponse is returned by ForkSession. +type HostedAgentForkSessionResponse struct { + Sessions []HostedAgentSession `json:"sessions"` +} + +type hostedAgentCheckpointRoot struct { + Checkpoint *HostedAgentCheckpoint `json:"checkpoint"` +} + +// CreateCheckpoint captures a save point for the session. The call blocks until +// the checkpoint reaches a terminal READY state (or the server returns an error). +// Checkpoints are only allowed between turns (at run.completed). +func (s *HostedAgentsServiceOp) CreateCheckpoint(ctx context.Context, sessionID string, create *HostedAgentCheckpointCreateRequest) (*HostedAgentCheckpoint, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if create == nil { + create = &HostedAgentCheckpointCreateRequest{} + } + path := fmt.Sprintf(hostedAgentSessionCheckpointsPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, create) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentCheckpointRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Checkpoint == nil { + return nil, resp, errors.New("hosted agents: create checkpoint returned no checkpoint") + } + return root.Checkpoint, resp, nil +} + +// ListCheckpoints returns checkpoints for a session, newest first. +func (s *HostedAgentsServiceOp) ListCheckpoints(ctx context.Context, sessionID string, opt *HostedAgentCheckpointListOptions) (*HostedAgentCheckpointsListResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + path := fmt.Sprintf(hostedAgentSessionCheckpointsPath, sessionID) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentCheckpointsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// GetCheckpoint returns a single checkpoint by ID. +func (s *HostedAgentsServiceOp) GetCheckpoint(ctx context.Context, sessionID, checkpointID string) (*HostedAgentCheckpoint, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if checkpointID == "" { + return nil, nil, errors.New("hosted agents: checkpoint id is required") + } + path := fmt.Sprintf(hostedAgentSessionCheckpointByIDPath, sessionID, checkpointID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentCheckpointRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Checkpoint == nil { + return nil, resp, errors.New("hosted agents: get checkpoint returned no checkpoint") + } + return root.Checkpoint, resp, nil +} + +// DeleteCheckpoint removes a checkpoint (DB row + substrate capture). Idempotent: +// deleting an already-gone checkpoint still returns success. +func (s *HostedAgentsServiceOp) DeleteCheckpoint(ctx context.Context, sessionID, checkpointID string) (*HostedAgentCheckpointDeleteResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if checkpointID == "" { + return nil, nil, errors.New("hosted agents: checkpoint id is required") + } + path := fmt.Sprintf(hostedAgentSessionCheckpointByIDPath, sessionID, checkpointID) + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentCheckpointDeleteResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ForkSession creates N independent child sessions from a checkpoint (or from +// "now" when FromCheckpointID is empty). Count defaults to 1; max is HostedAgentForkMaxCount. +// The operation is all-or-nothing: on failure nothing is left running. +func (s *HostedAgentsServiceOp) ForkSession(ctx context.Context, sessionID string, fork *HostedAgentForkSessionRequest) (*HostedAgentForkSessionResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if fork == nil { + fork = &HostedAgentForkSessionRequest{} + } + if fork.Count < 0 || fork.Count > HostedAgentForkMaxCount { + return nil, nil, fmt.Errorf("hosted agents: fork count must be between 1 and %d (got %d)", HostedAgentForkMaxCount, fork.Count) + } + path := fmt.Sprintf(hostedAgentSessionForkPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, fork) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentForkSessionResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// RollbackToCheckpoint rewinds the session in place to the given checkpoint. +// The session_id is unchanged; the underlying sandbox is replaced. Returns the +// updated session. +func (s *HostedAgentsServiceOp) RollbackToCheckpoint(ctx context.Context, sessionID, checkpointID string) (*HostedAgentSession, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if checkpointID == "" { + return nil, nil, errors.New("hosted agents: checkpoint id is required") + } + path := fmt.Sprintf(hostedAgentSessionCheckpointRollbackPath, sessionID, checkpointID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, struct{}{}) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentSessionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Session == nil { + return nil, resp, errors.New("hosted agents: rollback returned no session") + } + return root.Session, resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/hosted_agent_configs.go b/vendor/github.com/digitalocean/godo/hosted_agent_configs.go new file mode 100644 index 000000000..4e319c008 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/hosted_agent_configs.go @@ -0,0 +1,228 @@ +package godo + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" +) + +const ( + hostedAgentsConfigsBasePath = "/v2/agents/configs" + hostedAgentConfigByIDPath = hostedAgentsConfigsBasePath + "/%s" + hostedAgentConfigSessionsPath = hostedAgentConfigByIDPath + "/sessions" +) + +// HostedAgentConfigCredentialSlot is redacted credential metadata on GET/create. +// It echoes only the manifest's declaration for the slot; secret values are +// never returned. Whether a slot is fulfilled is implied by Source: a +// tenantSecret is always stored at create, and an oauth slot is a declaration +// only until oauth brokering is implemented. +// +// Prefer HostedEnvironmentCredentialSlot; this name remains for compatibility. +type HostedAgentConfigCredentialSlot struct { + Name string `json:"name"` + Source string `json:"source,omitempty"` + Provider string `json:"provider,omitempty"` +} + +// HostedAgentConfig is an immutable team-scoped environment definition +// (product name: Environment; historically called Agent Config). +// Prefer HostedEnvironment; this name remains for compatibility. +type HostedAgentConfig struct { + ID string `json:"id"` + Name string `json:"name"` + AgentSpecSchemaVersion string `json:"agentspec_schema_version"` + Manifest json.RawMessage `json:"manifest"` + ContentHash string `json:"content_hash"` + CreatedBy string `json:"created_by"` + CreatedAt Timestamp `json:"created_at"` + UpdatedAt Timestamp `json:"updated_at"` + Credentials []HostedAgentConfigCredentialSlot `json:"credentials,omitempty"` + // Warnings carries non-fatal create-time advisories computed from the + // manifest. It is populated on the create response only. + Warnings []string `json:"warnings,omitempty"` +} + +// HostedAgentConfigSummary is the list view (no manifest / credentials). +// Prefer HostedEnvironmentSummary; this name remains for compatibility. +type HostedAgentConfigSummary struct { + ID string `json:"id"` + Name string `json:"name"` + AgentSpecSchemaVersion string `json:"agentspec_schema_version"` + ContentHash string `json:"content_hash"` + CreatedBy string `json:"created_by"` + CreatedAt Timestamp `json:"created_at"` + UpdatedAt Timestamp `json:"updated_at"` +} + +// HostedAgentConfigCreateRequest is the body for POST /v2/agents/configs. +// Credentials are not request fields: every secret is declared in the manifest +// under secrets (Environment Spec) or the legacy agents.yaml shape, and a +// tenantSecret slot carries its plaintext in a write-only value that is +// extracted server-side and never persisted or returned. Sending the retired +// secrets or oauth_assignments maps is rejected with a 400. +// Prefer HostedEnvironmentCreateRequest; this name remains for compatibility. +type HostedAgentConfigCreateRequest struct { + Name string `json:"name"` + ManifestYAML string `json:"manifest_yaml"` +} + +// HostedAgentConfigListOptions specifies optional list pagination. +// Prefer HostedEnvironmentListOptions; this name remains for compatibility. +type HostedAgentConfigListOptions struct { + PageToken string `url:"page_token,omitempty"` + PageSize int `url:"page_size,omitempty"` +} + +// HostedAgentConfigsListResponse is returned by GET /v2/agents/configs. +// The JSON field remains "configs" on the wire. Prefer +// HostedEnvironmentsListResponse; this name remains for compatibility. +type HostedAgentConfigsListResponse struct { + Configs []HostedAgentConfigSummary `json:"configs"` + NextPageToken string `json:"next_page_token"` +} + +type hostedAgentConfigRoot struct { + Config *HostedAgentConfig `json:"config"` +} + +// Advertised Environment aliases. Prefer these over the HostedAgentConfig* +// names; the Agent Config identifiers remain as compatible aliases. +type ( + HostedEnvironment = HostedAgentConfig + HostedEnvironmentCredentialSlot = HostedAgentConfigCredentialSlot + HostedEnvironmentSummary = HostedAgentConfigSummary + HostedEnvironmentCreateRequest = HostedAgentConfigCreateRequest + HostedEnvironmentListOptions = HostedAgentConfigListOptions + HostedEnvironmentsListResponse = HostedAgentConfigsListResponse +) + +// ListEnvironments lists active Environments for the caller's team. +func (s *HostedAgentsServiceOp) ListEnvironments(ctx context.Context, opt *HostedEnvironmentListOptions) (*HostedEnvironmentsListResponse, *Response, error) { + return s.ListAgentConfigs(ctx, opt) +} + +// GetEnvironment returns one Environment with redacted credential slots. +func (s *HostedAgentsServiceOp) GetEnvironment(ctx context.Context, environmentID string) (*HostedEnvironment, *Response, error) { + return s.GetAgentConfig(ctx, environmentID) +} + +// CreateEnvironment creates an immutable Environment from a name and +// environment spec YAML. Secret values belong in the manifest's secrets map +// (or legacy agents.yaml secret slots), not as separate request fields. +func (s *HostedAgentsServiceOp) CreateEnvironment(ctx context.Context, create *HostedEnvironmentCreateRequest) (*HostedEnvironment, *Response, error) { + return s.CreateAgentConfig(ctx, create) +} + +// DeleteEnvironment soft-deletes an Environment. The API returns HTTP 204. +func (s *HostedAgentsServiceOp) DeleteEnvironment(ctx context.Context, environmentID string) (*Response, error) { + return s.DeleteAgentConfig(ctx, environmentID) +} + +// ListEnvironmentSessions lists sessions created from an Environment. +func (s *HostedAgentsServiceOp) ListEnvironmentSessions(ctx context.Context, environmentID string, opt *HostedAgentSessionListOptions) (*HostedAgentSessionsListResponse, *Response, error) { + return s.ListAgentConfigSessions(ctx, environmentID, opt) +} + +// ListAgentConfigs lists active Environments for the caller's team. +// Prefer ListEnvironments; this name remains for compatibility. +func (s *HostedAgentsServiceOp) ListAgentConfigs(ctx context.Context, opt *HostedAgentConfigListOptions) (*HostedAgentConfigsListResponse, *Response, error) { + path, err := addOptions(hostedAgentsConfigsBasePath, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentConfigsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// GetAgentConfig returns one Environment with redacted credential slots. +// Prefer GetEnvironment; this name remains for compatibility. +func (s *HostedAgentsServiceOp) GetAgentConfig(ctx context.Context, configID string) (*HostedAgentConfig, *Response, error) { + if configID == "" { + return nil, nil, errors.New("hosted agents: config id is required") + } + path := fmt.Sprintf(hostedAgentConfigByIDPath, configID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentConfigRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Config == nil { + return nil, resp, errors.New("hosted agents: get config returned no config") + } + return root.Config, resp, nil +} + +// CreateAgentConfig creates an immutable Environment from a name and +// environment spec YAML. Prefer CreateEnvironment; this name remains for +// compatibility. Secret values belong in the manifest, not as separate +// request fields. +func (s *HostedAgentsServiceOp) CreateAgentConfig(ctx context.Context, create *HostedAgentConfigCreateRequest) (*HostedAgentConfig, *Response, error) { + if create == nil { + return nil, nil, errors.New("hosted agents: create request is required") + } + req, err := s.client.NewRequest(ctx, http.MethodPost, hostedAgentsConfigsBasePath, create) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentConfigRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Config == nil { + return nil, resp, errors.New("hosted agents: create config returned no config") + } + return root.Config, resp, nil +} + +// DeleteAgentConfig soft-deletes an Environment. The API returns HTTP 204. +// Prefer DeleteEnvironment; this name remains for compatibility. +func (s *HostedAgentsServiceOp) DeleteAgentConfig(ctx context.Context, configID string) (*Response, error) { + if configID == "" { + return nil, errors.New("hosted agents: config id is required") + } + path := fmt.Sprintf(hostedAgentConfigByIDPath, configID) + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ListAgentConfigSessions lists sessions created from an Environment. +// Prefer ListEnvironmentSessions; this name remains for compatibility. +func (s *HostedAgentsServiceOp) ListAgentConfigSessions(ctx context.Context, configID string, opt *HostedAgentSessionListOptions) (*HostedAgentSessionsListResponse, *Response, error) { + if configID == "" { + return nil, nil, errors.New("hosted agents: config id is required") + } + path := fmt.Sprintf(hostedAgentConfigSessionsPath, configID) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentSessionsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/hosted_agent_policy.go b/vendor/github.com/digitalocean/godo/hosted_agent_policy.go new file mode 100644 index 000000000..b95323fcc --- /dev/null +++ b/vendor/github.com/digitalocean/godo/hosted_agent_policy.go @@ -0,0 +1,95 @@ +package godo + +import ( + "bytes" + "context" + "errors" +) + +// HostedAgentPolicyAction is a tool-permission disposition. +type HostedAgentPolicyAction string + +const ( + HostedAgentPolicyActionAllow HostedAgentPolicyAction = "allow" + HostedAgentPolicyActionAsk HostedAgentPolicyAction = "ask" + HostedAgentPolicyActionDeny HostedAgentPolicyAction = "deny" +) + +// HostedAgentPolicyEnforcement controls how strictly a rule is enforced. +type HostedAgentPolicyEnforcement string + +const ( + HostedAgentPolicyEnforcementStrict HostedAgentPolicyEnforcement = "strict" + HostedAgentPolicyEnforcementBestEffort HostedAgentPolicyEnforcement = "best-effort" +) + +// HostedAgentPolicyVerdict is the validation outcome for one resolved rule. +type HostedAgentPolicyVerdict string + +const ( + HostedAgentPolicyVerdictExact HostedAgentPolicyVerdict = "exact" + HostedAgentPolicyVerdictDegraded HostedAgentPolicyVerdict = "degraded" + HostedAgentPolicyVerdictUnsupported HostedAgentPolicyVerdict = "unsupported" + HostedAgentPolicyVerdictUnverified HostedAgentPolicyVerdict = "unverified" + HostedAgentPolicyVerdictDelegated HostedAgentPolicyVerdict = "delegated" +) + +// HostedAgentPolicyResolvedRule is a rule after resolution, carrying a stable +// id for audit correlation. +type HostedAgentPolicyResolvedRule struct { + ID string `json:"id,omitempty"` + Tool string `json:"tool,omitempty"` + Match map[string]string `json:"match,omitempty"` + Action HostedAgentPolicyAction `json:"action,omitempty"` + Enforcement HostedAgentPolicyEnforcement `json:"enforcement,omitempty"` +} + +// HostedAgentPolicyRuleVerdict pairs a resolved rule with its validation outcome. +type HostedAgentPolicyRuleVerdict struct { + Rule HostedAgentPolicyResolvedRule `json:"rule,omitempty"` + Verdict HostedAgentPolicyVerdict `json:"verdict,omitempty"` + Rendered HostedAgentPolicyAction `json:"rendered,omitempty"` + Reason string `json:"reason,omitempty"` + Suggestion string `json:"suggestion,omitempty"` +} + +// HostedAgentPolicyValidationResult is returned by ValidatePolicy. +// The same shape is persisted per session in session_policies.verdicts. +type HostedAgentPolicyValidationResult struct { + Agent string `json:"agent,omitempty"` + Version string `json:"version,omitempty"` + OK bool `json:"ok"` + // DefaultAction is the catch-all disposition as shipped (after unverified + // allow→ask downgrade). When DefaultActionVerdict is degraded, this remains + // the shipped value; see DefaultActionRendered for what the agent enforces. + DefaultAction HostedAgentPolicyAction `json:"defaultAction,omitempty"` + // DefaultActionVerdict classifies how faithfully the agent can enforce the + // catch-all defaultAction. + DefaultActionVerdict HostedAgentPolicyVerdict `json:"defaultActionVerdict,omitempty"` + // DefaultActionRendered is the disposition the agent will actually enforce + // as the catch-all. + DefaultActionRendered HostedAgentPolicyAction `json:"defaultActionRendered,omitempty"` + DefaultActionReason string `json:"defaultActionReason,omitempty"` + Verdicts []HostedAgentPolicyRuleVerdict `json:"verdicts,omitempty"` + // ActionGatewayReason is set when a do.actions tool/toolbelt reference failed + // tool-registry's existence check. + ActionGatewayReason string `json:"actionGatewayReason,omitempty"` +} + +// ValidatePolicy checks a manifest's tool-permission policy against the agent's +// capabilities without creating a session. +func (s *HostedAgentsServiceOp) ValidatePolicy(ctx context.Context, manifest []byte) (*HostedAgentPolicyValidationResult, *Response, error) { + if len(bytes.TrimSpace(manifest)) == 0 { + return nil, nil, errors.New("hosted agents: manifest is required") + } + req, err := s.newCreateSessionPostRequest(ctx, hostedAgentPolicyValidatePath, bytes.NewReader(manifest), hostedAgentManifestMediaType) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentPolicyValidationResult) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/hosted_agent_templates.go b/vendor/github.com/digitalocean/godo/hosted_agent_templates.go new file mode 100644 index 000000000..a51c3b7cb --- /dev/null +++ b/vendor/github.com/digitalocean/godo/hosted_agent_templates.go @@ -0,0 +1,310 @@ +package godo + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +const ( + hostedAgentsTemplatesBasePath = "/v2/agents/templates" + hostedAgentTemplateByIDPath = hostedAgentsTemplatesBasePath + "/%s" + hostedAgentTemplateBuildsPath = hostedAgentTemplateByIDPath + "/builds" + hostedAgentTemplateBuildByIDPath = hostedAgentTemplateBuildsPath + "/%s" + hostedAgentTemplateBuildLogsByIDPath = hostedAgentTemplateBuildByIDPath + "/logs" +) + +// HostedAgentTemplateStatus is the lifecycle status of a team custom template. +type HostedAgentTemplateStatus string + +const ( + HostedAgentTemplateStatusUnspecified HostedAgentTemplateStatus = "TEMPLATE_STATUS_UNSPECIFIED" + HostedAgentTemplateStatusPending HostedAgentTemplateStatus = "TEMPLATE_STATUS_PENDING" + HostedAgentTemplateStatusBuilding HostedAgentTemplateStatus = "TEMPLATE_STATUS_BUILDING" + HostedAgentTemplateStatusReady HostedAgentTemplateStatus = "TEMPLATE_STATUS_READY" + HostedAgentTemplateStatusFailed HostedAgentTemplateStatus = "TEMPLATE_STATUS_FAILED" +) + +// HostedAgentTemplateBuildStatus is the lifecycle status of a template build. +type HostedAgentTemplateBuildStatus string + +const ( + HostedAgentTemplateBuildStatusUnspecified HostedAgentTemplateBuildStatus = "STATUS_UNSPECIFIED" + HostedAgentTemplateBuildStatusPending HostedAgentTemplateBuildStatus = "PENDING" + HostedAgentTemplateBuildStatusBuilding HostedAgentTemplateBuildStatus = "BUILDING" + HostedAgentTemplateBuildStatusSucceeded HostedAgentTemplateBuildStatus = "SUCCEEDED" + HostedAgentTemplateBuildStatusFailed HostedAgentTemplateBuildStatus = "FAILED" +) + +// HostedAgentTemplateImageSource is the customer OCI image used as template input. +type HostedAgentTemplateImageSource struct { + Registry string `json:"registry,omitempty"` + Repository string `json:"repository,omitempty"` + Tag string `json:"tag,omitempty"` + Digest string `json:"digest,omitempty"` +} + +// HostedAgentTemplateSpec is the resolved template source. +type HostedAgentTemplateSpec struct { + // BaseTemplate is the platform base key: coding-base | coding-codex | coding-opencode. + BaseTemplate string `json:"base_template,omitempty"` + Image *HostedAgentTemplateImageSource `json:"image,omitempty"` +} + +// HostedAgentTemplate is a team-owned custom sandbox template. +type HostedAgentTemplate struct { + TemplateID string `json:"template_id"` + Name string `json:"name"` + Spec *HostedAgentTemplateSpec `json:"spec,omitempty"` + Status HostedAgentTemplateStatus `json:"status"` + CreatedAt Timestamp `json:"created_at"` + UpdatedAt Timestamp `json:"updated_at"` +} + +// HostedAgentTemplateBuild is one immutable build of a template. +type HostedAgentTemplateBuild struct { + BuildID string `json:"build_id"` + TemplateID string `json:"template_id,omitempty"` + Name string `json:"name,omitempty"` + Spec *HostedAgentTemplateSpec `json:"spec,omitempty"` + Status HostedAgentTemplateBuildStatus `json:"status"` + Error string `json:"error,omitempty"` + CreatedAt Timestamp `json:"created_at"` + UpdatedAt Timestamp `json:"updated_at"` +} + +// HostedAgentTemplateCreateRequest is the body for POST /v2/agents/templates. +type HostedAgentTemplateCreateRequest struct { + Name string `json:"name"` + BaseTemplate string `json:"base_template"` + SourceOCIRef string `json:"source_oci_ref"` +} + +// HostedAgentTemplateUpdateRequest is the body for PUT /v2/agents/templates/{template_id}. +// Updating source kicks a new build. +type HostedAgentTemplateUpdateRequest struct { + SourceOCIRef string `json:"source_oci_ref,omitempty"` + BaseTemplate string `json:"base_template,omitempty"` +} + +// HostedAgentTemplateListOptions paginates ListTemplates. +type HostedAgentTemplateListOptions struct { + PageToken string `url:"page_token,omitempty"` + PageSize int `url:"page_size,omitempty"` +} + +// HostedAgentTemplatesListResponse is returned by GET /v2/agents/templates. +type HostedAgentTemplatesListResponse struct { + Templates []HostedAgentTemplate `json:"templates"` + NextPageToken string `json:"next_page_token,omitempty"` +} + +// HostedAgentTemplateDeleteResponse is returned by DELETE /v2/agents/templates/{template_id}. +type HostedAgentTemplateDeleteResponse struct { + TemplateID string `json:"template_id"` + Deleted bool `json:"deleted"` +} + +// HostedAgentTemplateBuildListOptions paginates ListTemplateBuilds. +type HostedAgentTemplateBuildListOptions struct { + PageToken string `url:"page_token,omitempty"` + PageSize int `url:"page_size,omitempty"` +} + +// HostedAgentTemplateBuildsListResponse is returned by GET .../builds. +type HostedAgentTemplateBuildsListResponse struct { + Builds []HostedAgentTemplateBuild `json:"builds"` + NextPageToken string `json:"next_page_token,omitempty"` +} + +// HostedAgentTemplateBuildLogs is returned by GET .../builds/{build_id}/logs. +type HostedAgentTemplateBuildLogs struct { + // SignedURL is a short-lived (15-minute) pre-signed Spaces GET URL. + SignedURL string `json:"signed_url"` +} + +type hostedAgentTemplateRoot struct { + Template *HostedAgentTemplate `json:"template"` +} + +type hostedAgentTemplateBuildRoot struct { + Build *HostedAgentTemplateBuild `json:"build"` +} + +// ListTemplates returns the caller's team custom templates. +func (s *HostedAgentsServiceOp) ListTemplates(ctx context.Context, opt *HostedAgentTemplateListOptions) (*HostedAgentTemplatesListResponse, *Response, error) { + path, err := addOptions(hostedAgentsTemplatesBasePath, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTemplatesListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// CreateTemplate creates a team custom template and kicks a build. +func (s *HostedAgentsServiceOp) CreateTemplate(ctx context.Context, create *HostedAgentTemplateCreateRequest) (*HostedAgentTemplate, *Response, error) { + if create == nil { + return nil, nil, errors.New("hosted agents: create template request is required") + } + if create.Name == "" { + return nil, nil, errors.New("hosted agents: name is required") + } + if create.BaseTemplate == "" { + return nil, nil, errors.New("hosted agents: base_template is required") + } + if create.SourceOCIRef == "" { + return nil, nil, errors.New("hosted agents: source_oci_ref is required") + } + req, err := s.client.NewRequest(ctx, http.MethodPost, hostedAgentsTemplatesBasePath, create) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTemplateRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Template == nil { + return nil, resp, errors.New("hosted agents: create template returned no template") + } + return root.Template, resp, nil +} + +// GetTemplate returns a team custom template by public id. +func (s *HostedAgentsServiceOp) GetTemplate(ctx context.Context, templateID string) (*HostedAgentTemplate, *Response, error) { + if templateID == "" { + return nil, nil, errors.New("hosted agents: template id is required") + } + path := fmt.Sprintf(hostedAgentTemplateByIDPath, templateID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTemplateRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Template == nil { + return nil, resp, errors.New("hosted agents: get template returned no template") + } + return root.Template, resp, nil +} + +// UpdateTemplate updates a team template's source and kicks a new build. +func (s *HostedAgentsServiceOp) UpdateTemplate(ctx context.Context, templateID string, update *HostedAgentTemplateUpdateRequest) (*HostedAgentTemplate, *Response, error) { + if templateID == "" { + return nil, nil, errors.New("hosted agents: template id is required") + } + if update == nil { + return nil, nil, errors.New("hosted agents: update template request is required") + } + path := fmt.Sprintf(hostedAgentTemplateByIDPath, templateID) + req, err := s.client.NewRequest(ctx, http.MethodPut, path, update) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTemplateRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Template == nil { + return nil, resp, errors.New("hosted agents: update template returned no template") + } + return root.Template, resp, nil +} + +// DeleteTemplate deletes a team custom template. +func (s *HostedAgentsServiceOp) DeleteTemplate(ctx context.Context, templateID string) (*HostedAgentTemplateDeleteResponse, *Response, error) { + if templateID == "" { + return nil, nil, errors.New("hosted agents: template id is required") + } + path := fmt.Sprintf(hostedAgentTemplateByIDPath, templateID) + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTemplateDeleteResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ListTemplateBuilds returns build history for a template. +func (s *HostedAgentsServiceOp) ListTemplateBuilds(ctx context.Context, templateID string, opt *HostedAgentTemplateBuildListOptions) (*HostedAgentTemplateBuildsListResponse, *Response, error) { + if templateID == "" { + return nil, nil, errors.New("hosted agents: template id is required") + } + path := fmt.Sprintf(hostedAgentTemplateBuildsPath, templateID) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTemplateBuildsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// GetTemplateBuild returns a single template build. +func (s *HostedAgentsServiceOp) GetTemplateBuild(ctx context.Context, templateID, buildID string) (*HostedAgentTemplateBuild, *Response, error) { + if templateID == "" { + return nil, nil, errors.New("hosted agents: template id is required") + } + if buildID == "" { + return nil, nil, errors.New("hosted agents: build id is required") + } + path := fmt.Sprintf(hostedAgentTemplateBuildByIDPath, templateID, buildID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTemplateBuildRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Build == nil { + return nil, resp, errors.New("hosted agents: get template build returned no build") + } + return root.Build, resp, nil +} + +// GetTemplateBuildLogs returns a short-lived signed URL for archived build logs. +func (s *HostedAgentsServiceOp) GetTemplateBuildLogs(ctx context.Context, templateID, buildID string) (*HostedAgentTemplateBuildLogs, *Response, error) { + if templateID == "" { + return nil, nil, errors.New("hosted agents: template id is required") + } + if buildID == "" { + return nil, nil, errors.New("hosted agents: build id is required") + } + path := fmt.Sprintf(hostedAgentTemplateBuildLogsByIDPath, templateID, buildID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTemplateBuildLogs) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/hosted_agent_triggers.go b/vendor/github.com/digitalocean/godo/hosted_agent_triggers.go new file mode 100644 index 000000000..83154ec31 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/hosted_agent_triggers.go @@ -0,0 +1,581 @@ +package godo + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +const ( + hostedAgentTriggersBasePath = "/v2/agents/triggers" + hostedAgentTriggerByIDPath = hostedAgentTriggersBasePath + "/%s" + hostedAgentTriggerRotateSecretPath = hostedAgentTriggerByIDPath + "/rotate-secret" + hostedAgentTriggerExecutionsPath = hostedAgentTriggerByIDPath + "/executions" + hostedAgentTriggerExecutionByIDPath = hostedAgentTriggerExecutionsPath + "/%s" + hostedAgentTriggerExecutionCancelPath = hostedAgentTriggerExecutionByIDPath + "/cancel" + hostedAgentTriggerBySessionPath = hostedAgentTriggersBasePath + "/by-session/%s" + hostedAgentReusableSessionsPath = hostedAgentTriggersBasePath + "/reusable-sessions" + hostedAgentWebhookProvidersPath = "/v2/agents/webhook-providers" +) + +// HostedAgentTriggersService exposes the Hosted Agents Triggers public REST API +// (webhook & cron triggers for agent runs) under /v2/agents/triggers and +// /v2/agents/webhook-providers. +type HostedAgentTriggersService interface { + List(context.Context, *HostedAgentTriggerListOptions) (*HostedAgentTriggersListResponse, *Response, error) + Create(context.Context, *HostedAgentTriggerCreateRequest) (*HostedAgentTriggerCreateResponse, *Response, error) + Get(context.Context, string) (*HostedAgentTrigger, *Response, error) + Update(context.Context, string, *HostedAgentTriggerUpdateRequest) (*HostedAgentTrigger, *Response, error) + Delete(context.Context, string) (*Response, error) + RotateSecret(context.Context, string, *HostedAgentTriggerRotateSecretOptions) (*HostedAgentTriggerRotateSecretResponse, *Response, error) + ListExecutions(context.Context, string, *HostedAgentTriggerExecutionListOptions) (*HostedAgentTriggerExecutionsListResponse, *Response, error) + GetExecution(context.Context, string, string) (*HostedAgentTriggerExecution, *Response, error) + Cancel(context.Context, string, string, *HostedAgentTriggerCancelExecutionOptions) (*HostedAgentTriggerExecution, *Response, error) + GetBySession(context.Context, string) (*HostedAgentTrigger, *Response, error) + ListReusableSessions(context.Context, *HostedAgentReusableSessionListOptions) (*HostedAgentReusableSessionsListResponse, *Response, error) + ListWebhookProviders(context.Context) (*HostedAgentWebhookProvidersListResponse, *Response, error) +} + +// HostedAgentTriggersServiceOp handles communication with Hosted Agents Trigger methods. +type HostedAgentTriggersServiceOp struct { + client *Client +} + +var _ HostedAgentTriggersService = &HostedAgentTriggersServiceOp{} + +// HostedAgentTriggerKind is how a trigger fires. +type HostedAgentTriggerKind string + +const ( + HostedAgentTriggerKindWebhook HostedAgentTriggerKind = "webhook" + HostedAgentTriggerKindCron HostedAgentTriggerKind = "cron" +) + +// HostedAgentTriggerStatus is the trigger lifecycle state. +type HostedAgentTriggerStatus string + +const ( + HostedAgentTriggerStatusActive HostedAgentTriggerStatus = "active" + HostedAgentTriggerStatusPaused HostedAgentTriggerStatus = "paused" +) + +// HostedAgentTriggerSessionMode controls whether each firing creates a new +// session or reuses a bound paused session. +type HostedAgentTriggerSessionMode string + +const ( + HostedAgentTriggerSessionModeFresh HostedAgentTriggerSessionMode = "fresh" + HostedAgentTriggerSessionModeReuse HostedAgentTriggerSessionMode = "reuse" +) + +// HostedAgentTriggerOutputMode is how run output is delivered. +type HostedAgentTriggerOutputMode string + +const ( + HostedAgentTriggerOutputModeNone HostedAgentTriggerOutputMode = "none" + HostedAgentTriggerOutputModeEmail HostedAgentTriggerOutputMode = "email" + // HostedAgentTriggerOutputModeSlack delivers run output to a Slack incoming webhook. + HostedAgentTriggerOutputModeSlack HostedAgentTriggerOutputMode = "slack" +) + +// HostedAgentWebhookProviderKey identifies the signature-verification scheme. +type HostedAgentWebhookProviderKey string + +const ( + HostedAgentWebhookProviderGitHub HostedAgentWebhookProviderKey = "github" + HostedAgentWebhookProviderGitLab HostedAgentWebhookProviderKey = "gitlab" + HostedAgentWebhookProviderCustom HostedAgentWebhookProviderKey = "custom" +) + +// HostedAgentTriggerExecutionStatus is the per-firing outcome. +type HostedAgentTriggerExecutionStatus string + +const ( + HostedAgentTriggerExecutionStatusPending HostedAgentTriggerExecutionStatus = "pending" + HostedAgentTriggerExecutionStatusRunning HostedAgentTriggerExecutionStatus = "running" + HostedAgentTriggerExecutionStatusSucceeded HostedAgentTriggerExecutionStatus = "succeeded" + HostedAgentTriggerExecutionStatusFailed HostedAgentTriggerExecutionStatus = "failed" +) + +// HostedAgentWebhookSignatureScheme is how an external system authenticates a delivery. +type HostedAgentWebhookSignatureScheme string + +const ( + HostedAgentWebhookSignatureHMACSHA256 HostedAgentWebhookSignatureScheme = "hmac-sha256" + HostedAgentWebhookSignaturePlaintext HostedAgentWebhookSignatureScheme = "plaintext" +) + +// HostedAgentTrigger is a webhook or cron automation bound to an agent/session. +type HostedAgentTrigger struct { + TriggerID string `json:"trigger_id,omitempty"` + TeamID int64 `json:"team_id,omitempty"` + Kind HostedAgentTriggerKind `json:"kind,omitempty"` + Name string `json:"name,omitempty"` + Status HostedAgentTriggerStatus `json:"status,omitempty"` + SessionMode HostedAgentTriggerSessionMode `json:"session_mode,omitempty"` + AgentKind HostedAgentKind `json:"agent_kind,omitempty"` + PromptTemplate string `json:"prompt_template,omitempty"` + Output *HostedAgentTriggerOutputRead `json:"output,omitempty"` + SessionTemplate string `json:"session_template,omitempty"` + BoundSessionID string `json:"bound_session_id,omitempty"` + Webhook *HostedAgentWebhookConfig `json:"webhook,omitempty"` + Cron *HostedAgentCronConfig `json:"cron,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + UpdatedAt Timestamp `json:"updated_at,omitempty"` +} + +// HostedAgentTriggerOutputRead is output-delivery config as returned on reads. +// Email addresses and Slack webhook URLs are write-only and never returned. +type HostedAgentTriggerOutputRead struct { + Mode HostedAgentTriggerOutputMode `json:"mode,omitempty"` + EmailConfigured bool `json:"email_configured,omitempty"` + SlackConfigured bool `json:"slack_configured,omitempty"` +} + +// HostedAgentTriggerOutputWrite is output-delivery config on create/update. +// Email is required when mode=email; Slack is required when mode=slack. +// Destinations are stored encrypted and never returned on reads. +type HostedAgentTriggerOutputWrite struct { + Mode HostedAgentTriggerOutputMode `json:"mode"` + Email string `json:"email,omitempty"` + Slack *HostedAgentTriggerSlackOutputWrite `json:"slack,omitempty"` +} + +// HostedAgentTriggerSlackOutputWrite is the Slack destination on create/update. +type HostedAgentTriggerSlackOutputWrite struct { + WebhookURL string `json:"webhook_url"` +} + +// HostedAgentWebhookConfig is present when kind=webhook. +type HostedAgentWebhookConfig struct { + Provider HostedAgentWebhookProviderKey `json:"provider,omitempty"` + WebhookURL string `json:"webhook_url,omitempty"` +} + +// HostedAgentCronConfig is present when kind=cron. +type HostedAgentCronConfig struct { + CronExpr string `json:"cron_expr,omitempty"` + Timezone string `json:"timezone,omitempty"` + NextRunAt Timestamp `json:"next_run_at,omitempty"` +} + +// HostedAgentTriggerCreateRequest creates a webhook or cron trigger. +type HostedAgentTriggerCreateRequest struct { + Kind HostedAgentTriggerKind `json:"kind"` + Name string `json:"name"` + SessionMode HostedAgentTriggerSessionMode `json:"session_mode"` + PromptTemplate string `json:"prompt_template"` + Output HostedAgentTriggerOutputWrite `json:"output"` + SessionTemplate string `json:"session_template,omitempty"` + BoundSessionID string `json:"bound_session_id,omitempty"` + Webhook *HostedAgentCreateWebhookConfig `json:"webhook,omitempty"` + Cron *HostedAgentCreateCronConfig `json:"cron,omitempty"` +} + +// HostedAgentCreateWebhookConfig is the webhook block on create. +type HostedAgentCreateWebhookConfig struct { + Provider HostedAgentWebhookProviderKey `json:"provider,omitempty"` +} + +// HostedAgentCreateCronConfig is the cron block on create/update. +type HostedAgentCreateCronConfig struct { + CronExpr string `json:"cron_expr"` + Timezone string `json:"timezone"` +} + +// HostedAgentTriggerUpdateRequest is a partial update (pause/re-enable via status). +type HostedAgentTriggerUpdateRequest struct { + Name string `json:"name,omitempty"` + Status HostedAgentTriggerStatus `json:"status,omitempty"` + PromptTemplate string `json:"prompt_template,omitempty"` + Output *HostedAgentTriggerOutputWrite `json:"output,omitempty"` + SessionTemplate string `json:"session_template,omitempty"` + BoundSessionID string `json:"bound_session_id,omitempty"` + Cron *HostedAgentCreateCronConfig `json:"cron,omitempty"` +} + +// HostedAgentTriggerCreateResponse is returned on create. +// WebhookSecret is present for webhook triggers only and shown exactly once. +type HostedAgentTriggerCreateResponse struct { + Trigger *HostedAgentTrigger `json:"trigger,omitempty"` + WebhookSecret string `json:"webhook_secret,omitempty"` +} + +// HostedAgentTriggerListOptions specifies optional list filters and pagination. +type HostedAgentTriggerListOptions struct { + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` + Kind HostedAgentTriggerKind `url:"kind,omitempty"` + Status HostedAgentTriggerStatus `url:"status,omitempty"` +} + +// HostedAgentTriggersListResponse is returned by List. +type HostedAgentTriggersListResponse struct { + Triggers []HostedAgentTrigger `json:"triggers"` + NextPageToken string `json:"next_page_token,omitempty"` +} + +// HostedAgentTriggerRotateSecretOptions specifies optional rotation behaviour. +// A nil options pointer, or GracePeriodSeconds left nil, selects the server +// default (5 minutes). Pass a pointer to 0 to revoke the outgoing secret +// immediately; any positive value is a custom handoff window in seconds +// (server-enforced max, default 1 hour). +type HostedAgentTriggerRotateSecretOptions struct { + // GracePeriodSeconds is how long the outgoing secret keeps verifying. + // Nil omits the query parameter (server default). Non-nil 0 revokes now. + GracePeriodSeconds *int `url:"grace_period_seconds,omitempty"` +} + +// HostedAgentTriggerRotateSecretResponse is returned by RotateSecret. +// Exactly one of PreviousSecretExpiresAt and PreviousSecretRevoked is set, so a +// caller never has to infer the outcome from a missing field. +type HostedAgentTriggerRotateSecretResponse struct { + WebhookSecret string `json:"webhook_secret,omitempty"` + // PreviousSecretExpiresAt is when the outgoing secret stops verifying + // deliveries. Nil when the secret was revoked on the rotate call. + PreviousSecretExpiresAt *Timestamp `json:"previous_secret_expires_at,omitempty"` + // PreviousSecretRevoked reports that the outgoing secret is already dead. + PreviousSecretRevoked bool `json:"previous_secret_revoked,omitempty"` +} + +// HostedAgentTriggerExecution is one row per firing. +type HostedAgentTriggerExecution struct { + ExecutionID string `json:"execution_id,omitempty"` + TriggerID string `json:"trigger_id,omitempty"` + Status HostedAgentTriggerExecutionStatus `json:"status,omitempty"` + SessionID string `json:"session_id,omitempty"` + RunID string `json:"run_id,omitempty"` + FailureReason string `json:"failure_reason,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + UpdatedAt Timestamp `json:"updated_at,omitempty"` + Payload string `json:"payload,omitempty"` + OutputText string `json:"output_text,omitempty"` + OutputTruncated bool `json:"output_truncated,omitempty"` +} + +// HostedAgentTriggerExecutionListOptions specifies optional execution list filters. +type HostedAgentTriggerExecutionListOptions struct { + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` + Status HostedAgentTriggerExecutionStatus `url:"status,omitempty"` +} + +// HostedAgentTriggerExecutionsListResponse is returned by ListExecutions. +type HostedAgentTriggerExecutionsListResponse struct { + Executions []HostedAgentTriggerExecution `json:"executions"` + NextPageToken string `json:"next_page_token,omitempty"` +} + +// HostedAgentTriggerCancelExecutionOptions specifies optional cancel behaviour. +type HostedAgentTriggerCancelExecutionOptions struct { + // Force cancels even if the execution has not started a run yet (the + // worker's own dispatch may still be in flight). Without this, that + // case returns 409; the server-side reclaim sweep clears it + // automatically within 15m regardless. + Force bool `url:"force,omitempty"` +} + +// HostedAgentWebhookProviderSignature describes how a provider signs deliveries. +type HostedAgentWebhookProviderSignature struct { + Header string `json:"header,omitempty"` + Scheme HostedAgentWebhookSignatureScheme `json:"scheme,omitempty"` +} + +// HostedAgentWebhookProvider is a static registry entry for the create-trigger UI. +type HostedAgentWebhookProvider struct { + Key HostedAgentWebhookProviderKey `json:"key,omitempty"` + DisplayName string `json:"display_name,omitempty"` + Description string `json:"description,omitempty"` + DocsURL string `json:"docs_url,omitempty"` + PasteHint string `json:"paste_hint,omitempty"` + Signature *HostedAgentWebhookProviderSignature `json:"signature,omitempty"` +} + +// HostedAgentWebhookProvidersListResponse is returned by ListWebhookProviders. +type HostedAgentWebhookProvidersListResponse struct { + Providers []HostedAgentWebhookProvider `json:"providers"` +} + +// HostedAgentReusableSession is a paused session suitable for session_mode=reuse. +type HostedAgentReusableSession struct { + SessionID string `json:"session_id,omitempty"` + Name string `json:"name,omitempty"` + AgentKind HostedAgentKind `json:"agent_kind,omitempty"` + Status HostedAgentSessionStatus `json:"status,omitempty"` + CreatedAt Timestamp `json:"created_at,omitempty"` + LastEventAt Timestamp `json:"last_event_at,omitempty"` +} + +// HostedAgentReusableSessionListOptions specifies pagination for reusable sessions. +type HostedAgentReusableSessionListOptions struct { + PageSize int `url:"page_size,omitempty"` + PageToken string `url:"page_token,omitempty"` +} + +// HostedAgentReusableSessionsListResponse is returned by ListReusableSessions. +type HostedAgentReusableSessionsListResponse struct { + Sessions []HostedAgentReusableSession `json:"sessions"` + NextPageToken string `json:"next_page_token,omitempty"` +} + +type hostedAgentTriggerRoot struct { + Trigger *HostedAgentTrigger `json:"trigger"` +} + +type hostedAgentTriggerExecutionRoot struct { + Execution *HostedAgentTriggerExecution `json:"execution"` +} + +// List enumerates the calling team's triggers. +func (s *HostedAgentTriggersServiceOp) List(ctx context.Context, opt *HostedAgentTriggerListOptions) (*HostedAgentTriggersListResponse, *Response, error) { + path, err := addOptions(hostedAgentTriggersBasePath, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTriggersListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// Create creates a webhook or cron trigger. +// For webhook triggers, WebhookSecret is returned exactly once. +func (s *HostedAgentTriggersServiceOp) Create(ctx context.Context, create *HostedAgentTriggerCreateRequest) (*HostedAgentTriggerCreateResponse, *Response, error) { + if create == nil { + return nil, nil, NewArgError("create", "cannot be nil") + } + req, err := s.client.NewRequest(ctx, http.MethodPost, hostedAgentTriggersBasePath, create) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTriggerCreateResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// Get returns a single trigger by ID. +func (s *HostedAgentTriggersServiceOp) Get(ctx context.Context, triggerID string) (*HostedAgentTrigger, *Response, error) { + if triggerID == "" { + return nil, nil, errors.New("hosted agent triggers: trigger id is required") + } + path := fmt.Sprintf(hostedAgentTriggerByIDPath, triggerID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTriggerRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Trigger == nil { + return nil, resp, errors.New("hosted agent triggers: get returned no trigger") + } + return root.Trigger, resp, nil +} + +// Update partially updates, pauses, or re-enables a trigger. +func (s *HostedAgentTriggersServiceOp) Update(ctx context.Context, triggerID string, update *HostedAgentTriggerUpdateRequest) (*HostedAgentTrigger, *Response, error) { + if triggerID == "" { + return nil, nil, errors.New("hosted agent triggers: trigger id is required") + } + if update == nil { + return nil, nil, NewArgError("update", "cannot be nil") + } + path := fmt.Sprintf(hostedAgentTriggerByIDPath, triggerID) + req, err := s.client.NewRequest(ctx, http.MethodPatch, path, update) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTriggerRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Trigger == nil { + return nil, resp, errors.New("hosted agent triggers: update returned no trigger") + } + return root.Trigger, resp, nil +} + +// Delete soft-deletes a trigger. The API returns HTTP 204 on success. +func (s *HostedAgentTriggersServiceOp) Delete(ctx context.Context, triggerID string) (*Response, error) { + if triggerID == "" { + return nil, errors.New("hosted agent triggers: trigger id is required") + } + path := fmt.Sprintf(hostedAgentTriggerByIDPath, triggerID) + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// RotateSecret issues a new webhook secret (webhook triggers only). +// By default (nil options, or GracePeriodSeconds left nil) the outgoing secret +// keeps verifying for the server default window (5 minutes) and +// PreviousSecretExpiresAt reports when it dies. Pass GracePeriodSeconds pointing +// at 0 to revoke it on this call, or at a positive value for a custom window. +func (s *HostedAgentTriggersServiceOp) RotateSecret(ctx context.Context, triggerID string, opt *HostedAgentTriggerRotateSecretOptions) (*HostedAgentTriggerRotateSecretResponse, *Response, error) { + if triggerID == "" { + return nil, nil, errors.New("hosted agent triggers: trigger id is required") + } + path := fmt.Sprintf(hostedAgentTriggerRotateSecretPath, triggerID) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodPost, path, struct{}{}) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTriggerRotateSecretResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ListExecutions returns a trigger's execution history (payload omitted from list items). +func (s *HostedAgentTriggersServiceOp) ListExecutions(ctx context.Context, triggerID string, opt *HostedAgentTriggerExecutionListOptions) (*HostedAgentTriggerExecutionsListResponse, *Response, error) { + if triggerID == "" { + return nil, nil, errors.New("hosted agent triggers: trigger id is required") + } + path := fmt.Sprintf(hostedAgentTriggerExecutionsPath, triggerID) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentTriggerExecutionsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// GetExecution returns a single execution, including its payload. +func (s *HostedAgentTriggersServiceOp) GetExecution(ctx context.Context, triggerID, executionID string) (*HostedAgentTriggerExecution, *Response, error) { + if triggerID == "" { + return nil, nil, errors.New("hosted agent triggers: trigger id is required") + } + if executionID == "" { + return nil, nil, errors.New("hosted agent triggers: execution id is required") + } + path := fmt.Sprintf(hostedAgentTriggerExecutionByIDPath, triggerID, executionID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTriggerExecutionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Execution == nil { + return nil, resp, errors.New("hosted agent triggers: get execution returned no execution") + } + return root.Execution, resp, nil +} + +// Cancel ends a running execution: fails it and frees the trigger's +// in-flight slot. A fresh-mode session is destroyed outright; a reuse-mode +// session is only paused, since the API has no run-interrupt call -- the +// agent's turn may still be in flight server-side. Cancelling a +// non-running execution is a no-op that still succeeds. See +// HostedAgentTriggerCancelExecutionOptions.Force for the 409 this can +// return when the execution has not started a run yet. +func (s *HostedAgentTriggersServiceOp) Cancel(ctx context.Context, triggerID, executionID string, opt *HostedAgentTriggerCancelExecutionOptions) (*HostedAgentTriggerExecution, *Response, error) { + if triggerID == "" { + return nil, nil, errors.New("hosted agent triggers: trigger id is required") + } + if executionID == "" { + return nil, nil, errors.New("hosted agent triggers: execution id is required") + } + path := fmt.Sprintf(hostedAgentTriggerExecutionCancelPath, triggerID, executionID) + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTriggerExecutionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Execution == nil { + return nil, resp, errors.New("hosted agent triggers: cancel returned no execution") + } + return root.Execution, resp, nil +} + +// GetBySession reverse-looks-up the trigger that produced or binds a session. +func (s *HostedAgentTriggersServiceOp) GetBySession(ctx context.Context, sessionID string) (*HostedAgentTrigger, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agent triggers: session id is required") + } + path := fmt.Sprintf(hostedAgentTriggerBySessionPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentTriggerRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Trigger == nil { + return nil, resp, errors.New("hosted agent triggers: get by session returned no trigger") + } + return root.Trigger, resp, nil +} + +// ListReusableSessions lists the team's PAUSED sessions for the reuse picker. +func (s *HostedAgentTriggersServiceOp) ListReusableSessions(ctx context.Context, opt *HostedAgentReusableSessionListOptions) (*HostedAgentReusableSessionsListResponse, *Response, error) { + path, err := addOptions(hostedAgentReusableSessionsPath, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentReusableSessionsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ListWebhookProviders lists supported webhook providers for the create-trigger UI. +func (s *HostedAgentTriggersServiceOp) ListWebhookProviders(ctx context.Context) (*HostedAgentWebhookProvidersListResponse, *Response, error) { + req, err := s.client.NewRequest(ctx, http.MethodGet, hostedAgentWebhookProvidersPath, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentWebhookProvidersListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} diff --git a/vendor/github.com/digitalocean/godo/hosted_agents.go b/vendor/github.com/digitalocean/godo/hosted_agents.go new file mode 100644 index 000000000..2ac04d7d2 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/hosted_agents.go @@ -0,0 +1,1780 @@ +package godo + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "hash" + "io" + "net/http" + "net/url" + "strconv" + "strings" +) + +const ( + hostedAgentManifestMediaType = "application/x-yaml" + hostedAgentWorkspaceMediaType = "application/octet-stream" + + hostedAgentsSessionsBasePath = "/v2/agents/sessions" + hostedAgentPolicyValidatePath = hostedAgentsSessionsBasePath + "/policy/validate" + hostedAgentSessionByIDPath = hostedAgentsSessionsBasePath + "/%s" + hostedAgentSessionEventsPath = hostedAgentSessionByIDPath + "/events" + hostedAgentSessionInputPath = hostedAgentSessionByIDPath + "/input" + hostedAgentSessionRequestPath = hostedAgentSessionByIDPath + "/request" + hostedAgentSessionHITLPath = hostedAgentSessionByIDPath + "/hitl/%s" + hostedAgentSessionSandboxExecPath = hostedAgentSessionByIDPath + "/sandbox/exec" + hostedAgentSandboxSizesPath = hostedAgentsSessionsBasePath + "/sandbox/sizes" + hostedAgentSessionPausePath = hostedAgentSessionByIDPath + "/pause" + hostedAgentSessionResumePath = hostedAgentSessionByIDPath + "/resume" + hostedAgentSessionWorkspaceUploadPath = hostedAgentSessionByIDPath + "/workspace/upload" + hostedAgentSessionWorkspaceDownloadPath = hostedAgentSessionByIDPath + "/workspace/download" + hostedAgentSessionWorkspaceTransfersPath = hostedAgentSessionByIDPath + "/workspace/transfers" + hostedAgentSessionWorkspaceTransferByIDPath = hostedAgentSessionWorkspaceTransfersPath + "/%s" + hostedAgentSessionWorkspaceTransferPartURLsPath = hostedAgentSessionWorkspaceTransferByIDPath + "/part-upload-urls" + hostedAgentSessionWorkspaceTransferCommitPath = hostedAgentSessionWorkspaceTransferByIDPath + "/commit" + hostedAgentSessionWorkspaceTransferCancelPath = hostedAgentSessionWorkspaceTransferByIDPath + "/cancel" + + // Team-scoped external-provider (e.g. GitHub) OAuth connect flow. + hostedAgentProviderAuthPath = "/v2/agents/auth/%s" + hostedAgentProviderAuthPollPath = hostedAgentProviderAuthPath + "/poll" + + hostedAgentSessionCheckpointsPath = hostedAgentSessionByIDPath + "/checkpoints" + hostedAgentSessionCheckpointByIDPath = hostedAgentSessionCheckpointsPath + "/%s" + hostedAgentSessionCheckpointRollbackPath = hostedAgentSessionCheckpointByIDPath + "/rollback" + hostedAgentSessionForkPath = hostedAgentSessionByIDPath + "/fork" + + // HostedAgentForkMaxCount is the v1 cap on children created by one fork call. + HostedAgentForkMaxCount = 4 + + workspaceContentSHA256Header = "X-Content-Sha256" + workspaceIsArchiveHeader = "X-Workspace-Is-Archive" + workspaceSizeBytesHeader = "X-Workspace-Size-Bytes" + + // sseLastEventIDHeader is the standard SSE resume cursor. The data-plane + // events endpoint takes the cursor here rather than as a query parameter. + sseLastEventIDHeader = "Last-Event-ID" + + // workspaceDownloadFooter is appended by OHS after a successful download + // payload so integrity survives intermediaries that strip HTTP trailers + // (e.g. Cloudflare). Format: DOWSSHA1 + 64 lowercase hex + '\n' = 73 bytes. + workspaceDownloadFooterPrefix = "DOWSSHA1" + workspaceDownloadFooterLen = len(workspaceDownloadFooterPrefix) + 64 + 1 +) + +// HostedAgentsService exposes the DigitalOcean Hosted Agents session API +// (HarnessAPI from harness.proto). Routes live under /v2/agents/sessions. +type HostedAgentsService interface { + // CreateSession provisions a session using the legacy JSON body (agent_kind, + // repo_hint, idle_timeout_seconds). Prefer CreateSessionFromManifest for + // Environment Spec YAML (or CreateSessionFromEnvironment for a saved + // Environment). + CreateSession(context.Context, *HostedAgentSessionCreateRequest) (*HostedAgentSession, *Response, error) + // CreateSessionFromManifest uploads an environment spec YAML document + // (Content-Type: application/x-yaml). Legacy agents.digitalocean.com/v1alpha1 + // Agent manifests are also accepted where the API still supports them. + // For OpenAI sandbox-provider sessions (adapter codex-agentapi), pass + // OpenAISessionID in opt so harness-api can persist openai_session_id for + // attach correlation (?openai_session_id=). doctl resolves ${...} + // placeholders client-side before calling this API; there is no + // server-side variables map. + CreateSessionFromManifest(context.Context, []byte, *HostedAgentManifestCreateOptions) (*HostedAgentSession, *Response, error) + // CreateSessionFromEnvironment provisions a session from an existing + // immutable Environment, referenced by config_id. The request uses + // Content-Type: application/json with a {name, config_id} body (optional + // resume_on_topoff); the environment's stored manifest and declared + // credentials seed the session at create time. + CreateSessionFromEnvironment(context.Context, *HostedAgentSessionFromEnvironmentRequest) (*HostedAgentSession, *Response, error) + // CreateSessionFromConfig is an alias for CreateSessionFromEnvironment. + // Prefer CreateSessionFromEnvironment; this name remains for compatibility. + CreateSessionFromConfig(context.Context, *HostedAgentSessionFromConfigRequest) (*HostedAgentSession, *Response, error) + // ValidatePolicy checks a manifest's tool-permission policy against the + // agent's capabilities without creating a session. Manifest is the same YAML + // document accepted by CreateSessionFromManifest (Content-Type: + // application/x-yaml). Returns per-rule verdicts; OK is false when any strict + // rule is unsupported or unverified (i.e. CreateSession would reject it). + ValidatePolicy(context.Context, []byte) (*HostedAgentPolicyValidationResult, *Response, error) + ListSessions(context.Context, *HostedAgentSessionListOptions) (*HostedAgentSessionsListResponse, *Response, error) + GetSession(context.Context, string) (*HostedAgentSession, *Response, error) + // UpdateSession patches session-scoped settings on an existing session and + // returns the updated snapshot. Never touches the agent manifest or config. + UpdateSession(context.Context, string, *HostedAgentSessionUpdateRequest) (*HostedAgentSession, *Response, error) + DestroySession(context.Context, string) (*Response, error) + PauseSession(context.Context, string) (*Response, error) + ResumeSession(context.Context, string) (*Response, error) + StreamSession(context.Context, string, *HostedAgentSessionStreamOptions) (*HostedAgentSessionStream, *Response, error) + SendInput(context.Context, string, *HostedAgentSendInputRequest) (*HostedAgentSendInputResponse, *Response, error) + RelayRequest(context.Context, string, *HostedAgentRelayRequest) (*HostedAgentRelayResponse, *Response, error) + ResolveHITL(context.Context, string, string, *HostedAgentResolveHITLRequest) (*Response, error) + + // StartProviderAuth begins (or resumes) the team-scoped connect flow for an + // external provider (e.g. "github"). The team is derived from the + // authenticated principal; there is no request body. + StartProviderAuth(context.Context, string) (*HostedAgentProviderAuthStart, *Response, error) + // PollProviderAuth reports whether a pending connect link has been + // authorized. pollURL is the PollURL returned by StartProviderAuth. + PollProviderAuth(context.Context, string, string) (*HostedAgentProviderAuthPoll, *Response, error) + ExecInSandbox(context.Context, string, *HostedAgentSandboxExecRequest) (*HostedAgentSandboxExecResponse, *Response, error) + // ListSandboxSizes returns the customer-selectable sandbox (microVM) size + // catalog (GET /v2/agents/sessions/sandbox/sizes). Every slug is accepted by + // CreateSession as spec.sandbox.sizeSlug. Ordered smallest-to-largest; no + // pagination. + ListSandboxSizes(context.Context) (*HostedAgentSandboxSizesResponse, *Response, error) + UploadWorkspace(context.Context, string, *HostedAgentWorkspaceUploadRequest) (*HostedAgentWorkspaceUploadResponse, *Response, error) + DownloadWorkspace(context.Context, string, *HostedAgentWorkspaceDownloadRequest) (*HostedAgentWorkspaceDownload, *Response, error) + + // Large-file (>~50 MiB) staged workspace transfer APIs. Streaming + // UploadWorkspace / DownloadWorkspace remain for smaller payloads. + CreateWorkspaceTransfer(context.Context, string, *HostedAgentWorkspaceTransferCreateRequest) (*HostedAgentWorkspaceTransfer, *Response, error) + CreateWorkspaceTransferPartUploadURLs(context.Context, string, string, *HostedAgentWorkspaceTransferPartUploadURLsRequest) (*HostedAgentWorkspaceTransferPartUploadURLs, *Response, error) + CommitWorkspaceTransfer(context.Context, string, string, *HostedAgentWorkspaceTransferCommitRequest) (*HostedAgentWorkspaceTransfer, *Response, error) + GetWorkspaceTransfer(context.Context, string, string) (*HostedAgentWorkspaceTransfer, *Response, error) + CancelWorkspaceTransfer(context.Context, string, string, *HostedAgentWorkspaceTransferCancelRequest) (*HostedAgentWorkspaceTransferCancelResponse, *Response, error) + + // Checkpoint / fork / rollback (session save points). + CreateCheckpoint(context.Context, string, *HostedAgentCheckpointCreateRequest) (*HostedAgentCheckpoint, *Response, error) + ListCheckpoints(context.Context, string, *HostedAgentCheckpointListOptions) (*HostedAgentCheckpointsListResponse, *Response, error) + GetCheckpoint(context.Context, string, string) (*HostedAgentCheckpoint, *Response, error) + DeleteCheckpoint(context.Context, string, string) (*HostedAgentCheckpointDeleteResponse, *Response, error) + ForkSession(context.Context, string, *HostedAgentForkSessionRequest) (*HostedAgentForkSessionResponse, *Response, error) + RollbackToCheckpoint(context.Context, string, string) (*HostedAgentSession, *Response, error) + + // Environments (immutable team-scoped environment definitions). Routes + // live under /v2/agents/configs. Prefer the Environment* methods; the + // AgentConfig* names remain as compatible aliases. + ListEnvironments(context.Context, *HostedEnvironmentListOptions) (*HostedEnvironmentsListResponse, *Response, error) + GetEnvironment(context.Context, string) (*HostedEnvironment, *Response, error) + CreateEnvironment(context.Context, *HostedEnvironmentCreateRequest) (*HostedEnvironment, *Response, error) + DeleteEnvironment(context.Context, string) (*Response, error) + ListEnvironmentSessions(context.Context, string, *HostedAgentSessionListOptions) (*HostedAgentSessionsListResponse, *Response, error) + // Prefer ListEnvironments / GetEnvironment / CreateEnvironment / + // DeleteEnvironment / ListEnvironmentSessions. + ListAgentConfigs(context.Context, *HostedAgentConfigListOptions) (*HostedAgentConfigsListResponse, *Response, error) + GetAgentConfig(context.Context, string) (*HostedAgentConfig, *Response, error) + CreateAgentConfig(context.Context, *HostedAgentConfigCreateRequest) (*HostedAgentConfig, *Response, error) + DeleteAgentConfig(context.Context, string) (*Response, error) + ListAgentConfigSessions(context.Context, string, *HostedAgentSessionListOptions) (*HostedAgentSessionsListResponse, *Response, error) + + // Team custom templates (BYOA). Routes live under /v2/agents/templates. + ListTemplates(context.Context, *HostedAgentTemplateListOptions) (*HostedAgentTemplatesListResponse, *Response, error) + CreateTemplate(context.Context, *HostedAgentTemplateCreateRequest) (*HostedAgentTemplate, *Response, error) + GetTemplate(context.Context, string) (*HostedAgentTemplate, *Response, error) + UpdateTemplate(context.Context, string, *HostedAgentTemplateUpdateRequest) (*HostedAgentTemplate, *Response, error) + DeleteTemplate(context.Context, string) (*HostedAgentTemplateDeleteResponse, *Response, error) + ListTemplateBuilds(context.Context, string, *HostedAgentTemplateBuildListOptions) (*HostedAgentTemplateBuildsListResponse, *Response, error) + GetTemplateBuild(context.Context, string, string) (*HostedAgentTemplateBuild, *Response, error) + GetTemplateBuildLogs(context.Context, string, string) (*HostedAgentTemplateBuildLogs, *Response, error) +} + +// HostedAgentsServiceOp handles communication with Hosted Agents session methods. +type HostedAgentsServiceOp struct { + client *Client +} + +var _ HostedAgentsService = &HostedAgentsServiceOp{} + +// HostedAgentKind identifies the agent runtime provisioned for a session. +type HostedAgentKind string + +const ( + HostedAgentKindUnspecified HostedAgentKind = "AGENT_KIND_UNSPECIFIED" + HostedAgentKindClaudeCode HostedAgentKind = "AGENT_KIND_CLAUDE_CODE" + HostedAgentKindOpenCode HostedAgentKind = "AGENT_KIND_OPENCODE" + HostedAgentKindCodexCLI HostedAgentKind = "AGENT_KIND_CODEX_CLI" + HostedAgentKindCursorCLI HostedAgentKind = "AGENT_KIND_CURSOR_CLI" + // HostedAgentKindOpenAICodex is the OpenAI Agents API sandbox-provider kind + // (adapter codex-agentapi). Attach bridges to OpenAI, not DO's SSE stream. + HostedAgentKindOpenAICodex HostedAgentKind = "AGENT_KIND_OPENAI_CODEX" + HostedAgentKindNone HostedAgentKind = "AGENT_KIND_NONE" + HostedAgentKindCustom HostedAgentKind = "AGENT_KIND_CUSTOM" +) + +// HostedAgentSessionStatus is the lifecycle status of a hosted agent session. +type HostedAgentSessionStatus string + +const ( + HostedAgentSessionStatusUnspecified HostedAgentSessionStatus = "SESSION_STATUS_UNSPECIFIED" + HostedAgentSessionStatusProvisioning HostedAgentSessionStatus = "SESSION_STATUS_PROVISIONING" + HostedAgentSessionStatusReady HostedAgentSessionStatus = "SESSION_STATUS_READY" + HostedAgentSessionStatusDetached HostedAgentSessionStatus = "SESSION_STATUS_DETACHED" + HostedAgentSessionStatusDestroying HostedAgentSessionStatus = "SESSION_STATUS_DESTROYING" + HostedAgentSessionStatusDestroyed HostedAgentSessionStatus = "SESSION_STATUS_DESTROYED" + HostedAgentSessionStatusFailed HostedAgentSessionStatus = "SESSION_STATUS_FAILED" + HostedAgentSessionStatusPaused HostedAgentSessionStatus = "SESSION_STATUS_PAUSED" +) + +// HostedAgentSessionPauseReason is why a paused session was paused. The API +// reserves the right to add reasons, so an unrecognized value is an opaque +// reason string rather than an error: compare against the constants below to +// recognize a reason, but pass anything else through as-is. +type HostedAgentSessionPauseReason string + +const ( + // HostedAgentSessionPauseReasonManual is a pause requested through PauseSession. + HostedAgentSessionPauseReasonManual HostedAgentSessionPauseReason = "manual" + // HostedAgentSessionPauseReasonIdle is an automatic pause after inactivity. + HostedAgentSessionPauseReasonIdle HostedAgentSessionPauseReason = "idle" + // HostedAgentSessionPauseReasonZeroBalance is a pause by the prepayment gate. + // It is the only reason the server auto-resumes from, and only for sessions + // with ResumeOnTopoff set. + HostedAgentSessionPauseReasonZeroBalance HostedAgentSessionPauseReason = "zero_balance" + // Deprecated: Use HostedAgentSessionPauseReasonZeroBalance. Servers now return + // "zero_balance"; this constant is kept for backward compatibility with older API responses. + HostedAgentSessionPauseReasonLowBalance HostedAgentSessionPauseReason = "low_balance" +) + +// HostedAgentProviderAuthState tracks OAuth authorization for an external provider. +type HostedAgentProviderAuthState string + +const ( + HostedAgentProviderAuthStateUnspecified HostedAgentProviderAuthState = "PROVIDER_AUTH_STATE_UNSPECIFIED" + HostedAgentProviderAuthStateNone HostedAgentProviderAuthState = "PROVIDER_AUTH_STATE_NONE" + HostedAgentProviderAuthStatePending HostedAgentProviderAuthState = "PROVIDER_AUTH_STATE_PENDING" + HostedAgentProviderAuthStateAuthorized HostedAgentProviderAuthState = "PROVIDER_AUTH_STATE_AUTHORIZED" + HostedAgentProviderAuthStateExpired HostedAgentProviderAuthState = "PROVIDER_AUTH_STATE_EXPIRED" +) + +// HostedAgentRunState is the lifecycle state of a single agent run. +type HostedAgentRunState string + +const ( + HostedAgentRunStateUnspecified HostedAgentRunState = "RUN_STATE_UNSPECIFIED" + HostedAgentRunStateQueued HostedAgentRunState = "RUN_STATE_QUEUED" + HostedAgentRunStateRunning HostedAgentRunState = "RUN_STATE_RUNNING" + HostedAgentRunStateAwaitingHITL HostedAgentRunState = "RUN_STATE_AWAITING_HITL" + HostedAgentRunStatePaused HostedAgentRunState = "RUN_STATE_PAUSED" + HostedAgentRunStateCompleted HostedAgentRunState = "RUN_STATE_COMPLETED" + HostedAgentRunStateFailed HostedAgentRunState = "RUN_STATE_FAILED" +) + +// HostedAgentHITLActionKind classifies a human-in-the-loop approval request. +type HostedAgentHITLActionKind string + +const ( + HostedAgentHITLActionUnspecified HostedAgentHITLActionKind = "HITL_ACTION_KIND_UNSPECIFIED" + HostedAgentHITLActionBash HostedAgentHITLActionKind = "HITL_ACTION_BASH" + HostedAgentHITLActionFileWriteOutsideWorkspace HostedAgentHITLActionKind = "HITL_ACTION_FILE_WRITE_OUTSIDE_WORKSPACE" + HostedAgentHITLActionGitHubCommitPush HostedAgentHITLActionKind = "HITL_ACTION_GITHUB_COMMIT_PUSH" + HostedAgentHITLActionGitHubCreatePR HostedAgentHITLActionKind = "HITL_ACTION_GITHUB_CREATE_PR" + HostedAgentHITLActionGitHubBranchDelete HostedAgentHITLActionKind = "HITL_ACTION_GITHUB_BRANCH_DELETE" + HostedAgentHITLActionGitHubForcePush HostedAgentHITLActionKind = "HITL_ACTION_GITHUB_FORCE_PUSH" +) + +// HostedAgentHITLOutcome is the user's decision on a HITL request. +type HostedAgentHITLOutcome string + +const ( + HostedAgentHITLOutcomeUnspecified HostedAgentHITLOutcome = "HITL_OUTCOME_UNSPECIFIED" + HostedAgentHITLOutcomeApprove HostedAgentHITLOutcome = "HITL_OUTCOME_APPROVE" + HostedAgentHITLOutcomeReject HostedAgentHITLOutcome = "HITL_OUTCOME_REJECT" + HostedAgentHITLOutcomeDefer HostedAgentHITLOutcome = "HITL_OUTCOME_DEFER" +) + +// HostedAgentResolutionSource records where a HITL decision originated. +type HostedAgentResolutionSource string + +const ( + HostedAgentResolutionSourceUnspecified HostedAgentResolutionSource = "RESOLUTION_SOURCE_UNSPECIFIED" + HostedAgentResolutionSourceInlineKeystroke HostedAgentResolutionSource = "RESOLUTION_SOURCE_INLINE_KEYSTROKE" + HostedAgentResolutionSourceOutOfBand HostedAgentResolutionSource = "RESOLUTION_SOURCE_OUT_OF_BAND" +) + +// HostedAgentRunFailureCode classifies a failed run. +type HostedAgentRunFailureCode string + +const ( + HostedAgentRunFailureCodeUnspecified HostedAgentRunFailureCode = "RUN_FAILURE_CODE_UNSPECIFIED" + HostedAgentRunFailureCodeModelError HostedAgentRunFailureCode = "RUN_FAILURE_CODE_MODEL_ERROR" + HostedAgentRunFailureCodeModelTimeout HostedAgentRunFailureCode = "RUN_FAILURE_CODE_MODEL_TIMEOUT" + HostedAgentRunFailureCodeToolError HostedAgentRunFailureCode = "RUN_FAILURE_CODE_TOOL_ERROR" + HostedAgentRunFailureCodeSandboxLost HostedAgentRunFailureCode = "RUN_FAILURE_CODE_SANDBOX_LOST" + HostedAgentRunFailureCodeHITLRejected HostedAgentRunFailureCode = "RUN_FAILURE_CODE_HITL_REJECTED" + HostedAgentRunFailureCodeBudgetExceeded HostedAgentRunFailureCode = "RUN_FAILURE_CODE_BUDGET_EXCEEDED" + HostedAgentRunFailureCodeInternal HostedAgentRunFailureCode = "RUN_FAILURE_CODE_INTERNAL" +) + +// HostedAgentEventKind is the SSE event discriminator for session stream +// payloads. The values are the canonical SPI event type names (dot-separated) +// emitted on the wire's `type` field — NOT proto enum names. They mirror the +// spi.EventType constants owned by the hosted-agents stack. +type HostedAgentEventKind string + +const ( + HostedAgentEventKindUnspecified HostedAgentEventKind = "" + HostedAgentEventKindRunStarted HostedAgentEventKind = "run.started" + HostedAgentEventKindTokenChunk HostedAgentEventKind = "run.token_delta" + HostedAgentEventKindToolCallStarted HostedAgentEventKind = "run.tool_call_started" + HostedAgentEventKindToolCallCompleted HostedAgentEventKind = "run.tool_call_completed" + HostedAgentEventKindHITLRequested HostedAgentEventKind = "run.human_input_requested" + HostedAgentEventKindHITLResolved HostedAgentEventKind = "run.human_input_received" + HostedAgentEventKindRunCompleted HostedAgentEventKind = "run.completed" + HostedAgentEventKindRunFailed HostedAgentEventKind = "run.failed" + HostedAgentEventKindRunPaused HostedAgentEventKind = "run.paused" + HostedAgentEventKindRunResumed HostedAgentEventKind = "run.resumed" + HostedAgentEventKindSessionUpdated HostedAgentEventKind = "session.updated" + HostedAgentEventKindRunStateCheckpointed HostedAgentEventKind = "run.state_checkpointed" + HostedAgentEventKindRunHandoff HostedAgentEventKind = "run.handoff" + HostedAgentEventKindRunUsageRecorded HostedAgentEventKind = "run.usage_recorded" + HostedAgentEventKindRunSandboxAllocated HostedAgentEventKind = "run.sandbox_allocated" + HostedAgentEventKindRunSandboxReleased HostedAgentEventKind = "run.sandbox_released" + HostedAgentEventKindRunCostAccrued HostedAgentEventKind = "run.cost_accrued" + HostedAgentEventKindRunLog HostedAgentEventKind = "run.log" + + // HostedAgentEventKindStreamState is a transport control frame, not an agent + // event: it reports the health of the SSE connection itself. Only the + // data-plane events endpoint emits it. It arrives in the same envelope as an + // event, but only SessionID, At and Payload are meaningful — decode Payload + // with HostedAgentStreamState. Renderers should skip it rather than display + // it as session activity. + HostedAgentEventKindStreamState HostedAgentEventKind = "stream.state" +) + +// HostedAgentStreamState is the payload of a HostedAgentEventKindStreamState +// frame: the current health of the SSE connection. +type HostedAgentStreamState struct { + State HostedAgentStreamStateValue `json:"state"` + Cursor string `json:"cursor,omitempty"` +} + +// HostedAgentStreamStateValue enumerates the stream health states. +type HostedAgentStreamStateValue string + +const ( + // HostedAgentStreamStateLive means events are being delivered contiguously. + HostedAgentStreamStateLive HostedAgentStreamStateValue = "live" + // HostedAgentStreamStateCatchingUp means the connection is replaying recent + // history before it joins the live tail. + HostedAgentStreamStateCatchingUp HostedAgentStreamStateValue = "catching_up" + // HostedAgentStreamStateDegraded means delivery continues with reduced + // guarantees (the server fell back to polling). + HostedAgentStreamStateDegraded HostedAgentStreamStateValue = "degraded" + // HostedAgentStreamStateSuperseded means a newer connection from the same + // device took over. The server closes this stream; the client should stop + // rather than reconnect, or the two connections will evict each other. + HostedAgentStreamStateSuperseded HostedAgentStreamStateValue = "superseded" +) + +// HostedAgentSessionOriginProduct identifies the product workflow that created +// a session. Wire values match harness SessionOrigin.product. +type HostedAgentSessionOriginProduct string + +const ( + HostedAgentSessionOriginProductDirect HostedAgentSessionOriginProduct = "direct" + HostedAgentSessionOriginProductSimulation HostedAgentSessionOriginProduct = "simulation" + HostedAgentSessionOriginProductEvaluation HostedAgentSessionOriginProduct = "evaluation" +) + +// HostedAgentSessionOriginRequest is the product-provenance claim on create. +// Omission creates a verified direct session. Simulation and evaluation require +// resource_id; direct forbids it. verified is server-assigned and must not be +// sent on create (see HostedAgentSessionOrigin). +type HostedAgentSessionOriginRequest struct { + Product HostedAgentSessionOriginProduct `json:"product"` + ResourceID string `json:"resource_id,omitempty"` +} + +// HostedAgentSessionOrigin is server-returned product-workflow provenance. +// Verified is true for direct sessions and for product claims established +// through a trusted adapter. +type HostedAgentSessionOrigin struct { + Product HostedAgentSessionOriginProduct `json:"product"` + ResourceID string `json:"resource_id,omitempty"` + Verified bool `json:"verified"` +} + +// HostedAgentSession is a provisioned hosted-agent sandbox session. +type HostedAgentSession struct { + SessionID string `json:"session_id"` + Name string `json:"name,omitempty"` + AgentKind HostedAgentKind `json:"agent_kind"` + Status HostedAgentSessionStatus `json:"status"` + CreatedAt Timestamp `json:"created_at"` + LastEventAt Timestamp `json:"last_event_at"` + RepoHint string `json:"repo_hint,omitempty"` + ProviderAuth map[string]HostedAgentProviderAuthState `json:"provider_auth,omitempty"` + // Origin is present for newly created sessions (including direct). Older + // sessions may omit it. + Origin *HostedAgentSessionOrigin `json:"origin,omitempty"` + // PauseReason qualifies Status when it is SESSION_STATUS_PAUSED, and is + // omitted otherwise. Unrecognized values are opaque, not errors — see + // HostedAgentSessionPauseReason. + PauseReason HostedAgentSessionPauseReason `json:"pause_reason,omitempty"` + // SandboxID is the sandbox identity for support / debugging. Informational; + // omitted until a sandbox is allocated. + SandboxID string `json:"sandbox_id,omitempty"` + // ResumeOnTopoff is the consent to auto-resume this session when a + // prepayment balance is restored. Set at create (manifest query param or + // config JSON body), toggled afterwards with UpdateSession, and returned on + // get/list so clients can confirm enrollment. Never inherited by forks. + // Omitted when false. + ResumeOnTopoff bool `json:"resume_on_topoff,omitempty"` + // OpenAISessionID is the OpenAI Agents session id (sess_…) linked to this DO + // sandbox for AGENT_KIND_OPENAI_CODEX. Used by attach to bridge to OpenAI; + // omitempty for other agent kinds. + OpenAISessionID string `json:"openai_session_id,omitempty"` + // OpenAIEnvironmentID is captured from the resolved CODEX_ENVIRONMENT_ID + // guest env value at create. Non-secret correlation metadata. + OpenAIEnvironmentID string `json:"openai_environment_id,omitempty"` + // ParentSessionID is set on forked child sessions; empty/omitted for roots. + ParentSessionID string `json:"parent_session_id,omitempty"` + // ForkID is a branch label on forked sessions; empty/omitted for roots. + ForkID string `json:"fork_id,omitempty"` + // ConfigID is the durable Environment UUID the session was created from + // (inline manifests persist an environment too, named after the session). + // Returned on create/get/list; omitted for sessions created before + // environment references were stored. Historically called Agent Config. + ConfigID string `json:"config_id,omitempty"` + // Warnings carries non-fatal create-time advisories from the server + // (manifest parse + policy fidelity). Populated on the create response + // only; omitted on get/list. + Warnings []string `json:"warnings,omitempty"` +} + +// HostedAgentRun represents a single execution within a session. +type HostedAgentRun struct { + RunID string `json:"run_id"` + SessionID string `json:"session_id"` + State HostedAgentRunState `json:"state"` + StartedAt Timestamp `json:"started_at"` + EndedAt *Timestamp `json:"ended_at,omitempty"` + CumulativeCostMicros int64 `json:"cumulative_cost_micros"` +} + +// HostedAgentHITLRequest is a pending human-in-the-loop approval. +type HostedAgentHITLRequest struct { + RequestID string `json:"request_id"` + SessionID string `json:"session_id"` + RunID string `json:"run_id"` + Action HostedAgentHITLActionKind `json:"action"` + Details map[string]interface{} `json:"details,omitempty"` + Workdir string `json:"workdir,omitempty"` + Deadline *Timestamp `json:"deadline,omitempty"` +} + +// HostedAgentHITLDecision records a resolved HITL request. +type HostedAgentHITLDecision struct { + RequestID string `json:"request_id"` + Outcome HostedAgentHITLOutcome `json:"outcome"` + Actor string `json:"actor,omitempty"` + At Timestamp `json:"at"` + Reason string `json:"reason,omitempty"` +} + +// HostedAgentEvent is one SSE payload from a session stream (see StreamSession). +// +// The server serializes the SPI canonical event envelope, whose JSON shape +// differs from this struct's field names: the discriminator is `type` (not +// `kind`), the per-kind body is `data` (not `payload`), and the timestamp is +// `timestamp` (not `at`). UnmarshalJSON maps that wire shape onto these fields, +// so callers read Kind/Payload/At directly. +type HostedAgentEvent struct { + EventID string + SessionID string + RunID string + Seq uint64 + At Timestamp + Kind HostedAgentEventKind + Payload json.RawMessage + + // SourceEventID is the native event id from the agent runtime before + // canonical mapping (Event.source_event_id). Empty when the runtime does + // not supply stable ids or the server does not forward it. + SourceEventID string + // SourceEventType is the native event type label from the agent runtime + // (e.g. codex's "item/agentMessage/delta"). Empty when not forwarded. + SourceEventType string + // SourceRaw is the exact native event bytes the in-sandbox adapter + // captured before canonical mapping (Event.source_raw) — for codex, one + // JSON-RPC frame as read off the app-server transport. Only present when + // the stream was opened with HostedAgentSessionStreamOptions.IncludeRaw + // and the server retained the bytes; base64 on the wire (JSON []byte). + SourceRaw []byte +} + +// hostedAgentEventWire is the on-the-wire SPI canonical event envelope. +type hostedAgentEventWire struct { + EventID string `json:"event_id"` + RunID string `json:"run_id"` + SessionID string `json:"session_id"` + Timestamp Timestamp `json:"timestamp"` + Seq uint64 `json:"seq"` + SourceEventID string `json:"source_event_id,omitempty"` + SourceEventType string `json:"source_event_type,omitempty"` + SourceRaw []byte `json:"source_raw,omitempty"` + Type HostedAgentEventKind `json:"type"` + Data json.RawMessage `json:"data"` +} + +// UnmarshalJSON decodes the SPI canonical event wire shape. +func (e *HostedAgentEvent) UnmarshalJSON(b []byte) error { + var w hostedAgentEventWire + if err := json.Unmarshal(b, &w); err != nil { + return err + } + e.EventID = w.EventID + e.RunID = w.RunID + e.SessionID = w.SessionID + e.Seq = w.Seq + e.At = w.Timestamp + e.Kind = w.Type + e.Payload = w.Data + e.SourceEventID = w.SourceEventID + e.SourceEventType = w.SourceEventType + e.SourceRaw = w.SourceRaw + return nil +} + +// HostedAgentSessionCreateRequest is the body for POST /v2/agents/sessions. +type HostedAgentSessionCreateRequest struct { + AgentKind HostedAgentKind `json:"agent_kind"` + RepoHint string `json:"repo_hint,omitempty"` + IdleTimeoutSeconds int64 `json:"idle_timeout_seconds,omitempty"` + // Origin claims product-workflow provenance. Omit for a verified direct + // session. Simulation/evaluation require resource_id. + Origin *HostedAgentSessionOriginRequest `json:"origin,omitempty"` +} + +// HostedAgentSessionFromConfigRequest creates a session from an existing +// immutable Environment (POST /v2/agents/sessions with a JSON body). Name is +// required and becomes the new session name; ConfigID must be the UUID of an +// Environment owned by the caller's team. +// Prefer HostedAgentSessionFromEnvironmentRequest; this name remains for +// compatibility. The JSON field remains "config_id" on the wire. +type HostedAgentSessionFromConfigRequest struct { + Name string `json:"name"` + ConfigID string `json:"config_id"` + // ResumeOnTopoff opts the new session in to automatic resumption when the + // team's prepayment balance is restored. Session-scoped, not + // environment-scoped: the referenced Environment never confers it, so one + // shared Environment cannot enrol every session created from it. Omitted + // when false, which leaves the server default (also false). + ResumeOnTopoff bool `json:"resume_on_topoff,omitempty"` +} + +// HostedAgentSessionFromEnvironmentRequest is the advertised alias for +// HostedAgentSessionFromConfigRequest. +type HostedAgentSessionFromEnvironmentRequest = HostedAgentSessionFromConfigRequest + +// HostedAgentSessionUpdateRequest is the body for PATCH +// /v2/agents/sessions/{session_id}: an allowlisted subset of session-scoped +// settings, never an agents.yaml or Agent Config mutation. The server rejects +// unknown top-level keys, and rejects an empty object, so every field is a +// pointer: nil omits it and leaves the server-side value untouched. +type HostedAgentSessionUpdateRequest struct { + // ResumeOnTopoff sets whether the session opts in to automatic resumption + // once the team's prepayment balance is restored after a low-balance pause. + // Nil leaves the current consent unchanged. Revoking takes effect + // immediately for later sweeps; granting it does not by itself resume a + // session that is already paused. + ResumeOnTopoff *bool `json:"resume_on_topoff,omitempty"` +} + +// empty reports whether the request would serialize to {}, which the server +// rejects with a 400. Checked client-side so a no-op call fails locally with a +// useful message instead of spending a round-trip to earn a certain error. +func (r *HostedAgentSessionUpdateRequest) empty() bool { + return r == nil || r.ResumeOnTopoff == nil +} + +// HostedAgentManifestCreateOptions configures CreateSessionFromManifest. +// OpenAISessionID is sent as the openai_session_id query parameter (not in the +// YAML body). harness-api persists it for AGENT_KIND_OPENAI_CODEX attach +// correlation. See docs/design/openai-sandbox-provider.md. +type HostedAgentManifestCreateOptions struct { + OpenAISessionID string `url:"openai_session_id,omitempty"` + // ResumeOnTopoff opts the new session in to automatic resumption when the + // team's prepayment balance is restored after a low-balance pause. It is a + // query parameter rather than an agents.yaml field because that document is + // immutable and shared through Agent Configs, while spending consent has to + // stay per-session. Omitted when false (the server default). + ResumeOnTopoff bool `url:"resume_on_topoff,omitempty"` +} + +// HostedAgentSessionListOptions specifies optional list filters. +type HostedAgentSessionListOptions struct { + PageToken string `url:"page_token,omitempty"` + PageSize int `url:"page_size,omitempty"` + Status HostedAgentSessionStatus `url:"status,omitempty"` + Name string `url:"name,omitempty"` + // ParentSessionID lists child (forked) sessions of the given parent. + ParentSessionID string `url:"parent_session_id,omitempty"` +} + +// HostedAgentSessionsListResponse is returned by GET /v2/agents/sessions. +type HostedAgentSessionsListResponse struct { + Sessions []HostedAgentSession `json:"sessions"` + NextPageToken string `json:"next_page_token"` +} + +// HostedAgentSessionStreamOptions configures the session SSE stream. +// +// ReplayOnly selects between the two modes StreamSession can open on the events +// endpoint (see StreamSession); Before and Limit page backwards through history +// within the replay-only mode. +type HostedAgentSessionStreamOptions struct { + // ReplayFrom is the resume cursor: the id of the last event the caller + // already rendered. On the live stream it is sent as Last-Event-ID; on a + // ReplayOnly read it is sent as the replay_from query parameter. + ReplayFrom string + // ReplayOnly reads the stored event history and ends the stream at the last + // stored event instead of holding the connection open for live events. + ReplayOnly bool + + // Before turns the request into a single backward page of durable + // history: the events strictly older than this event id, oldest-first, + // after which the stream closes without live events. Set it to the + // oldest event id already held. Requires ReplayOnly, since a live + // attach cannot start in the past. + // + // A cursorless replay only covers the newest window of a session's + // history (the server's replay budget), so walking backwards from its + // oldest event is how older history is read. HasMore reports whether + // the walk can continue. + Before string + + // Limit caps the events in one history page. Only meaningful with + // Before. Zero leaves the server's default (200); the server also caps + // any request at its replay budget. + Limit int + + // IncludeRaw asks the server to include each event's native source bytes + // (HostedAgentEvent.SourceRaw) alongside the canonical payload. Raw + // payloads meaningfully fatten every event, so this is opt-in; consumers + // that don't translate native protocols should leave it off. + IncludeRaw bool +} + +// HostedAgentSendInputRequest is the body for POST .../input. +type HostedAgentSendInputRequest struct { + Text string `json:"text"` + + // SourceRaw optionally carries the client's exact native protocol frame + // this input was extracted from — for codex, the TUI's turn/start + // JSON-RPC message with its full params. The in-sandbox adapter uses it + // as the template for the turn it drives, so client intent beyond plain + // text (input items, model, effort, approval policy, ...) survives the + // text reduction. Only meaningful when the caller speaks the session's + // own agent protocol; Text stays required either way. Inbound + // counterpart of HostedAgentEvent.SourceRaw. Base64 on the wire per the + // proto bytes JSON mapping — encoding/json does that for []byte. + SourceRaw []byte `json:"source_raw,omitempty"` +} + +// HostedAgentSendInputResponse is returned by POST .../input. +type HostedAgentSendInputResponse struct { + RunID string `json:"run_id"` +} + +// HostedAgentRelayRequest is the body for POST .../request: one native +// agent-protocol request frame, forwarded to the session's agent verbatim. +// +// Where SendInput carries the one message with a canonical meaning ("the user +// said something"), this carries everything else a client that speaks the +// session's own protocol needs to ask — for codex, the requests behind +// interrupts, slash commands, and model pickers. The control plane never +// parses the frame; only the in-sandbox adapter decides what is safe to +// forward. +type HostedAgentRelayRequest struct { + // SourceRaw is the caller's native protocol request frame — for codex, a + // single JSON-RPC request object carrying the caller's own id. Named to + // match SendInput's field: both mean "my own frame, verbatim". Base64 on + // the wire per the proto bytes JSON mapping. + SourceRaw []byte `json:"source_raw"` +} + +// HostedAgentRelayResponse is the reply to POST .../request. +type HostedAgentRelayResponse struct { + // SourceRaw is the agent's reply frame, addressed to the caller's own + // request id but otherwise verbatim. A protocol-level failure (a JSON-RPC + // error object) is a normal reply and arrives here rather than as an HTTP + // error. + // + // Empty means the in-sandbox adapter declined to forward the method. + // Callers must answer their own caller on that case rather than waiting + // for something that will not come. + SourceRaw []byte `json:"source_raw,omitempty"` +} + +// HostedAgentResolveHITLRequest is the body for POST .../hitl/{requestID}. +type HostedAgentResolveHITLRequest struct { + Outcome HostedAgentHITLOutcome `json:"outcome"` + Reason string `json:"reason,omitempty"` + Source HostedAgentResolutionSource `json:"source,omitempty"` + + // SourceRaw is the client's reply in the agent's own protocol, forwarded + // to the in-sandbox agent untouched. It carries what Outcome cannot: an + // elicitation's content, a tool's requested input, a scope beyond this one + // call. Outcome stays required alongside it — it is what the audit trail + // records, and what the agent falls back to when this is absent. + SourceRaw []byte `json:"source_raw,omitempty"` + + // Content is a structured answer for a gated request that asks for DATA + // rather than just a verdict — an MCP *form* elicitation sends a + // requestedSchema and expects an object matching it back (e.g. + // {"site_url": "https://acme.atlassian.net"} for a Jira connection). Its + // keys are the requesting server's, never fixed here. Omitted for plain + // approvals, where Outcome alone is the whole answer. Mirrors + // harness-api's resolveHITLBody.Content; forwarded to the run and never + // persisted (form content can carry user-entered secrets). + Content map[string]any `json:"content,omitempty"` +} + +// HostedAgentProviderAuthStart is returned by POST /v2/agents/auth/{provider}. +// Status is "pending" when the user must still authorize in a browser +// (ConnectURL/PollURL/VerificationCode are set), or "success" when the team is +// already connected (those fields are empty). It is a free-form connect-flow +// status, distinct from the HostedAgentProviderAuthState session field. The +// authorization handle is never exposed: tokens are exchanged server-side at +// session time. +type HostedAgentProviderAuthStart struct { + Provider string `json:"provider"` + Status string `json:"status"` + ConnectURL string `json:"connect_url,omitempty"` + PollURL string `json:"poll_url,omitempty"` + VerificationCode string `json:"verification_code,omitempty"` + ExpiresAt *Timestamp `json:"expires_at,omitempty"` +} + +// HostedAgentProviderAuthPoll is returned by GET +// /v2/agents/auth/{provider}/poll. It reports only whether authorization has +// completed ("pending" or "success"); no secret is returned. +type HostedAgentProviderAuthPoll struct { + Provider string `json:"provider"` + Status string `json:"status"` + ExpiresAt *Timestamp `json:"expires_at,omitempty"` +} + +// HostedAgentSandboxExecRequest is the body for POST .../sandbox/exec. +type HostedAgentSandboxExecRequest struct { + Argv []string `json:"argv"` + Workdir string `json:"workdir,omitempty"` + TimeoutSeconds int64 `json:"timeout_seconds,omitempty"` +} + +// HostedAgentSandboxExecResponse is returned by POST .../sandbox/exec. +type HostedAgentSandboxExecResponse struct { + ExitCode int `json:"exit_code"` + Stdout string `json:"stdout,omitempty"` + Stderr string `json:"stderr,omitempty"` +} + +// HostedAgentSandboxSize is a customer-selectable sandbox (microVM) size from +// GET /v2/agents/sessions/sandbox/sizes. Submit Slug as spec.sandbox.sizeSlug +// on CreateSession; VCPUs, MemoryMB, and the price fields are for display. +type HostedAgentSandboxSize struct { + Slug string `json:"slug,omitempty"` + VCPUs int `json:"vcpus,omitempty"` + MemoryMB int `json:"memory_mb,omitempty"` + PricePerHourUSD float64 `json:"price_per_hour_usd,omitempty"` + PricePerMonthUSD float64 `json:"price_per_month_usd,omitempty"` +} + +// HostedAgentSandboxSizesResponse is returned by ListSandboxSizes. +type HostedAgentSandboxSizesResponse struct { + // Sizes is ordered smallest-to-largest. + Sizes []HostedAgentSandboxSize `json:"sizes"` +} + +// HostedAgentWorkspaceUploadRequest is the input for UploadWorkspace. +type HostedAgentWorkspaceUploadRequest struct { + // Path is the destination resolved inside the workspace root (/workspace). Required. + Path string + // IsArchive indicates Body is a tar archive to extract at Path. + IsArchive bool + // ContentSHA256 is an optional hex SHA-256 digest of the payload, forwarded to the guest for verification. + ContentSHA256 string + // Body is the raw file or tar bytes to upload. Required. + Body io.Reader +} + +// HostedAgentWorkspaceUploadResponse is returned by UploadWorkspace. +type HostedAgentWorkspaceUploadResponse struct { + Path string `json:"path"` + BytesWritten int64 `json:"bytes_written"` +} + +// HostedAgentWorkspaceDownloadRequest is the input for DownloadWorkspace. +type HostedAgentWorkspaceDownloadRequest struct { + // Path is the source resolved inside the workspace root (/workspace). Required. + Path string + // AsArchive tar-streams the directory at Path when true. + AsArchive bool +} + +// HostedAgentWorkspaceDownload is the streaming result of DownloadWorkspace. +// Body strips the trailing integrity footer and verifies SHA-256 of the +// payload: read it to EOF and then Close it. A missing, invalid, or mismatched +// footer is an error. An HTTP X-Content-Sha256 trailer may still be present +// but is best-effort only; the body footer is the source of truth. +type HostedAgentWorkspaceDownload struct { + Body io.ReadCloser + // IsArchive is true when the payload is a tar stream. + IsArchive bool + // SizeBytes is the X-Workspace-Size-Bytes hint (0 when unknown); payload + // size only (excludes the integrity footer). Not a Content-Length. + SizeBytes int64 +} + +// HostedAgentWorkspaceTransferDirection is the direction of a staged transfer. +type HostedAgentWorkspaceTransferDirection string + +const ( + HostedAgentWorkspaceTransferDirectionUpload HostedAgentWorkspaceTransferDirection = "upload" + HostedAgentWorkspaceTransferDirectionDownload HostedAgentWorkspaceTransferDirection = "download" +) + +// HostedAgentWorkspaceTransferStatus is the status of a staged transfer. +type HostedAgentWorkspaceTransferStatus string + +const ( + HostedAgentWorkspaceTransferStatusPending HostedAgentWorkspaceTransferStatus = "pending" + HostedAgentWorkspaceTransferStatusInProgress HostedAgentWorkspaceTransferStatus = "in_progress" + HostedAgentWorkspaceTransferStatusCompleted HostedAgentWorkspaceTransferStatus = "completed" + HostedAgentWorkspaceTransferStatusFailed HostedAgentWorkspaceTransferStatus = "failed" +) + +// HostedAgentWorkspaceTransferCreateRequest starts a large upload or download. +// Upload uses IsArchive; download uses AsArchive (per OHS HTTP contract). +type HostedAgentWorkspaceTransferCreateRequest struct { + Direction HostedAgentWorkspaceTransferDirection `json:"direction"` + Path string `json:"path"` + IsArchive bool `json:"is_archive,omitempty"` + AsArchive bool `json:"as_archive,omitempty"` + SizeBytes int64 `json:"size_bytes,omitempty"` + SHA256 string `json:"sha256,omitempty"` +} + +// HostedAgentWorkspaceTransfer is returned by create/commit/get transfer calls. +type HostedAgentWorkspaceTransfer struct { + TransferID string `json:"transfer_id"` + Direction HostedAgentWorkspaceTransferDirection `json:"direction,omitempty"` + Status HostedAgentWorkspaceTransferStatus `json:"status"` + UploadID string `json:"upload_id,omitempty"` + PartSize int64 `json:"part_size,omitempty"` + ExpiresAt *Timestamp `json:"expires_at,omitempty"` + SizeBytes int64 `json:"size_bytes,omitempty"` + BytesWritten int64 `json:"bytes_written,omitempty"` + SHA256 string `json:"sha256,omitempty"` + DownloadURL string `json:"download_url,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` +} + +// HostedAgentWorkspaceTransferPartUploadURLsRequest requests presigned URLs for +// one or more upload parts. PartNumbers are 1-based. +type HostedAgentWorkspaceTransferPartUploadURLsRequest struct { + PartNumbers []int `json:"part_numbers"` +} + +// HostedAgentWorkspaceTransferPartUploadURL is one entry in a part-upload-urls response. +// PUT the part bytes directly to UploadURL (not through OHS). +type HostedAgentWorkspaceTransferPartUploadURL struct { + PartNumber int `json:"part_number"` + UploadURL string `json:"upload_url"` +} + +// HostedAgentWorkspaceTransferPartUploadURLs is returned by CreateWorkspaceTransferPartUploadURLs. +type HostedAgentWorkspaceTransferPartUploadURLs struct { + PartURLs []HostedAgentWorkspaceTransferPartUploadURL `json:"part_urls"` +} + +// HostedAgentWorkspaceTransferCommitRequest finalizes an upload after all parts are PUT. +type HostedAgentWorkspaceTransferCommitRequest struct { + SHA256 string `json:"sha256,omitempty"` +} + +// HostedAgentWorkspaceTransferCancelRequest aborts an in-flight transfer. +type HostedAgentWorkspaceTransferCancelRequest struct { + Reason string `json:"reason,omitempty"` +} + +// HostedAgentWorkspaceTransferCancelResponse is returned by CancelWorkspaceTransfer. +type HostedAgentWorkspaceTransferCancelResponse struct { + TransferID string `json:"transfer_id"` + Aborted bool `json:"aborted"` + Status HostedAgentWorkspaceTransferStatus `json:"status"` +} + +type hostedAgentSessionRoot struct { + Session *HostedAgentSession `json:"session"` +} + +// CreateSession provisions a new hosted agent session. +func (s *HostedAgentsServiceOp) CreateSession(ctx context.Context, create *HostedAgentSessionCreateRequest) (*HostedAgentSession, *Response, error) { + if create == nil { + return nil, nil, errors.New("hosted agents: create request is required") + } + if create.AgentKind == "" || create.AgentKind == HostedAgentKindUnspecified { + return nil, nil, errors.New("hosted agents: agent_kind is required") + } + req, err := s.client.NewRequest(ctx, http.MethodPost, hostedAgentsSessionsBasePath, create) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentSessionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Session == nil { + return nil, resp, errors.New("hosted agents: create session returned no session") + } + return root.Session, resp, nil +} + +func (s *HostedAgentsServiceOp) CreateSessionFromManifest(ctx context.Context, manifest []byte, opt *HostedAgentManifestCreateOptions) (*HostedAgentSession, *Response, error) { + if len(bytes.TrimSpace(manifest)) == 0 { + return nil, nil, errors.New("hosted agents: manifest is required") + } + path, err := addOptions(hostedAgentsSessionsBasePath, opt) + if err != nil { + return nil, nil, err + } + req, err := s.newCreateSessionPostRequest(ctx, path, bytes.NewReader(manifest), hostedAgentManifestMediaType) + if err != nil { + return nil, nil, err + } + return s.doCreateSession(ctx, req) +} + +// CreateSessionFromEnvironment provisions a session from a durable Environment. +// The JSON {name, config_id} body selects the environment-backed create path on +// the server, which reloads the Environment's stored manifest and resolves its +// declared credentials into the new session. +func (s *HostedAgentsServiceOp) CreateSessionFromEnvironment(ctx context.Context, create *HostedAgentSessionFromEnvironmentRequest) (*HostedAgentSession, *Response, error) { + return s.CreateSessionFromConfig(ctx, create) +} + +// CreateSessionFromConfig provisions a session from a durable Environment. +// Prefer CreateSessionFromEnvironment; this name remains for compatibility. +// The JSON {name, config_id} body selects the environment-backed create path on +// the server, which reloads the Environment's stored manifest and resolves its +// declared credentials into the new session. +func (s *HostedAgentsServiceOp) CreateSessionFromConfig(ctx context.Context, create *HostedAgentSessionFromConfigRequest) (*HostedAgentSession, *Response, error) { + if create == nil { + return nil, nil, errors.New("hosted agents: create request is required") + } + if create.Name == "" { + return nil, nil, errors.New("hosted agents: name is required") + } + if create.ConfigID == "" { + return nil, nil, errors.New("hosted agents: config_id is required") + } + req, err := s.client.NewRequest(ctx, http.MethodPost, hostedAgentsSessionsBasePath, create) + if err != nil { + return nil, nil, err + } + return s.doCreateSession(ctx, req) +} + +func (s *HostedAgentsServiceOp) doCreateSession(ctx context.Context, req *http.Request) (*HostedAgentSession, *Response, error) { + root := new(hostedAgentSessionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Session == nil { + return nil, resp, errors.New("hosted agents: create session returned no session") + } + return root.Session, resp, nil +} + +func (s *HostedAgentsServiceOp) newCreateSessionPostRequest(ctx context.Context, path string, body io.Reader, contentType string) (*http.Request, error) { + u, err := s.client.BaseURL.Parse(path) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + req.Header.Set("Accept", mediaType) + req.Header.Set("User-Agent", s.client.UserAgent) + return req, nil +} + +// ListSessions returns sessions visible to the caller's team. +// +// The server omits simulation and evaluation sessions from the list so customer +// surfaces stay free of product-owned internal runs. GetSession by session_id +// still returns those sessions for the owning product workflow. +func (s *HostedAgentsServiceOp) ListSessions(ctx context.Context, opt *HostedAgentSessionListOptions) (*HostedAgentSessionsListResponse, *Response, error) { + path, err := addOptions(hostedAgentsSessionsBasePath, opt) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentSessionsListResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// GetSession returns a single session by ID. +func (s *HostedAgentsServiceOp) GetSession(ctx context.Context, sessionID string) (*HostedAgentSession, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + path := fmt.Sprintf(hostedAgentSessionByIDPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentSessionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Session == nil { + return nil, resp, errors.New("hosted agents: get session returned no session") + } + return root.Session, resp, nil +} + +// UpdateSession patches session-scoped settings and returns the updated +// session. Only the fields set on update are sent; the rest are left alone. +// A session in a terminal state (destroyed, failed) cannot be patched and the +// API answers 409. +func (s *HostedAgentsServiceOp) UpdateSession(ctx context.Context, sessionID string, update *HostedAgentSessionUpdateRequest) (*HostedAgentSession, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if update.empty() { + return nil, nil, errors.New("hosted agents: update request must set at least one field") + } + path := fmt.Sprintf(hostedAgentSessionByIDPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPatch, path, update) + if err != nil { + return nil, nil, err + } + root := new(hostedAgentSessionRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if root.Session == nil { + return nil, resp, errors.New("hosted agents: update session returned no session") + } + return root.Session, resp, nil +} + +// DestroySession tears down a session. The API returns HTTP 204 on success. +func (s *HostedAgentsServiceOp) DestroySession(ctx context.Context, sessionID string) (*Response, error) { + if sessionID == "" { + return nil, errors.New("hosted agents: session id is required") + } + path := fmt.Sprintf(hostedAgentSessionByIDPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +func (s *HostedAgentsServiceOp) PauseSession(ctx context.Context, sessionID string) (*Response, error) { + if sessionID == "" { + return nil, errors.New("hosted agents: session id is required") + } + path := fmt.Sprintf(hostedAgentSessionPausePath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, struct{}{}) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +func (s *HostedAgentsServiceOp) ResumeSession(ctx context.Context, sessionID string) (*Response, error) { + if sessionID == "" { + return nil, errors.New("hosted agents: session id is required") + } + path := fmt.Sprintf(hostedAgentSessionResumePath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, struct{}{}) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// StreamSession opens the SSE stream for a session. Callers MUST Close the stream. +// +// Both reads are served by the data plane at GET .../sessions/{id}/events; the +// two modes differ in where the stream starts and whether it ends: +// +// - Live (the default) delivers forward-only from the moment of attach, +// preceded by whatever recent history the server decides to replay, and +// holds the connection open. ReplayFrom is sent as the standard +// Last-Event-ID header. +// - ReplayOnly adds ?replay_only=true: the server writes the session's stored +// event history and then ends the stream, so the read terminates on its own. +// ReplayFrom is sent as the replay_from query parameter, since it is an +// explicit pagination cursor here rather than a resume hint. Before and +// Limit page backwards from an event id within this mode; see +// HostedAgentSessionStreamOptions.Before and HasMore. +// +// Both carry HostedAgentEventKindStreamState control frames (see +// HostedAgentStreamState). A replay-only read reports catching_up and then +// simply ends; it never reaches live. +func (s *HostedAgentsServiceOp) StreamSession(ctx context.Context, sessionID string, opt *HostedAgentSessionStreamOptions) (*HostedAgentSessionStream, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + replayOnly := opt != nil && opt.ReplayOnly + cursor := "" + before := "" + limit := 0 + includeRaw := false + if opt != nil { + // The server answers this combination with a 400; rejecting it here + // spends no round trip to learn the same thing. + if opt.Before != "" && !opt.ReplayOnly { + return nil, nil, errors.New("hosted agents: before requires replay only") + } + cursor = opt.ReplayFrom + before = opt.Before + limit = opt.Limit + includeRaw = opt.IncludeRaw + } + + path := fmt.Sprintf(hostedAgentSessionEventsPath, sessionID) + q := url.Values{} + if replayOnly { + q.Set("replay_only", "true") + if cursor != "" { + q.Set("replay_from", cursor) + } + if before != "" { + q.Set("before", before) + } + if limit > 0 { + q.Set("limit", strconv.Itoa(limit)) + } + } + if includeRaw { + q.Set("include_raw", "true") + } + if encoded := q.Encode(); encoded != "" { + path += "?" + encoded + } + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", "text/event-stream") + req.Header.Set("Cache-Control", "no-cache") + req.Header.Set("Connection", "keep-alive") + if !replayOnly && cursor != "" { + req.Header.Set(sseLastEventIDHeader, cursor) + } + + resp, err := s.client.DoStream(ctx, req) + if err != nil { + return nil, resp, err + } + stream := &HostedAgentSessionStream{ + raw: NewSSEReader(resp.Body), + body: resp.Body, + } + stream.raw.OnComment = stream.observeComment + return stream, resp, nil +} + +// SendInput enqueues user text for the in-sandbox agent runtime. +func (s *HostedAgentsServiceOp) SendInput(ctx context.Context, sessionID string, input *HostedAgentSendInputRequest) (*HostedAgentSendInputResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if input == nil { + return nil, nil, errors.New("hosted agents: input is required") + } + if strings.TrimSpace(input.Text) == "" { + return nil, nil, errors.New("hosted agents: text is required") + } + path := fmt.Sprintf(hostedAgentSessionInputPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, input) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentSendInputResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// RelayRequest forwards one native agent-protocol request frame to the +// session's agent and returns its reply verbatim. Blocks on the agent, so it +// is slower than the other session calls; an empty reply means the in-sandbox +// adapter declined the method. +func (s *HostedAgentsServiceOp) RelayRequest(ctx context.Context, sessionID string, body *HostedAgentRelayRequest) (*HostedAgentRelayResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if body == nil || len(body.SourceRaw) == 0 { + return nil, nil, errors.New("hosted agents: source_raw is required") + } + path := fmt.Sprintf(hostedAgentSessionRequestPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, body) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentRelayResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ResolveHITL submits a human-in-the-loop decision. The API returns HTTP 204 on success. +func (s *HostedAgentsServiceOp) ResolveHITL(ctx context.Context, sessionID, requestID string, body *HostedAgentResolveHITLRequest) (*Response, error) { + if sessionID == "" { + return nil, errors.New("hosted agents: session id is required") + } + if requestID == "" { + return nil, errors.New("hosted agents: request id is required") + } + if body == nil { + return nil, errors.New("hosted agents: resolve request is required") + } + if body.Outcome == "" || body.Outcome == HostedAgentHITLOutcomeUnspecified { + return nil, errors.New("hosted agents: outcome is required") + } + path := fmt.Sprintf(hostedAgentSessionHITLPath, sessionID, requestID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, body) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// StartProviderAuth begins (or resumes) the team-scoped connect flow for an +// external provider. The server derives the team from the authenticated +// principal, so the POST carries an empty JSON object body. +func (s *HostedAgentsServiceOp) StartProviderAuth(ctx context.Context, provider string) (*HostedAgentProviderAuthStart, *Response, error) { + if provider == "" { + return nil, nil, errors.New("hosted agents: provider is required") + } + path := fmt.Sprintf(hostedAgentProviderAuthPath, provider) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, struct{}{}) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentProviderAuthStart) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// PollProviderAuth checks whether a pending connect link has been authorized. +// pollURL is the PollURL returned by StartProviderAuth; it is forwarded to the +// server as the poll_url query parameter. +func (s *HostedAgentsServiceOp) PollProviderAuth(ctx context.Context, provider, pollURL string) (*HostedAgentProviderAuthPoll, *Response, error) { + if provider == "" { + return nil, nil, errors.New("hosted agents: provider is required") + } + if pollURL == "" { + return nil, nil, errors.New("hosted agents: poll url is required") + } + path := fmt.Sprintf(hostedAgentProviderAuthPollPath, provider) + q := url.Values{} + q.Set("poll_url", pollURL) + path += "?" + q.Encode() + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentProviderAuthPoll) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ExecInSandbox runs a command inside the session sandbox. +func (s *HostedAgentsServiceOp) ExecInSandbox(ctx context.Context, sessionID string, body *HostedAgentSandboxExecRequest) (*HostedAgentSandboxExecResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if body == nil { + return nil, nil, errors.New("hosted agents: exec request is required") + } + if len(body.Argv) == 0 { + return nil, nil, errors.New("hosted agents: argv is required") + } + path := fmt.Sprintf(hostedAgentSessionSandboxExecPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, body) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentSandboxExecResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// ListSandboxSizes returns the customer-selectable sandbox (microVM) size +// catalog. The set is the single source of truth shared with create-time +// validation; every returned slug is accepted as spec.sandbox.sizeSlug. +// Sizes are ordered smallest-to-largest. The catalog is static and independent +// of team/session; there is no pagination. +func (s *HostedAgentsServiceOp) ListSandboxSizes(ctx context.Context) (*HostedAgentSandboxSizesResponse, *Response, error) { + req, err := s.client.NewRequest(ctx, http.MethodGet, hostedAgentSandboxSizesPath, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentSandboxSizesResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// UploadWorkspace streams a file (or tar archive) into the session workspace. +func (s *HostedAgentsServiceOp) UploadWorkspace(ctx context.Context, sessionID string, input *HostedAgentWorkspaceUploadRequest) (*HostedAgentWorkspaceUploadResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if input == nil { + return nil, nil, errors.New("hosted agents: upload request is required") + } + if input.Path == "" { + return nil, nil, errors.New("hosted agents: path is required") + } + if input.Body == nil { + return nil, nil, errors.New("hosted agents: body is required") + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceUploadPath, sessionID) + u, err := s.client.BaseURL.Parse(path) + if err != nil { + return nil, nil, err + } + q := url.Values{} + q.Set("path", input.Path) + if input.IsArchive { + q.Set("is_archive", "true") + } + u.RawQuery = q.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), input.Body) + if err != nil { + return nil, nil, err + } + req.Header.Set("Content-Type", hostedAgentWorkspaceMediaType) + req.Header.Set("Accept", mediaType) + req.Header.Set("User-Agent", s.client.UserAgent) + if input.ContentSHA256 != "" { + req.Header.Set(workspaceContentSHA256Header, input.ContentSHA256) + } + + root := new(HostedAgentWorkspaceUploadResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// DownloadWorkspace streams a file (or tar archive) out of the session +// workspace. Callers MUST read the returned Body to EOF and then Close it; +// the body strips the trailing integrity footer and verifies SHA-256 of the +// payload. +func (s *HostedAgentsServiceOp) DownloadWorkspace(ctx context.Context, sessionID string, input *HostedAgentWorkspaceDownloadRequest) (*HostedAgentWorkspaceDownload, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if input == nil { + return nil, nil, errors.New("hosted agents: download request is required") + } + if input.Path == "" { + return nil, nil, errors.New("hosted agents: path is required") + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceDownloadPath, sessionID) + q := url.Values{} + q.Set("path", input.Path) + if input.AsArchive { + q.Set("as_archive", "true") + } + path += "?" + q.Encode() + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", hostedAgentWorkspaceMediaType) + + resp, err := s.client.DoStream(ctx, req) + if err != nil { + return nil, resp, err + } + + out := &HostedAgentWorkspaceDownload{ + Body: &workspaceDownloadBody{ + body: resp.Body, + hasher: sha256.New(), + }, + } + if archive, perr := strconv.ParseBool(strings.TrimSpace(resp.Header.Get(workspaceIsArchiveHeader))); perr == nil { + out.IsArchive = archive + } + if hint := strings.TrimSpace(resp.Header.Get(workspaceSizeBytesHeader)); hint != "" { + if n, perr := strconv.ParseInt(hint, 10, 64); perr == nil { + out.SizeBytes = n + } + } + return out, resp, nil +} + +// CreateWorkspaceTransfer starts a large-file staged upload or download. +// For uploads, response includes part_size for client-side chunking. +func (s *HostedAgentsServiceOp) CreateWorkspaceTransfer(ctx context.Context, sessionID string, input *HostedAgentWorkspaceTransferCreateRequest) (*HostedAgentWorkspaceTransfer, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if input == nil { + return nil, nil, errors.New("hosted agents: transfer create request is required") + } + if input.Direction == "" { + return nil, nil, errors.New("hosted agents: direction is required") + } + if input.Path == "" { + return nil, nil, errors.New("hosted agents: path is required") + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceTransfersPath, sessionID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, input) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentWorkspaceTransfer) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// CreateWorkspaceTransferPartUploadURLs returns presigned URLs to PUT one or more +// upload parts. Upload only. Part numbers start at 1. If a URL expires, request +// that part_number again. +func (s *HostedAgentsServiceOp) CreateWorkspaceTransferPartUploadURLs(ctx context.Context, sessionID, transferID string, input *HostedAgentWorkspaceTransferPartUploadURLsRequest) (*HostedAgentWorkspaceTransferPartUploadURLs, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if transferID == "" { + return nil, nil, errors.New("hosted agents: transfer id is required") + } + if input == nil { + return nil, nil, errors.New("hosted agents: part upload URLs request is required") + } + if len(input.PartNumbers) == 0 { + return nil, nil, errors.New("hosted agents: part_numbers must not be empty") + } + for _, n := range input.PartNumbers { + if n < 1 { + return nil, nil, errors.New("hosted agents: part_numbers must all be >= 1") + } + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceTransferPartURLsPath, sessionID, transferID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, input) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentWorkspaceTransferPartUploadURLs) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// CommitWorkspaceTransfer finalizes an upload after all parts have been PUT and +// starts applying the file into the workspace. Poll GetWorkspaceTransfer afterward. +func (s *HostedAgentsServiceOp) CommitWorkspaceTransfer(ctx context.Context, sessionID, transferID string, input *HostedAgentWorkspaceTransferCommitRequest) (*HostedAgentWorkspaceTransfer, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if transferID == "" { + return nil, nil, errors.New("hosted agents: transfer id is required") + } + if input == nil { + input = &HostedAgentWorkspaceTransferCommitRequest{} + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceTransferCommitPath, sessionID, transferID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, input) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentWorkspaceTransfer) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// GetWorkspaceTransfer polls transfer status. On a completed download, DownloadURL +// and SHA256 are set; verify SHA256(file) == SHA256 after fetching DownloadURL. +// This path has no DOWSSHA1 body footer. +func (s *HostedAgentsServiceOp) GetWorkspaceTransfer(ctx context.Context, sessionID, transferID string) (*HostedAgentWorkspaceTransfer, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if transferID == "" { + return nil, nil, errors.New("hosted agents: transfer id is required") + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceTransferByIDPath, sessionID, transferID) + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentWorkspaceTransfer) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// CancelWorkspaceTransfer aborts an in-flight transfer. Idempotent. +func (s *HostedAgentsServiceOp) CancelWorkspaceTransfer(ctx context.Context, sessionID, transferID string, input *HostedAgentWorkspaceTransferCancelRequest) (*HostedAgentWorkspaceTransferCancelResponse, *Response, error) { + if sessionID == "" { + return nil, nil, errors.New("hosted agents: session id is required") + } + if transferID == "" { + return nil, nil, errors.New("hosted agents: transfer id is required") + } + if input == nil { + input = &HostedAgentWorkspaceTransferCancelRequest{} + } + + path := fmt.Sprintf(hostedAgentSessionWorkspaceTransferCancelPath, sessionID, transferID) + req, err := s.client.NewRequest(ctx, http.MethodPost, path, input) + if err != nil { + return nil, nil, err + } + root := new(HostedAgentWorkspaceTransferCancelResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + return root, resp, nil +} + +// workspaceDownloadBody holds back the trailing integrity footer while +// streaming, hashes the payload, and verifies the footer at EOF. +type workspaceDownloadBody struct { + body io.ReadCloser + hasher hash.Hash + pending []byte + scratch []byte + sawEOF bool + verified bool + verr error +} + +func (b *workspaceDownloadBody) Read(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + if b.verified { + if b.verr != nil { + return 0, b.verr + } + return 0, io.EOF + } + + for { + if overflow := len(b.pending) - workspaceDownloadFooterLen; overflow > 0 { + n := overflow + if n > len(p) { + n = len(p) + } + copy(p, b.pending[:n]) + b.hasher.Write(b.pending[:n]) + b.pending = b.pending[n:] + return n, nil + } + + if b.sawEOF { + if err := b.verifyFooter(); err != nil { + return 0, err + } + return 0, io.EOF + } + + if b.scratch == nil { + b.scratch = make([]byte, 32*1024) + } + nr, err := b.body.Read(b.scratch) + if nr > 0 { + b.pending = append(b.pending, b.scratch[:nr]...) + } + if errors.Is(err, io.EOF) { + b.sawEOF = true + continue + } + if err != nil { + return 0, err + } + } +} + +func (b *workspaceDownloadBody) verifyFooter() error { + if b.verified { + return b.verr + } + b.verified = true + + if len(b.pending) != workspaceDownloadFooterLen { + b.verr = errors.New("hosted agents: missing or truncated workspace download integrity footer") + return b.verr + } + want, ok := parseWorkspaceDownloadFooter(b.pending) + if !ok { + b.verr = errors.New("hosted agents: invalid workspace download integrity footer") + return b.verr + } + got := hex.EncodeToString(b.hasher.Sum(nil)) + if !strings.EqualFold(want, got) { + b.verr = fmt.Errorf("hosted agents: workspace download checksum mismatch: want %q, got %q", want, got) + return b.verr + } + b.pending = nil + return nil +} + +// parseWorkspaceDownloadFooter validates DOWSSHA1<64-hex>\n and returns the digest. +func parseWorkspaceDownloadFooter(footer []byte) (string, bool) { + if len(footer) != workspaceDownloadFooterLen { + return "", false + } + if string(footer[:len(workspaceDownloadFooterPrefix)]) != workspaceDownloadFooterPrefix { + return "", false + } + if footer[workspaceDownloadFooterLen-1] != '\n' { + return "", false + } + digest := string(footer[len(workspaceDownloadFooterPrefix) : workspaceDownloadFooterLen-1]) + for i := 0; i < len(digest); i++ { + c := digest[i] + switch { + case c >= '0' && c <= '9': + case c >= 'a' && c <= 'f': + case c >= 'A' && c <= 'F': + default: + return "", false + } + } + return digest, true +} + +func (b *workspaceDownloadBody) Close() error { + if cerr := b.body.Close(); cerr != nil { + return cerr + } + if b.verified { + return b.verr + } + return nil +} + +// HostedAgentSessionStream is a typed iterator over a session SSE stream. +type HostedAgentSessionStream struct { + raw *SSEReader + body io.ReadCloser + current HostedAgentEvent + err error + done bool + hasMore bool +} + +// hasMoreComment is the SSE comment a history page (see +// HostedAgentSessionStreamOptions.Before) ends with, reporting whether events +// older than the page remain. +const hasMoreComment = "has_more=" + +// observeComment records the has_more trailer of a history page. Wired as the +// reader's OnComment hook, so it runs while Next drains the stream. +func (s *HostedAgentSessionStream) observeComment(comment []byte) { + value, ok := strings.CutPrefix(string(comment), hasMoreComment) + if !ok { + return + } + s.hasMore = value == "true" +} + +// HasMore reports whether the history page just read has older events behind +// it. Only meaningful once a stream opened with +// HostedAgentSessionStreamOptions.Before has been drained to its end: the +// trailer arrives after the last event. +func (s *HostedAgentSessionStream) HasMore() bool { return s.hasMore } + +// Next advances to the next event. Returns false on EOF or error. +func (s *HostedAgentSessionStream) Next() bool { + for { + if s.done || s.err != nil { + return false + } + ev, err := s.raw.Next() + if errors.Is(err, io.EOF) { + s.done = true + return false + } + if err != nil { + s.err = err + return false + } + if len(ev.Data) == 0 { + continue + } + var event HostedAgentEvent + if err := json.Unmarshal(ev.Data, &event); err != nil { + s.err = err + return false + } + if event.EventID == "" && ev.ID != "" { + event.EventID = ev.ID + } + if event.Kind == "" && ev.Event != "" { + event.Kind = HostedAgentEventKind(ev.Event) + } + s.current = event + return true + } +} + +// Current returns the most recent event produced by Next. +func (s *HostedAgentSessionStream) Current() HostedAgentEvent { return s.current } + +// Err returns any non-EOF error encountered during iteration. +func (s *HostedAgentSessionStream) Err() error { return s.err } + +// Close releases the underlying HTTP response body. Always call Close. +func (s *HostedAgentSessionStream) Close() error { + if s.body == nil { + return nil + } + return s.body.Close() +} diff --git a/vendor/github.com/digitalocean/godo/kubernetes.go b/vendor/github.com/digitalocean/godo/kubernetes.go index 609ffd06c..fe6a6fa9d 100644 --- a/vendor/github.com/digitalocean/godo/kubernetes.go +++ b/vendor/github.com/digitalocean/godo/kubernetes.go @@ -98,6 +98,7 @@ type KubernetesClusterCreateRequest struct { CorednsAutoscaler *KubernetesCorednsAutoscaler `json:"coredns_autoscaler,omitempty"` SSO *KubernetesClusterSSO `json:"sso,omitempty"` P2pOciRegistryPlugin *KubernetesP2pOciRegistry `json:"p2p_oci_registry_plugin,omitempty"` + NfsCsiPlugin *KubernetesNfsCsiPlugin `json:"nfs_csi_plugin,omitempty"` // IsolatedWorkers enables isolated worker nodes. When true, the cluster's VPC // must already have a NAT gateway attached, or the create request fails with a // 422. This can only be set at creation time. @@ -123,6 +124,7 @@ type KubernetesClusterUpdateRequest struct { CorednsAutoscaler *KubernetesCorednsAutoscaler `json:"coredns_autoscaler,omitempty"` SSO *KubernetesClusterSSO `json:"sso,omitempty"` P2pOciRegistryPlugin *KubernetesP2pOciRegistry `json:"p2p_oci_registry_plugin,omitempty"` + NfsCsiPlugin *KubernetesNfsCsiPlugin `json:"nfs_csi_plugin,omitempty"` // Convert cluster to run highly available control plane HA *bool `json:"ha,omitempty"` @@ -279,6 +281,7 @@ type KubernetesCluster struct { CorednsAutoscaler *KubernetesCorednsAutoscaler `json:"coredns_autoscaler,omitempty"` SSO *KubernetesClusterSSO `json:"sso,omitempty"` P2pOciRegistryPlugin *KubernetesP2pOciRegistry `json:"p2p_oci_registry_plugin,omitempty"` + NfsCsiPlugin *KubernetesNfsCsiPlugin `json:"nfs_csi_plugin,omitempty"` IsolatedWorkers bool `json:"isolated_workers,omitempty"` Status *KubernetesClusterStatus `json:"status,omitempty"` @@ -379,6 +382,11 @@ type KubernetesP2pOciRegistry struct { Enabled *bool `json:"enabled"` } +// KubernetesNfsCsiPlugin represents information about the NFS CSI driver cluster plugin. +type KubernetesNfsCsiPlugin struct { + Enabled *bool `json:"enabled"` +} + // KubernetesClusterSSO configures Single Sign-On (SSO) for a Kubernetes cluster. type KubernetesClusterSSO struct { Enabled bool `json:"enabled"` diff --git a/vendor/github.com/digitalocean/godo/micro_droplets.go b/vendor/github.com/digitalocean/godo/micro_droplets.go deleted file mode 100644 index d3a364ce4..000000000 --- a/vendor/github.com/digitalocean/godo/micro_droplets.go +++ /dev/null @@ -1,551 +0,0 @@ -package godo - -import ( - "context" - "fmt" - "net/http" -) - -const ( - microDropletBasePath = "v2/microdroplets" - microDropletCheckpointsBasePath = "v2/microdroplets/checkpoints" - microDropletOptionsPath = "v2/microdroplets/options" -) - -// MicroDropletState represents the lifecycle state of a MicroDroplet. -type MicroDropletState string - -// Possible lifecycle states for a MicroDroplet. -const ( - MicroDropletStateUnknown = MicroDropletState("unknown") - MicroDropletStateCreating = MicroDropletState("creating") - MicroDropletStateRunning = MicroDropletState("running") - MicroDropletStatePausing = MicroDropletState("pausing") - MicroDropletStatePaused = MicroDropletState("paused") - MicroDropletStateResuming = MicroDropletState("resuming") - MicroDropletStateTerminating = MicroDropletState("terminating") - MicroDropletStateTerminated = MicroDropletState("terminated") - MicroDropletStateFailed = MicroDropletState("failed") -) - -// MicroDropletNetworking represents the networking mode of a MicroDroplet. -type MicroDropletNetworking string - -// Possible networking modes for a MicroDroplet. -const ( - MicroDropletNetworkingUnknown = MicroDropletNetworking("unknown") - MicroDropletNetworkingPublic = MicroDropletNetworking("public") - MicroDropletNetworkingVPC = MicroDropletNetworking("vpc") -) - -// MicroDropletHTTPProtocol represents the HTTP protocol option for a MicroDroplet. -type MicroDropletHTTPProtocol string - -// Possible HTTP protocol values for a MicroDroplet. -const ( - MicroDropletHTTPProtocolHTTP = MicroDropletHTTPProtocol("http") - MicroDropletHTTPProtocolHTTP2 = MicroDropletHTTPProtocol("http2") -) - -// MicroDropletURLStatus represents the lifecycle of a MicroDroplet URL. -type MicroDropletURLStatus string - -// Possible statuses for a MicroDroplet URL. -const ( - MicroDropletURLStatusPending = MicroDropletURLStatus("PENDING") - MicroDropletURLStatusActive = MicroDropletURLStatus("ACTIVE") -) - -// MicroDropletCheckpointStatus represents the status of a MicroDroplet checkpoint. -type MicroDropletCheckpointStatus string - -// Possible states for a MicroDroplet checkpoint. -const ( - MicroDropletCheckpointStatusUnknown = MicroDropletCheckpointStatus("CHECKPOINT_UNKNOWN") - MicroDropletCheckpointStatusCreating = MicroDropletCheckpointStatus("CHECKPOINT_CREATING") - MicroDropletCheckpointStatusAvailable = MicroDropletCheckpointStatus("CHECKPOINT_AVAILABLE") - MicroDropletCheckpointStatusFailed = MicroDropletCheckpointStatus("CHECKPOINT_FAILED") - MicroDropletCheckpointStatusDeleted = MicroDropletCheckpointStatus("CHECKPOINT_DELETED") - MicroDropletCheckpointStatusDeleting = MicroDropletCheckpointStatus("CHECKPOINT_DELETING") -) - -// MicroDropletsService is an interface for interfacing with the MicroDroplet -// endpoints of the DigitalOcean API. -// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/MicroDroplets -type MicroDropletsService interface { - List(ctx context.Context, opt *ListOptions) ([]MicroDroplet, *Response, error) - ListByRegion(ctx context.Context, region string, opt *ListOptions) ([]MicroDroplet, *Response, error) - ListByName(ctx context.Context, name string, opt *ListOptions) ([]MicroDroplet, *Response, error) - Get(ctx context.Context, id string) (*MicroDroplet, *Response, error) - Create(ctx context.Context, createRequest *MicroDropletCreateRequest) (*MicroDroplet, *Response, error) - Pause(ctx context.Context, id string) (*MicroDroplet, *Response, error) - Resume(ctx context.Context, id string) (*MicroDroplet, *Response, error) - Delete(ctx context.Context, id string) (*Response, error) - - ListCheckpoints(ctx context.Context, opt *ListMicroDropletCheckpointsOptions) ([]MicroDropletCheckpoint, *Response, error) - CreateCheckpoint(ctx context.Context, microDropletID string, createRequest *MicroDropletCheckpointCreateRequest) (*MicroDropletCheckpoint, *Response, error) - GetCheckpoint(ctx context.Context, id string) (*MicroDropletCheckpoint, *Response, error) - DeleteCheckpoint(ctx context.Context, id string) (*Response, error) - - GetCreateOptions(ctx context.Context) (*MicroDropletCreateOptions, *Response, error) -} - -// MicroDropletsServiceOp handles communication with the MicroDroplet related -// methods of the DigitalOcean API. -type MicroDropletsServiceOp struct { - client *Client -} - -var _ MicroDropletsService = &MicroDropletsServiceOp{} - -// MicroDropletSize is the compute capacity reported on a MicroDroplet. -type MicroDropletSize struct { - CPU uint32 `json:"cpu,omitempty"` - Memory uint32 `json:"memory,omitempty"` - Disk uint64 `json:"disk,omitempty"` -} - -// MicroDropletSizeRequest is the compute capacity requested at create time. -// Disk is provisioned with the size and is not requested. -type MicroDropletSizeRequest struct { - CPU uint32 `json:"cpu"` - Memory uint32 `json:"memory"` -} - -// MicroDropletSource names what a MicroDroplet runs. Exactly one of OCIRef or -// CheckpointID must be set on create; the API rejects both or neither. -type MicroDropletSource struct { - OCIRef string `json:"oci_ref,omitempty"` - CheckpointID string `json:"checkpoint_id,omitempty"` -} - -// MicroDropletURL is one ingress URL attached to a MicroDroplet. -type MicroDropletURL struct { - Hostname string `json:"hostname,omitempty"` - Port int `json:"port,omitempty"` - Default bool `json:"default,omitempty"` - Status MicroDropletURLStatus `json:"status,omitempty"` -} - -// MicroDroplet represents a DigitalOcean MicroDroplet. -type MicroDroplet struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Region string `json:"region,omitempty"` - State MicroDropletState `json:"state,omitempty"` - Size *MicroDropletSize `json:"size,omitempty"` - URLs []MicroDropletURL `json:"urls,omitempty"` - Ports []uint32 `json:"ports,omitempty"` - FailureReason string `json:"failure_reason,omitempty"` - Networking MicroDropletNetworking `json:"networking,omitempty"` - Source *MicroDropletSource `json:"source,omitempty"` - AutoPause *AutoPauseConfig `json:"auto_pause,omitempty"` - AutoResume *bool `json:"auto_resume,omitempty"` - Created string `json:"created_at,omitempty"` - Tags []string `json:"tags,omitempty"` -} - -// AutoPauseConfig configures MicroDroplet auto-pause behavior. IdleTimeout is -// a Go duration string (e.g. "5m", "30s") describing how long the MicroDroplet -// must be idle before it is paused. -type AutoPauseConfig struct { - Enabled *bool `json:"enabled,omitempty"` - IdleTimeout string `json:"idle_timeout,omitempty"` -} - -// MicroDropletCheckpoint represents a checkpoint of a MicroDroplet -// (persisted memory + disk state). -type MicroDropletCheckpoint struct { - ID string `json:"id,omitempty"` - MicroDropletID string `json:"micro_droplet_id,omitempty"` - MicroDropletName string `json:"micro_droplet_name,omitempty"` - Name string `json:"name,omitempty"` - Region string `json:"region,omitempty"` - Status MicroDropletCheckpointStatus `json:"status,omitempty"` - MemoryBytes uint64 `json:"memory_bytes,omitempty"` - DiskBytes uint64 `json:"disk_bytes,omitempty"` - Created string `json:"created_at,omitempty"` -} - -// MicroDropletCreateRequest represents a request to create a MicroDroplet. -// Size and Region may be omitted when restoring from a checkpoint so the API -// can inherit them from the checkpoint (api-v2 C6). -type MicroDropletCreateRequest struct { - Name string `json:"name"` - Region string `json:"region,omitempty"` - Size *MicroDropletSizeRequest `json:"size,omitempty"` - Source *MicroDropletSource `json:"source"` - Networking MicroDropletNetworking `json:"networking,omitempty"` - VPCUUID string `json:"vpc_uuid,omitempty"` - AutoPause *AutoPauseConfig `json:"auto_pause,omitempty"` - AutoResume *bool `json:"auto_resume,omitempty"` - HTTPPort uint32 `json:"http_port,omitempty"` - HTTPProtocol MicroDropletHTTPProtocol `json:"http_protocol,omitempty"` - Ports []uint32 `json:"ports,omitempty"` - Environment map[string]string `json:"environment,omitempty"` - Tags []string `json:"tags,omitempty"` -} - -// MicroDropletCheckpointCreateRequest represents a request to create a checkpoint. -type MicroDropletCheckpointCreateRequest struct { - Name string `json:"name,omitempty"` -} - -// ListMicroDropletCheckpointsOptions are the optional parameters for listing -// checkpoints. MicroDropletID filters to checkpoints captured from that -// MicroDroplet; omit it to list all checkpoints for the team. -type ListMicroDropletCheckpointsOptions struct { - ListOptions - MicroDropletID string `url:"micro_droplet_id,omitempty"` -} - -// MicroDropletCreateOptions is the response from GET /v2/microdroplets/options. -type MicroDropletCreateOptions struct { - Regions []MicroDropletRegionOption `json:"regions,omitempty"` - DefaultRegion string `json:"default_region,omitempty"` - Sizes []MicroDropletSizeOption `json:"sizes,omitempty"` - Features []MicroDropletFeatureOption `json:"features,omitempty"` - AccountLimits *MicroDropletAccountLimits `json:"account_limits,omitempty"` -} - -// MicroDropletRegionOption is one region evaluated for the authenticated team. -type MicroDropletRegionOption struct { - Slug string `json:"slug,omitempty"` - Available bool `json:"available"` - UnavailableReason string `json:"unavailable_reason,omitempty"` -} - -// MicroDropletSizeOption is one supported size evaluated for the team. -type MicroDropletSizeOption struct { - Size MicroDropletSize `json:"size"` - Available bool `json:"available"` - Pricing *MicroDropletSizePricing `json:"pricing,omitempty"` -} - -// MicroDropletSizePricing is the USD price of one size. -type MicroDropletSizePricing struct { - PricePerHour float64 `json:"price_per_hour"` - PricePerMonth float64 `json:"price_per_month"` -} - -// MicroDropletFeatureOption is one product feature gate for the team. -type MicroDropletFeatureOption struct { - Name string `json:"name,omitempty"` - Enabled bool `json:"enabled"` -} - -// MicroDropletAccountLimits are the team's effective MicroDroplet limits. -type MicroDropletAccountLimits struct { - MaxConcurrentRunning uint64 `json:"max_concurrent_running,omitempty"` - MaxTotalCount uint64 `json:"max_total_count,omitempty"` - MaxMemoryBytes uint64 `json:"max_memory_bytes,omitempty"` - MaxDiskBytes uint64 `json:"max_disk_bytes,omitempty"` - MaxIdleTimeoutSeconds uint64 `json:"max_idle_timeout_seconds,omitempty"` -} - -// String returns a human-readable description of a MicroDroplet. -func (m MicroDroplet) String() string { - return Stringify(m) -} - -// URN returns the MicroDroplet ID in a valid DO API URN form. -func (m MicroDroplet) URN() string { - return ToURN("MicroDroplet", m.ID) -} - -// String returns a human-readable description of a MicroDropletCheckpoint. -func (c MicroDropletCheckpoint) String() string { - return Stringify(c) -} - -// String returns a human-readable description of a MicroDropletCreateRequest. -func (r MicroDropletCreateRequest) String() string { - return Stringify(r) -} - -type microDropletRoot struct { - MicroDroplet *MicroDroplet `json:"micro_droplet"` -} - -type microDropletsRoot struct { - MicroDroplets []MicroDroplet `json:"micro_droplets"` - Links *Links `json:"links"` - Meta *Meta `json:"meta"` -} - -type microDropletCheckpointRoot struct { - Checkpoint *MicroDropletCheckpoint `json:"checkpoint"` -} - -type microDropletCheckpointsRoot struct { - Checkpoints []MicroDropletCheckpoint `json:"checkpoints"` - Links *Links `json:"links"` - Meta *Meta `json:"meta"` -} - -// listMicroDropletOptions holds MicroDroplet-specific list filters that are -// not part of the shared ListOptions. -type listMicroDropletOptions struct { - Region string `url:"region,omitempty"` - Name string `url:"name,omitempty"` -} - -// List lists all MicroDroplets, with optional pagination. -func (s *MicroDropletsServiceOp) List(ctx context.Context, opt *ListOptions) ([]MicroDroplet, *Response, error) { - return s.list(ctx, opt, nil) -} - -// ListByRegion lists MicroDroplets filtered by region slug, with optional pagination. -func (s *MicroDropletsServiceOp) ListByRegion(ctx context.Context, region string, opt *ListOptions) ([]MicroDroplet, *Response, error) { - if region == "" { - return nil, nil, NewArgError("region", "cannot be empty") - } - return s.list(ctx, opt, &listMicroDropletOptions{Region: region}) -} - -// ListByName lists MicroDroplets filtered by exact name match, with optional pagination. -func (s *MicroDropletsServiceOp) ListByName(ctx context.Context, name string, opt *ListOptions) ([]MicroDroplet, *Response, error) { - if name == "" { - return nil, nil, NewArgError("name", "cannot be empty") - } - return s.list(ctx, opt, &listMicroDropletOptions{Name: name}) -} - -func (s *MicroDropletsServiceOp) list(ctx context.Context, opt *ListOptions, listOpt *listMicroDropletOptions) ([]MicroDroplet, *Response, error) { - path := microDropletBasePath - path, err := addOptions(path, opt) - if err != nil { - return nil, nil, err - } - path, err = addOptions(path, listOpt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, nil, err - } - - root := new(microDropletsRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - if l := root.Links; l != nil { - resp.Links = l - } - if m := root.Meta; m != nil { - resp.Meta = m - } - - return root.MicroDroplets, resp, nil -} - -// Get retrieves a MicroDroplet by its ID. -func (s *MicroDropletsServiceOp) Get(ctx context.Context, id string) (*MicroDroplet, *Response, error) { - if id == "" { - return nil, nil, NewArgError("id", "cannot be empty") - } - - path := fmt.Sprintf("%s/%s", microDropletBasePath, id) - - req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, nil, err - } - - root := new(microDropletRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - - return root.MicroDroplet, resp, nil -} - -// Create provisions a new MicroDroplet with the provided configuration. -func (s *MicroDropletsServiceOp) Create(ctx context.Context, createRequest *MicroDropletCreateRequest) (*MicroDroplet, *Response, error) { - if createRequest == nil { - return nil, nil, NewArgError("createRequest", "cannot be nil") - } - - req, err := s.client.NewRequest(ctx, http.MethodPost, microDropletBasePath, createRequest) - if err != nil { - return nil, nil, err - } - - root := new(microDropletRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - - return root.MicroDroplet, resp, nil -} - -// Pause synchronously transitions a RUNNING MicroDroplet to PAUSED. It blocks -// until the platform has durably paused the MicroDroplet and returns the -// updated resource. The call is idempotent: pausing a MicroDroplet that is -// already PAUSED returns the current MicroDroplet with no side effects. -func (s *MicroDropletsServiceOp) Pause(ctx context.Context, id string) (*MicroDroplet, *Response, error) { - return s.doTransition(ctx, id, "pause") -} - -// Resume synchronously transitions a PAUSED MicroDroplet to RUNNING. It blocks -// until the platform has durably resumed the MicroDroplet and returns the -// updated resource. The call is idempotent: resuming a MicroDroplet that is -// already RUNNING returns the current MicroDroplet with no side effects. -func (s *MicroDropletsServiceOp) Resume(ctx context.Context, id string) (*MicroDroplet, *Response, error) { - return s.doTransition(ctx, id, "resume") -} - -// doTransition posts an empty body to a MicroDroplet transition sub-resource -// (e.g. /pause, /resume) and decodes the returned MicroDroplet. -func (s *MicroDropletsServiceOp) doTransition(ctx context.Context, id, action string) (*MicroDroplet, *Response, error) { - if id == "" { - return nil, nil, NewArgError("id", "cannot be empty") - } - - path := fmt.Sprintf("%s/%s/%s", microDropletBasePath, id, action) - - req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) - if err != nil { - return nil, nil, err - } - - root := new(microDropletRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - - return root.MicroDroplet, resp, nil -} - -// Delete removes a MicroDroplet by its ID. The DigitalOcean API returns a 204 -// on success and does not include a response body. -func (s *MicroDropletsServiceOp) Delete(ctx context.Context, id string) (*Response, error) { - if id == "" { - return nil, NewArgError("id", "cannot be empty") - } - - path := fmt.Sprintf("%s/%s", microDropletBasePath, id) - - req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) - if err != nil { - return nil, err - } - - return s.client.Do(ctx, req, nil) -} - -// ListCheckpoints lists checkpoints for the authenticated team. Pass a -// MicroDropletID on opt to filter to checkpoints captured from that MicroDroplet. -func (s *MicroDropletsServiceOp) ListCheckpoints(ctx context.Context, opt *ListMicroDropletCheckpointsOptions) ([]MicroDropletCheckpoint, *Response, error) { - path := microDropletCheckpointsBasePath - path, err := addOptions(path, opt) - if err != nil { - return nil, nil, err - } - - req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, nil, err - } - - root := new(microDropletCheckpointsRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - if l := root.Links; l != nil { - resp.Links = l - } - if m := root.Meta; m != nil { - resp.Meta = m - } - - return root.Checkpoints, resp, nil -} - -// CreateCheckpoint starts an asynchronous checkpoint of a running MicroDroplet. -func (s *MicroDropletsServiceOp) CreateCheckpoint(ctx context.Context, microDropletID string, createRequest *MicroDropletCheckpointCreateRequest) (*MicroDropletCheckpoint, *Response, error) { - if microDropletID == "" { - return nil, nil, NewArgError("microDropletID", "cannot be empty") - } - if createRequest == nil { - createRequest = &MicroDropletCheckpointCreateRequest{} - } - - path := fmt.Sprintf("%s/%s/checkpoints", microDropletBasePath, microDropletID) - - req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest) - if err != nil { - return nil, nil, err - } - - root := new(microDropletCheckpointRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - - return root.Checkpoint, resp, nil -} - -// GetCheckpoint retrieves a checkpoint by its ID. -func (s *MicroDropletsServiceOp) GetCheckpoint(ctx context.Context, id string) (*MicroDropletCheckpoint, *Response, error) { - if id == "" { - return nil, nil, NewArgError("id", "cannot be empty") - } - - path := fmt.Sprintf("%s/%s", microDropletCheckpointsBasePath, id) - - req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) - if err != nil { - return nil, nil, err - } - - root := new(microDropletCheckpointRoot) - resp, err := s.client.Do(ctx, req, root) - if err != nil { - return nil, resp, err - } - - return root.Checkpoint, resp, nil -} - -// DeleteCheckpoint releases the state stored by a checkpoint. Returns a 204 -// on success with no response body. -func (s *MicroDropletsServiceOp) DeleteCheckpoint(ctx context.Context, id string) (*Response, error) { - if id == "" { - return nil, NewArgError("id", "cannot be empty") - } - - path := fmt.Sprintf("%s/%s", microDropletCheckpointsBasePath, id) - - req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) - if err != nil { - return nil, err - } - - return s.client.Do(ctx, req, nil) -} - -// GetCreateOptions returns the regions, sizes, features, and account limits -// available to the authenticated team when creating a MicroDroplet. -func (s *MicroDropletsServiceOp) GetCreateOptions(ctx context.Context) (*MicroDropletCreateOptions, *Response, error) { - req, err := s.client.NewRequest(ctx, http.MethodGet, microDropletOptionsPath, nil) - if err != nil { - return nil, nil, err - } - - opts := new(MicroDropletCreateOptions) - resp, err := s.client.Do(ctx, req, opts) - if err != nil { - return nil, resp, err - } - - return opts, resp, nil -} diff --git a/vendor/github.com/digitalocean/godo/microvms.go b/vendor/github.com/digitalocean/godo/microvms.go new file mode 100644 index 000000000..e440720e9 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/microvms.go @@ -0,0 +1,744 @@ +package godo + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" +) + +const ( + microVMBasePath = "v2/microvms" + microVMCheckpointsBasePath = "v2/microvms/checkpoints" + microVMOptionsPath = "v2/microvms/options" +) + +// MicroVMState represents the lifecycle state of a MicroVM. +type MicroVMState string + +// Possible lifecycle states for a MicroVM. +const ( + MicroVMStateUnknown = MicroVMState("unknown") + MicroVMStateCreating = MicroVMState("creating") + MicroVMStateRunning = MicroVMState("running") + MicroVMStatePausing = MicroVMState("pausing") + MicroVMStatePaused = MicroVMState("paused") + MicroVMStateResuming = MicroVMState("resuming") + MicroVMStateTerminating = MicroVMState("terminating") + MicroVMStateTerminated = MicroVMState("terminated") + MicroVMStateFailed = MicroVMState("failed") +) + +// MicroVMNetworking represents the networking mode of a MicroVM. +type MicroVMNetworking string + +// Possible networking modes for a MicroVM. +const ( + MicroVMNetworkingUnknown = MicroVMNetworking("unknown") + MicroVMNetworkingPublic = MicroVMNetworking("public") + MicroVMNetworkingVPC = MicroVMNetworking("vpc") +) + +// MicroVMHTTPProtocol represents the HTTP protocol option for a MicroVM. +type MicroVMHTTPProtocol string + +// Possible HTTP protocol values for a MicroVM. +const ( + MicroVMHTTPProtocolHTTP = MicroVMHTTPProtocol("http") + MicroVMHTTPProtocolHTTP2 = MicroVMHTTPProtocol("http2") +) + +// MicroVMURLStatus represents the lifecycle of a MicroVM URL. +type MicroVMURLStatus string + +// Possible statuses for a MicroVM URL. +const ( + MicroVMURLStatusPending = MicroVMURLStatus("PENDING") + MicroVMURLStatusActive = MicroVMURLStatus("ACTIVE") +) + +// MicroVMCheckpointStatus represents the status of a MicroVM checkpoint. +type MicroVMCheckpointStatus string + +// Possible states for a MicroVM checkpoint. +const ( + MicroVMCheckpointStatusUnknown = MicroVMCheckpointStatus("CHECKPOINT_UNKNOWN") + MicroVMCheckpointStatusCreating = MicroVMCheckpointStatus("CHECKPOINT_CREATING") + MicroVMCheckpointStatusAvailable = MicroVMCheckpointStatus("CHECKPOINT_AVAILABLE") + MicroVMCheckpointStatusFailed = MicroVMCheckpointStatus("CHECKPOINT_FAILED") + MicroVMCheckpointStatusDeleted = MicroVMCheckpointStatus("CHECKPOINT_DELETED") + MicroVMCheckpointStatusDeleting = MicroVMCheckpointStatus("CHECKPOINT_DELETING") +) + +// MicroVMsService is an interface for interfacing with the MicroVM +// endpoints of the DigitalOcean API. +// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/MicroVMs +type MicroVMsService interface { + List(ctx context.Context, opt *ListOptions) ([]MicroVM, *Response, error) + ListByRegion(ctx context.Context, region string, opt *ListOptions) ([]MicroVM, *Response, error) + ListByName(ctx context.Context, name string, opt *ListOptions) ([]MicroVM, *Response, error) + ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]MicroVM, *Response, error) + ListFiltered(ctx context.Context, opt *ListMicroVMsOptions) ([]MicroVM, *Response, error) + Get(ctx context.Context, id string) (*MicroVM, *Response, error) + Create(ctx context.Context, createRequest *MicroVMCreateRequest) (*MicroVM, *Response, error) + Pause(ctx context.Context, id string) (*MicroVM, *Response, error) + Resume(ctx context.Context, id string) (*MicroVM, *Response, error) + Delete(ctx context.Context, id string) (*Response, error) + + ListCheckpoints(ctx context.Context, opt *ListMicroVMCheckpointsOptions) ([]MicroVMCheckpoint, *Response, error) + CreateCheckpoint(ctx context.Context, microVMID string, createRequest *MicroVMCheckpointCreateRequest) (*MicroVMCheckpoint, *Response, error) + GetCheckpoint(ctx context.Context, id string) (*MicroVMCheckpoint, *Response, error) + DeleteCheckpoint(ctx context.Context, id string) (*Response, error) + + GetCreateOptions(ctx context.Context) (*MicroVMCreateOptions, *Response, error) + + Exec(ctx context.Context, id string, execRequest *MicroVMExecRequest) (*MicroVMExecResult, *Response, error) + ConsoleURL(id string, opt *MicroVMConsoleOptions) (string, error) +} + +// MicroVMsServiceOp handles communication with the MicroVM related +// methods of the DigitalOcean API. +type MicroVMsServiceOp struct { + client *Client +} + +var _ MicroVMsService = &MicroVMsServiceOp{} + +// MicroVMSize is the compute capacity reported on a MicroVM. +type MicroVMSize struct { + CPU uint32 `json:"cpu,omitempty"` + Memory uint32 `json:"memory,omitempty"` + Disk uint64 `json:"disk,omitempty"` +} + +// MicroVMSizeRequest is the compute capacity requested at create time. +// Disk is provisioned with the size and is not requested. +type MicroVMSizeRequest struct { + CPU uint32 `json:"cpu"` + Memory uint32 `json:"memory"` +} + +// MicroVMSource names what a MicroVM runs. Exactly one of OCIRef or +// CheckpointID must be set on create; the API rejects both or neither. +type MicroVMSource struct { + OCIRef string `json:"oci_ref,omitempty"` + CheckpointID string `json:"checkpoint_id,omitempty"` +} + +// MicroVMURL is one ingress URL attached to a MicroVM. +type MicroVMURL struct { + Hostname string `json:"hostname,omitempty"` + Port int `json:"port,omitempty"` + Default bool `json:"default,omitempty"` + Status MicroVMURLStatus `json:"status,omitempty"` +} + +// MicroVM represents a DigitalOcean MicroVM. +type MicroVM struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Region string `json:"region,omitempty"` + State MicroVMState `json:"state,omitempty"` + Size *MicroVMSize `json:"size,omitempty"` + URLs []MicroVMURL `json:"urls,omitempty"` + Ports []uint32 `json:"ports,omitempty"` + FailureReason string `json:"failure_reason,omitempty"` + Networking MicroVMNetworking `json:"networking,omitempty"` + Source *MicroVMSource `json:"source,omitempty"` + AutoPause *AutoPauseConfig `json:"auto_pause,omitempty"` + AutoResume *bool `json:"auto_resume,omitempty"` + HTTPProtocol MicroVMHTTPProtocol `json:"http_protocol,omitempty"` + Created string `json:"created_at,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +// AutoPauseConfig configures MicroVM auto-pause behavior. IdleTimeout is +// a Go duration string (e.g. "5m", "30s") describing how long the MicroVM +// must be idle before it is paused. +type AutoPauseConfig struct { + Enabled *bool `json:"enabled,omitempty"` + IdleTimeout string `json:"idle_timeout,omitempty"` +} + +// MicroVMCheckpoint represents a checkpoint of a MicroVM +// (persisted memory + disk state). +type MicroVMCheckpoint struct { + ID string `json:"id,omitempty"` + MicroVMID string `json:"microvm_id,omitempty"` + MicroVMName string `json:"microvm_name,omitempty"` + Name string `json:"name,omitempty"` + Region string `json:"region,omitempty"` + // Size is the guest shape this checkpoint restores at. Omitted when the + // checkpoint does not record one; a create from it must then name a size. + Size *MicroVMSize `json:"size,omitempty"` + Status MicroVMCheckpointStatus `json:"status,omitempty"` + MemoryBytes uint64 `json:"memory_bytes,omitempty"` + DiskBytes uint64 `json:"disk_bytes,omitempty"` + Created string `json:"created_at,omitempty"` +} + +// MicroVMCreateRequest represents a request to create a MicroVM. +// Size and Region may be omitted when restoring from a checkpoint so the API +// can inherit them from the checkpoint (api-v2 C6). +type MicroVMCreateRequest struct { + Name string `json:"name"` + Region string `json:"region,omitempty"` + Size *MicroVMSizeRequest `json:"size,omitempty"` + Source *MicroVMSource `json:"source"` + Networking MicroVMNetworking `json:"networking,omitempty"` + VPCUUID string `json:"vpc_uuid,omitempty"` + AutoPause *AutoPauseConfig `json:"auto_pause,omitempty"` + AutoResume *bool `json:"auto_resume,omitempty"` + HTTPPort uint32 `json:"http_port,omitempty"` + HTTPProtocol MicroVMHTTPProtocol `json:"http_protocol,omitempty"` + Ports []uint32 `json:"ports,omitempty"` + Environment map[string]string `json:"environment,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +// MicroVMCheckpointCreateRequest represents a request to create a checkpoint. +type MicroVMCheckpointCreateRequest struct { + Name string `json:"name,omitempty"` +} + +// ListMicroVMCheckpointsOptions are the optional parameters for listing +// checkpoints. MicroVMID filters to checkpoints captured from that +// MicroVM; omit it to list all checkpoints for the team. +type ListMicroVMCheckpointsOptions struct { + ListOptions + MicroVMID string `url:"microvm_id,omitempty"` +} + +// MicroVMCreateOptions is the response from GET /v2/microvms/options. +type MicroVMCreateOptions struct { + DefaultRegion string `json:"default_region,omitempty"` + Sizes []MicroVMSizeOption `json:"sizes,omitempty"` + Features []MicroVMFeatureOption `json:"features,omitempty"` + AccountLimits *MicroVMAccountLimits `json:"account_limits,omitempty"` +} + +// MicroVMSizeOption is one supported size evaluated for the team. Regions +// holds the slugs where the team can create this size; a region it cannot use +// is absent rather than listed as unavailable. +type MicroVMSizeOption struct { + CPU uint32 `json:"cpu"` + Memory uint32 `json:"memory"` + Disk uint64 `json:"disk"` + Available bool `json:"available"` + Regions []string `json:"regions"` + Pricing *MicroVMSizePricing `json:"pricing,omitempty"` +} + +// MicroVMSizePricing is the USD price of one size. +type MicroVMSizePricing struct { + PricePerHour float64 `json:"price_per_hour"` + PricePerMonth float64 `json:"price_per_month"` +} + +// MicroVMFeatureOption is one product feature gate for the team. +type MicroVMFeatureOption struct { + Name string `json:"name,omitempty"` + Enabled bool `json:"enabled"` +} + +// MicroVMAccountLimits are the team's effective MicroVM limits. +type MicroVMAccountLimits struct { + MaxConcurrentRunning uint64 `json:"max_concurrent_running,omitempty"` + MaxTotalCount uint64 `json:"max_total_count,omitempty"` + MaxMemoryBytes uint64 `json:"max_memory_bytes,omitempty"` + MaxDiskBytes uint64 `json:"max_disk_bytes,omitempty"` + MaxIdleTimeoutSeconds uint64 `json:"max_idle_timeout_seconds,omitempty"` +} + +// MicroVMExecRequest is a one-shot, non-PTY command run in a MicroVM's +// workload container. Argv must be non-empty (e.g. ["echo", "hi"]). +type MicroVMExecRequest struct { + Argv []string `json:"argv"` + Cwd string `json:"cwd,omitempty"` +} + +// MicroVMExecResult is the outcome of a one-shot exec. A non-zero ExitCode +// is not a client error — the API returns HTTP 200 with the exit code set. +// Truncated is true when the server clipped stdout/stderr to its output bound. +type MicroVMExecResult struct { + Stdout string `json:"stdout"` + Stderr string `json:"stderr"` + ExitCode int32 `json:"exit_code"` + Truncated bool `json:"truncated,omitempty"` +} + +// MicroVMConsoleOptions sets the initial PTY size for ConsoleURL. +// The server defaults to 24 rows by 80 columns when either is zero / unset. +type MicroVMConsoleOptions struct { + Rows uint32 + Cols uint32 +} + +// Stable console WebSocket error.code values (server → client text frames). +const ( + MicroVMConsoleErrDialFailed = "dial_failed" + MicroVMConsoleErrStartFailed = "start_failed" + MicroVMConsoleErrAgentError = "agent_error" +) + +// MicroVMConsoleControl is a decoded server → client text control frame. +// Exactly one of Error, Exit, or Status is set for a well-formed frame. +type MicroVMConsoleControl struct { + Error *MicroVMConsoleError `json:"error,omitempty"` + Exit *MicroVMConsoleExit `json:"exit,omitempty"` + Status *MicroVMConsoleStatus `json:"status,omitempty"` +} + +// MicroVMConsoleError is the payload of an {"error":...} control frame. +type MicroVMConsoleError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +// MicroVMConsoleExit is the payload of an {"exit":...} control frame. +type MicroVMConsoleExit struct { + Code int32 `json:"code"` +} + +// MicroVMConsoleStatus is the payload of a {"status":...} control frame +// (e.g. state "resuming" while a paused MicroVM is auto-resumed). +type MicroVMConsoleStatus struct { + State string `json:"state"` +} + +// String returns a human-readable description of a MicroVM. +func (m MicroVM) String() string { + return Stringify(m) +} + +// URN returns the MicroVM ID in a valid DO API URN form. +func (m MicroVM) URN() string { + return ToURN("MicroVM", m.ID) +} + +// String returns a human-readable description of a MicroVMCheckpoint. +func (c MicroVMCheckpoint) String() string { + return Stringify(c) +} + +// String returns a human-readable description of a MicroVMCreateRequest. +func (r MicroVMCreateRequest) String() string { + return Stringify(r) +} + +type microVMRoot struct { + MicroVM *MicroVM `json:"microvm"` +} + +type microVMsRoot struct { + MicroVMs []MicroVM `json:"microvms"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +type microVMCheckpointRoot struct { + Checkpoint *MicroVMCheckpoint `json:"checkpoint"` +} + +type microVMCheckpointsRoot struct { + Checkpoints []MicroVMCheckpoint `json:"checkpoints"` + Links *Links `json:"links"` + Meta *Meta `json:"meta"` +} + +// ListMicroVMsOptions filters GET /v2/microvms. Pagination lives on the +// embedded ListOptions. Region, Name, and TagName are omitted when empty +// and combine: the API ANDs whichever are set. +type ListMicroVMsOptions struct { + ListOptions + Region string `url:"region,omitempty"` + Name string `url:"name,omitempty"` + TagName string `url:"tag_name,omitempty"` +} + +// List lists all MicroVMs, with optional pagination. +func (s *MicroVMsServiceOp) List(ctx context.Context, opt *ListOptions) ([]MicroVM, *Response, error) { + var filtered *ListMicroVMsOptions + if opt != nil { + filtered = &ListMicroVMsOptions{ListOptions: *opt} + } + return s.ListFiltered(ctx, filtered) +} + +// ListByRegion lists MicroVMs filtered by region slug, with optional pagination. +func (s *MicroVMsServiceOp) ListByRegion(ctx context.Context, region string, opt *ListOptions) ([]MicroVM, *Response, error) { + if region == "" { + return nil, nil, NewArgError("region", "cannot be empty") + } + filtered := &ListMicroVMsOptions{Region: region} + if opt != nil { + filtered.ListOptions = *opt + } + return s.ListFiltered(ctx, filtered) +} + +// ListByName lists MicroVMs filtered by exact name match, with optional pagination. +func (s *MicroVMsServiceOp) ListByName(ctx context.Context, name string, opt *ListOptions) ([]MicroVM, *Response, error) { + if name == "" { + return nil, nil, NewArgError("name", "cannot be empty") + } + filtered := &ListMicroVMsOptions{Name: name} + if opt != nil { + filtered.ListOptions = *opt + } + return s.ListFiltered(ctx, filtered) +} + +// ListByTag lists MicroVMs that carry the given resource tag. A tag that +// matches nothing returns an empty list rather than an error. +func (s *MicroVMsServiceOp) ListByTag(ctx context.Context, tag string, opt *ListOptions) ([]MicroVM, *Response, error) { + if tag == "" { + return nil, nil, NewArgError("tag", "cannot be empty") + } + filtered := &ListMicroVMsOptions{TagName: tag} + if opt != nil { + filtered.ListOptions = *opt + } + return s.ListFiltered(ctx, filtered) +} + +// ListFiltered lists MicroVMs using any combination of region, name, and tag. +func (s *MicroVMsServiceOp) ListFiltered(ctx context.Context, opt *ListMicroVMsOptions) ([]MicroVM, *Response, error) { + path := microVMBasePath + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(microVMsRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + + return root.MicroVMs, resp, nil +} + +// Get retrieves a MicroVM by its ID. +func (s *MicroVMsServiceOp) Get(ctx context.Context, id string) (*MicroVM, *Response, error) { + if id == "" { + return nil, nil, NewArgError("id", "cannot be empty") + } + + path := fmt.Sprintf("%s/%s", microVMBasePath, id) + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(microVMRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.MicroVM, resp, nil +} + +// Create provisions a new MicroVM with the provided configuration. +func (s *MicroVMsServiceOp) Create(ctx context.Context, createRequest *MicroVMCreateRequest) (*MicroVM, *Response, error) { + if createRequest == nil { + return nil, nil, NewArgError("createRequest", "cannot be nil") + } + + req, err := s.client.NewRequest(ctx, http.MethodPost, microVMBasePath, createRequest) + if err != nil { + return nil, nil, err + } + + root := new(microVMRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.MicroVM, resp, nil +} + +// Pause synchronously transitions a RUNNING MicroVM to PAUSED. It blocks +// until the platform has durably paused the MicroVM and returns the +// updated resource. The call is idempotent: pausing a MicroVM that is +// already PAUSED returns the current MicroVM with no side effects. +func (s *MicroVMsServiceOp) Pause(ctx context.Context, id string) (*MicroVM, *Response, error) { + return s.doTransition(ctx, id, "pause") +} + +// Resume synchronously transitions a PAUSED MicroVM to RUNNING. It blocks +// until the platform has durably resumed the MicroVM and returns the +// updated resource. The call is idempotent: resuming a MicroVM that is +// already RUNNING returns the current MicroVM with no side effects. +func (s *MicroVMsServiceOp) Resume(ctx context.Context, id string) (*MicroVM, *Response, error) { + return s.doTransition(ctx, id, "resume") +} + +// doTransition posts an empty body to a MicroVM transition sub-resource +// (e.g. /pause, /resume) and decodes the returned MicroVM. +func (s *MicroVMsServiceOp) doTransition(ctx context.Context, id, action string) (*MicroVM, *Response, error) { + if id == "" { + return nil, nil, NewArgError("id", "cannot be empty") + } + + path := fmt.Sprintf("%s/%s/%s", microVMBasePath, id, action) + + req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(microVMRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.MicroVM, resp, nil +} + +// Delete removes a MicroVM by its ID. The DigitalOcean API returns a 204 +// on success and does not include a response body. +func (s *MicroVMsServiceOp) Delete(ctx context.Context, id string) (*Response, error) { + if id == "" { + return nil, NewArgError("id", "cannot be empty") + } + + path := fmt.Sprintf("%s/%s", microVMBasePath, id) + + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// ListCheckpoints lists checkpoints for the authenticated team. Pass a +// MicroVMID on opt to filter to checkpoints captured from that MicroVM. +func (s *MicroVMsServiceOp) ListCheckpoints(ctx context.Context, opt *ListMicroVMCheckpointsOptions) ([]MicroVMCheckpoint, *Response, error) { + path := microVMCheckpointsBasePath + path, err := addOptions(path, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(microVMCheckpointsRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + if l := root.Links; l != nil { + resp.Links = l + } + if m := root.Meta; m != nil { + resp.Meta = m + } + + return root.Checkpoints, resp, nil +} + +// CreateCheckpoint starts an asynchronous checkpoint of a running MicroVM. +func (s *MicroVMsServiceOp) CreateCheckpoint(ctx context.Context, microVMID string, createRequest *MicroVMCheckpointCreateRequest) (*MicroVMCheckpoint, *Response, error) { + if microVMID == "" { + return nil, nil, NewArgError("microVMID", "cannot be empty") + } + if createRequest == nil { + createRequest = &MicroVMCheckpointCreateRequest{} + } + + path := fmt.Sprintf("%s/%s/checkpoints", microVMBasePath, microVMID) + + req, err := s.client.NewRequest(ctx, http.MethodPost, path, createRequest) + if err != nil { + return nil, nil, err + } + + root := new(microVMCheckpointRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Checkpoint, resp, nil +} + +// GetCheckpoint retrieves a checkpoint by its ID. +func (s *MicroVMsServiceOp) GetCheckpoint(ctx context.Context, id string) (*MicroVMCheckpoint, *Response, error) { + if id == "" { + return nil, nil, NewArgError("id", "cannot be empty") + } + + path := fmt.Sprintf("%s/%s", microVMCheckpointsBasePath, id) + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(microVMCheckpointRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Checkpoint, resp, nil +} + +// DeleteCheckpoint releases the state stored by a checkpoint. Returns a 204 +// on success with no response body. +func (s *MicroVMsServiceOp) DeleteCheckpoint(ctx context.Context, id string) (*Response, error) { + if id == "" { + return nil, NewArgError("id", "cannot be empty") + } + + path := fmt.Sprintf("%s/%s", microVMCheckpointsBasePath, id) + + req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// GetCreateOptions returns the sizes, the regions each one can be created in, +// the feature gates, and the account limits available to the authenticated +// team when creating a MicroVM. +func (s *MicroVMsServiceOp) GetCreateOptions(ctx context.Context) (*MicroVMCreateOptions, *Response, error) { + req, err := s.client.NewRequest(ctx, http.MethodGet, microVMOptionsPath, nil) + if err != nil { + return nil, nil, err + } + + opts := new(MicroVMCreateOptions) + resp, err := s.client.Do(ctx, req, opts) + if err != nil { + return nil, resp, err + } + + return opts, resp, nil +} + +// Exec runs a one-shot, non-PTY command in the MicroVM's workload container +// and returns its stdout, stderr, and exit code. Requires the exec_pty feature +// gate (see GetCreateOptions Features). A paused MicroVM is auto-resumed. +// A non-zero ExitCode is not a Go error — the API returns HTTP 200. +func (s *MicroVMsServiceOp) Exec(ctx context.Context, id string, execRequest *MicroVMExecRequest) (*MicroVMExecResult, *Response, error) { + if id == "" { + return nil, nil, NewArgError("id", "cannot be empty") + } + if execRequest == nil { + return nil, nil, NewArgError("execRequest", "cannot be nil") + } + if len(execRequest.Argv) == 0 { + return nil, nil, NewArgError("execRequest.Argv", "cannot be empty") + } + + path := fmt.Sprintf("%s/%s/exec", microVMBasePath, id) + + req, err := s.client.NewRequest(ctx, http.MethodPost, path, execRequest) + if err != nil { + return nil, nil, err + } + + result := new(MicroVMExecResult) + resp, err := s.client.Do(ctx, req, result) + if err != nil { + return nil, resp, err + } + + return result, resp, nil +} + +// ConsoleURL builds the WebSocket URL for an interactive PTY console on the +// MicroVM (GET /v2/microvms/{id}/console). godo does not dial WebSockets; the +// caller supplies its own client. The bearer token lives on godo's HTTP +// transport and is not attached to requests built here, so the caller must set +// Authorization: Bearer on the WebSocket handshake. Binary frames are +// raw terminal I/O; text frames carry resize (client→server) and error/exit/ +// status control (server→client) — see MarshalMicroVMConsoleResize and +// ParseMicroVMConsoleControl. +func (s *MicroVMsServiceOp) ConsoleURL(id string, opt *MicroVMConsoleOptions) (string, error) { + if id == "" { + return "", NewArgError("id", "cannot be empty") + } + if s.client == nil || s.client.BaseURL == nil { + return "", NewArgError("client.BaseURL", "cannot be nil") + } + + u, err := s.client.BaseURL.Parse(fmt.Sprintf("%s/%s/console", microVMBasePath, id)) + if err != nil { + return "", err + } + + switch u.Scheme { + case "https": + u.Scheme = "wss" + case "http": + u.Scheme = "ws" + } + + if opt != nil { + q := url.Values{} + if opt.Rows > 0 { + q.Set("rows", strconv.FormatUint(uint64(opt.Rows), 10)) + } + if opt.Cols > 0 { + q.Set("cols", strconv.FormatUint(uint64(opt.Cols), 10)) + } + u.RawQuery = q.Encode() + } + + return u.String(), nil +} + +// MarshalMicroVMConsoleResize encodes a client → server text frame that +// resizes the console PTY: {"resize":{"rows":N,"cols":M}}. +func MarshalMicroVMConsoleResize(rows, cols uint32) ([]byte, error) { + return json.Marshal(struct { + Resize struct { + Rows uint32 `json:"rows"` + Cols uint32 `json:"cols"` + } `json:"resize"` + }{Resize: struct { + Rows uint32 `json:"rows"` + Cols uint32 `json:"cols"` + }{Rows: rows, Cols: cols}}) +} + +// ParseMicroVMConsoleControl decodes a server → client text control frame +// (error, exit, or status). Binary frames are raw PTY stdout and should not +// be passed here. Returns an error if the payload is not valid JSON or does +// not contain any recognized control field. +func ParseMicroVMConsoleControl(data []byte) (*MicroVMConsoleControl, error) { + ctrl := new(MicroVMConsoleControl) + if err := json.Unmarshal(data, ctrl); err != nil { + return nil, err + } + if ctrl.Error == nil && ctrl.Exit == nil && ctrl.Status == nil { + return nil, fmt.Errorf("microvm console: unrecognized control frame") + } + return ctrl, nil +} diff --git a/vendor/github.com/digitalocean/godo/nfs.go b/vendor/github.com/digitalocean/godo/nfs.go index c47b8a8a5..a357b8408 100644 --- a/vendor/github.com/digitalocean/godo/nfs.go +++ b/vendor/github.com/digitalocean/godo/nfs.go @@ -176,6 +176,7 @@ type NfsAccessPoint struct { UpdatedAt string `json:"updated_at"` IsDefault bool `json:"is_default"` VpcID *string `json:"vpc_id,omitempty"` + VpcIDs []string `json:"vpc_ids,omitempty"` } // NfsCreateRequest represents a request to create an NFS share. diff --git a/vendor/github.com/digitalocean/godo/prepayment.go b/vendor/github.com/digitalocean/godo/prepayment.go new file mode 100644 index 000000000..2b9cf4d18 --- /dev/null +++ b/vendor/github.com/digitalocean/godo/prepayment.go @@ -0,0 +1,96 @@ +package godo + +import ( + "context" + "net/http" + "time" +) + +// PrepaymentService is an interface for interfacing with the Prepayment +// endpoints of the DigitalOcean API. +type PrepaymentService interface { + GetConfig(context.Context) (*PrepaymentConfigResponse, *Response, error) + GetStatus(context.Context) (*PrepaymentStatusResponse, *Response, error) +} + +// PrepaymentServiceOp handles communication with the Prepayment related methods of +// the DigitalOcean API. +type PrepaymentServiceOp struct { + client *Client +} + +var _ PrepaymentService = &PrepaymentServiceOp{} + +// PrepaymentConfig represents prepayment gate configuration for an account. +type PrepaymentConfig struct { + SpendLimit string `json:"spend_limit"` + IsAutoPrepayEnabled bool `json:"is_auto_prepay_enabled"` + PrepayAmount string `json:"prepay_amount"` + PrepayThreshold string `json:"prepay_threshold"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// PrepaymentStatus represents prepayment gate status for an account. +type PrepaymentStatus struct { + Balance string `json:"balance"` + IsAutoPrepayEnabled bool `json:"is_auto_prepay_enabled"` + Blocked bool `json:"blocked"` + Eligible bool `json:"eligible"` + MonthToDateBalance string `json:"month_to_date_balance"` +} + +// PrepaymentConfigResponse is the response from the public prepayment config endpoint. +type PrepaymentConfigResponse struct { + Config *PrepaymentConfig `json:"config"` + Status *PrepaymentStatus `json:"status"` +} + +func (r PrepaymentConfigResponse) String() string { + return Stringify(r) +} + +// PrepaymentStatusResponse is the response from the public prepayment status endpoint. +type PrepaymentStatusResponse struct { + Status *PrepaymentStatus `json:"status"` +} + +func (r PrepaymentStatusResponse) String() string { + return Stringify(r) +} + +// GetConfig retrieves the authenticated customer's prepayment configuration and status. +func (s *PrepaymentServiceOp) GetConfig(ctx context.Context) (*PrepaymentConfigResponse, *Response, error) { + path := "v2/customers/my/prepayment_config" + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(PrepaymentConfigResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, err +} + +// GetStatus retrieves the authenticated customer's prepayment status. +func (s *PrepaymentServiceOp) GetStatus(ctx context.Context) (*PrepaymentStatusResponse, *Response, error) { + path := "v2/customers/my/prepayment_status" + + req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(PrepaymentStatusResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, err +} diff --git a/vendor/github.com/digitalocean/godo/streaming.go b/vendor/github.com/digitalocean/godo/streaming.go index d35c66e2f..c13070a4e 100644 --- a/vendor/github.com/digitalocean/godo/streaming.go +++ b/vendor/github.com/digitalocean/godo/streaming.go @@ -20,6 +20,13 @@ type SSEEvent struct { // SSEReader parses a "text/event-stream" byte stream into SSEEvents. // Bare-CR line terminators are not supported. Not safe for concurrent use. type SSEReader struct { + // OnComment, when set, is called with the text of each comment line + // (after the leading ':' and one optional space). Comments are still + // never dispatched as events; this only makes them observable, for + // servers that carry out-of-band metadata on comment lines. The slice + // is only valid for the duration of the call. + OnComment func(comment []byte) + r *bufio.Reader lastID string scratch []byte @@ -75,6 +82,13 @@ func (s *SSEReader) Next() (*SSEEvent, error) { } if line[0] == ':' { + if s.OnComment != nil { + comment := line[1:] + if len(comment) > 0 && comment[0] == ' ' { + comment = comment[1:] + } + s.OnComment(comment) + } if eof { s.err = io.EOF return nil, io.EOF diff --git a/vendor/modules.txt b/vendor/modules.txt index 5ead63ac8..737c3a90e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -33,7 +33,7 @@ github.com/coreos/go-systemd/v22/journal # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc ## explicit github.com/davecgh/go-spew/spew -# github.com/digitalocean/godo v1.208.0 +# github.com/digitalocean/godo v1.217.0 ## explicit; go 1.23.0 github.com/digitalocean/godo github.com/digitalocean/godo/metrics