diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a9d0cc1..56ee145 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.69.0" + ".": "0.70.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 4424fe7..db9991c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 120 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-cde481b2f320ce48f83db84ae96226b0e7568146c9387c4fefebf286ecb0dd0a.yml -openapi_spec_hash: 6bd86d767290fcd7e2a6aae26dff5417 -config_hash: 03c7e57f268c750e2415831662e95969 +configured_endpoints: 121 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-bccc42bb10d1afe703f0374d11f33e775522069d69a13bb2345cc566a49ba4f3.yml +openapi_spec_hash: 9f00975c0e741ed84011413674313be0 +config_hash: 3a50aee540dce69a53bb8942f5086f5e diff --git a/CHANGELOG.md b/CHANGELOG.md index b73a808..088dc0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.70.0 (2026-06-23) + +Full Changelog: [v0.69.0...v0.70.0](https://github.com/kernel/kernel-go-sdk/compare/v0.69.0...v0.70.0) + +### Features + +* Align browser-pool timeout/viewport/fill-rate contract with implementation; reject save_changes on update ([86fd2d4](https://github.com/kernel/kernel-go-sdk/commit/86fd2d44854ed0bfebdc83f848d2546fc64b84b4)) +* **api:** add GET /extensions/{id_or_name}/metadata ([95a4d55](https://github.com/kernel/kernel-go-sdk/commit/95a4d55245360ff26318260f11ca393955e9dde0)) + ## 0.69.0 (2026-06-18) Full Changelog: [v0.68.0...v0.69.0](https://github.com/kernel/kernel-go-sdk/compare/v0.68.0...v0.69.0) diff --git a/README.md b/README.md index 2a3c9f8..acd9c09 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.69.0' +go get -u 'github.com/kernel/kernel-go-sdk@v0.70.0' ``` diff --git a/api.md b/api.md index 0c51245..2334965 100644 --- a/api.md +++ b/api.md @@ -317,6 +317,7 @@ Methods: Response Types: - kernel.ExtensionListResponse +- kernel.ExtensionGetResponse - kernel.ExtensionUploadResponse Methods: @@ -325,6 +326,7 @@ Methods: - client.Extensions.Delete(ctx context.Context, idOrName string) error - client.Extensions.Download(ctx context.Context, idOrName string) (\*http.Response, error) - client.Extensions.DownloadFromChromeStore(ctx context.Context, query kernel.ExtensionDownloadFromChromeStoreParams) (\*http.Response, error) +- client.Extensions.Get(ctx context.Context, idOrName string) (\*kernel.ExtensionGetResponse, error) - client.Extensions.Upload(ctx context.Context, body kernel.ExtensionUploadParams) (\*kernel.ExtensionUploadResponse, error) # BrowserPools diff --git a/browserpool.go b/browserpool.go index 3b65f0e..60dff73 100644 --- a/browserpool.go +++ b/browserpool.go @@ -198,7 +198,9 @@ type BrowserPoolBrowserPoolConfig struct { ChromePolicy map[string]any `json:"chrome_policy"` // List of browser extensions to load into the session. Provide each by id or name. Extensions []shared.BrowserExtension `json:"extensions"` - // Percentage of the pool to fill per minute. Defaults to 10%. + // Percentage of the pool to fill per minute. Defaults to 10. The cap is 25 for + // most organizations but can be raised per-organization, so only the lower bound + // is enforced here. FillRatePerMinute int64 `json:"fill_rate_per_minute"` // If true, launches the browser using a headless image. Defaults to false. Headless bool `json:"headless"` @@ -224,7 +226,7 @@ type BrowserPoolBrowserPoolConfig struct { // mechanisms. Stealth bool `json:"stealth"` // Default idle timeout in seconds for browsers acquired from this pool before they - // are destroyed. Defaults to 600 seconds if not specified + // are destroyed. Defaults to 600 seconds. Minimum 10, maximum 259200 (72 hours). TimeoutSeconds int64 `json:"timeout_seconds"` // Initial browser window size in pixels with optional refresh rate. If omitted, // image defaults apply (1920x1080@25). For GPU images, the default is @@ -370,7 +372,9 @@ type BrowserPoolNewParams struct { // your organization's pooled sessions limit (the sum of all pool sizes cannot // exceed your limit). Size int64 `json:"size" api:"required"` - // Percentage of the pool to fill per minute. Defaults to 10%. + // Percentage of the pool to fill per minute. Defaults to 10. The cap is 25 for + // most organizations but can be raised per-organization, so only the lower bound + // is enforced here. FillRatePerMinute param.Opt[int64] `json:"fill_rate_per_minute,omitzero"` // If true, launches the browser using a headless image. Defaults to false. Headless param.Opt[bool] `json:"headless,omitzero"` @@ -392,7 +396,7 @@ type BrowserPoolNewParams struct { // mechanisms. Stealth param.Opt[bool] `json:"stealth,omitzero"` // Default idle timeout in seconds for browsers acquired from this pool before they - // are destroyed. Defaults to 600 seconds if not specified + // are destroyed. Defaults to 600 seconds. Minimum 10, maximum 259200 (72 hours). TimeoutSeconds param.Opt[int64] `json:"timeout_seconds,omitzero"` // Custom Chrome enterprise policy overrides applied to all browsers in this pool. // Keys are Chrome enterprise policy names; values must match their expected types. @@ -433,7 +437,9 @@ type BrowserPoolUpdateParams struct { // Whether to discard all idle browsers and rebuild the pool immediately. Defaults // to false. DiscardAllIdle param.Opt[bool] `json:"discard_all_idle,omitzero"` - // Percentage of the pool to fill per minute. Defaults to 10%. + // Percentage of the pool to fill per minute. Defaults to 10. The cap is 25 for + // most organizations but can be raised per-organization, so only the lower bound + // is enforced here. FillRatePerMinute param.Opt[int64] `json:"fill_rate_per_minute,omitzero"` // If true, launches the browser using a headless image. Defaults to false. Headless param.Opt[bool] `json:"headless,omitzero"` @@ -459,7 +465,7 @@ type BrowserPoolUpdateParams struct { // mechanisms. Stealth param.Opt[bool] `json:"stealth,omitzero"` // Default idle timeout in seconds for browsers acquired from this pool before they - // are destroyed. Defaults to 600 seconds if not specified + // are destroyed. Defaults to 600 seconds. Minimum 10, maximum 259200 (72 hours). TimeoutSeconds param.Opt[int64] `json:"timeout_seconds,omitzero"` // Custom Chrome enterprise policy overrides applied to all browsers in this pool. // Keys are Chrome enterprise policy names; values must match their expected types. diff --git a/browserpool_test.go b/browserpool_test.go index 4f12f7c..abdd8ec 100644 --- a/browserpool_test.go +++ b/browserpool_test.go @@ -48,7 +48,7 @@ func TestBrowserPoolNewWithOptionalParams(t *testing.T) { ProxyID: kernel.String("proxy_id"), StartURL: kernel.String("https://example.com"), Stealth: kernel.Bool(true), - TimeoutSeconds: kernel.Int(60), + TimeoutSeconds: kernel.Int(10), Viewport: shared.BrowserViewportParam{ Height: 800, Width: 1280, @@ -125,7 +125,7 @@ func TestBrowserPoolUpdateWithOptionalParams(t *testing.T) { Size: kernel.Int(10), StartURL: kernel.String("https://example.com"), Stealth: kernel.Bool(true), - TimeoutSeconds: kernel.Int(60), + TimeoutSeconds: kernel.Int(10), Viewport: shared.BrowserViewportParam{ Height: 800, Width: 1280, diff --git a/extension.go b/extension.go index d34878a..9839b63 100644 --- a/extension.go +++ b/extension.go @@ -104,6 +104,19 @@ func (r *ExtensionService) DownloadFromChromeStore(ctx context.Context, query Ex return res, err } +// Get an extension's metadata (name, size, timestamps) by ID or name, without +// downloading the archive. +func (r *ExtensionService) Get(ctx context.Context, idOrName string, opts ...option.RequestOption) (res *ExtensionGetResponse, err error) { + opts = slices.Concat(r.Options, opts) + if idOrName == "" { + err = errors.New("missing required id_or_name parameter") + return nil, err + } + path := fmt.Sprintf("extensions/%s/metadata", idOrName) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...) + return res, err +} + // Upload a zip file containing an unpacked browser extension. Optionally provide a // unique name for later reference. func (r *ExtensionService) Upload(ctx context.Context, body ExtensionUploadParams, opts ...option.RequestOption) (res *ExtensionUploadResponse, err error) { @@ -144,6 +157,37 @@ func (r *ExtensionListResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// A browser extension uploaded to Kernel. +type ExtensionGetResponse struct { + // Unique identifier for the extension + ID string `json:"id" api:"required"` + // Timestamp when the extension was created + CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"` + // Size of the extension archive in bytes + SizeBytes int64 `json:"size_bytes" api:"required"` + // Timestamp when the extension was last used + LastUsedAt time.Time `json:"last_used_at" api:"nullable" format:"date-time"` + // Optional, easier-to-reference name for the extension. Must be unique within the + // project. + Name string `json:"name" api:"nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + ID respjson.Field + CreatedAt respjson.Field + SizeBytes respjson.Field + LastUsedAt respjson.Field + Name respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r ExtensionGetResponse) RawJSON() string { return r.JSON.raw } +func (r *ExtensionGetResponse) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + // A browser extension uploaded to Kernel. type ExtensionUploadResponse struct { // Unique identifier for the extension diff --git a/extension_test.go b/extension_test.go index 8884e17..1944009 100644 --- a/extension_test.go +++ b/extension_test.go @@ -138,6 +138,29 @@ func TestExtensionDownloadFromChromeStoreWithOptionalParams(t *testing.T) { } } +func TestExtensionGet(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.Extensions.Get(context.TODO(), "id_or_name") + if err != nil { + var apierr *kernel.Error + if errors.As(err, &apierr) { + t.Log(string(apierr.DumpRequest(true))) + } + t.Fatalf("err should be nil: %s", err.Error()) + } +} + func TestExtensionUploadWithOptionalParams(t *testing.T) { t.Skip("Mock server tests are disabled") baseURL := "http://localhost:4010" diff --git a/internal/version.go b/internal/version.go index 0b83351..d62062c 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.69.0" // x-release-please-version +const PackageVersion = "0.70.0" // x-release-please-version diff --git a/shared/shared.go b/shared/shared.go index 09471ec..366ff9e 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -168,12 +168,12 @@ func (r *BrowserProfileParam) UnmarshalJSON(data []byte) error { // based on the resolution (higher resolutions use lower refresh rates to keep // bandwidth reasonable). type BrowserViewport struct { - // Browser window height in pixels. + // Browser window height in pixels. Any positive integer is accepted. Height int64 `json:"height" api:"required"` - // Browser window width in pixels. + // Browser window width in pixels. Any positive integer is accepted. Width int64 `json:"width" api:"required"` - // Display refresh rate in Hz. If omitted, automatically determined from width and - // height. + // Display refresh rate in Hz. Any positive integer is accepted; if omitted, + // automatically determined from width and height. RefreshRate int64 `json:"refresh_rate"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -215,12 +215,12 @@ func (r BrowserViewport) ToParam() BrowserViewportParam { // // The properties Height, Width are required. type BrowserViewportParam struct { - // Browser window height in pixels. + // Browser window height in pixels. Any positive integer is accepted. Height int64 `json:"height" api:"required"` - // Browser window width in pixels. + // Browser window width in pixels. Any positive integer is accepted. Width int64 `json:"width" api:"required"` - // Display refresh rate in Hz. If omitted, automatically determined from width and - // height. + // Display refresh rate in Hz. Any positive integer is accepted; if omitted, + // automatically determined from width and height. RefreshRate param.Opt[int64] `json:"refresh_rate,omitzero"` paramObj }