From 5e04228a5fdd20381b24b230c64e65761a444300 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:31:09 +0000 Subject: [PATCH 1/3] feat: Phani/deploy with GitHub url --- .stats.yml | 6 +-- README.md | 9 ++-- api.md | 6 +-- deployment.go | 70 ++++++++++++++++++++++++++++-- deployment_test.go | 21 ++++++--- extension.go | 104 ++++++++++++++++++++++----------------------- extension_test.go | 102 ++++++++++++++++++++++---------------------- 7 files changed, 195 insertions(+), 123 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6bb4af8..2bd40cc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 57 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6c765f1c4ce1c4dd4ceb371f56bf047aa79af36031ba43cbd68fa16a5fdb9bb3.yml -openapi_spec_hash: e9086f69281360f4e0895c9274a59531 -config_hash: deadfc4d2b0a947673bcf559b5db6e1b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6eaa6f5654abc94549962d7db1e8c7936af1f815bb3abe2f8249959394da1278.yml +openapi_spec_hash: 31ece7cd801e74228b80a8112a762e56 +config_hash: 3fc2057ce765bc5f27785a694ed0f553 diff --git a/README.md b/README.md index 6d5b4dd..ce6e3e1 100644 --- a/README.md +++ b/README.md @@ -391,20 +391,17 @@ which can be used to wrap any `io.Reader` with the appropriate file name and con // A file from the file system file, err := os.Open("/path/to/file") kernel.DeploymentNewParams{ - EntrypointRelPath: "src/app.py", - File: file, + File: file, } // A file from a string kernel.DeploymentNewParams{ - EntrypointRelPath: "src/app.py", - File: strings.NewReader("my file contents"), + File: strings.NewReader("my file contents"), } // With a custom filename and contentType kernel.DeploymentNewParams{ - EntrypointRelPath: "src/app.py", - File: kernel.File(strings.NewReader(`{"hello": "foo"}`), "file.go", "application/json"), + File: kernel.File(strings.NewReader(`{"hello": "foo"}`), "file.go", "application/json"), } ``` diff --git a/api.md b/api.md index 9c71da3..9f88845 100644 --- a/api.md +++ b/api.md @@ -181,13 +181,13 @@ Methods: Response Types: +- kernel.ExtensionNewResponse - kernel.ExtensionListResponse -- kernel.ExtensionUploadResponse Methods: +- client.Extensions.New(ctx context.Context, body kernel.ExtensionNewParams) (kernel.ExtensionNewResponse, error) +- client.Extensions.Get(ctx context.Context, idOrName string) (http.Response, error) - client.Extensions.List(ctx context.Context) ([]kernel.ExtensionListResponse, error) - client.Extensions.Delete(ctx context.Context, idOrName string) error -- client.Extensions.Download(ctx context.Context, idOrName string) (http.Response, error) - client.Extensions.DownloadFromChromeStore(ctx context.Context, query kernel.ExtensionDownloadFromChromeStoreParams) (http.Response, error) -- client.Extensions.Upload(ctx context.Context, body kernel.ExtensionUploadParams) (kernel.ExtensionUploadResponse, error) diff --git a/deployment.go b/deployment.go index c6d4779..7feea55 100644 --- a/deployment.go +++ b/deployment.go @@ -449,9 +449,7 @@ func (r *DeploymentFollowResponseAppVersionSummaryEvent) UnmarshalJSON(data []by type DeploymentNewParams struct { // Relative path to the entrypoint of the application - EntrypointRelPath string `json:"entrypoint_rel_path,required"` - // ZIP file containing the application source directory - File io.Reader `json:"file,omitzero,required" format:"binary"` + EntrypointRelPath param.Opt[string] `json:"entrypoint_rel_path,omitzero"` // Allow overwriting an existing app version Force param.Opt[bool] `json:"force,omitzero"` // Version of the application. Can be any string. @@ -459,10 +457,14 @@ type DeploymentNewParams struct { // Map of environment variables to set for the deployed application. Each key-value // pair represents an environment variable. EnvVars map[string]string `json:"env_vars,omitzero"` + // ZIP file containing the application source directory + File io.Reader `json:"file,omitzero" format:"binary"` // Region for deployment. Currently we only support "aws.us-east-1a" // // Any of "aws.us-east-1a". Region DeploymentNewParamsRegion `json:"region,omitzero"` + // Source from which to fetch application code. + Source DeploymentNewParamsSource `json:"source,omitzero"` paramObj } @@ -491,6 +493,68 @@ const ( DeploymentNewParamsRegionAwsUsEast1a DeploymentNewParamsRegion = "aws.us-east-1a" ) +// Source from which to fetch application code. +// +// The properties Entrypoint, Ref, Type, URL are required. +type DeploymentNewParamsSource struct { + // Relative path to the application entrypoint within the selected path. + Entrypoint string `json:"entrypoint,required"` + // Git ref (branch, tag, or commit SHA) to fetch. + Ref string `json:"ref,required"` + // Source type identifier. + // + // Any of "github". + Type string `json:"type,omitzero,required"` + // Base repository URL (without blob/tree suffixes). + URL string `json:"url,required"` + // Path within the repo to deploy (omit to use repo root). + Path param.Opt[string] `json:"path,omitzero"` + // Authentication for private repositories. + Auth DeploymentNewParamsSourceAuth `json:"auth,omitzero"` + paramObj +} + +func (r DeploymentNewParamsSource) MarshalJSON() (data []byte, err error) { + type shadow DeploymentNewParamsSource + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *DeploymentNewParamsSource) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +func init() { + apijson.RegisterFieldValidator[DeploymentNewParamsSource]( + "type", "github", + ) +} + +// Authentication for private repositories. +// +// The properties Token, Method are required. +type DeploymentNewParamsSourceAuth struct { + // GitHub PAT or installation access token + Token string `json:"token,required" format:"password"` + // Auth method + // + // Any of "github_token". + Method string `json:"method,omitzero,required"` + paramObj +} + +func (r DeploymentNewParamsSourceAuth) MarshalJSON() (data []byte, err error) { + type shadow DeploymentNewParamsSourceAuth + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *DeploymentNewParamsSourceAuth) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +func init() { + apijson.RegisterFieldValidator[DeploymentNewParamsSourceAuth]( + "method", "github_token", + ) +} + type DeploymentListParams struct { // Filter results by application name. AppName param.Opt[string] `query:"app_name,omitzero" json:"-"` diff --git a/deployment_test.go b/deployment_test.go index 81715eb..5472dfb 100644 --- a/deployment_test.go +++ b/deployment_test.go @@ -29,13 +29,24 @@ func TestDeploymentNewWithOptionalParams(t *testing.T) { option.WithAPIKey("My API Key"), ) _, err := client.Deployments.New(context.TODO(), kernel.DeploymentNewParams{ - EntrypointRelPath: "src/app.py", - File: io.Reader(bytes.NewBuffer([]byte("some file contents"))), + EntrypointRelPath: kernel.String("src/app.py"), EnvVars: map[string]string{ - "foo": "string", + "FOO": "bar", + }, + File: io.Reader(bytes.NewBuffer([]byte("some file contents"))), + Force: kernel.Bool(false), + Region: kernel.DeploymentNewParamsRegionAwsUsEast1a, + Source: kernel.DeploymentNewParamsSource{ + Entrypoint: "src/index.ts", + Ref: "main", + Type: "github", + URL: "https://github.com/org/repo", + Auth: kernel.DeploymentNewParamsSourceAuth{ + Token: "ghs_***", + Method: "github_token", + }, + Path: kernel.String("apps/api"), }, - Force: kernel.Bool(false), - Region: kernel.DeploymentNewParamsRegionAwsUsEast1a, Version: kernel.String("1.0.0"), }) if err != nil { diff --git a/extension.go b/extension.go index 204b126..b1af774 100644 --- a/extension.go +++ b/extension.go @@ -42,37 +42,46 @@ func NewExtensionService(opts ...option.RequestOption) (r ExtensionService) { return } -// List extensions owned by the caller's organization. -func (r *ExtensionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]ExtensionListResponse, err error) { +// Upload a zip file containing an unpacked browser extension. Optionally provide a +// unique name for later reference. +func (r *ExtensionService) New(ctx context.Context, body ExtensionNewParams, opts ...option.RequestOption) (res *ExtensionNewResponse, err error) { opts = slices.Concat(r.Options, opts) path := "extensions" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return } -// Delete an extension by its ID or by its name. -func (r *ExtensionService) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error) { +// Download the extension as a ZIP archive by ID or name. +func (r *ExtensionService) Get(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...) if idOrName == "" { err = errors.New("missing required id_or_name parameter") return } path := fmt.Sprintf("extensions/%s", idOrName) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return } -// Download the extension as a ZIP archive by ID or name. -func (r *ExtensionService) Download(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error) { +// List extensions owned by the caller's organization. +func (r *ExtensionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]ExtensionListResponse, err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...) + path := "extensions" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return +} + +// Delete an extension by its ID or by its name. +func (r *ExtensionService) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) if idOrName == "" { err = errors.New("missing required id_or_name parameter") return } path := fmt.Sprintf("extensions/%s", idOrName) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) return } @@ -86,17 +95,8 @@ func (r *ExtensionService) DownloadFromChromeStore(ctx context.Context, query Ex return } -// Upload a zip file containing an unpacked browser extension. Optionally provide a -// unique name for later reference. -func (r *ExtensionService) Upload(ctx context.Context, body ExtensionUploadParams, opts ...option.RequestOption) (res *ExtensionUploadResponse, err error) { - opts = slices.Concat(r.Options, opts) - path := "extensions" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return -} - // A browser extension uploaded to Kernel. -type ExtensionListResponse struct { +type ExtensionNewResponse struct { // Unique identifier for the extension ID string `json:"id,required"` // Timestamp when the extension was created @@ -121,13 +121,13 @@ type ExtensionListResponse struct { } // Returns the unmodified JSON received from the API -func (r ExtensionListResponse) RawJSON() string { return r.JSON.raw } -func (r *ExtensionListResponse) UnmarshalJSON(data []byte) error { +func (r ExtensionNewResponse) RawJSON() string { return r.JSON.raw } +func (r *ExtensionNewResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } // A browser extension uploaded to Kernel. -type ExtensionUploadResponse struct { +type ExtensionListResponse struct { // Unique identifier for the extension ID string `json:"id,required"` // Timestamp when the extension was created @@ -152,11 +152,37 @@ type ExtensionUploadResponse struct { } // Returns the unmodified JSON received from the API -func (r ExtensionUploadResponse) RawJSON() string { return r.JSON.raw } -func (r *ExtensionUploadResponse) UnmarshalJSON(data []byte) error { +func (r ExtensionListResponse) RawJSON() string { return r.JSON.raw } +func (r *ExtensionListResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +type ExtensionNewParams struct { + // ZIP file containing the browser extension. + File io.Reader `json:"file,omitzero,required" format:"binary"` + // Optional unique name within the organization to reference this extension. + Name param.Opt[string] `json:"name,omitzero"` + paramObj +} + +func (r ExtensionNewParams) MarshalMultipart() (data []byte, contentType string, err error) { + buf := bytes.NewBuffer(nil) + writer := multipart.NewWriter(buf) + err = apiform.MarshalRoot(r, writer) + if err == nil { + err = apiform.WriteExtras(writer, r.ExtraFields()) + } + if err != nil { + writer.Close() + return nil, "", err + } + err = writer.Close() + if err != nil { + return nil, "", err + } + return buf.Bytes(), writer.FormDataContentType(), nil +} + type ExtensionDownloadFromChromeStoreParams struct { // Chrome Web Store URL for the extension. URL string `query:"url,required" json:"-"` @@ -184,29 +210,3 @@ const ( ExtensionDownloadFromChromeStoreParamsOsMac ExtensionDownloadFromChromeStoreParamsOs = "mac" ExtensionDownloadFromChromeStoreParamsOsLinux ExtensionDownloadFromChromeStoreParamsOs = "linux" ) - -type ExtensionUploadParams struct { - // ZIP file containing the browser extension. - File io.Reader `json:"file,omitzero,required" format:"binary"` - // Optional unique name within the organization to reference this extension. - Name param.Opt[string] `json:"name,omitzero"` - paramObj -} - -func (r ExtensionUploadParams) MarshalMultipart() (data []byte, contentType string, err error) { - buf := bytes.NewBuffer(nil) - writer := multipart.NewWriter(buf) - err = apiform.MarshalRoot(r, writer) - if err == nil { - err = apiform.WriteExtras(writer, r.ExtraFields()) - } - if err != nil { - writer.Close() - return nil, "", err - } - err = writer.Close() - if err != nil { - return nil, "", err - } - return buf.Bytes(), writer.FormDataContentType(), nil -} diff --git a/extension_test.go b/extension_test.go index af2089a..2df18d2 100644 --- a/extension_test.go +++ b/extension_test.go @@ -17,7 +17,7 @@ import ( "github.com/onkernel/kernel-go-sdk/option" ) -func TestExtensionList(t *testing.T) { +func TestExtensionNewWithOptionalParams(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -30,7 +30,10 @@ func TestExtensionList(t *testing.T) { option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) - _, err := client.Extensions.List(context.TODO()) + _, err := client.Extensions.New(context.TODO(), kernel.ExtensionNewParams{ + File: io.Reader(bytes.NewBuffer([]byte("some file contents"))), + Name: kernel.String("name"), + }) if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -40,20 +43,18 @@ func TestExtensionList(t *testing.T) { } } -func TestExtensionDelete(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } +func TestExtensionGet(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte("abc")) + })) + defer server.Close() + baseURL := server.URL client := kernel.NewClient( option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) - err := client.Extensions.Delete(context.TODO(), "id_or_name") + resp, err := client.Extensions.Get(context.TODO(), "id_or_name") if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -61,20 +62,35 @@ func TestExtensionDelete(t *testing.T) { } t.Fatalf("err should be nil: %s", err.Error()) } + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } + if !bytes.Equal(b, []byte("abc")) { + t.Fatalf("return value not %s: %s", "abc", b) + } } -func TestExtensionDownload(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - w.Write([]byte("abc")) - })) - defer server.Close() - baseURL := server.URL +func TestExtensionList(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } client := kernel.NewClient( option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) - resp, err := client.Extensions.Download(context.TODO(), "id_or_name") + _, err := client.Extensions.List(context.TODO()) if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -82,9 +98,22 @@ func TestExtensionDownload(t *testing.T) { } t.Fatalf("err should be nil: %s", err.Error()) } - defer resp.Body.Close() +} - b, err := io.ReadAll(resp.Body) +func TestExtensionDelete(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Extensions.Delete(context.TODO(), "id_or_name") if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -92,9 +121,6 @@ func TestExtensionDownload(t *testing.T) { } t.Fatalf("err should be nil: %s", err.Error()) } - if !bytes.Equal(b, []byte("abc")) { - t.Fatalf("return value not %s: %s", "abc", b) - } } func TestExtensionDownloadFromChromeStoreWithOptionalParams(t *testing.T) { @@ -133,29 +159,3 @@ func TestExtensionDownloadFromChromeStoreWithOptionalParams(t *testing.T) { t.Fatalf("return value not %s: %s", "abc", b) } } - -func TestExtensionUploadWithOptionalParams(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := kernel.NewClient( - option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), - ) - _, err := client.Extensions.Upload(context.TODO(), kernel.ExtensionUploadParams{ - File: io.Reader(bytes.NewBuffer([]byte("some file contents"))), - Name: kernel.String("name"), - }) - if err != nil { - var apierr *kernel.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} From b1dcdedf9662f112048d27622a787b2bacb5ac84 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:33:21 +0000 Subject: [PATCH 2/3] feat: click mouse, move mouse, screenshot --- .stats.yml | 8 +- api.md | 18 ++- browser.go | 12 +- browsercomputer.go | 323 ++++++++++++++++++++++++++++++++++++++++ browsercomputer_test.go | 256 +++++++++++++++++++++++++++++++ extension.go | 104 ++++++------- extension_test.go | 102 ++++++------- 7 files changed, 708 insertions(+), 115 deletions(-) create mode 100644 browsercomputer.go create mode 100644 browsercomputer_test.go diff --git a/.stats.yml b/.stats.yml index 2bd40cc..b4dc606 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 57 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6eaa6f5654abc94549962d7db1e8c7936af1f815bb3abe2f8249959394da1278.yml -openapi_spec_hash: 31ece7cd801e74228b80a8112a762e56 -config_hash: 3fc2057ce765bc5f27785a694ed0f553 +configured_endpoints: 64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e21f0324774a1762bc2bba0da3a8a6b0d0e74720d7a1c83dec813f9e027fcf58.yml +openapi_spec_hash: f1b636abfd6cb8e7c2ba7ffb8e53b9ba +config_hash: 09a2df23048cb16689c9a390d9e5bc47 diff --git a/api.md b/api.md index 9f88845..906226c 100644 --- a/api.md +++ b/api.md @@ -152,6 +152,18 @@ Methods: - client.Browsers.Logs.Stream(ctx context.Context, id string, query kernel.BrowserLogStreamParams) (shared.LogEvent, error) +## Computer + +Methods: + +- client.Browsers.Computer.CaptureScreenshot(ctx context.Context, id string, body kernel.BrowserComputerCaptureScreenshotParams) (http.Response, error) +- client.Browsers.Computer.ClickMouse(ctx context.Context, id string, body kernel.BrowserComputerClickMouseParams) error +- client.Browsers.Computer.DragMouse(ctx context.Context, id string, body kernel.BrowserComputerDragMouseParams) error +- client.Browsers.Computer.MoveMouse(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams) error +- client.Browsers.Computer.PressKey(ctx context.Context, id string, body kernel.BrowserComputerPressKeyParams) error +- client.Browsers.Computer.Scroll(ctx context.Context, id string, body kernel.BrowserComputerScrollParams) error +- client.Browsers.Computer.TypeText(ctx context.Context, id string, body kernel.BrowserComputerTypeTextParams) error + # Profiles Methods: @@ -181,13 +193,13 @@ Methods: Response Types: -- kernel.ExtensionNewResponse - kernel.ExtensionListResponse +- kernel.ExtensionUploadResponse Methods: -- client.Extensions.New(ctx context.Context, body kernel.ExtensionNewParams) (kernel.ExtensionNewResponse, error) -- client.Extensions.Get(ctx context.Context, idOrName string) (http.Response, error) - client.Extensions.List(ctx context.Context) ([]kernel.ExtensionListResponse, error) - client.Extensions.Delete(ctx context.Context, idOrName string) error +- client.Extensions.Download(ctx context.Context, idOrName string) (http.Response, error) - client.Extensions.DownloadFromChromeStore(ctx context.Context, query kernel.ExtensionDownloadFromChromeStoreParams) (http.Response, error) +- client.Extensions.Upload(ctx context.Context, body kernel.ExtensionUploadParams) (kernel.ExtensionUploadResponse, error) diff --git a/browser.go b/browser.go index b5b4222..6e067e8 100644 --- a/browser.go +++ b/browser.go @@ -31,11 +31,12 @@ import ( // automatically. You should not instantiate this service directly, and instead use // the [NewBrowserService] method instead. type BrowserService struct { - Options []option.RequestOption - Replays BrowserReplayService - Fs BrowserFService - Process BrowserProcessService - Logs BrowserLogService + Options []option.RequestOption + Replays BrowserReplayService + Fs BrowserFService + Process BrowserProcessService + Logs BrowserLogService + Computer BrowserComputerService } // NewBrowserService generates a new service that applies the given options to each @@ -48,6 +49,7 @@ func NewBrowserService(opts ...option.RequestOption) (r BrowserService) { r.Fs = NewBrowserFService(opts...) r.Process = NewBrowserProcessService(opts...) r.Logs = NewBrowserLogService(opts...) + r.Computer = NewBrowserComputerService(opts...) return } diff --git a/browsercomputer.go b/browsercomputer.go new file mode 100644 index 0000000..4158a7c --- /dev/null +++ b/browsercomputer.go @@ -0,0 +1,323 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package kernel + +import ( + "context" + "errors" + "fmt" + "net/http" + "slices" + + "github.com/onkernel/kernel-go-sdk/internal/apijson" + "github.com/onkernel/kernel-go-sdk/internal/requestconfig" + "github.com/onkernel/kernel-go-sdk/option" + "github.com/onkernel/kernel-go-sdk/packages/param" +) + +// BrowserComputerService contains methods and other services that help with +// interacting with the kernel API. +// +// Note, unlike clients, this service does not read variables from the environment +// automatically. You should not instantiate this service directly, and instead use +// the [NewBrowserComputerService] method instead. +type BrowserComputerService struct { + Options []option.RequestOption +} + +// NewBrowserComputerService generates a new service that applies the given options +// to each request. These options are applied after the parent client's options (if +// there is one), and before any request-specific options. +func NewBrowserComputerService(opts ...option.RequestOption) (r BrowserComputerService) { + r = BrowserComputerService{} + r.Options = opts + return +} + +// Capture a screenshot of the browser instance +func (r *BrowserComputerService) CaptureScreenshot(ctx context.Context, id string, body BrowserComputerCaptureScreenshotParams, opts ...option.RequestOption) (res *http.Response, err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "image/png")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/screenshot", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + +// Simulate a mouse click action on the browser instance +func (r *BrowserComputerService) ClickMouse(ctx context.Context, id string, body BrowserComputerClickMouseParams, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/click_mouse", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) + return +} + +// Drag the mouse along a path +func (r *BrowserComputerService) DragMouse(ctx context.Context, id string, body BrowserComputerDragMouseParams, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/drag_mouse", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) + return +} + +// Move the mouse cursor to the specified coordinates on the browser instance +func (r *BrowserComputerService) MoveMouse(ctx context.Context, id string, body BrowserComputerMoveMouseParams, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/move_mouse", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) + return +} + +// Press one or more keys on the host computer +func (r *BrowserComputerService) PressKey(ctx context.Context, id string, body BrowserComputerPressKeyParams, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/press_key", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) + return +} + +// Scroll the mouse wheel at a position on the host computer +func (r *BrowserComputerService) Scroll(ctx context.Context, id string, body BrowserComputerScrollParams, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/scroll", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) + return +} + +// Type text on the browser instance +func (r *BrowserComputerService) TypeText(ctx context.Context, id string, body BrowserComputerTypeTextParams, opts ...option.RequestOption) (err error) { + opts = slices.Concat(r.Options, opts) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("browsers/%s/computer/type", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) + return +} + +type BrowserComputerCaptureScreenshotParams struct { + Region BrowserComputerCaptureScreenshotParamsRegion `json:"region,omitzero"` + paramObj +} + +func (r BrowserComputerCaptureScreenshotParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerCaptureScreenshotParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerCaptureScreenshotParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// The properties Height, Width, X, Y are required. +type BrowserComputerCaptureScreenshotParamsRegion struct { + // Height of the region in pixels + Height int64 `json:"height,required"` + // Width of the region in pixels + Width int64 `json:"width,required"` + // X coordinate of the region's top-left corner + X int64 `json:"x,required"` + // Y coordinate of the region's top-left corner + Y int64 `json:"y,required"` + paramObj +} + +func (r BrowserComputerCaptureScreenshotParamsRegion) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerCaptureScreenshotParamsRegion + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerCaptureScreenshotParamsRegion) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type BrowserComputerClickMouseParams struct { + // X coordinate of the click position + X int64 `json:"x,required"` + // Y coordinate of the click position + Y int64 `json:"y,required"` + // Number of times to repeat the click + NumClicks param.Opt[int64] `json:"num_clicks,omitzero"` + // Mouse button to interact with + // + // Any of "left", "right", "middle", "back", "forward". + Button BrowserComputerClickMouseParamsButton `json:"button,omitzero"` + // Type of click action + // + // Any of "down", "up", "click". + ClickType BrowserComputerClickMouseParamsClickType `json:"click_type,omitzero"` + // Modifier keys to hold during the click + HoldKeys []string `json:"hold_keys,omitzero"` + paramObj +} + +func (r BrowserComputerClickMouseParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerClickMouseParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerClickMouseParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Mouse button to interact with +type BrowserComputerClickMouseParamsButton string + +const ( + BrowserComputerClickMouseParamsButtonLeft BrowserComputerClickMouseParamsButton = "left" + BrowserComputerClickMouseParamsButtonRight BrowserComputerClickMouseParamsButton = "right" + BrowserComputerClickMouseParamsButtonMiddle BrowserComputerClickMouseParamsButton = "middle" + BrowserComputerClickMouseParamsButtonBack BrowserComputerClickMouseParamsButton = "back" + BrowserComputerClickMouseParamsButtonForward BrowserComputerClickMouseParamsButton = "forward" +) + +// Type of click action +type BrowserComputerClickMouseParamsClickType string + +const ( + BrowserComputerClickMouseParamsClickTypeDown BrowserComputerClickMouseParamsClickType = "down" + BrowserComputerClickMouseParamsClickTypeUp BrowserComputerClickMouseParamsClickType = "up" + BrowserComputerClickMouseParamsClickTypeClick BrowserComputerClickMouseParamsClickType = "click" +) + +type BrowserComputerDragMouseParams struct { + // Ordered list of [x, y] coordinate pairs to move through while dragging. Must + // contain at least 2 points. + Path [][]int64 `json:"path,omitzero,required"` + // Delay in milliseconds between button down and starting to move along the path. + Delay param.Opt[int64] `json:"delay,omitzero"` + // Delay in milliseconds between relative steps while dragging (not the initial + // delay). + StepDelayMs param.Opt[int64] `json:"step_delay_ms,omitzero"` + // Number of relative move steps per segment in the path. Minimum 1. + StepsPerSegment param.Opt[int64] `json:"steps_per_segment,omitzero"` + // Mouse button to drag with + // + // Any of "left", "middle", "right". + Button BrowserComputerDragMouseParamsButton `json:"button,omitzero"` + // Modifier keys to hold during the drag + HoldKeys []string `json:"hold_keys,omitzero"` + paramObj +} + +func (r BrowserComputerDragMouseParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerDragMouseParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerDragMouseParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Mouse button to drag with +type BrowserComputerDragMouseParamsButton string + +const ( + BrowserComputerDragMouseParamsButtonLeft BrowserComputerDragMouseParamsButton = "left" + BrowserComputerDragMouseParamsButtonMiddle BrowserComputerDragMouseParamsButton = "middle" + BrowserComputerDragMouseParamsButtonRight BrowserComputerDragMouseParamsButton = "right" +) + +type BrowserComputerMoveMouseParams struct { + // X coordinate to move the cursor to + X int64 `json:"x,required"` + // Y coordinate to move the cursor to + Y int64 `json:"y,required"` + // Modifier keys to hold during the move + HoldKeys []string `json:"hold_keys,omitzero"` + paramObj +} + +func (r BrowserComputerMoveMouseParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerMoveMouseParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerMoveMouseParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type BrowserComputerPressKeyParams struct { + // List of key symbols to press. Each item should be a key symbol supported by + // xdotool (see X11 keysym definitions). Examples include "Return", "Shift", + // "Ctrl", "Alt", "F5". Items in this list could also be combinations, e.g. + // "Ctrl+t" or "Ctrl+Shift+Tab". + Keys []string `json:"keys,omitzero,required"` + // Duration to hold the keys down in milliseconds. If omitted or 0, keys are + // tapped. + Duration param.Opt[int64] `json:"duration,omitzero"` + // Optional modifier keys to hold during the key press sequence. + HoldKeys []string `json:"hold_keys,omitzero"` + paramObj +} + +func (r BrowserComputerPressKeyParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerPressKeyParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerPressKeyParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type BrowserComputerScrollParams struct { + // X coordinate at which to perform the scroll + X int64 `json:"x,required"` + // Y coordinate at which to perform the scroll + Y int64 `json:"y,required"` + // Horizontal scroll amount. Positive scrolls right, negative scrolls left. + DeltaX param.Opt[int64] `json:"delta_x,omitzero"` + // Vertical scroll amount. Positive scrolls down, negative scrolls up. + DeltaY param.Opt[int64] `json:"delta_y,omitzero"` + // Modifier keys to hold during the scroll + HoldKeys []string `json:"hold_keys,omitzero"` + paramObj +} + +func (r BrowserComputerScrollParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerScrollParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerScrollParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type BrowserComputerTypeTextParams struct { + // Text to type on the browser instance + Text string `json:"text,required"` + // Delay in milliseconds between keystrokes + Delay param.Opt[int64] `json:"delay,omitzero"` + paramObj +} + +func (r BrowserComputerTypeTextParams) MarshalJSON() (data []byte, err error) { + type shadow BrowserComputerTypeTextParams + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *BrowserComputerTypeTextParams) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} diff --git a/browsercomputer_test.go b/browsercomputer_test.go new file mode 100644 index 0000000..0391cd9 --- /dev/null +++ b/browsercomputer_test.go @@ -0,0 +1,256 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package kernel_test + +import ( + "bytes" + "context" + "errors" + "io" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/onkernel/kernel-go-sdk" + "github.com/onkernel/kernel-go-sdk/internal/testutil" + "github.com/onkernel/kernel-go-sdk/option" +) + +func TestBrowserComputerCaptureScreenshotWithOptionalParams(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte("abc")) + })) + defer server.Close() + baseURL := server.URL + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + resp, err := client.Browsers.Computer.CaptureScreenshot( + context.TODO(), + "id", + kernel.BrowserComputerCaptureScreenshotParams{ + Region: kernel.BrowserComputerCaptureScreenshotParamsRegion{ + Height: 0, + Width: 0, + X: 0, + Y: 0, + }, + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } + if !bytes.Equal(b, []byte("abc")) { + t.Fatalf("return value not %s: %s", "abc", b) + } +} + +func TestBrowserComputerClickMouseWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Browsers.Computer.ClickMouse( + context.TODO(), + "id", + kernel.BrowserComputerClickMouseParams{ + X: 0, + Y: 0, + Button: kernel.BrowserComputerClickMouseParamsButtonLeft, + ClickType: kernel.BrowserComputerClickMouseParamsClickTypeDown, + HoldKeys: []string{"string"}, + NumClicks: kernel.Int(0), + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestBrowserComputerDragMouseWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Browsers.Computer.DragMouse( + context.TODO(), + "id", + kernel.BrowserComputerDragMouseParams{ + Path: [][]int64{{0, 0}, {0, 0}}, + Button: kernel.BrowserComputerDragMouseParamsButtonLeft, + Delay: kernel.Int(0), + HoldKeys: []string{"string"}, + StepDelayMs: kernel.Int(0), + StepsPerSegment: kernel.Int(1), + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestBrowserComputerMoveMouseWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Browsers.Computer.MoveMouse( + context.TODO(), + "id", + kernel.BrowserComputerMoveMouseParams{ + X: 0, + Y: 0, + HoldKeys: []string{"string"}, + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestBrowserComputerPressKeyWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Browsers.Computer.PressKey( + context.TODO(), + "id", + kernel.BrowserComputerPressKeyParams{ + Keys: []string{"string"}, + Duration: kernel.Int(0), + HoldKeys: []string{"string"}, + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestBrowserComputerScrollWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Browsers.Computer.Scroll( + context.TODO(), + "id", + kernel.BrowserComputerScrollParams{ + X: 0, + Y: 0, + DeltaX: kernel.Int(0), + DeltaY: kernel.Int(0), + HoldKeys: []string{"string"}, + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + +func TestBrowserComputerTypeTextWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + err := client.Browsers.Computer.TypeText( + context.TODO(), + "id", + kernel.BrowserComputerTypeTextParams{ + Text: "text", + Delay: kernel.Int(0), + }, + ) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} diff --git a/extension.go b/extension.go index b1af774..204b126 100644 --- a/extension.go +++ b/extension.go @@ -42,46 +42,37 @@ func NewExtensionService(opts ...option.RequestOption) (r ExtensionService) { return } -// Upload a zip file containing an unpacked browser extension. Optionally provide a -// unique name for later reference. -func (r *ExtensionService) New(ctx context.Context, body ExtensionNewParams, opts ...option.RequestOption) (res *ExtensionNewResponse, err error) { +// List extensions owned by the caller's organization. +func (r *ExtensionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]ExtensionListResponse, err error) { opts = slices.Concat(r.Options, opts) path := "extensions" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return } -// Download the extension as a ZIP archive by ID or name. -func (r *ExtensionService) Get(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error) { +// Delete an extension by its ID or by its name. +func (r *ExtensionService) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) if idOrName == "" { err = errors.New("missing required id_or_name parameter") return } path := fmt.Sprintf("extensions/%s", idOrName) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) - return -} - -// List extensions owned by the caller's organization. -func (r *ExtensionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]ExtensionListResponse, err error) { - opts = slices.Concat(r.Options, opts) - path := "extensions" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) return } -// Delete an extension by its ID or by its name. -func (r *ExtensionService) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error) { +// Download the extension as a ZIP archive by ID or name. +func (r *ExtensionService) Download(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...) if idOrName == "" { err = errors.New("missing required id_or_name parameter") return } path := fmt.Sprintf("extensions/%s", idOrName) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) return } @@ -95,8 +86,17 @@ func (r *ExtensionService) DownloadFromChromeStore(ctx context.Context, query Ex return } +// Upload a zip file containing an unpacked browser extension. Optionally provide a +// unique name for later reference. +func (r *ExtensionService) Upload(ctx context.Context, body ExtensionUploadParams, opts ...option.RequestOption) (res *ExtensionUploadResponse, err error) { + opts = slices.Concat(r.Options, opts) + path := "extensions" + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + // A browser extension uploaded to Kernel. -type ExtensionNewResponse struct { +type ExtensionListResponse struct { // Unique identifier for the extension ID string `json:"id,required"` // Timestamp when the extension was created @@ -121,13 +121,13 @@ type ExtensionNewResponse struct { } // Returns the unmodified JSON received from the API -func (r ExtensionNewResponse) RawJSON() string { return r.JSON.raw } -func (r *ExtensionNewResponse) UnmarshalJSON(data []byte) error { +func (r ExtensionListResponse) RawJSON() string { return r.JSON.raw } +func (r *ExtensionListResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } // A browser extension uploaded to Kernel. -type ExtensionListResponse struct { +type ExtensionUploadResponse struct { // Unique identifier for the extension ID string `json:"id,required"` // Timestamp when the extension was created @@ -152,37 +152,11 @@ type ExtensionListResponse struct { } // Returns the unmodified JSON received from the API -func (r ExtensionListResponse) RawJSON() string { return r.JSON.raw } -func (r *ExtensionListResponse) UnmarshalJSON(data []byte) error { +func (r ExtensionUploadResponse) RawJSON() string { return r.JSON.raw } +func (r *ExtensionUploadResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -type ExtensionNewParams struct { - // ZIP file containing the browser extension. - File io.Reader `json:"file,omitzero,required" format:"binary"` - // Optional unique name within the organization to reference this extension. - Name param.Opt[string] `json:"name,omitzero"` - paramObj -} - -func (r ExtensionNewParams) MarshalMultipart() (data []byte, contentType string, err error) { - buf := bytes.NewBuffer(nil) - writer := multipart.NewWriter(buf) - err = apiform.MarshalRoot(r, writer) - if err == nil { - err = apiform.WriteExtras(writer, r.ExtraFields()) - } - if err != nil { - writer.Close() - return nil, "", err - } - err = writer.Close() - if err != nil { - return nil, "", err - } - return buf.Bytes(), writer.FormDataContentType(), nil -} - type ExtensionDownloadFromChromeStoreParams struct { // Chrome Web Store URL for the extension. URL string `query:"url,required" json:"-"` @@ -210,3 +184,29 @@ const ( ExtensionDownloadFromChromeStoreParamsOsMac ExtensionDownloadFromChromeStoreParamsOs = "mac" ExtensionDownloadFromChromeStoreParamsOsLinux ExtensionDownloadFromChromeStoreParamsOs = "linux" ) + +type ExtensionUploadParams struct { + // ZIP file containing the browser extension. + File io.Reader `json:"file,omitzero,required" format:"binary"` + // Optional unique name within the organization to reference this extension. + Name param.Opt[string] `json:"name,omitzero"` + paramObj +} + +func (r ExtensionUploadParams) MarshalMultipart() (data []byte, contentType string, err error) { + buf := bytes.NewBuffer(nil) + writer := multipart.NewWriter(buf) + err = apiform.MarshalRoot(r, writer) + if err == nil { + err = apiform.WriteExtras(writer, r.ExtraFields()) + } + if err != nil { + writer.Close() + return nil, "", err + } + err = writer.Close() + if err != nil { + return nil, "", err + } + return buf.Bytes(), writer.FormDataContentType(), nil +} diff --git a/extension_test.go b/extension_test.go index 2df18d2..af2089a 100644 --- a/extension_test.go +++ b/extension_test.go @@ -17,7 +17,7 @@ import ( "github.com/onkernel/kernel-go-sdk/option" ) -func TestExtensionNewWithOptionalParams(t *testing.T) { +func TestExtensionList(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -30,41 +30,7 @@ func TestExtensionNewWithOptionalParams(t *testing.T) { option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) - _, err := client.Extensions.New(context.TODO(), kernel.ExtensionNewParams{ - File: io.Reader(bytes.NewBuffer([]byte("some file contents"))), - Name: kernel.String("name"), - }) - if err != nil { - var apierr *kernel.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} - -func TestExtensionGet(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - w.Write([]byte("abc")) - })) - defer server.Close() - baseURL := server.URL - client := kernel.NewClient( - option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), - ) - resp, err := client.Extensions.Get(context.TODO(), "id_or_name") - if err != nil { - var apierr *kernel.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } - defer resp.Body.Close() - - b, err := io.ReadAll(resp.Body) + _, err := client.Extensions.List(context.TODO()) if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -72,12 +38,9 @@ func TestExtensionGet(t *testing.T) { } t.Fatalf("err should be nil: %s", err.Error()) } - if !bytes.Equal(b, []byte("abc")) { - t.Fatalf("return value not %s: %s", "abc", b) - } } -func TestExtensionList(t *testing.T) { +func TestExtensionDelete(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { @@ -90,7 +53,7 @@ func TestExtensionList(t *testing.T) { option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) - _, err := client.Extensions.List(context.TODO()) + err := client.Extensions.Delete(context.TODO(), "id_or_name") if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -100,20 +63,28 @@ func TestExtensionList(t *testing.T) { } } -func TestExtensionDelete(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } +func TestExtensionDownload(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Write([]byte("abc")) + })) + defer server.Close() + baseURL := server.URL client := kernel.NewClient( option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) - err := client.Extensions.Delete(context.TODO(), "id_or_name") + resp, err := client.Extensions.Download(context.TODO(), "id_or_name") + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) if err != nil { var apierr *kernel.Error if errors.As(err, &apierr) { @@ -121,6 +92,9 @@ func TestExtensionDelete(t *testing.T) { } t.Fatalf("err should be nil: %s", err.Error()) } + if !bytes.Equal(b, []byte("abc")) { + t.Fatalf("return value not %s: %s", "abc", b) + } } func TestExtensionDownloadFromChromeStoreWithOptionalParams(t *testing.T) { @@ -159,3 +133,29 @@ func TestExtensionDownloadFromChromeStoreWithOptionalParams(t *testing.T) { t.Fatalf("return value not %s: %s", "abc", b) } } + +func TestExtensionUploadWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + baseURL := "http://localhost:4010" + if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { + baseURL = envURL + } + if !testutil.CheckTestServer(t, baseURL) { + return + } + client := kernel.NewClient( + option.WithBaseURL(baseURL), + option.WithAPIKey("My API Key"), + ) + _, err := client.Extensions.Upload(context.TODO(), kernel.ExtensionUploadParams{ + File: io.Reader(bytes.NewBuffer([]byte("some file contents"))), + Name: kernel.String("name"), + }) + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} From cd965956eada2de5e2e55257c66840a0b3d8c7a4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:33:38 +0000 Subject: [PATCH 3/3] release: 0.15.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ README.md | 2 +- internal/version.go | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3d26904..8f3e0a4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.14.2" + ".": "0.15.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c9f937..0155032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.15.0 (2025-10-17) + +Full Changelog: [v0.14.2...v0.15.0](https://github.com/onkernel/kernel-go-sdk/compare/v0.14.2...v0.15.0) + +### Features + +* click mouse, move mouse, screenshot ([b1dcded](https://github.com/onkernel/kernel-go-sdk/commit/b1dcdedf9662f112048d27622a787b2bacb5ac84)) +* Phani/deploy with GitHub url ([5e04228](https://github.com/onkernel/kernel-go-sdk/commit/5e04228a5fdd20381b24b230c64e65761a444300)) + ## 0.14.2 (2025-10-16) Full Changelog: [v0.14.1...v0.14.2](https://github.com/onkernel/kernel-go-sdk/compare/v0.14.1...v0.14.2) diff --git a/README.md b/README.md index ce6e3e1..ba522c0 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or to pin the version: ```sh -go get -u 'github.com/onkernel/kernel-go-sdk@v0.14.2' +go get -u 'github.com/onkernel/kernel-go-sdk@v0.15.0' ``` diff --git a/internal/version.go b/internal/version.go index c843cf5..1f338c3 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.14.2" // x-release-please-version +const PackageVersion = "0.15.0" // x-release-please-version