diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 71811d1..5eb5472 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,12 +1,14 @@
name: CI
on:
push:
- branches-ignore:
- - 'generated'
- - 'codegen/**'
- - 'integrated/**'
- - 'stl-preview-head/**'
- - 'stl-preview-base/**'
+ branches:
+ - '**'
+ - '!integrated/**'
+ - '!stl-preview-head/**'
+ - '!stl-preview-base/**'
+ - '!generated'
+ - '!codegen/**'
+ - 'codegen/stl/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index fe87cd9..cc51f6f 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.43.0"
+ ".": "0.44.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index ae22a71..be60802 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 103
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-bda5e58fa0bbd08761f27a1e0edbc602c44141ac9483bf6c96d52b7f4d10d9a7.yml
-openapi_spec_hash: 10833b36358e8cda023e5bb0abeab0ba
-config_hash: cff4d43372b6fa66b64e2d4150f6aa76
+configured_endpoints: 104
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-bb2ac8e0d3a1c08e8afcbcbad7cb733d0f84bd22a8d233c1ec3100a01ee078ae.yml
+openapi_spec_hash: a83f7d1c422c85d6dc6158af7afe1d09
+config_hash: 16e4457a0bb26e98a335a1c2a572290a
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 882c1e9..7785f78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
# Changelog
+## 0.44.0 (2026-03-20)
+
+Full Changelog: [v0.43.0...v0.44.0](https://github.com/kernel/kernel-go-sdk/compare/v0.43.0...v0.44.0)
+
+### Features
+
+* Add GPU viewport presets and GPU encoder defaults ([5096099](https://github.com/kernel/kernel-go-sdk/commit/5096099db1afd3d90b384423c8daf18576e167c7))
+* Adds description to OAS spec for docs about delta_x, delta_y ([5d60a03](https://github.com/kernel/kernel-go-sdk/commit/5d60a03c51ab0a14ae115552909c09a43f730631))
+* Drop headless GPU support and disable pooling ([188a32b](https://github.com/kernel/kernel-go-sdk/commit/188a32bf2e2bfb3e0aa33a595b2477557db168ea))
+* Enhance managed authentication with CUA support and new features ([b1c79e6](https://github.com/kernel/kernel-go-sdk/commit/b1c79e61fba350ba9324c8ebaa2205f7ca96332a))
+* expose smooth drag mouse movement via public API ([1bcd6f5](https://github.com/kernel/kernel-go-sdk/commit/1bcd6f5635cbd6bf4073efdbf9b3e97471cff826))
+* Rename hardware acceleration UI/docs wording to GPU acceleration ([d225ff6](https://github.com/kernel/kernel-go-sdk/commit/d225ff61b6d63308babf283816ac60ad2902f91e))
+
+
+### Chores
+
+* **internal:** minor cleanup ([2e50750](https://github.com/kernel/kernel-go-sdk/commit/2e507500fb10787ef597819c04bef36baecef0bf))
+* **internal:** tweak CI branches ([3a66a84](https://github.com/kernel/kernel-go-sdk/commit/3a66a8427998fe16353cbff5b14f4822e2f278ff))
+* **internal:** use explicit returns ([4023de1](https://github.com/kernel/kernel-go-sdk/commit/4023de1ca30ac2bd31a4003941b35ad0e75b8a86))
+* **internal:** use explicit returns in more places ([503a87c](https://github.com/kernel/kernel-go-sdk/commit/503a87cf7e6c0ffc76c1c063ff90f419ad0c78db))
+
## 0.43.0 (2026-03-10)
Full Changelog: [v0.42.1...v0.43.0](https://github.com/kernel/kernel-go-sdk/compare/v0.42.1...v0.43.0)
diff --git a/README.md b/README.md
index d30ccaa..9594193 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Or to pin the version:
```sh
-go get -u 'github.com/kernel/kernel-go-sdk@v0.43.0'
+go get -u 'github.com/kernel/kernel-go-sdk@v0.44.0'
```
diff --git a/aliases.go b/aliases.go
index ac9edb1..fb2a35c 100644
--- a/aliases.go
+++ b/aliases.go
@@ -48,9 +48,13 @@ type BrowserProfile = shared.BrowserProfile
type BrowserProfileParam = shared.BrowserProfileParam
// Initial browser window size in pixels with optional refresh rate. If omitted,
-// image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
-// but the following configurations are known-good and fully tested: 2560x1440@10,
-// 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+// image defaults apply (1920x1080@25). For GPU images, the default is
+// 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+// Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+// 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+// presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+// 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+// 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -60,9 +64,13 @@ type BrowserProfileParam = shared.BrowserProfileParam
type BrowserViewport = shared.BrowserViewport
// Initial browser window size in pixels with optional refresh rate. If omitted,
-// image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
-// but the following configurations are known-good and fully tested: 2560x1440@10,
-// 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+// image defaults apply (1920x1080@25). For GPU images, the default is
+// 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+// Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+// 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+// presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+// 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+// 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
diff --git a/api.md b/api.md
index 7b22092..4271fc8 100644
--- a/api.md
+++ b/api.md
@@ -220,6 +220,7 @@ Methods:
Params Types:
- kernel.ManagedAuthCreateRequestParam
+- kernel.ManagedAuthUpdateRequestParam
- kernel.SubmitFieldsRequestParam
Response Types:
@@ -233,6 +234,7 @@ Methods:
- client.Auth.Connections.New(ctx context.Context, body kernel.AuthConnectionNewParams) (\*kernel.ManagedAuth, error)
- client.Auth.Connections.Get(ctx context.Context, id string) (\*kernel.ManagedAuth, error)
+- client.Auth.Connections.Update(ctx context.Context, id string, body kernel.AuthConnectionUpdateParams) (\*kernel.ManagedAuth, error)
- client.Auth.Connections.List(ctx context.Context, query kernel.AuthConnectionListParams) (\*pagination.OffsetPagination[kernel.ManagedAuth], error)
- client.Auth.Connections.Delete(ctx context.Context, id string) error
- client.Auth.Connections.Follow(ctx context.Context, id string) (\*kernel.AuthConnectionFollowResponseUnion, error)
diff --git a/authconnection.go b/authconnection.go
index f6e1d13..a9df222 100644
--- a/authconnection.go
+++ b/authconnection.go
@@ -52,7 +52,7 @@ func (r *AuthConnectionService) New(ctx context.Context, body AuthConnectionNewP
opts = slices.Concat(r.Options, opts)
path := "auth/connections"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Retrieve an auth connection by its ID. Includes current flow state if a login is
@@ -61,11 +61,24 @@ func (r *AuthConnectionService) Get(ctx context.Context, id string, opts ...opti
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("auth/connections/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
+}
+
+// Update an auth connection's configuration. Only the fields provided will be
+// updated.
+func (r *AuthConnectionService) Update(ctx context.Context, id string, body AuthConnectionUpdateParams, opts ...option.RequestOption) (res *ManagedAuth, err error) {
+ opts = slices.Concat(r.Options, opts)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return nil, err
+ }
+ path := fmt.Sprintf("auth/connections/%s", id)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
+ return res, err
}
// List auth connections with optional filters for profile_name and domain.
@@ -101,11 +114,11 @@ func (r *AuthConnectionService) Delete(ctx context.Context, id string, opts ...o
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("auth/connections/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Establishes a Server-Sent Events (SSE) stream that delivers real-time login flow
@@ -120,7 +133,7 @@ func (r *AuthConnectionService) FollowStreaming(ctx context.Context, id string,
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return ssestream.NewStream[AuthConnectionFollowResponseUnion](nil, err)
}
path := fmt.Sprintf("auth/connections/%s/events", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
@@ -134,11 +147,11 @@ func (r *AuthConnectionService) Login(ctx context.Context, id string, body AuthC
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("auth/connections/%s/login", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Submits field values for the login form. Poll the auth connection to track
@@ -147,11 +160,11 @@ func (r *AuthConnectionService) Submit(ctx context.Context, id string, body Auth
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("auth/connections/%s/submit", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Response from starting a login flow
@@ -288,6 +301,9 @@ type ManagedAuth struct {
PostLoginURL string `json:"post_login_url" format:"uri"`
// ID of the proxy associated with this connection, if any.
ProxyID string `json:"proxy_id"`
+ // Non-MFA choices presented during the auth flow, such as account selection or org
+ // pickers (present when flow_step=awaiting_input).
+ SignInOptions []ManagedAuthSignInOption `json:"sign_in_options" api:"nullable"`
// SSO provider being used (e.g., google, github, microsoft)
SSOProvider string `json:"sso_provider" api:"nullable"`
// Visible error message from the website (e.g., 'Incorrect password'). Present
@@ -320,6 +336,7 @@ type ManagedAuth struct {
PendingSSOButtons respjson.Field
PostLoginURL respjson.Field
ProxyID respjson.Field
+ SignInOptions respjson.Field
SSOProvider respjson.Field
WebsiteError respjson.Field
ExtraFields map[string]respjson.Field
@@ -496,6 +513,31 @@ func (r *ManagedAuthPendingSSOButton) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+// A non-MFA choice presented during the auth flow (e.g. account selection, org
+// picker)
+type ManagedAuthSignInOption struct {
+ // Unique identifier for this option (used to submit selection back)
+ ID string `json:"id" api:"required"`
+ // Display text for the option
+ Label string `json:"label" api:"required"`
+ // Additional context such as email address or org name
+ Description string `json:"description" api:"nullable"`
+ // JSON contains metadata for fields, check presence with [respjson.Field.Valid].
+ JSON struct {
+ ID respjson.Field
+ Label respjson.Field
+ Description respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
+ } `json:"-"`
+}
+
+// Returns the unmodified JSON received from the API
+func (r ManagedAuthSignInOption) RawJSON() string { return r.JSON.raw }
+func (r *ManagedAuthSignInOption) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
// Request to create an auth connection for a profile and domain
//
// The properties Domain, ProfileName are required.
@@ -596,13 +638,93 @@ func (r *ManagedAuthCreateRequestProxyParam) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
-// Request to submit field values, click an SSO button, or select an MFA method.
-// Provide exactly one of fields, sso_button_selector, or mfa_option_id.
+// Request to update an auth connection's configuration
+type ManagedAuthUpdateRequestParam struct {
+ // Interval in seconds between automatic health checks
+ HealthCheckInterval param.Opt[int64] `json:"health_check_interval,omitzero"`
+ // Login page URL. Set to empty string to clear.
+ LoginURL param.Opt[string] `json:"login_url,omitzero" format:"uri"`
+ // Whether to save credentials after every successful login
+ SaveCredentials param.Opt[bool] `json:"save_credentials,omitzero"`
+ // Additional domains valid for this auth flow (replaces existing list)
+ AllowedDomains []string `json:"allowed_domains,omitzero"`
+ // Reference to credentials for the auth connection. Use one of:
+ //
+ // - { name } for Kernel credentials
+ // - { provider, path } for external provider item
+ // - { provider, auto: true } for external provider domain lookup
+ Credential ManagedAuthUpdateRequestCredentialParam `json:"credential,omitzero"`
+ // Proxy selection. Provide either id or name. The proxy must belong to the
+ // caller's org.
+ Proxy ManagedAuthUpdateRequestProxyParam `json:"proxy,omitzero"`
+ paramObj
+}
+
+func (r ManagedAuthUpdateRequestParam) MarshalJSON() (data []byte, err error) {
+ type shadow ManagedAuthUpdateRequestParam
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *ManagedAuthUpdateRequestParam) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+// Reference to credentials for the auth connection. Use one of:
+//
+// - { name } for Kernel credentials
+// - { provider, path } for external provider item
+// - { provider, auto: true } for external provider domain lookup
+type ManagedAuthUpdateRequestCredentialParam struct {
+ // If true, lookup by domain from the specified provider
+ Auto param.Opt[bool] `json:"auto,omitzero"`
+ // Kernel credential name
+ Name param.Opt[string] `json:"name,omitzero"`
+ // Provider-specific path (e.g., "VaultName/ItemName" for 1Password)
+ Path param.Opt[string] `json:"path,omitzero"`
+ // External provider name (e.g., "my-1p")
+ Provider param.Opt[string] `json:"provider,omitzero"`
+ paramObj
+}
+
+func (r ManagedAuthUpdateRequestCredentialParam) MarshalJSON() (data []byte, err error) {
+ type shadow ManagedAuthUpdateRequestCredentialParam
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *ManagedAuthUpdateRequestCredentialParam) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+// Proxy selection. Provide either id or name. The proxy must belong to the
+// caller's org.
+type ManagedAuthUpdateRequestProxyParam struct {
+ // Proxy ID
+ ID param.Opt[string] `json:"id,omitzero"`
+ // Proxy name
+ Name param.Opt[string] `json:"name,omitzero"`
+ paramObj
+}
+
+func (r ManagedAuthUpdateRequestProxyParam) MarshalJSON() (data []byte, err error) {
+ type shadow ManagedAuthUpdateRequestProxyParam
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *ManagedAuthUpdateRequestProxyParam) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+// Request to submit field values, click an SSO button, select an MFA method, or
+// select a sign-in option. Provide exactly one of fields, sso_button_selector,
+// sso_provider, mfa_option_id, or sign_in_option_id.
type SubmitFieldsRequestParam struct {
- // Optional MFA option ID if user selected an MFA method
+ // The MFA method type to select (when mfa_options were returned)
MfaOptionID param.Opt[string] `json:"mfa_option_id,omitzero"`
- // Optional XPath selector if user chose to click an SSO button instead
+ // The sign-in option ID to select (when sign_in_options were returned)
+ SignInOptionID param.Opt[string] `json:"sign_in_option_id,omitzero"`
+ // XPath selector for the SSO button to click (ODA). Use sso_provider instead for
+ // CUA.
SSOButtonSelector param.Opt[string] `json:"sso_button_selector,omitzero"`
+ // SSO provider to click, matching the provider field from pending_sso_buttons
+ // (e.g., "google", "github"). Cannot be used with sso_button_selector.
+ SSOProvider param.Opt[string] `json:"sso_provider,omitzero"`
// Map of field name to value
Fields map[string]string `json:"fields,omitzero"`
paramObj
@@ -671,6 +793,8 @@ type AuthConnectionFollowResponseUnion struct {
// This field is from variant [AuthConnectionFollowResponseManagedAuthState].
PostLoginURL string `json:"post_login_url"`
// This field is from variant [AuthConnectionFollowResponseManagedAuthState].
+ SignInOptions []AuthConnectionFollowResponseManagedAuthStateSignInOption `json:"sign_in_options"`
+ // This field is from variant [AuthConnectionFollowResponseManagedAuthState].
WebsiteError string `json:"website_error"`
// This field is from variant [shared.ErrorEvent].
Error shared.ErrorModel `json:"error"`
@@ -689,6 +813,7 @@ type AuthConnectionFollowResponseUnion struct {
MfaOptions respjson.Field
PendingSSOButtons respjson.Field
PostLoginURL respjson.Field
+ SignInOptions respjson.Field
WebsiteError respjson.Field
Error respjson.Field
raw string
@@ -786,6 +911,9 @@ type AuthConnectionFollowResponseManagedAuthState struct {
PendingSSOButtons []AuthConnectionFollowResponseManagedAuthStatePendingSSOButton `json:"pending_sso_buttons"`
// URL where the browser landed after successful login.
PostLoginURL string `json:"post_login_url" format:"uri"`
+ // Non-MFA choices presented during the auth flow, such as account selection or org
+ // pickers (present when flow_step=AWAITING_INPUT).
+ SignInOptions []AuthConnectionFollowResponseManagedAuthStateSignInOption `json:"sign_in_options"`
// Visible error message from the website (e.g., 'Incorrect password'). Present
// when the website displays an error during login.
WebsiteError string `json:"website_error"`
@@ -805,6 +933,7 @@ type AuthConnectionFollowResponseManagedAuthState struct {
MfaOptions respjson.Field
PendingSSOButtons respjson.Field
PostLoginURL respjson.Field
+ SignInOptions respjson.Field
WebsiteError respjson.Field
ExtraFields map[string]respjson.Field
raw string
@@ -915,6 +1044,31 @@ func (r *AuthConnectionFollowResponseManagedAuthStatePendingSSOButton) Unmarshal
return apijson.UnmarshalRoot(data, r)
}
+// A non-MFA choice presented during the auth flow (e.g. account selection, org
+// picker)
+type AuthConnectionFollowResponseManagedAuthStateSignInOption struct {
+ // Unique identifier for this option (used to submit selection back)
+ ID string `json:"id" api:"required"`
+ // Display text for the option
+ Label string `json:"label" api:"required"`
+ // Additional context such as email address or org name
+ Description string `json:"description" api:"nullable"`
+ // JSON contains metadata for fields, check presence with [respjson.Field.Valid].
+ JSON struct {
+ ID respjson.Field
+ Label respjson.Field
+ Description respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
+ } `json:"-"`
+}
+
+// Returns the unmodified JSON received from the API
+func (r AuthConnectionFollowResponseManagedAuthStateSignInOption) RawJSON() string { return r.JSON.raw }
+func (r *AuthConnectionFollowResponseManagedAuthStateSignInOption) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
type AuthConnectionNewParams struct {
// Request to create an auth connection for a profile and domain
ManagedAuthCreateRequest ManagedAuthCreateRequestParam
@@ -928,6 +1082,19 @@ func (r *AuthConnectionNewParams) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &r.ManagedAuthCreateRequest)
}
+type AuthConnectionUpdateParams struct {
+ // Request to update an auth connection's configuration
+ ManagedAuthUpdateRequest ManagedAuthUpdateRequestParam
+ paramObj
+}
+
+func (r AuthConnectionUpdateParams) MarshalJSON() (data []byte, err error) {
+ return shimjson.Marshal(r.ManagedAuthUpdateRequest)
+}
+func (r *AuthConnectionUpdateParams) UnmarshalJSON(data []byte) error {
+ return json.Unmarshal(data, &r.ManagedAuthUpdateRequest)
+}
+
type AuthConnectionListParams struct {
// Filter by domain
Domain param.Opt[string] `query:"domain,omitzero" json:"-"`
@@ -983,8 +1150,9 @@ func (r *AuthConnectionLoginParamsProxy) UnmarshalJSON(data []byte) error {
}
type AuthConnectionSubmitParams struct {
- // Request to submit field values, click an SSO button, or select an MFA method.
- // Provide exactly one of fields, sso_button_selector, or mfa_option_id.
+ // Request to submit field values, click an SSO button, select an MFA method, or
+ // select a sign-in option. Provide exactly one of fields, sso_button_selector,
+ // sso_provider, mfa_option_id, or sign_in_option_id.
SubmitFieldsRequest SubmitFieldsRequestParam
paramObj
}
diff --git a/authconnection_test.go b/authconnection_test.go
index d58ec18..f83e246 100644
--- a/authconnection_test.go
+++ b/authconnection_test.go
@@ -78,6 +78,50 @@ func TestAuthConnectionGet(t *testing.T) {
}
}
+func TestAuthConnectionUpdateWithOptionalParams(t *testing.T) {
+ t.Skip("Mock server 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.Auth.Connections.Update(
+ context.TODO(),
+ "id",
+ kernel.AuthConnectionUpdateParams{
+ ManagedAuthUpdateRequest: kernel.ManagedAuthUpdateRequestParam{
+ AllowedDomains: []string{"login.netflix.com", "auth.netflix.com"},
+ Credential: kernel.ManagedAuthUpdateRequestCredentialParam{
+ Auto: kernel.Bool(true),
+ Name: kernel.String("my-netflix-creds"),
+ Path: kernel.String("Personal/Netflix"),
+ Provider: kernel.String("my-1p"),
+ },
+ HealthCheckInterval: kernel.Int(3600),
+ LoginURL: kernel.String("https://netflix.com/login"),
+ Proxy: kernel.ManagedAuthUpdateRequestProxyParam{
+ ID: kernel.String("id"),
+ Name: kernel.String("name"),
+ },
+ SaveCredentials: kernel.Bool(true),
+ },
+ },
+ )
+ 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 TestAuthConnectionListWithOptionalParams(t *testing.T) {
t.Skip("Mock server tests are disabled")
baseURL := "http://localhost:4010"
@@ -184,7 +228,9 @@ func TestAuthConnectionSubmitWithOptionalParams(t *testing.T) {
"password": "secret",
},
MfaOptionID: kernel.String("sms"),
+ SignInOptionID: kernel.String("work-account"),
SSOButtonSelector: kernel.String("xpath=//button[contains(text(), 'Continue with Google')]"),
+ SSOProvider: kernel.String("google"),
},
},
)
diff --git a/browser.go b/browser.go
index 9d3fc7f..4aef02f 100644
--- a/browser.go
+++ b/browser.go
@@ -69,7 +69,7 @@ func (r *BrowserService) New(ctx context.Context, body BrowserNewParams, opts ..
opts = slices.Concat(r.Options, opts)
path := "browsers"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Get information about a browser session.
@@ -77,11 +77,11 @@ func (r *BrowserService) Get(ctx context.Context, id string, query BrowserGetPar
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ return res, err
}
// Update a browser session.
@@ -89,11 +89,11 @@ func (r *BrowserService) Update(ctx context.Context, id string, body BrowserUpda
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
- return
+ return res, err
}
// List all browser sessions with pagination support. Use status parameter to
@@ -130,7 +130,7 @@ func (r *BrowserService) Delete(ctx context.Context, body BrowserDeleteParams, o
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
path := "browsers"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, nil, opts...)
- return
+ return err
}
// Delete a browser session by ID
@@ -139,11 +139,11 @@ func (r *BrowserService) DeleteByID(ctx context.Context, id string, opts ...opti
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Loads one or more unpacked extensions and restarts Chromium on the browser
@@ -153,11 +153,11 @@ func (r *BrowserService) LoadExtensions(ctx context.Context, id string, body Bro
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/extensions", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// DEPRECATED: Use timeout_seconds (up to 72 hours) and Profiles instead.
@@ -297,7 +297,8 @@ type BrowserNewResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// When the browser session was soft-deleted. Only present for deleted sessions.
DeletedAt time.Time `json:"deleted_at" format:"date-time"`
- // Whether the browser session has hardware-accelerated GPU rendering.
+ // Whether GPU acceleration is enabled for the browser session (only supported for
+ // headful sessions).
GPU bool `json:"gpu"`
// Whether the browser session is running in kiosk mode.
KioskMode bool `json:"kiosk_mode"`
@@ -314,9 +315,13 @@ type BrowserNewResponse struct {
// Session usage metrics.
Usage BrowserUsage `json:"usage"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -372,7 +377,8 @@ type BrowserGetResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// When the browser session was soft-deleted. Only present for deleted sessions.
DeletedAt time.Time `json:"deleted_at" format:"date-time"`
- // Whether the browser session has hardware-accelerated GPU rendering.
+ // Whether GPU acceleration is enabled for the browser session (only supported for
+ // headful sessions).
GPU bool `json:"gpu"`
// Whether the browser session is running in kiosk mode.
KioskMode bool `json:"kiosk_mode"`
@@ -389,9 +395,13 @@ type BrowserGetResponse struct {
// Session usage metrics.
Usage BrowserUsage `json:"usage"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -447,7 +457,8 @@ type BrowserUpdateResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// When the browser session was soft-deleted. Only present for deleted sessions.
DeletedAt time.Time `json:"deleted_at" format:"date-time"`
- // Whether the browser session has hardware-accelerated GPU rendering.
+ // Whether GPU acceleration is enabled for the browser session (only supported for
+ // headful sessions).
GPU bool `json:"gpu"`
// Whether the browser session is running in kiosk mode.
KioskMode bool `json:"kiosk_mode"`
@@ -464,9 +475,13 @@ type BrowserUpdateResponse struct {
// Session usage metrics.
Usage BrowserUsage `json:"usage"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -522,7 +537,8 @@ type BrowserListResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// When the browser session was soft-deleted. Only present for deleted sessions.
DeletedAt time.Time `json:"deleted_at" format:"date-time"`
- // Whether the browser session has hardware-accelerated GPU rendering.
+ // Whether GPU acceleration is enabled for the browser session (only supported for
+ // headful sessions).
GPU bool `json:"gpu"`
// Whether the browser session is running in kiosk mode.
KioskMode bool `json:"kiosk_mode"`
@@ -539,9 +555,13 @@ type BrowserListResponse struct {
// Session usage metrics.
Usage BrowserUsage `json:"usage"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -578,8 +598,8 @@ func (r *BrowserListResponse) UnmarshalJSON(data []byte) error {
}
type BrowserNewParams struct {
- // If true, launches a hardware-accelerated browser with GPU rendering. Requires
- // Start-Up or Enterprise plan.
+ // If true, enables GPU acceleration for the browser session. Requires Start-Up or
+ // Enterprise plan and headless=false.
GPU param.Opt[bool] `json:"gpu,omitzero"`
// If true, launches the browser using a headless image (no VNC/GUI). Defaults to
// false.
@@ -610,9 +630,13 @@ type BrowserNewParams struct {
// Profiles must be created beforehand.
Profile shared.BrowserProfileParam `json:"profile,omitzero"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
diff --git a/browsercomputer.go b/browsercomputer.go
index c2bf914..acd13c1 100644
--- a/browsercomputer.go
+++ b/browsercomputer.go
@@ -43,11 +43,11 @@ func (r *BrowserComputerService) Batch(ctx context.Context, id string, body Brow
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/batch", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Capture a screenshot of the browser instance
@@ -56,11 +56,11 @@ func (r *BrowserComputerService) CaptureScreenshot(ctx context.Context, id strin
opts = append([]option.RequestOption{option.WithHeader("Accept", "image/png")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/computer/screenshot", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Simulate a mouse click action on the browser instance
@@ -69,11 +69,11 @@ func (r *BrowserComputerService) ClickMouse(ctx context.Context, id string, body
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/click_mouse", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Drag the mouse along a path
@@ -82,11 +82,11 @@ func (r *BrowserComputerService) DragMouse(ctx context.Context, id string, body
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/drag_mouse", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Get the current mouse cursor position on the browser instance
@@ -94,11 +94,11 @@ func (r *BrowserComputerService) GetMousePosition(ctx context.Context, id string
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/computer/get_mouse_position", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
- return
+ return res, err
}
// Move the mouse cursor to the specified coordinates on the browser instance
@@ -107,11 +107,11 @@ func (r *BrowserComputerService) MoveMouse(ctx context.Context, id string, body
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/move_mouse", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Press one or more keys on the host computer
@@ -120,11 +120,11 @@ func (r *BrowserComputerService) PressKey(ctx context.Context, id string, body B
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/press_key", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Read text from the clipboard on the browser instance
@@ -132,11 +132,11 @@ func (r *BrowserComputerService) ReadClipboard(ctx context.Context, id string, o
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/computer/clipboard/read", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
- return
+ return res, err
}
// Scroll the mouse wheel at a position on the host computer
@@ -145,11 +145,11 @@ func (r *BrowserComputerService) Scroll(ctx context.Context, id string, body Bro
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/scroll", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Set cursor visibility
@@ -157,11 +157,11 @@ func (r *BrowserComputerService) SetCursorVisibility(ctx context.Context, id str
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/computer/cursor", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Type text on the browser instance
@@ -170,11 +170,11 @@ func (r *BrowserComputerService) TypeText(ctx context.Context, id string, body B
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/type", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Write text to the clipboard on the browser instance
@@ -183,11 +183,11 @@ func (r *BrowserComputerService) WriteClipboard(ctx context.Context, id string,
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/computer/clipboard/write", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
type BrowserComputerGetMousePositionResponse struct {
@@ -341,6 +341,12 @@ type BrowserComputerBatchParamsActionDragMouse struct {
Path [][]int64 `json:"path,omitzero" api:"required"`
// Delay in milliseconds between button down and starting to move along the path.
Delay param.Opt[int64] `json:"delay,omitzero"`
+ // Target total duration in milliseconds for the entire drag movement when
+ // smooth=true. Omit for automatic timing based on total path length.
+ DurationMs param.Opt[int64] `json:"duration_ms,omitzero"`
+ // Use human-like Bezier curves between path waypoints instead of linear
+ // interpolation. When true, steps_per_segment and step_delay_ms are ignored.
+ Smooth param.Opt[bool] `json:"smooth,omitzero"`
// Delay in milliseconds between relative steps while dragging (not the initial
// delay).
StepDelayMs param.Opt[int64] `json:"step_delay_ms,omitzero"`
@@ -422,9 +428,11 @@ type BrowserComputerBatchParamsActionScroll struct {
X int64 `json:"x" api:"required"`
// Y coordinate at which to perform the scroll
Y int64 `json:"y" api:"required"`
- // Horizontal scroll amount. Positive scrolls right, negative scrolls left.
+ // Horizontal scroll amount in xdotool "wheel units." Positive scrolls right,
+ // negative scrolls left.
DeltaX param.Opt[int64] `json:"delta_x,omitzero"`
- // Vertical scroll amount. Positive scrolls down, negative scrolls up.
+ // Vertical scroll amount in xdotool "wheel units." 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"`
@@ -576,6 +584,12 @@ type BrowserComputerDragMouseParams struct {
Path [][]int64 `json:"path,omitzero" api:"required"`
// Delay in milliseconds between button down and starting to move along the path.
Delay param.Opt[int64] `json:"delay,omitzero"`
+ // Target total duration in milliseconds for the entire drag movement when
+ // smooth=true. Omit for automatic timing based on total path length.
+ DurationMs param.Opt[int64] `json:"duration_ms,omitzero"`
+ // Use human-like Bezier curves between path waypoints instead of linear
+ // interpolation. When true, steps_per_segment and step_delay_ms are ignored.
+ Smooth param.Opt[bool] `json:"smooth,omitzero"`
// Delay in milliseconds between relative steps while dragging (not the initial
// delay).
StepDelayMs param.Opt[int64] `json:"step_delay_ms,omitzero"`
@@ -657,9 +671,11 @@ type BrowserComputerScrollParams struct {
X int64 `json:"x" api:"required"`
// Y coordinate at which to perform the scroll
Y int64 `json:"y" api:"required"`
- // Horizontal scroll amount. Positive scrolls right, negative scrolls left.
+ // Horizontal scroll amount in xdotool "wheel units." Positive scrolls right,
+ // negative scrolls left.
DeltaX param.Opt[int64] `json:"delta_x,omitzero"`
- // Vertical scroll amount. Positive scrolls down, negative scrolls up.
+ // Vertical scroll amount in xdotool "wheel units." 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"`
diff --git a/browsercomputer_test.go b/browsercomputer_test.go
index fa2199a..172173b 100644
--- a/browsercomputer_test.go
+++ b/browsercomputer_test.go
@@ -48,7 +48,9 @@ func TestBrowserComputerBatch(t *testing.T) {
Path: [][]int64{{0, 0}, {0, 0}},
Button: "left",
Delay: kernel.Int(0),
+ DurationMs: kernel.Int(50),
HoldKeys: []string{"string"},
+ Smooth: kernel.Bool(true),
StepDelayMs: kernel.Int(0),
StepsPerSegment: kernel.Int(1),
},
@@ -192,7 +194,9 @@ func TestBrowserComputerDragMouseWithOptionalParams(t *testing.T) {
Path: [][]int64{{0, 0}, {0, 0}},
Button: kernel.BrowserComputerDragMouseParamsButtonLeft,
Delay: kernel.Int(0),
+ DurationMs: kernel.Int(50),
HoldKeys: []string{"string"},
+ Smooth: kernel.Bool(true),
StepDelayMs: kernel.Int(0),
StepsPerSegment: kernel.Int(1),
},
diff --git a/browserf.go b/browserf.go
index 2365b08..4d3c9e6 100644
--- a/browserf.go
+++ b/browserf.go
@@ -53,11 +53,11 @@ func (r *BrowserFService) NewDirectory(ctx context.Context, id string, body Brow
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/create_directory", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, nil, opts...)
- return
+ return err
}
// Delete a directory
@@ -66,11 +66,11 @@ func (r *BrowserFService) DeleteDirectory(ctx context.Context, id string, body B
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/delete_directory", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, nil, opts...)
- return
+ return err
}
// Delete a file
@@ -79,11 +79,11 @@ func (r *BrowserFService) DeleteFile(ctx context.Context, id string, body Browse
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/delete_file", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, nil, opts...)
- return
+ return err
}
// Returns a ZIP file containing the contents of the specified directory.
@@ -92,11 +92,11 @@ func (r *BrowserFService) DownloadDirZip(ctx context.Context, id string, query B
opts = append([]option.RequestOption{option.WithHeader("Accept", "application/zip")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/fs/download_dir_zip", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ return res, err
}
// Get information about a file or directory
@@ -104,11 +104,11 @@ func (r *BrowserFService) FileInfo(ctx context.Context, id string, query Browser
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/fs/file_info", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ return res, err
}
// List files in a directory
@@ -116,11 +116,11 @@ func (r *BrowserFService) ListFiles(ctx context.Context, id string, query Browse
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/fs/list_files", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ return res, err
}
// Move or rename a file or directory
@@ -129,11 +129,11 @@ func (r *BrowserFService) Move(ctx context.Context, id string, body BrowserFMove
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/move", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, nil, opts...)
- return
+ return err
}
// Read file contents
@@ -142,11 +142,11 @@ func (r *BrowserFService) ReadFile(ctx context.Context, id string, query Browser
opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/fs/read_file", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ return res, err
}
// Set file or directory permissions/ownership
@@ -155,11 +155,11 @@ func (r *BrowserFService) SetFilePermissions(ctx context.Context, id string, bod
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/set_file_permissions", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, body, nil, opts...)
- return
+ return err
}
// Allows uploading single or multiple files to the remote filesystem.
@@ -168,11 +168,11 @@ func (r *BrowserFService) Upload(ctx context.Context, id string, body BrowserFUp
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/upload", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Upload a zip file and extract its contents to the specified destination path.
@@ -181,11 +181,11 @@ func (r *BrowserFService) UploadZip(ctx context.Context, id string, body Browser
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/upload_zip", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// Write or create a file
@@ -194,11 +194,11 @@ func (r *BrowserFService) WriteFile(ctx context.Context, id string, contents io.
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*"), option.WithRequestBody("application/octet-stream", contents)}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/write_file", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPut, path, params, nil, opts...)
- return
+ return err
}
type BrowserFFileInfoResponse struct {
diff --git a/browserfwatch.go b/browserfwatch.go
index 1c000fa..b780a1e 100644
--- a/browserfwatch.go
+++ b/browserfwatch.go
@@ -48,11 +48,11 @@ func (r *BrowserFWatchService) EventsStreaming(ctx context.Context, watchID stri
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
if query.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return ssestream.NewStream[BrowserFWatchEventsResponse](nil, err)
}
if watchID == "" {
err = errors.New("missing required watch_id parameter")
- return
+ return ssestream.NewStream[BrowserFWatchEventsResponse](nil, err)
}
path := fmt.Sprintf("browsers/%s/fs/watch/%s/events", query.ID, watchID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
@@ -64,11 +64,11 @@ func (r *BrowserFWatchService) Start(ctx context.Context, id string, body Browse
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/fs/watch", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Stop watching a directory
@@ -77,15 +77,15 @@ func (r *BrowserFWatchService) Stop(ctx context.Context, watchID string, body Br
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if body.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
if watchID == "" {
err = errors.New("missing required watch_id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/fs/watch/%s", body.ID, watchID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Filesystem change event.
diff --git a/browserlog.go b/browserlog.go
index ca06feb..58d75b6 100644
--- a/browserlog.go
+++ b/browserlog.go
@@ -49,7 +49,7 @@ func (r *BrowserLogService) StreamStreaming(ctx context.Context, id string, quer
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return ssestream.NewStream[shared.LogEvent](nil, err)
}
path := fmt.Sprintf("browsers/%s/logs/stream", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
diff --git a/browserplaywright.go b/browserplaywright.go
index 63abfb2..7a18760 100644
--- a/browserplaywright.go
+++ b/browserplaywright.go
@@ -45,11 +45,11 @@ func (r *BrowserPlaywrightService) Execute(ctx context.Context, id string, body
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/playwright/execute", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Result of Playwright code execution
diff --git a/browserpool.go b/browserpool.go
index f54102f..303f117 100644
--- a/browserpool.go
+++ b/browserpool.go
@@ -44,7 +44,7 @@ func (r *BrowserPoolService) New(ctx context.Context, body BrowserPoolNewParams,
opts = slices.Concat(r.Options, opts)
path := "browser_pools"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Retrieve details for a single browser pool by its ID or name.
@@ -52,11 +52,11 @@ func (r *BrowserPoolService) Get(ctx context.Context, idOrName string, opts ...o
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browser_pools/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Updates the configuration used to create browsers in the pool.
@@ -64,11 +64,11 @@ func (r *BrowserPoolService) Update(ctx context.Context, idOrName string, body B
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browser_pools/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
- return
+ return res, err
}
// List browser pools owned by the caller's organization.
@@ -76,7 +76,7 @@ func (r *BrowserPoolService) List(ctx context.Context, opts ...option.RequestOpt
opts = slices.Concat(r.Options, opts)
path := "browser_pools"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Delete a browser pool and all browsers in it. By default, deletion is blocked if
@@ -86,11 +86,11 @@ func (r *BrowserPoolService) Delete(ctx context.Context, idOrName string, body B
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return err
}
path := fmt.Sprintf("browser_pools/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, body, nil, opts...)
- return
+ return err
}
// Long-polling endpoint to acquire a browser from the pool. Returns immediately
@@ -101,11 +101,11 @@ func (r *BrowserPoolService) Acquire(ctx context.Context, idOrName string, body
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browser_pools/%s/acquire", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Destroys all idle browsers in the pool; leased browsers are not affected.
@@ -114,11 +114,11 @@ func (r *BrowserPoolService) Flush(ctx context.Context, idOrName string, opts ..
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return err
}
path := fmt.Sprintf("browser_pools/%s/flush", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, nil, opts...)
- return
+ return err
}
// Release a browser back to the pool, optionally recreating the browser instance.
@@ -127,11 +127,11 @@ func (r *BrowserPoolService) Release(ctx context.Context, idOrName string, body
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return err
}
path := fmt.Sprintf("browser_pools/%s/release", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...)
- return
+ return err
}
// A browser pool containing multiple identically configured browsers.
@@ -198,9 +198,13 @@ type BrowserPoolBrowserPoolConfig struct {
// are destroyed. Defaults to 600 seconds if not specified
TimeoutSeconds int64 `json:"timeout_seconds"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -250,7 +254,8 @@ type BrowserPoolAcquireResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// When the browser session was soft-deleted. Only present for deleted sessions.
DeletedAt time.Time `json:"deleted_at" format:"date-time"`
- // Whether the browser session has hardware-accelerated GPU rendering.
+ // Whether GPU acceleration is enabled for the browser session (only supported for
+ // headful sessions).
GPU bool `json:"gpu"`
// Whether the browser session is running in kiosk mode.
KioskMode bool `json:"kiosk_mode"`
@@ -267,9 +272,13 @@ type BrowserPoolAcquireResponse struct {
// Session usage metrics.
Usage BrowserUsage `json:"usage"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -335,9 +344,13 @@ type BrowserPoolNewParams struct {
// Profiles must be created beforehand.
Profile shared.BrowserProfileParam `json:"profile,omitzero"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -387,9 +400,13 @@ type BrowserPoolUpdateParams struct {
// Profiles must be created beforehand.
Profile shared.BrowserProfileParam `json:"profile,omitzero"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
diff --git a/browserprocess.go b/browserprocess.go
index 2cda1b9..09d59f4 100644
--- a/browserprocess.go
+++ b/browserprocess.go
@@ -44,11 +44,11 @@ func (r *BrowserProcessService) Exec(ctx context.Context, id string, body Browse
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/process/exec", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Send signal to process
@@ -56,15 +56,15 @@ func (r *BrowserProcessService) Kill(ctx context.Context, processID string, para
opts = slices.Concat(r.Options, opts)
if params.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
if processID == "" {
err = errors.New("missing required process_id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/process/%s/kill", params.ID, processID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
- return
+ return res, err
}
// Resize a PTY-backed process terminal
@@ -72,15 +72,15 @@ func (r *BrowserProcessService) Resize(ctx context.Context, processID string, pa
opts = slices.Concat(r.Options, opts)
if params.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
if processID == "" {
err = errors.New("missing required process_id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/process/%s/resize", params.ID, processID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
- return
+ return res, err
}
// Execute a command asynchronously
@@ -88,11 +88,11 @@ func (r *BrowserProcessService) Spawn(ctx context.Context, id string, body Brows
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/process/spawn", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Get process status
@@ -100,15 +100,15 @@ func (r *BrowserProcessService) Status(ctx context.Context, processID string, qu
opts = slices.Concat(r.Options, opts)
if query.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
if processID == "" {
err = errors.New("missing required process_id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/process/%s/status", query.ID, processID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Write to process stdin
@@ -116,15 +116,15 @@ func (r *BrowserProcessService) Stdin(ctx context.Context, processID string, par
opts = slices.Concat(r.Options, opts)
if params.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
if processID == "" {
err = errors.New("missing required process_id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/process/%s/stdin", params.ID, processID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...)
- return
+ return res, err
}
// Stream process stdout via SSE
@@ -137,11 +137,11 @@ func (r *BrowserProcessService) StdoutStreamStreaming(ctx context.Context, proce
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
if query.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return ssestream.NewStream[BrowserProcessStdoutStreamResponse](nil, err)
}
if processID == "" {
err = errors.New("missing required process_id parameter")
- return
+ return ssestream.NewStream[BrowserProcessStdoutStreamResponse](nil, err)
}
path := fmt.Sprintf("browsers/%s/process/%s/stdout/stream", query.ID, processID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
diff --git a/browserreplay.go b/browserreplay.go
index c1f7eeb..a506aa4 100644
--- a/browserreplay.go
+++ b/browserreplay.go
@@ -43,11 +43,11 @@ func (r *BrowserReplayService) List(ctx context.Context, id string, opts ...opti
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/replays", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Download or stream the specified replay recording.
@@ -56,15 +56,15 @@ func (r *BrowserReplayService) Download(ctx context.Context, replayID string, qu
opts = append([]option.RequestOption{option.WithHeader("Accept", "video/mp4")}, opts...)
if query.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
if replayID == "" {
err = errors.New("missing required replay_id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/replays/%s", query.ID, replayID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Start recording the browser session and return a replay ID.
@@ -72,11 +72,11 @@ func (r *BrowserReplayService) Start(ctx context.Context, id string, body Browse
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("browsers/%s/replays", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Stop the specified replay recording and persist the video.
@@ -85,15 +85,15 @@ func (r *BrowserReplayService) Stop(ctx context.Context, replayID string, body B
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if body.ID == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
if replayID == "" {
err = errors.New("missing required replay_id parameter")
- return
+ return err
}
path := fmt.Sprintf("browsers/%s/replays/%s/stop", body.ID, replayID)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, nil, opts...)
- return
+ return err
}
// Information about a browser replay recording.
diff --git a/client_test.go b/client_test.go
index c9d879c..e176136 100644
--- a/client_test.go
+++ b/client_test.go
@@ -39,7 +39,7 @@ func TestUserAgentHeader(t *testing.T) {
},
}),
)
- client.Browsers.New(context.Background(), kernel.BrowserNewParams{
+ _, _ = client.Browsers.New(context.Background(), kernel.BrowserNewParams{
Stealth: kernel.Bool(true),
})
if userAgent != fmt.Sprintf("Kernel/Go %s", internal.PackageVersion) {
diff --git a/credential.go b/credential.go
index 8844e85..28b5839 100644
--- a/credential.go
+++ b/credential.go
@@ -48,7 +48,7 @@ func (r *CredentialService) New(ctx context.Context, body CredentialNewParams, o
opts = slices.Concat(r.Options, opts)
path := "credentials"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Retrieve a credential by its ID or name. Credential values are not returned.
@@ -56,11 +56,11 @@ func (r *CredentialService) Get(ctx context.Context, idOrName string, opts ...op
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("credentials/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Update a credential's name or values. When values are provided, they are merged
@@ -69,11 +69,11 @@ func (r *CredentialService) Update(ctx context.Context, idOrName string, body Cr
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("credentials/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
- return
+ return res, err
}
// List credentials owned by the caller's organization. Credential values are not
@@ -107,11 +107,11 @@ func (r *CredentialService) Delete(ctx context.Context, idOrName string, opts ..
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return err
}
path := fmt.Sprintf("credentials/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Returns the current 6-digit TOTP code for a credential with a configured
@@ -121,11 +121,11 @@ func (r *CredentialService) TotpCode(ctx context.Context, idOrName string, opts
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("credentials/%s/totp-code", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Request to create a new credential
diff --git a/credentialprovider.go b/credentialprovider.go
index b1cdefb..244dd18 100644
--- a/credentialprovider.go
+++ b/credentialprovider.go
@@ -46,7 +46,7 @@ func (r *CredentialProviderService) New(ctx context.Context, body CredentialProv
opts = slices.Concat(r.Options, opts)
path := "org/credential_providers"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Retrieve a credential provider by its ID.
@@ -54,11 +54,11 @@ func (r *CredentialProviderService) Get(ctx context.Context, id string, opts ...
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("org/credential_providers/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Update a credential provider's configuration.
@@ -66,11 +66,11 @@ func (r *CredentialProviderService) Update(ctx context.Context, id string, body
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("org/credential_providers/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
- return
+ return res, err
}
// List external credential providers configured for the organization.
@@ -78,7 +78,7 @@ func (r *CredentialProviderService) List(ctx context.Context, opts ...option.Req
opts = slices.Concat(r.Options, opts)
path := "org/credential_providers"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Delete a credential provider by its ID.
@@ -87,11 +87,11 @@ func (r *CredentialProviderService) Delete(ctx context.Context, id string, opts
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("org/credential_providers/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Returns available credential items (e.g., 1Password login items) from the
@@ -100,11 +100,11 @@ func (r *CredentialProviderService) ListItems(ctx context.Context, id string, op
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("org/credential_providers/%s/items", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Validate the credential provider's token and list accessible vaults.
@@ -112,11 +112,11 @@ func (r *CredentialProviderService) Test(ctx context.Context, id string, opts ..
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("org/credential_providers/%s/test", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
- return
+ return res, err
}
// Request to create an external credential provider
diff --git a/deployment.go b/deployment.go
index c121ea1..37b7e64 100644
--- a/deployment.go
+++ b/deployment.go
@@ -54,7 +54,7 @@ func (r *DeploymentService) New(ctx context.Context, body DeploymentNewParams, o
opts = slices.Concat(r.Options, opts)
path := "deployments"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Get information about a deployment's status.
@@ -62,11 +62,11 @@ func (r *DeploymentService) Get(ctx context.Context, id string, opts ...option.R
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("deployments/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// List deployments. Optionally filter by application name and version.
@@ -99,11 +99,11 @@ func (r *DeploymentService) Delete(ctx context.Context, id string, opts ...optio
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("deployments/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Establishes a Server-Sent Events (SSE) stream that delivers real-time logs and
@@ -118,7 +118,7 @@ func (r *DeploymentService) FollowStreaming(ctx context.Context, id string, quer
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return ssestream.NewStream[DeploymentFollowResponseUnion](nil, err)
}
path := fmt.Sprintf("deployments/%s/events", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
diff --git a/extension.go b/extension.go
index f891772..09304dd 100644
--- a/extension.go
+++ b/extension.go
@@ -49,7 +49,7 @@ func (r *ExtensionService) List(ctx context.Context, opts ...option.RequestOptio
opts = slices.Concat(r.Options, opts)
path := "extensions"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Delete an extension by its ID or by its name.
@@ -58,11 +58,11 @@ func (r *ExtensionService) Delete(ctx context.Context, idOrName string, opts ...
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return err
}
path := fmt.Sprintf("extensions/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Download the extension as a ZIP archive by ID or name.
@@ -71,11 +71,11 @@ func (r *ExtensionService) Download(ctx context.Context, idOrName string, opts .
opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("extensions/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Returns a ZIP archive containing the unpacked extension fetched from the Chrome
@@ -85,7 +85,7 @@ func (r *ExtensionService) DownloadFromChromeStore(ctx context.Context, query Ex
opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...)
path := "extensions/from_chrome_store"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
- return
+ return res, err
}
// Upload a zip file containing an unpacked browser extension. Optionally provide a
@@ -94,7 +94,7 @@ func (r *ExtensionService) Upload(ctx context.Context, body ExtensionUploadParam
opts = slices.Concat(r.Options, opts)
path := "extensions"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// A browser extension uploaded to Kernel.
diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go
index 65d74d1..b93e5a3 100644
--- a/internal/apiform/encoder.go
+++ b/internal/apiform/encoder.go
@@ -469,5 +469,5 @@ func WriteExtras(writer *multipart.Writer, extras map[string]any) (err error) {
break
}
}
- return
+ return err
}
diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go
index b0b7ebe..76d8a92 100644
--- a/internal/apiform/form_test.go
+++ b/internal/apiform/form_test.go
@@ -585,14 +585,17 @@ func TestEncode(t *testing.T) {
t.Run(name, func(t *testing.T) {
buf := bytes.NewBuffer(nil)
writer := multipart.NewWriter(buf)
- writer.SetBoundary("xxx")
+ err := writer.SetBoundary("xxx")
+ if err != nil {
+ t.Errorf("setting boundary for %v failed with error %v", test.val, err)
+ }
- var arrayFmt string = "indices:dots"
+ arrayFmt := "indices:dots"
if tags := strings.Split(name, ","); len(tags) > 1 {
arrayFmt = tags[1]
}
- err := MarshalWithSettings(test.val, writer, arrayFmt)
+ err = MarshalWithSettings(test.val, writer, arrayFmt)
if err != nil {
t.Errorf("serialization of %v failed with error %v", test.val, err)
}
diff --git a/internal/apiform/tag.go b/internal/apiform/tag.go
index b353617..d9915d4 100644
--- a/internal/apiform/tag.go
+++ b/internal/apiform/tag.go
@@ -24,7 +24,7 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool
raw, ok = field.Tag.Lookup(jsonStructTag)
}
if !ok {
- return
+ return tag, ok
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
@@ -45,7 +45,7 @@ func parseFormStructTag(field reflect.StructField) (tag parsedStructTag, ok bool
}
parseApiStructTag(field, &tag)
- return
+ return tag, ok
}
func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) {
@@ -60,11 +60,13 @@ func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) {
tag.extras = true
case "required":
tag.required = true
+ case "metadata":
+ tag.metadata = true
}
}
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
- return
+ return format, ok
}
diff --git a/internal/apijson/decoder.go b/internal/apijson/decoder.go
index 1ed835a..fe2c4a0 100644
--- a/internal/apijson/decoder.go
+++ b/internal/apijson/decoder.go
@@ -393,7 +393,7 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc {
for _, decoder := range anonymousDecoders {
// ignore errors
- decoder.fn(node, value.FieldByIndex(decoder.idx), state)
+ _ = decoder.fn(node, value.FieldByIndex(decoder.idx), state)
}
for _, inlineDecoder := range inlineDecoders {
@@ -462,7 +462,7 @@ func (d *decoderBuilder) newStructTypeDecoder(t reflect.Type) decoderFunc {
// Handle null [param.Opt]
if itemNode.Type == gjson.Null && dest.IsValid() && dest.Type().Implements(reflect.TypeOf((*param.Optional)(nil)).Elem()) {
- dest.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(itemNode.Raw))
+ _ = dest.Addr().Interface().(json.Unmarshaler).UnmarshalJSON([]byte(itemNode.Raw))
continue
}
@@ -684,8 +684,5 @@ func guardUnknown(state *decoderState, v reflect.Value) bool {
constantString, ok := v.Interface().(interface{ Default() string })
named := v.Type() != stringType
- if guardStrict(state, ok && named && v.Equal(reflect.ValueOf(constantString.Default()))) {
- return true
- }
- return false
+ return guardStrict(state, ok && named && v.Equal(reflect.ValueOf(constantString.Default())))
}
diff --git a/internal/apijson/encoder.go b/internal/apijson/encoder.go
index ab7a3c1..0decb73 100644
--- a/internal/apijson/encoder.go
+++ b/internal/apijson/encoder.go
@@ -286,28 +286,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
return nil, err
}
}
- return
- }
-}
-
-func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc {
- f, _ := t.FieldByName("Value")
- enc := e.typeEncoder(f.Type)
-
- return func(value reflect.Value) (json []byte, err error) {
- present := value.FieldByName("Present")
- if !present.Bool() {
- return nil, nil
- }
- null := value.FieldByName("Null")
- if null.Bool() {
- return []byte("null"), nil
- }
- raw := value.FieldByName("Raw")
- if !raw.IsNil() {
- return e.typeEncoder(raw.Type())(raw)
- }
- return enc(value.FieldByName("Value"))
+ return json, err
}
}
diff --git a/internal/apijson/enum.go b/internal/apijson/enum.go
index 5bef11c..a1626a5 100644
--- a/internal/apijson/enum.go
+++ b/internal/apijson/enum.go
@@ -4,7 +4,6 @@ import (
"fmt"
"reflect"
"slices"
- "sync"
"github.com/tidwall/gjson"
)
@@ -15,7 +14,6 @@ import (
type validationEntry struct {
field reflect.StructField
- required bool
legalValues struct {
strings []string
// 1 represents true, 0 represents false, -1 represents either
@@ -24,9 +22,6 @@ type validationEntry struct {
}
}
-type validatorFunc func(reflect.Value) exactness
-
-var validators sync.Map
var validationRegistry = map[reflect.Type][]validationEntry{}
func RegisterFieldValidator[T any, V string | bool | int | float64](fieldName string, values ...V) {
@@ -111,9 +106,9 @@ func (state *decoderState) validateBool(v reflect.Value) {
return
}
b := v.Bool()
- if state.validator.legalValues.bools == 1 && b == false {
+ if state.validator.legalValues.bools == 1 && !b {
state.exactness = loose
- } else if state.validator.legalValues.bools == 0 && b == true {
+ } else if state.validator.legalValues.bools == 0 && b {
state.exactness = loose
}
}
diff --git a/internal/apijson/json_test.go b/internal/apijson/json_test.go
index fac9fcc..19b3614 100644
--- a/internal/apijson/json_test.go
+++ b/internal/apijson/json_test.go
@@ -87,7 +87,7 @@ type JSONFieldStruct struct {
C string `json:"c"`
D string `json:"d"`
ExtraFields map[string]int64 `json:"" api:"extrafields"`
- JSON JSONFieldStructJSON `json:",metadata"`
+ JSON JSONFieldStructJSON `json:"-" api:"metadata"`
}
type JSONFieldStructJSON struct {
@@ -113,12 +113,12 @@ type Union interface {
type Inline struct {
InlineField Primitives `json:",inline"`
- JSON InlineJSON `json:",metadata"`
+ JSON InlineJSON `json:"-" api:"metadata"`
}
type InlineArray struct {
InlineField []string `json:",inline"`
- JSON InlineJSON `json:",metadata"`
+ JSON InlineJSON `json:"-" api:"metadata"`
}
type InlineJSON struct {
@@ -268,7 +268,7 @@ type MarshallingUnionStruct struct {
func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) {
*r = MarshallingUnionStruct{}
err = UnmarshalRoot(data, &r.Union)
- return
+ return err
}
func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) {
diff --git a/internal/apijson/tag.go b/internal/apijson/tag.go
index 49731b8..17b2130 100644
--- a/internal/apijson/tag.go
+++ b/internal/apijson/tag.go
@@ -20,7 +20,7 @@ type parsedStructTag struct {
func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(jsonStructTag)
if !ok {
- return
+ return tag, ok
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
@@ -42,7 +42,7 @@ func parseJSONStructTag(field reflect.StructField) (tag parsedStructTag, ok bool
// the `api` struct tag is only used alongside `json` for custom behaviour
parseApiStructTag(field, &tag)
- return
+ return tag, ok
}
func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) {
@@ -57,11 +57,13 @@ func parseApiStructTag(field reflect.StructField, tag *parsedStructTag) {
tag.extras = true
case "required":
tag.required = true
+ case "metadata":
+ tag.metadata = true
}
}
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
- return
+ return format, ok
}
diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go
index c752c43..596fbb4 100644
--- a/internal/apiquery/encoder.go
+++ b/internal/apiquery/encoder.go
@@ -103,7 +103,7 @@ func (e *encoder) newTypeEncoder(t reflect.Type) encoderFunc {
encoder := e.typeEncoder(t.Elem())
return func(key string, value reflect.Value) (pairs []Pair, err error) {
if !value.IsValid() || value.IsNil() {
- return
+ return pairs, err
}
return encoder(key, value.Elem())
}
@@ -193,7 +193,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
return func(key string, value reflect.Value) (pairs []Pair, err error) {
for _, ef := range encoderFields {
- var subkey string = e.renderKeyPath(key, ef.tag.name)
+ subkey := e.renderKeyPath(key, ef.tag.name)
if ef.tag.inline {
subkey = key
}
@@ -205,7 +205,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc {
}
pairs = append(pairs, subpairs...)
}
- return
+ return pairs, err
}
}
@@ -256,7 +256,7 @@ func (e *encoder) newMapEncoder(t reflect.Type) encoderFunc {
}
pairs = append(pairs, subpairs...)
}
- return
+ return pairs, err
}
}
@@ -300,7 +300,7 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
}
pairs = append(pairs, subpairs...)
}
- return
+ return pairs, err
}
case ArrayQueryFormatIndices:
panic("The array indices format is not supported yet")
@@ -315,7 +315,7 @@ func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc {
}
pairs = append(pairs, subpairs...)
}
- return
+ return pairs, err
}
default:
panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat))
@@ -372,27 +372,6 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc {
}
}
-func (e *encoder) newFieldTypeEncoder(t reflect.Type) encoderFunc {
- f, _ := t.FieldByName("Value")
- enc := e.typeEncoder(f.Type)
-
- return func(key string, value reflect.Value) ([]Pair, error) {
- present := value.FieldByName("Present")
- if !present.Bool() {
- return nil, nil
- }
- null := value.FieldByName("Null")
- if null.Bool() {
- return nil, fmt.Errorf("apiquery: field cannot be null")
- }
- raw := value.FieldByName("Raw")
- if !raw.IsNil() {
- return e.typeEncoder(raw.Type())(key, raw)
- }
- return enc(key, value.FieldByName("Value"))
- }
-}
-
func (e *encoder) newTimeTypeEncoder(_ reflect.Type) encoderFunc {
format := e.dateFormat
return func(key string, value reflect.Value) ([]Pair, error) {
diff --git a/internal/apiquery/tag.go b/internal/apiquery/tag.go
index 772c40e..9e413ad 100644
--- a/internal/apiquery/tag.go
+++ b/internal/apiquery/tag.go
@@ -18,7 +18,7 @@ type parsedStructTag struct {
func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok bool) {
raw, ok := field.Tag.Lookup(queryStructTag)
if !ok {
- return
+ return tag, ok
}
parts := strings.Split(raw, ",")
if len(parts) == 0 {
@@ -35,10 +35,10 @@ func parseQueryStructTag(field reflect.StructField) (tag parsedStructTag, ok boo
tag.inline = true
}
}
- return
+ return tag, ok
}
func parseFormatStructTag(field reflect.StructField) (format string, ok bool) {
format, ok = field.Tag.Lookup(formatStructTag)
- return
+ return format, ok
}
diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go
index 65050f3..4be744f 100644
--- a/internal/requestconfig/requestconfig.go
+++ b/internal/requestconfig/requestconfig.go
@@ -461,7 +461,7 @@ func (cfg *RequestConfig) Execute() (err error) {
// Close the response body before retrying to prevent connection leaks
if res != nil && res.Body != nil {
- res.Body.Close()
+ _ = res.Body.Close()
}
select {
@@ -489,7 +489,7 @@ func (cfg *RequestConfig) Execute() (err error) {
if res.StatusCode >= 400 {
contents, err := io.ReadAll(res.Body)
- res.Body.Close()
+ _ = res.Body.Close()
if err != nil {
return err
}
@@ -520,7 +520,7 @@ func (cfg *RequestConfig) Execute() (err error) {
}
contents, err := io.ReadAll(res.Body)
- res.Body.Close()
+ _ = res.Body.Close()
if err != nil {
return fmt.Errorf("error reading response body: %w", err)
}
diff --git a/internal/version.go b/internal/version.go
index 32a2994..27842b2 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.43.0" // x-release-please-version
+const PackageVersion = "0.44.0" // x-release-please-version
diff --git a/invocation.go b/invocation.go
index 0fb52f5..3a75523 100644
--- a/invocation.go
+++ b/invocation.go
@@ -50,7 +50,7 @@ func (r *InvocationService) New(ctx context.Context, body InvocationNewParams, o
opts = slices.Concat(r.Options, opts)
path := "invocations"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Get details about an invocation's status and output.
@@ -58,11 +58,11 @@ func (r *InvocationService) Get(ctx context.Context, id string, opts ...option.R
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("invocations/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Update an invocation's status or output. This can be used to cancel an
@@ -71,11 +71,11 @@ func (r *InvocationService) Update(ctx context.Context, id string, body Invocati
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("invocations/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
- return
+ return res, err
}
// List invocations. Optionally filter by application name, action name, status,
@@ -109,11 +109,11 @@ func (r *InvocationService) DeleteBrowsers(ctx context.Context, id string, opts
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("invocations/%s/browsers", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Establishes a Server-Sent Events (SSE) stream that delivers real-time logs and
@@ -128,7 +128,7 @@ func (r *InvocationService) FollowStreaming(ctx context.Context, id string, quer
opts = append([]option.RequestOption{option.WithHeader("Accept", "text/event-stream")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return ssestream.NewStream[InvocationFollowResponseUnion](nil, err)
}
path := fmt.Sprintf("invocations/%s/events", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
@@ -140,11 +140,11 @@ func (r *InvocationService) ListBrowsers(ctx context.Context, id string, opts ..
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("invocations/%s/browsers", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// An event representing the current state of an invocation.
@@ -557,7 +557,8 @@ type InvocationListBrowsersResponseBrowser struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// When the browser session was soft-deleted. Only present for deleted sessions.
DeletedAt time.Time `json:"deleted_at" format:"date-time"`
- // Whether the browser session has hardware-accelerated GPU rendering.
+ // Whether GPU acceleration is enabled for the browser session (only supported for
+ // headful sessions).
GPU bool `json:"gpu"`
// Whether the browser session is running in kiosk mode.
KioskMode bool `json:"kiosk_mode"`
@@ -574,9 +575,13 @@ type InvocationListBrowsersResponseBrowser struct {
// Session usage metrics.
Usage BrowserUsage `json:"usage"`
// Initial browser window size in pixels with optional refresh rate. If omitted,
- // image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
- // but the following configurations are known-good and fully tested: 2560x1440@10,
- // 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+ // image defaults apply (1920x1080@25). For GPU images, the default is
+ // 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+ // Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+ // 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+ // presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+ // 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+ // 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
diff --git a/profile.go b/profile.go
index 9a36e16..e5c352f 100644
--- a/profile.go
+++ b/profile.go
@@ -45,7 +45,7 @@ func (r *ProfileService) New(ctx context.Context, body ProfileNewParams, opts ..
opts = slices.Concat(r.Options, opts)
path := "profiles"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Retrieve details for a single profile by its ID or name.
@@ -53,11 +53,11 @@ func (r *ProfileService) Get(ctx context.Context, idOrName string, opts ...optio
opts = slices.Concat(r.Options, opts)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("profiles/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// List profiles with optional filtering and pagination.
@@ -89,11 +89,11 @@ func (r *ProfileService) Delete(ctx context.Context, idOrName string, opts ...op
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return err
}
path := fmt.Sprintf("profiles/%s", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Download the profile. Profiles are JSON files containing the pieces of state
@@ -103,11 +103,11 @@ func (r *ProfileService) Download(ctx context.Context, idOrName string, opts ...
opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...)
if idOrName == "" {
err = errors.New("missing required id_or_name parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("profiles/%s/download", idOrName)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
type ProfileNewParams struct {
diff --git a/proxy.go b/proxy.go
index d193b06..12544df 100644
--- a/proxy.go
+++ b/proxy.go
@@ -44,7 +44,7 @@ func (r *ProxyService) New(ctx context.Context, body ProxyNewParams, opts ...opt
opts = slices.Concat(r.Options, opts)
path := "proxies"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
- return
+ return res, err
}
// Retrieve a proxy belonging to the caller's organization by ID.
@@ -52,11 +52,11 @@ func (r *ProxyService) Get(ctx context.Context, id string, opts ...option.Reques
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("proxies/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// List proxies owned by the caller's organization.
@@ -64,7 +64,7 @@ func (r *ProxyService) List(ctx context.Context, opts ...option.RequestOption) (
opts = slices.Concat(r.Options, opts)
path := "proxies"
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
- return
+ return res, err
}
// Soft delete a proxy. Sessions referencing it are not modified.
@@ -73,11 +73,11 @@ func (r *ProxyService) Delete(ctx context.Context, id string, opts ...option.Req
opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return err
}
path := fmt.Sprintf("proxies/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
- return
+ return err
}
// Run a health check on the proxy to verify it's working.
@@ -85,11 +85,11 @@ func (r *ProxyService) Check(ctx context.Context, id string, opts ...option.Requ
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
- return
+ return nil, err
}
path := fmt.Sprintf("proxies/%s/check", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, nil, &res, opts...)
- return
+ return res, err
}
// Configuration for routing traffic through a proxy.
diff --git a/shared/shared.go b/shared/shared.go
index 4ea1150..10fcf8f 100644
--- a/shared/shared.go
+++ b/shared/shared.go
@@ -156,9 +156,13 @@ func (r *BrowserProfileParam) UnmarshalJSON(data []byte) error {
}
// Initial browser window size in pixels with optional refresh rate. If omitted,
-// image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
-// but the following configurations are known-good and fully tested: 2560x1440@10,
-// 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+// image defaults apply (1920x1080@25). For GPU images, the default is
+// 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+// Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+// 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+// presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+// 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+// 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
@@ -197,9 +201,13 @@ func (r BrowserViewport) ToParam() BrowserViewportParam {
}
// Initial browser window size in pixels with optional refresh rate. If omitted,
-// image defaults apply (1920x1080@25). Arbitrary viewport dimensions are accepted,
-// but the following configurations are known-good and fully tested: 2560x1440@10,
-// 1920x1080@25, 1920x1200@25, 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60.
+// image defaults apply (1920x1080@25). For GPU images, the default is
+// 1920x1080@60. Arbitrary viewport dimensions and refresh rates are accepted.
+// Known-good presets include: 2560x1440@10, 1920x1080@25, 1920x1200@25,
+// 1440x900@25, 1280x800@60, 1024x768@60, 1200x800@60. For GPU images, recommended
+// presets use one of these resolutions with refresh rates 60, 30, 25, or 10:
+// 800x600, 960x720, 1024x576, 1024x768, 1152x648, 1200x800, 1280x720, 1368x768,
+// 1440x900, 1600x900, 1920x1080, 1920x1200, 390x844, 360x250, 768x1024, 800x1600.
// Viewports outside this list may exhibit unstable live view or recording
// behavior. If refresh_rate is not provided, it will be automatically determined
// based on the resolution (higher resolutions use lower refresh rates to keep
diff --git a/usage_test.go b/usage_test.go
index beb4e9a..3f243dc 100644
--- a/usage_test.go
+++ b/usage_test.go
@@ -13,6 +13,7 @@ import (
)
func TestUsage(t *testing.T) {
+ t.Skip("Mock server tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
baseURL = envURL
@@ -24,7 +25,6 @@ func TestUsage(t *testing.T) {
option.WithBaseURL(baseURL),
option.WithAPIKey("My API Key"),
)
- t.Skip("Mock server tests are disabled")
browser, err := client.Browsers.New(context.TODO(), kernel.BrowserNewParams{
Stealth: kernel.Bool(true),
})