Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ jobs:
github.repository == 'stainless-sdks/stagehand-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/stagehand-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());

Expand All @@ -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

Expand All @@ -68,10 +68,10 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/stagehand-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

Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.20.0"
".": "3.21.0"
}
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 8
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-6f6bfb81d092f30a5e2005328c97d61b9ea36132bb19e9e79e55294b9534ce20.yml
openapi_spec_hash: f3fc1e3688a38dc2c28f7178f7d534e5
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-c7910965e66e73ad8b65b6cc391d431094b2a6c6577c3e9d82feaa8138e74cff.yml
openapi_spec_hash: 37748bb69c22a9ce721d9b5a5861f964
config_hash: 1fb12ae9b478488bc1e56bfbdc210b01
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 3.21.0 (2026-05-27)

Full Changelog: [v3.20.0...v3.21.0](https://github.com/browserbase/stagehand-go/compare/v3.20.0...v3.21.0)

### Features

* [feat]: add `ignoreSelectors` to `observe()` ([501d30b](https://github.com/browserbase/stagehand-go/commit/501d30b5aa52a72eed5a25b064609b5c7c84d733))
* [STG-1756] forward Vertex model config ([e707c1c](https://github.com/browserbase/stagehand-go/commit/e707c1ca0b01fa7babe4ee081789766695e7d48e))
* Add `screenshot` option to Extract ([ba29e1e](https://github.com/browserbase/stagehand-go/commit/ba29e1e97d4744a504adf2990fa4cc1eeabf9bba))
* **client:** optimize json encoder for internal types ([539a65e](https://github.com/browserbase/stagehand-go/commit/539a65e84dfdd9a1330c6142d229ea47b2575376))
* STG-1756 add Vertex auth params to Stagehand spec ([b381980](https://github.com/browserbase/stagehand-go/commit/b381980c635b1d7968a2022709fa91921e77556e))


### Bug Fixes

* **go:** avoid panic when http.DefaultTransport is wrapped ([cea79d5](https://github.com/browserbase/stagehand-go/commit/cea79d5e25d6f072af7b2dde01d34be71106cb4b))


### Chores

* redact api-key headers in debug logs ([6378688](https://github.com/browserbase/stagehand-go/commit/637868870522a96e2e1d585949926a6116cb1e01))

## 3.20.0 (2026-05-06)

Full Changelog: [v3.19.3...v3.20.0](https://github.com/browserbase/stagehand-go/compare/v3.19.3...v3.20.0)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/browserbase/stagehand-go@v3.20.0'
go get -u 'github.com/browserbase/stagehand-go@v3.21.0'
```

<!-- x-release-please-end -->
Expand Down
1 change: 0 additions & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Params Types:

- <a href="https://pkg.go.dev/github.com/browserbase/stagehand-go/v3">stagehand</a>.<a href="https://pkg.go.dev/github.com/browserbase/stagehand-go/v3#ActionParam">ActionParam</a>
- <a href="https://pkg.go.dev/github.com/browserbase/stagehand-go/v3">stagehand</a>.<a href="https://pkg.go.dev/github.com/browserbase/stagehand-go/v3#ModelConfigParam">ModelConfigParam</a>

Response Types:

Expand Down
18 changes: 12 additions & 6 deletions default_http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ import (
const defaultResponseHeaderTimeout = 10 * time.Minute

// defaultHTTPClient returns an [*http.Client] used when the caller does not
// supply one via [option.WithHTTPClient]. It clones [http.DefaultTransport]
// and adds a [http.Transport.ResponseHeaderTimeout] so stuck connections
// fail fast instead of compounding across retries.
// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the
// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout]
// is set so stuck connections fail fast instead of compounding across retries.
// If [http.DefaultTransport] has been wrapped (for example by otelhttp for
// distributed tracing), the wrapping is preserved and the header timeout is
// skipped.
func defaultHTTPClient() *http.Client {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.ResponseHeaderTimeout = defaultResponseHeaderTimeout
return &http.Client{Transport: transport}
if t, ok := http.DefaultTransport.(*http.Transport); ok {
t = t.Clone()
t.ResponseHeaderTimeout = defaultResponseHeaderTimeout
return &http.Client{Transport: t}
}
return &http.Client{Transport: http.DefaultTransport}
}
21 changes: 15 additions & 6 deletions internal/encoding/json/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
17 changes: 14 additions & 3 deletions internal/encoding/json/indent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions internal/encoding/json/opt.go
Original file line number Diff line number Diff line change
@@ -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)
53 changes: 28 additions & 25 deletions internal/encoding/json/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package json

import (
"bytes"
"errors"
"io"
)

Expand Down Expand Up @@ -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:
//
Expand Down
2 changes: 1 addition & 1 deletion internal/encoding/json/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion internal/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package internal

const PackageVersion = "3.20.0" // x-release-please-version
const PackageVersion = "3.21.0" // x-release-please-version
46 changes: 44 additions & 2 deletions option/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"net/http/httputil"
)

// sensitiveLogHeaders are redacted before request and response content is
// written to the debug logger.
var sensitiveLogHeaders = []string{"authorization", "api-key", "x-api-key", "cookie", "set-cookie", "x-bb-api-key", "x-bb-project-id", "x-model-api-key"}

// WithDebugLog logs the HTTP request and response content.
// If the logger parameter is nil, it uses the default logger.
//
Expand All @@ -20,7 +24,7 @@ func WithDebugLog(logger *log.Logger) RequestOption {
logger = log.Default()
}

if reqBytes, err := httputil.DumpRequest(req, true); err == nil {
if reqBytes, err := dumpRedactedRequest(req); err == nil {
logger.Printf("Request Content:\n%s\n", reqBytes)
}

Expand All @@ -29,10 +33,48 @@ func WithDebugLog(logger *log.Logger) RequestOption {
return resp, err
}

if respBytes, err := httputil.DumpResponse(resp, true); err == nil {
if respBytes, err := dumpRedactedResponse(resp); err == nil {
logger.Printf("Response Content:\n%s\n", respBytes)
}

return resp, err
})
}

// dumpRedactedRequest dumps req with sensitive headers replaced. The
// original headers are restored via defer so a panic in DumpRequest cannot
// leak the placeholder map into the live request sent downstream.
func dumpRedactedRequest(req *http.Request) ([]byte, error) {
origHeaders := req.Header
req.Header = redactDebugHeaders(origHeaders)
defer func() { req.Header = origHeaders }()
return httputil.DumpRequest(req, true)
}

func dumpRedactedResponse(resp *http.Response) ([]byte, error) {
origHeaders := resp.Header
resp.Header = redactDebugHeaders(origHeaders)
defer func() { resp.Header = origHeaders }()
return httputil.DumpResponse(resp, true)
}

func redactDebugHeaders(headers http.Header) http.Header {
var redacted http.Header
for _, name := range sensitiveLogHeaders {
values := headers.Values(name)
if len(values) == 0 {
continue
}
if redacted == nil {
redacted = headers.Clone()
}
redacted.Del(name)
for range values {
redacted.Add(name, "***")
}
}
if redacted == nil {
return headers
}
return redacted
}
Loading
Loading