diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 091cfb1..f7014c3 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.10.0"
+ ".": "0.11.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index b791078..6ac19ba 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 41
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-a7c1df5070fe59642d7a1f168aa902a468227752bfc930cbf38930f7c205dbb6.yml
-openapi_spec_hash: eab65e39aef4f0a0952b82adeecf6b5b
-config_hash: 5de78bc29ac060562575cb54bb26826c
+configured_endpoints: 46
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e98d46c55826cdf541a9ee0df04ce92806ac6d4d92957ae79f897270b7d85b23.yml
+openapi_spec_hash: 8a1af54fc0a4417165b8a52e6354b685
+config_hash: 043ddc54629c6d8b889123770cb4769f
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcc69a6..b890e4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,23 @@
# Changelog
+## 0.11.0 (2025-09-04)
+
+Full Changelog: [v0.10.0...v0.11.0](https://github.com/onkernel/kernel-go-sdk/compare/v0.10.0...v0.11.0)
+
+### Features
+
+* **api:** adding support for browser profiles ([481cdb3](https://github.com/onkernel/kernel-go-sdk/commit/481cdb3500744c9e4ec050e340a920302d8fea19))
+
+
+### Bug Fixes
+
+* close body before retrying ([a6a2e40](https://github.com/onkernel/kernel-go-sdk/commit/a6a2e4054c629d6ee85997ed81a1b14e70e594dc))
+
+
+### Chores
+
+* **internal:** codegen related update ([a7030ab](https://github.com/onkernel/kernel-go-sdk/commit/a7030abb99c06c675f60a4f2afde43d376d9981f))
+
## 0.10.0 (2025-08-27)
Full Changelog: [v0.9.1...v0.10.0](https://github.com/onkernel/kernel-go-sdk/compare/v0.9.1...v0.10.0)
diff --git a/README.md b/README.md
index 63938fd..0b271ea 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,11 @@
# Kernel Go API Library
+
+
+
+
The Kernel Go library provides convenient access to the [Kernel REST API](https://docs.onkernel.com)
from applications written in Go.
@@ -24,7 +28,7 @@ Or to pin the version:
```sh
-go get -u 'github.com/onkernel/kernel-go-sdk@v0.10.0'
+go get -u 'github.com/onkernel/kernel-go-sdk@v0.11.0'
```
diff --git a/api.md b/api.md
index 702f8c4..0efea8f 100644
--- a/api.md
+++ b/api.md
@@ -61,6 +61,7 @@ Params Types:
Response Types:
- kernel.BrowserPersistence
+- kernel.Profile
- kernel.BrowserNewResponse
- kernel.BrowserGetResponse
- kernel.BrowserListResponse
@@ -147,3 +148,13 @@ Methods:
Methods:
- client.Browsers.Logs.Stream(ctx context.Context, id string, query kernel.BrowserLogStreamParams) (shared.LogEvent, error)
+
+# Profiles
+
+Methods:
+
+- client.Profiles.New(ctx context.Context, body kernel.ProfileNewParams) (kernel.Profile, error)
+- client.Profiles.Get(ctx context.Context, idOrName string) (kernel.Profile, error)
+- client.Profiles.List(ctx context.Context) ([]kernel.Profile, error)
+- client.Profiles.Delete(ctx context.Context, idOrName string) error
+- client.Profiles.Download(ctx context.Context, idOrName string) (http.Response, error)
diff --git a/browser.go b/browser.go
index f0194a3..9e85646 100644
--- a/browser.go
+++ b/browser.go
@@ -140,6 +140,36 @@ func (r *BrowserPersistenceParam) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+// Browser profile metadata.
+type Profile struct {
+ // Unique identifier for the profile
+ ID string `json:"id,required"`
+ // Timestamp when the profile was created
+ CreatedAt time.Time `json:"created_at,required" format:"date-time"`
+ // Timestamp when the profile was last used
+ LastUsedAt time.Time `json:"last_used_at" format:"date-time"`
+ // Optional, easier-to-reference name for the profile
+ Name string `json:"name,nullable"`
+ // Timestamp when the profile was last updated
+ UpdatedAt time.Time `json:"updated_at" format:"date-time"`
+ // JSON contains metadata for fields, check presence with [respjson.Field.Valid].
+ JSON struct {
+ ID respjson.Field
+ CreatedAt respjson.Field
+ LastUsedAt respjson.Field
+ Name respjson.Field
+ UpdatedAt respjson.Field
+ ExtraFields map[string]respjson.Field
+ raw string
+ } `json:"-"`
+}
+
+// Returns the unmodified JSON received from the API
+func (r Profile) RawJSON() string { return r.JSON.raw }
+func (r *Profile) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
type BrowserNewResponse struct {
// Websocket URL for Chrome DevTools Protocol connections to the browser session
CdpWsURL string `json:"cdp_ws_url,required"`
@@ -158,6 +188,8 @@ type BrowserNewResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// Optional persistence configuration for the browser session.
Persistence BrowserPersistence `json:"persistence"`
+ // Browser profile metadata.
+ Profile Profile `json:"profile"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
CdpWsURL respjson.Field
@@ -168,6 +200,7 @@ type BrowserNewResponse struct {
TimeoutSeconds respjson.Field
BrowserLiveViewURL respjson.Field
Persistence respjson.Field
+ Profile respjson.Field
ExtraFields map[string]respjson.Field
raw string
} `json:"-"`
@@ -197,6 +230,8 @@ type BrowserGetResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// Optional persistence configuration for the browser session.
Persistence BrowserPersistence `json:"persistence"`
+ // Browser profile metadata.
+ Profile Profile `json:"profile"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
CdpWsURL respjson.Field
@@ -207,6 +242,7 @@ type BrowserGetResponse struct {
TimeoutSeconds respjson.Field
BrowserLiveViewURL respjson.Field
Persistence respjson.Field
+ Profile respjson.Field
ExtraFields map[string]respjson.Field
raw string
} `json:"-"`
@@ -236,6 +272,8 @@ type BrowserListResponse struct {
BrowserLiveViewURL string `json:"browser_live_view_url"`
// Optional persistence configuration for the browser session.
Persistence BrowserPersistence `json:"persistence"`
+ // Browser profile metadata.
+ Profile Profile `json:"profile"`
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
JSON struct {
CdpWsURL respjson.Field
@@ -246,6 +284,7 @@ type BrowserListResponse struct {
TimeoutSeconds respjson.Field
BrowserLiveViewURL respjson.Field
Persistence respjson.Field
+ Profile respjson.Field
ExtraFields map[string]respjson.Field
raw string
} `json:"-"`
@@ -272,6 +311,10 @@ type BrowserNewParams struct {
TimeoutSeconds param.Opt[int64] `json:"timeout_seconds,omitzero"`
// Optional persistence configuration for the browser session.
Persistence BrowserPersistenceParam `json:"persistence,omitzero"`
+ // Profile selection for the browser session. Provide either id or name. If
+ // specified, the matching profile will be loaded into the browser session.
+ // Profiles must be created beforehand.
+ Profile BrowserNewParamsProfile `json:"profile,omitzero"`
paramObj
}
@@ -283,6 +326,29 @@ func (r *BrowserNewParams) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+// Profile selection for the browser session. Provide either id or name. If
+// specified, the matching profile will be loaded into the browser session.
+// Profiles must be created beforehand.
+type BrowserNewParamsProfile struct {
+ // Profile ID to load for this browser session
+ ID param.Opt[string] `json:"id,omitzero"`
+ // Profile name to load for this browser session (instead of id). Must be 1-255
+ // characters, using letters, numbers, dots, underscores, or hyphens.
+ Name param.Opt[string] `json:"name,omitzero"`
+ // If true, save changes made during the session back to the profile when the
+ // session ends.
+ SaveChanges param.Opt[bool] `json:"save_changes,omitzero"`
+ paramObj
+}
+
+func (r BrowserNewParamsProfile) MarshalJSON() (data []byte, err error) {
+ type shadow BrowserNewParamsProfile
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *BrowserNewParamsProfile) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
+
type BrowserDeleteParams struct {
// Persistent browser identifier
PersistentID string `query:"persistent_id,required" json:"-"`
diff --git a/browser_test.go b/browser_test.go
index d7d9898..66fb631 100644
--- a/browser_test.go
+++ b/browser_test.go
@@ -32,6 +32,11 @@ func TestBrowserNewWithOptionalParams(t *testing.T) {
Persistence: kernel.BrowserPersistenceParam{
ID: "my-awesome-browser-for-user-1234",
},
+ Profile: kernel.BrowserNewParamsProfile{
+ ID: kernel.String("id"),
+ Name: kernel.String("name"),
+ SaveChanges: kernel.Bool(true),
+ },
Stealth: kernel.Bool(true),
TimeoutSeconds: kernel.Int(0),
})
diff --git a/client.go b/client.go
index 526823e..4d9b871 100644
--- a/client.go
+++ b/client.go
@@ -20,6 +20,7 @@ type Client struct {
Apps AppService
Invocations InvocationService
Browsers BrowserService
+ Profiles ProfileService
}
// DefaultClientOptions read from the environment (KERNEL_API_KEY,
@@ -48,6 +49,7 @@ func NewClient(opts ...option.RequestOption) (r Client) {
r.Apps = NewAppService(opts...)
r.Invocations = NewInvocationService(opts...)
r.Browsers = NewBrowserService(opts...)
+ r.Profiles = NewProfileService(opts...)
return
}
diff --git a/internal/requestconfig/requestconfig.go b/internal/requestconfig/requestconfig.go
index e1e3825..7a93e18 100644
--- a/internal/requestconfig/requestconfig.go
+++ b/internal/requestconfig/requestconfig.go
@@ -461,6 +461,11 @@ func (cfg *RequestConfig) Execute() (err error) {
break
}
+ // Close the response body before retrying to prevent connection leaks
+ if res != nil && res.Body != nil {
+ res.Body.Close()
+ }
+
time.Sleep(retryDelay(res, retryCount))
}
diff --git a/internal/version.go b/internal/version.go
index 3a404f6..153db11 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.10.0" // x-release-please-version
+const PackageVersion = "0.11.0" // x-release-please-version
diff --git a/profile.go b/profile.go
new file mode 100644
index 0000000..603641f
--- /dev/null
+++ b/profile.go
@@ -0,0 +1,104 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package kernel
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "github.com/onkernel/kernel-go-sdk/internal/apijson"
+ "github.com/onkernel/kernel-go-sdk/internal/requestconfig"
+ "github.com/onkernel/kernel-go-sdk/option"
+ "github.com/onkernel/kernel-go-sdk/packages/param"
+)
+
+// ProfileService contains methods and other services that help with interacting
+// with the kernel API.
+//
+// Note, unlike clients, this service does not read variables from the environment
+// automatically. You should not instantiate this service directly, and instead use
+// the [NewProfileService] method instead.
+type ProfileService struct {
+ Options []option.RequestOption
+}
+
+// NewProfileService generates a new service that applies the given options to each
+// request. These options are applied after the parent client's options (if there
+// is one), and before any request-specific options.
+func NewProfileService(opts ...option.RequestOption) (r ProfileService) {
+ r = ProfileService{}
+ r.Options = opts
+ return
+}
+
+// Create a browser profile that can be used to load state into future browser
+// sessions.
+func (r *ProfileService) New(ctx context.Context, body ProfileNewParams, opts ...option.RequestOption) (res *Profile, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "profiles"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ return
+}
+
+// Retrieve details for a single profile by its ID or name.
+func (r *ProfileService) Get(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *Profile, err error) {
+ opts = append(r.Options[:], opts...)
+ if idOrName == "" {
+ err = errors.New("missing required id_or_name parameter")
+ return
+ }
+ path := fmt.Sprintf("profiles/%s", idOrName)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ return
+}
+
+// List profiles with optional filtering and pagination.
+func (r *ProfileService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Profile, err error) {
+ opts = append(r.Options[:], opts...)
+ path := "profiles"
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ return
+}
+
+// Delete a profile by its ID or by its name.
+func (r *ProfileService) Delete(ctx context.Context, idOrName string, opts ...option.RequestOption) (err error) {
+ opts = append(r.Options[:], opts...)
+ opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...)
+ if idOrName == "" {
+ err = errors.New("missing required id_or_name parameter")
+ return
+ }
+ path := fmt.Sprintf("profiles/%s", idOrName)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodDelete, path, nil, nil, opts...)
+ return
+}
+
+// Download the profile. Profiles are JSON files containing the pieces of state
+// that we save.
+func (r *ProfileService) Download(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *http.Response, err error) {
+ opts = append(r.Options[:], opts...)
+ opts = append([]option.RequestOption{option.WithHeader("Accept", "application/octet-stream")}, opts...)
+ if idOrName == "" {
+ err = errors.New("missing required id_or_name parameter")
+ return
+ }
+ path := fmt.Sprintf("profiles/%s/download", idOrName)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ return
+}
+
+type ProfileNewParams struct {
+ // Optional name of the profile. Must be unique within the organization.
+ Name param.Opt[string] `json:"name,omitzero"`
+ paramObj
+}
+
+func (r ProfileNewParams) MarshalJSON() (data []byte, err error) {
+ type shadow ProfileNewParams
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *ProfileNewParams) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
diff --git a/profile_test.go b/profile_test.go
new file mode 100644
index 0000000..a39e93f
--- /dev/null
+++ b/profile_test.go
@@ -0,0 +1,146 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package kernel_test
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/onkernel/kernel-go-sdk"
+ "github.com/onkernel/kernel-go-sdk/internal/testutil"
+ "github.com/onkernel/kernel-go-sdk/option"
+)
+
+func TestProfileNewWithOptionalParams(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ _, err := client.Profiles.New(context.TODO(), kernel.ProfileNewParams{
+ Name: kernel.String("name"),
+ })
+ if err != nil {
+ var apierr *kernel.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
+func TestProfileGet(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ _, err := client.Profiles.Get(context.TODO(), "id_or_name")
+ if err != nil {
+ var apierr *kernel.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
+func TestProfileList(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ _, err := client.Profiles.List(context.TODO())
+ 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 TestProfileDelete(t *testing.T) {
+ t.Skip("Prism tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ err := client.Profiles.Delete(context.TODO(), "id_or_name")
+ if err != nil {
+ var apierr *kernel.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
+func TestProfileDownload(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ w.Write([]byte("abc"))
+ }))
+ defer server.Close()
+ baseURL := server.URL
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ resp, err := client.Profiles.Download(context.TODO(), "id_or_name")
+ if err != nil {
+ var apierr *kernel.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+ defer resp.Body.Close()
+
+ b, err := io.ReadAll(resp.Body)
+ if err != nil {
+ var apierr *kernel.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+ if !bytes.Equal(b, []byte("abc")) {
+ t.Fatalf("return value not %s: %s", "abc", b)
+ }
+}