From 72da70213d5c3749a88d8ddd4975807fe464a0f8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 02:40:48 +0000 Subject: [PATCH 1/5] ci: pin GitHub Actions to commit SHAs Pin all GitHub Actions referenced in generated workflows (both first-party `actions/*` and third-party) to immutable commit SHAs. Updating pinned actions is now a deliberate codegen-side bump rather than implicit on every workflow run. --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee48f67..7a9dc7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,14 +26,14 @@ jobs: github.repository == 'stainless-sdks/kernel-go' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Get GitHub OIDC Token if: |- github.repository == 'stainless-sdks/kernel-go' && !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: core.setOutput('github_token', await core.getIDToken()); @@ -53,10 +53,10 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod @@ -68,10 +68,10 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/kernel-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup go - uses: actions/setup-go@v5 + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 with: go-version-file: ./go.mod From e7f436591debbdedd4150a0a4b2fcbdce2320cd1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 03:19:11 +0000 Subject: [PATCH 2/5] feat(client): optimize json encoder for internal types --- internal/encoding/json/encode.go | 21 ++-- internal/encoding/json/indent.go | 17 ++- internal/encoding/json/opt.go | 24 +++++ internal/encoding/json/stream.go | 53 +++++----- internal/encoding/json/time.go | 2 +- packages/param/encoder.go | 4 +- packages/param/encoder_test.go | 176 +++++++++++++++++++++++++++++++ 7 files changed, 260 insertions(+), 37 deletions(-) create mode 100644 internal/encoding/json/opt.go diff --git a/internal/encoding/json/encode.go b/internal/encoding/json/encode.go index 835168e..10d0563 100644 --- a/internal/encoding/json/encode.go +++ b/internal/encoding/json/encode.go @@ -173,15 +173,21 @@ import ( // JSON cannot represent cyclic data structures and Marshal does not // handle them. Passing cyclic structures to Marshal will result in // an error. -func Marshal(v any) ([]byte, error) { +// EDIT(begin): add optimization options +func Marshal(v any, opts ...Option) ([]byte, error) { + // EDIT(end): add optimization options e := newEncodeState() defer encodeStatePool.Put(e) - // SHIM(begin): don't escape HTML by default - err := e.marshal(v, encOpts{escapeHTML: shims.EscapeHTMLByDefault}) + // EDIT(begin): don't escape HTML by default, and apply options + encOpts := encOpts{escapeHTML: shims.EscapeHTMLByDefault} + if opts != nil { + encOpts = encOpts.apply(opts...) + } + err := e.marshal(v, encOpts) // ORIGINAL: // err := e.marshal(v, encOpts{escapeHTML: true}) - // SHIM(end) + // EDIT(end) if err != nil { return nil, err } @@ -352,6 +358,9 @@ type encOpts struct { // EDIT(begin): save the timefmt timefmt string // EDIT(end) + // EDIT(begin): add optimization to skip compaction + skipCompaction bool + // EDIT(end) } type encoderFunc func(e *encodeState, v reflect.Value, opts encOpts) @@ -483,7 +492,7 @@ func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { if err == nil { e.Grow(len(b)) out := e.AvailableBuffer() - out, err = appendCompact(out, b, opts.escapeHTML) + out, err = appendCompact(out, b, opts) e.Buffer.Write(out) } if err != nil { @@ -509,7 +518,7 @@ func addrMarshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) { if err == nil { e.Grow(len(b)) out := e.AvailableBuffer() - out, err = appendCompact(out, b, opts.escapeHTML) + out, err = appendCompact(out, b, opts) e.Buffer.Write(out) } if err != nil { diff --git a/internal/encoding/json/indent.go b/internal/encoding/json/indent.go index 01bfdf6..c9d6ca5 100644 --- a/internal/encoding/json/indent.go +++ b/internal/encoding/json/indent.go @@ -4,7 +4,9 @@ package json -import "bytes" +import ( + "bytes" +) // HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 // characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 @@ -41,12 +43,21 @@ func appendHTMLEscape(dst, src []byte) []byte { func Compact(dst *bytes.Buffer, src []byte) error { dst.Grow(len(src)) b := dst.AvailableBuffer() - b, err := appendCompact(b, src, false) + b, err := appendCompact(b, src, encOpts{}) dst.Write(b) return err } -func appendCompact(dst, src []byte, escape bool) ([]byte, error) { +func appendCompact(dst, src []byte, opts encOpts) ([]byte, error) { + // EDIT(begin): optimize for skipCompaction + if opts.skipCompaction { + dst = append(dst, src...) + return dst, nil + } + + escape := opts.escapeHTML + // EDIT(end) + origLen := len(dst) scan := newScanner() defer freeScanner(scan) diff --git a/internal/encoding/json/opt.go b/internal/encoding/json/opt.go new file mode 100644 index 0000000..fd6f8d2 --- /dev/null +++ b/internal/encoding/json/opt.go @@ -0,0 +1,24 @@ +// EDIT(begin): add custom options for JSON encoding +package json + +type Option func(*encOpts) + +// Every time a sub-type of [json.Marshaler] is encountered, +// skip a redundant and costly compaction step, trust it to self-compact. +// +// This is a divergence from the standard library behavior, and is only guaranteed +// safe with SDK types. +func WithSkipCompaction(b bool) Option { + return func(eos *encOpts) { + eos.skipCompaction = true + } +} + +func (eos encOpts) apply(opts ...Option) encOpts { + for _, opt := range opts { + opt(&eos) + } + return eos +} + +// EDIT(end) diff --git a/internal/encoding/json/stream.go b/internal/encoding/json/stream.go index e2d9470..652522c 100644 --- a/internal/encoding/json/stream.go +++ b/internal/encoding/json/stream.go @@ -6,7 +6,6 @@ package json import ( "bytes" - "errors" "io" ) @@ -253,30 +252,34 @@ func (enc *Encoder) SetEscapeHTML(on bool) { enc.escapeHTML = on } -// RawMessage is a raw encoded JSON value. -// It implements [Marshaler] and [Unmarshaler] and can -// be used to delay JSON decoding or precompute a JSON encoding. -type RawMessage []byte - -// MarshalJSON returns m as the JSON encoding of m. -func (m RawMessage) MarshalJSON() ([]byte, error) { - if m == nil { - return []byte("null"), nil - } - return m, nil -} - -// UnmarshalJSON sets *m to a copy of data. -func (m *RawMessage) UnmarshalJSON(data []byte) error { - if m == nil { - return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") - } - *m = append((*m)[0:0], data...) - return nil -} - -var _ Marshaler = (*RawMessage)(nil) -var _ Unmarshaler = (*RawMessage)(nil) +// EDIT(begin): remove RawMessage +// +// // RawMessage is a raw encoded JSON value. +// // It implements [Marshaler] and [Unmarshaler] and can +// // be used to delay JSON decoding or precompute a JSON encoding. +// type RawMessage []byte +// +// // MarshalJSON returns m as the JSON encoding of m. +// func (m RawMessage) MarshalJSON() ([]byte, error) { +// if m == nil { +// return []byte("null"), nil +// } +// return m, nil +// } +// +// // UnmarshalJSON sets *m to a copy of data. +// func (m *RawMessage) UnmarshalJSON(data []byte) error { +// if m == nil { +// return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") +// } +// *m = append((*m)[0:0], data...) +// return nil +// } +// +// var _ Marshaler = (*RawMessage)(nil) +// var _ Unmarshaler = (*RawMessage)(nil) +// +// EDIT(end) // A Token holds a value of one of these types: // diff --git a/internal/encoding/json/time.go b/internal/encoding/json/time.go index 3038827..2cba0d5 100644 --- a/internal/encoding/json/time.go +++ b/internal/encoding/json/time.go @@ -50,7 +50,7 @@ func timeMarshalEncoder(e *encodeState, v reflect.Value, opts encOpts) bool { if b != nil { e.Grow(len(b)) out := e.AvailableBuffer() - out, _ = appendCompact(out, b, opts.escapeHTML) + out, _ = appendCompact(out, b, opts) e.Buffer.Write(out) return true } diff --git a/packages/param/encoder.go b/packages/param/encoder.go index 14ef222..356d9a7 100644 --- a/packages/param/encoder.go +++ b/packages/param/encoder.go @@ -66,7 +66,7 @@ func MarshalWithExtras[T ParamStruct, R any](f T, underlying any, extras map[str } else if ovr, ok := f.Overrides(); ok { return shimjson.Marshal(ovr) } else { - return shimjson.Marshal(underlying) + return shimjson.Marshal(underlying, shimjson.WithSkipCompaction(true)) } } @@ -96,7 +96,7 @@ func MarshalUnion[T ParamStruct](metadata T, variants ...any) ([]byte, error) { Err: fmt.Errorf("expected union to have only one present variant, got %d", nPresent), } } - return shimjson.Marshal(variants[presentIdx]) + return shimjson.Marshal(variants[presentIdx], shimjson.WithSkipCompaction(true)) } // typeFor is shimmed from Go 1.23 "reflect" package diff --git a/packages/param/encoder_test.go b/packages/param/encoder_test.go index db41620..c31e328 100644 --- a/packages/param/encoder_test.go +++ b/packages/param/encoder_test.go @@ -1,10 +1,13 @@ package param_test import ( + "bytes" "encoding/json" + "reflect" "testing" "time" + shimjson "github.com/kernel/kernel-go-sdk/internal/encoding/json" "github.com/kernel/kernel-go-sdk/packages/param" ) @@ -375,3 +378,176 @@ func TestNullStructUnion(t *testing.T) { t.Fatalf("expected null, received %s", string(b)) } } + +// +// Compaction optimization +// + +type NonCompactedDoubleParent struct { + Prop string `json:"prop"` + Parent NonCompactedParent `json:"parent"` + + param.APIObject +} + +type NonCompactedParent struct { + BadChild NonCompacted `json:"bad_child"` + + param.APIObject +} + +type NonCompacted struct { + Raw string + + param.APIObject +} + +func (a NonCompactedDoubleParent) MarshalJSON() ([]byte, error) { + type shadow NonCompactedDoubleParent + return param.MarshalObject(a, (*shadow)(&a)) +} + +func (a NonCompactedParent) MarshalJSON() ([]byte, error) { + type shadow NonCompactedParent + return param.MarshalObject(a, (*shadow)(&a)) +} + +func (a NonCompacted) MarshalJSON() ([]byte, error) { + if a.Raw == "" { + a.Raw = nonCompactedRaw + } + return []byte(a.Raw), nil +} + +var nonCompactedRaw string = ` { "foo": "bar" } ` + +func TestAppendCompactBroken(t *testing.T) { + tests := map[string]struct { + value json.Marshaler + }{ + "red/illegal-json": { + NonCompacted{Raw: `{ "broken": "json" `}, + }, + "red/nested-with-illegal-json": { + NonCompactedParent{BadChild: NonCompacted{ + Raw: `{ "broken": "json" `, + }}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + v, err := json.Marshal(test.value) + if err == nil { + t.Fatal("expected error got", v) + } + }) + } +} + +// TestAppendCompact validates an optimization for internal SDK types to +// avoid O(keys^2) iteration over each JSON object. +// +// It's possible to intentionally trigger this behavior as both a user and +// SDK developer. However, the edge case is quite pathological and requires +// calling [json.Marshaler.MarshalJSON] rather than [json.Marshal]. +func TestAppendCompact(t *testing.T) { + + tests := map[string]struct { + value json.Marshaler + expected string + }{ + // + // Non-compacted cases + // + // Note this is how to exploit the compacter to fail, you must call [json.Marshaler.MarshalJSON] rather than [json.Marshal]. + // The type must also embed [param.APIObject] and return non-compacted JSON. + // + + "no-compact/fails-compaction": { + NonCompacted{Raw: nonCompactedRaw}, + nonCompactedRaw, + }, + "no-compact/nested-with-bad-child": { + NonCompactedParent{BadChild: NonCompacted{ + Raw: nonCompactedRaw, + }}, + `{"bad_child":` + nonCompactedRaw + `}`, + }, + "no-compact/double-nested-with-bad-child": { + NonCompactedDoubleParent{Prop: "1", Parent: NonCompactedParent{BadChild: NonCompacted{ + Raw: nonCompactedRaw, + }}}, + `{"prop":"1","parent":{"bad_child":` + nonCompactedRaw + `}}`, + }, + + // + // Compacted cases + // + + "override/spaces-within": { + param.Override[NonCompactedDoubleParent](json.RawMessage(`{"com": "pact"}`)), + `{"com":"pact"}`, + }, + "override/spaces-after": { + param.Override[NonCompactedDoubleParent](json.RawMessage(`{"com":"pact"} `)), + `{"com":"pact"}`, + }, + "override/spaces-before": { + param.Override[NonCompactedDoubleParent](json.RawMessage(` {"com":"pact"}`)), + `{"com":"pact"}`, + }, + "override/spaces-around": { + param.Override[NonCompactedDoubleParent](json.RawMessage(` { "com": "pact" }`)), + `{"com":"pact"}`, + }, + "override/override-with-nested": { + param.Override[NonCompactedDoubleParent](NonCompactedParent{}), + `{"bad_child":{"foo":"bar"}}`, + }, + "override/override-with-non-compacted": { + param.Override[NonCompactedDoubleParent](NonCompacted{}), + `{"foo":"bar"}`, + }, + } + + for name, test := range tests { + t.Run(name+"/marshal-json", func(t *testing.T) { + b, err := test.value.MarshalJSON() + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + if string(b) != test.expected { + t.Fatalf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b)) + } + }) + + t.Run(name+"/json-marshal", func(t *testing.T) { + b, err := json.Marshal(test.value) + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + + // expected output of JSON Marshal should always be compacted + var compactedExpected bytes.Buffer + err = json.Compact(&compactedExpected, []byte(test.expected)) + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + + if string(b) != compactedExpected.String() { + t.Fatalf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b)) + } + }) + + t.Run(name+"/shimjson-marshal", func(t *testing.T) { + b, err := shimjson.Marshal(test.value) + if err != nil { + t.Fatalf("didn't expect error %v, expected %s", err, test.expected) + } + if string(b) != test.expected { + t.Logf("expected %s (%s), received %s", test.expected, reflect.TypeOf(test.value), string(b)) + } + }) + } +} From 2d82565e34904922fa4a6b87e076bb1790af43bd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 18:20:16 +0000 Subject: [PATCH 3/5] feat: Polish start URL OpenAPI descriptions --- .stats.yml | 4 ++-- browser.go | 20 +++++++------------- browserpool.go | 27 ++++++++++----------------- invocation.go | 3 +-- 4 files changed, 20 insertions(+), 34 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7915e04..35cab47 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 112 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-a33e59aa1758ba51f13538838ecd70b0a23ed69739b3022e8c2ce0622e42b904.yml -openapi_spec_hash: c042d2f6880c927be09aa9fa79d7241e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-b7a19ff1fbd93322c8cffcd0b397ce2536ca8bff91594e0081bd030d4bec879f.yml +openapi_spec_hash: 490520e6f0a8b1ebc89e9c0add46082d config_hash: 08d55086449943a8fec212b870061a3f diff --git a/browser.go b/browser.go index 7a753a1..0fd6ffd 100644 --- a/browser.go +++ b/browser.go @@ -328,8 +328,7 @@ type BrowserNewResponse struct { Profile Profile `json:"profile"` // ID of the proxy associated with this browser session, if any. ProxyID string `json:"proxy_id"` - // URL the session was asked to navigate to on creation, if any. Recorded for - // debugging — navigation is best-effort and may have failed. + // Start URL requested for the session, if provided. StartURL string `json:"start_url"` // Session usage metrics. Usage BrowserUsage `json:"usage"` @@ -415,8 +414,7 @@ type BrowserGetResponse struct { Profile Profile `json:"profile"` // ID of the proxy associated with this browser session, if any. ProxyID string `json:"proxy_id"` - // URL the session was asked to navigate to on creation, if any. Recorded for - // debugging — navigation is best-effort and may have failed. + // Start URL requested for the session, if provided. StartURL string `json:"start_url"` // Session usage metrics. Usage BrowserUsage `json:"usage"` @@ -502,8 +500,7 @@ type BrowserUpdateResponse struct { Profile Profile `json:"profile"` // ID of the proxy associated with this browser session, if any. ProxyID string `json:"proxy_id"` - // URL the session was asked to navigate to on creation, if any. Recorded for - // debugging — navigation is best-effort and may have failed. + // Start URL requested for the session, if provided. StartURL string `json:"start_url"` // Session usage metrics. Usage BrowserUsage `json:"usage"` @@ -589,8 +586,7 @@ type BrowserListResponse struct { Profile Profile `json:"profile"` // ID of the proxy associated with this browser session, if any. ProxyID string `json:"proxy_id"` - // URL the session was asked to navigate to on creation, if any. Recorded for - // debugging — navigation is best-effort and may have failed. + // Start URL requested for the session, if provided. StartURL string `json:"start_url"` // Session usage metrics. Usage BrowserUsage `json:"usage"` @@ -681,11 +677,9 @@ type BrowserNewParams struct { // Optional proxy to associate to the browser session. Must reference a proxy // belonging to the caller's org. ProxyID param.Opt[string] `json:"proxy_id,omitzero"` - // Optional URL to navigate to immediately after the browser is created. - // Best-effort: failures to navigate do not fail browser creation. Any pre-existing - // tabs are reduced to a single tab which is then navigated. Accepts any URL - // Chromium can resolve, including chrome:// pages. Ignored when reusing an - // existing persistent session. + // Optional URL to open when the browser session is created. Navigation is + // best-effort, so navigation failures do not prevent the session from being + // created. StartURL param.Opt[string] `json:"start_url,omitzero"` // If true, launches the browser in stealth mode to reduce detection by anti-bot // mechanisms. diff --git a/browserpool.go b/browserpool.go index d3558d2..cdacc31 100644 --- a/browserpool.go +++ b/browserpool.go @@ -196,11 +196,9 @@ type BrowserPoolBrowserPoolConfig struct { // Optional proxy to associate to the browser session. Must reference a proxy // belonging to the caller's org. ProxyID string `json:"proxy_id"` - // Optional URL to navigate to when a new browser is warmed into the pool. - // Best-effort: failures to navigate do not fail pool fill. Only applied to - // newly-warmed browsers — browsers reused via release/acquire keep whatever URL - // the previous lease left them on. Accepts any URL Chromium can resolve, including - // chrome:// pages. + // Optional URL to open when a browser is created for the pool. Navigation is + // best-effort, so navigation failures do not prevent the pool from filling. Reused + // browsers keep the page left by the previous lease. StartURL string `json:"start_url"` // If true, launches the browser in stealth mode to reduce detection by anti-bot // mechanisms. @@ -284,8 +282,7 @@ type BrowserPoolAcquireResponse struct { Profile Profile `json:"profile"` // ID of the proxy associated with this browser session, if any. ProxyID string `json:"proxy_id"` - // URL the session was asked to navigate to on creation, if any. Recorded for - // debugging — navigation is best-effort and may have failed. + // Start URL requested for the session, if provided. StartURL string `json:"start_url"` // Session usage metrics. Usage BrowserUsage `json:"usage"` @@ -351,11 +348,9 @@ type BrowserPoolNewParams struct { // Optional proxy to associate to the browser session. Must reference a proxy // belonging to the caller's org. ProxyID param.Opt[string] `json:"proxy_id,omitzero"` - // Optional URL to navigate to when a new browser is warmed into the pool. - // Best-effort: failures to navigate do not fail pool fill. Only applied to - // newly-warmed browsers — browsers reused via release/acquire keep whatever URL - // the previous lease left them on. Accepts any URL Chromium can resolve, including - // chrome:// pages. + // Optional URL to open when a browser is created for the pool. Navigation is + // best-effort, so navigation failures do not prevent the pool from filling. Reused + // browsers keep the page left by the previous lease. StartURL param.Opt[string] `json:"start_url,omitzero"` // If true, launches the browser in stealth mode to reduce detection by anti-bot // mechanisms. @@ -418,11 +413,9 @@ type BrowserPoolUpdateParams struct { // Optional proxy to associate to the browser session. Must reference a proxy // belonging to the caller's org. ProxyID param.Opt[string] `json:"proxy_id,omitzero"` - // Optional URL to navigate to when a new browser is warmed into the pool. - // Best-effort: failures to navigate do not fail pool fill. Only applied to - // newly-warmed browsers — browsers reused via release/acquire keep whatever URL - // the previous lease left them on. Accepts any URL Chromium can resolve, including - // chrome:// pages. + // Optional URL to open when a browser is created for the pool. Navigation is + // best-effort, so navigation failures do not prevent the pool from filling. Reused + // browsers keep the page left by the previous lease. StartURL param.Opt[string] `json:"start_url,omitzero"` // If true, launches the browser in stealth mode to reduce detection by anti-bot // mechanisms. diff --git a/invocation.go b/invocation.go index 190c3e5..ae95443 100644 --- a/invocation.go +++ b/invocation.go @@ -574,8 +574,7 @@ type InvocationListBrowsersResponseBrowser struct { Profile Profile `json:"profile"` // ID of the proxy associated with this browser session, if any. ProxyID string `json:"proxy_id"` - // URL the session was asked to navigate to on creation, if any. Recorded for - // debugging — navigation is best-effort and may have failed. + // Start URL requested for the session, if provided. StartURL string `json:"start_url"` // Session usage metrics. Usage BrowserUsage `json:"usage"` From e381d8ed28c0d4fe978c0d27d724c0bc9b43d3d5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 20:32:22 +0000 Subject: [PATCH 4/5] feat: Add health check and auto-reauth controls for managed auth connections --- .stats.yml | 2 +- authconnection.go | 43 ++++++++++++++++++++++++++++++++++++++++++ authconnection_test.go | 4 ++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 35cab47..6b78eac 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 112 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-b7a19ff1fbd93322c8cffcd0b397ce2536ca8bff91594e0081bd030d4bec879f.yml -openapi_spec_hash: 490520e6f0a8b1ebc89e9c0add46082d +openapi_spec_hash: 9dd204b37a357b19032aea9eb4496645 config_hash: 08d55086449943a8fec212b870061a3f diff --git a/authconnection.go b/authconnection.go index fd9fa05..43c5260 100644 --- a/authconnection.go +++ b/authconnection.go @@ -249,6 +249,14 @@ type ManagedAuth struct { // - OneLogin: \*.onelogin.com // - Ping Identity: _.pingone.com, _.pingidentity.com AllowedDomains []string `json:"allowed_domains"` + // Whether automatic re-authentication is permitted for this connection. This is an + // opt-in flag only — it does not check whether re-auth is actually feasible. Even + // when true, re-auth only runs when the system has what it needs to perform it + // (for example, saved credentials for the required login fields), and only after a + // scheduled health check detects an expired session — so this flag has no effect + // when `health_checks` is false. When false, expired sessions detected by a health + // check are marked as `NEEDS_AUTH` instead of attempting re-auth. + AutoReauth bool `json:"auto_reauth"` // ID of the underlying browser session driving the current flow (present when flow // in progress). Use this to inspect or terminate the browser session via the // `/browsers` API. @@ -298,6 +306,12 @@ type ManagedAuth struct { // depends on your plan: Enterprise: 300 (5 minutes), Startup: 1200 (20 minutes), // Hobbyist: 3600 (1 hour). HealthCheckInterval int64 `json:"health_check_interval" api:"nullable"` + // Whether periodic health checks are enabled for this connection. When false, the + // system will not automatically verify authentication status, and `auto_reauth` + // has no effect on the automatic flow (since re-auth is only triggered by a failed + // scheduled health check). Manually triggering a health check via the API still + // works regardless of this setting. + HealthChecks bool `json:"health_checks"` // URL to redirect user to for hosted login (present when flow in progress) HostedURL string `json:"hosted_url" api:"nullable" format:"uri"` // Deprecated alias for `last_auth_check_at`. Despite the name, this is the last @@ -344,6 +358,7 @@ type ManagedAuth struct { SaveCredentials respjson.Field Status respjson.Field AllowedDomains respjson.Field + AutoReauth respjson.Field BrowserSessionID respjson.Field CanReauth respjson.Field CanReauthReason respjson.Field @@ -357,6 +372,7 @@ type ManagedAuth struct { FlowStep respjson.Field FlowType respjson.Field HealthCheckInterval respjson.Field + HealthChecks respjson.Field HostedURL respjson.Field LastAuthAt respjson.Field LastAuthCheckAt respjson.Field @@ -583,12 +599,26 @@ type ManagedAuthCreateRequestParam struct { // Name of the profile to manage authentication for. If the profile does not exist, // it is created automatically. ProfileName string `json:"profile_name" api:"required"` + // Whether to permit automatic re-authentication when a scheduled health check + // detects an expired session. This is an opt-in flag only — it does not check + // whether re-auth is actually feasible. Even when true, re-auth only runs when the + // system has what it needs to perform it (for example, saved credentials for the + // required login fields), and only after a scheduled health check detects an + // expired session — so this flag has no effect when `health_checks` is false. When + // false, expired sessions are marked as `NEEDS_AUTH` instead of attempting + // re-auth. Defaults to true. + AutoReauth param.Opt[bool] `json:"auto_reauth,omitzero"` // Interval in seconds between automatic health checks. When set, the system // periodically verifies the authentication status and triggers re-authentication // if needed. Maximum is 86400 (24 hours). Default is 3600 (1 hour). The minimum // depends on your plan: Enterprise: 300 (5 minutes), Startup: 1200 (20 minutes), // Hobbyist: 3600 (1 hour). HealthCheckInterval param.Opt[int64] `json:"health_check_interval,omitzero"` + // Whether to enable periodic health checks. When false, the system will not + // automatically verify authentication status, and `auto_reauth` has no effect on + // the automatic flow (since re-auth is only triggered by a failed scheduled health + // check). Defaults to true. + HealthChecks param.Opt[bool] `json:"health_checks,omitzero"` // Optional login page URL to skip discovery LoginURL param.Opt[string] `json:"login_url,omitzero" format:"uri"` // Whether to record browser sessions for this connection by default. Useful for @@ -680,8 +710,21 @@ func (r *ManagedAuthCreateRequestProxyParam) UnmarshalJSON(data []byte) error { // Request to update an auth connection's configuration type ManagedAuthUpdateRequestParam struct { + // Whether automatic re-authentication is permitted for this connection. This is an + // opt-in flag only — it does not check whether re-auth is actually feasible. Even + // when true, re-auth only runs when the system has what it needs to perform it + // (for example, saved credentials for the required login fields), and only after a + // scheduled health check detects an expired session — so this flag has no effect + // when `health_checks` is false. When false, expired sessions detected by a health + // check are marked as `NEEDS_AUTH` instead of attempting re-auth. + AutoReauth param.Opt[bool] `json:"auto_reauth,omitzero"` // Interval in seconds between automatic health checks HealthCheckInterval param.Opt[int64] `json:"health_check_interval,omitzero"` + // Whether periodic health checks are enabled. When set to false, the system will + // not automatically verify authentication status, and `auto_reauth` has no effect + // on the automatic flow (since re-auth is only triggered by a failed scheduled + // health check). + HealthChecks param.Opt[bool] `json:"health_checks,omitzero"` // Login page URL. Set to empty string to clear. LoginURL param.Opt[string] `json:"login_url,omitzero" format:"uri"` // Whether to record browser sessions for this connection by default diff --git a/authconnection_test.go b/authconnection_test.go index f54dc72..cd3e251 100644 --- a/authconnection_test.go +++ b/authconnection_test.go @@ -31,6 +31,7 @@ func TestAuthConnectionNewWithOptionalParams(t *testing.T) { Domain: "netflix.com", ProfileName: "user-123", AllowedDomains: []string{"login.netflix.com", "auth.netflix.com"}, + AutoReauth: kernel.Bool(true), Credential: kernel.ManagedAuthCreateRequestCredentialParam{ Auto: kernel.Bool(true), Name: kernel.String("my-netflix-creds"), @@ -38,6 +39,7 @@ func TestAuthConnectionNewWithOptionalParams(t *testing.T) { Provider: kernel.String("my-1p"), }, HealthCheckInterval: kernel.Int(3600), + HealthChecks: kernel.Bool(true), LoginURL: kernel.String("https://netflix.com/login"), Proxy: kernel.ManagedAuthCreateRequestProxyParam{ ID: kernel.String("id"), @@ -98,6 +100,7 @@ func TestAuthConnectionUpdateWithOptionalParams(t *testing.T) { kernel.AuthConnectionUpdateParams{ ManagedAuthUpdateRequest: kernel.ManagedAuthUpdateRequestParam{ AllowedDomains: []string{"login.netflix.com", "auth.netflix.com"}, + AutoReauth: kernel.Bool(true), Credential: kernel.ManagedAuthUpdateRequestCredentialParam{ Auto: kernel.Bool(true), Name: kernel.String("my-netflix-creds"), @@ -105,6 +108,7 @@ func TestAuthConnectionUpdateWithOptionalParams(t *testing.T) { Provider: kernel.String("my-1p"), }, HealthCheckInterval: kernel.Int(3600), + HealthChecks: kernel.Bool(true), LoginURL: kernel.String("https://netflix.com/login"), Proxy: kernel.ManagedAuthUpdateRequestProxyParam{ ID: kernel.String("id"), From 6b6d8c0ad28d27e119170f7909a328ddd98ce1f5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 20:46:34 +0000 Subject: [PATCH 5/5] release: 0.55.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 10 ++++++++++ README.md | 2 +- internal/version.go | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c3e01e1..d940b60 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.53.0" + ".": "0.55.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index dc39612..665275d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.55.0 (2026-05-15) + +Full Changelog: [v0.53.0...v0.55.0](https://github.com/kernel/kernel-go-sdk/compare/v0.53.0...v0.55.0) + +### Features + +* Add health check and auto-reauth controls for managed auth connections ([e381d8e](https://github.com/kernel/kernel-go-sdk/commit/e381d8ed28c0d4fe978c0d27d724c0bc9b43d3d5)) +* **client:** optimize json encoder for internal types ([e7f4365](https://github.com/kernel/kernel-go-sdk/commit/e7f436591debbdedd4150a0a4b2fcbdce2320cd1)) +* Polish start URL OpenAPI descriptions ([2d82565](https://github.com/kernel/kernel-go-sdk/commit/2d82565e34904922fa4a6b87e076bb1790af43bd)) + ## 0.53.0 (2026-05-12) Full Changelog: [v0.52.0...v0.53.0](https://github.com/kernel/kernel-go-sdk/compare/v0.52.0...v0.53.0) diff --git a/README.md b/README.md index 3c30013..6ec283e 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.53.0' +go get -u 'github.com/kernel/kernel-go-sdk@v0.55.0' ``` diff --git a/internal/version.go b/internal/version.go index fd0f8e3..bfe56a9 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.53.0" // x-release-please-version +const PackageVersion = "0.55.0" // x-release-please-version