Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2c6c4be
codegen metadata
stainless-app[bot] Apr 3, 2026
40cb10e
feat: Bedrock auth passthrough
stainless-app[bot] Apr 8, 2026
52c4636
feat: [STG-1798] feat: support Browserbase verified sessions
stainless-app[bot] Apr 8, 2026
1f99ac3
feat: Revert "[STG-1573] Add providerOptions for extensible model aut…
stainless-app[bot] Apr 9, 2026
4a6d856
fix(client): properly generate file params
stainless-app[bot] Apr 11, 2026
17f1ece
fix(client): resolve serialization issue with unions and enums
stainless-app[bot] Apr 18, 2026
0ba1b6f
fix: populate enum-typed properties with enum instances
stainless-app[bot] Apr 18, 2026
ca1dd7a
fix: revert enum parsing change that lead to unconditional failure
stainless-app[bot] Apr 28, 2026
81f1991
[STG-1808] Prefer STAGEHAND_API_URL env fallback
monadoid Apr 30, 2026
4e816f6
Deprecate browserbase project ID (#58)
monadoid May 6, 2026
d6235c1
feat: support setting headers via env
stainless-app[bot] May 6, 2026
7265084
codegen metadata
stainless-app[bot] Apr 30, 2026
d1e7744
codegen metadata
stainless-app[bot] May 1, 2026
eb093f7
fix(release): use canonical GitHub URL in Packagist publish script
stainless-app[bot] May 6, 2026
4d548b6
feat: [feat]: add `ignoreSelectors` to `extract()`
stainless-app[bot] May 6, 2026
5ce5e23
feat: [STG-1808] Deprecate Browserbase project ID
stainless-app[bot] May 6, 2026
7271c1e
feat: remove experimental requirement on agent variables (#2079)
stainless-app[bot] May 6, 2026
a93f6d4
release: 3.20.0
stainless-app[bot] May 6, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/publish-packagist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

- name: Publish to Packagist
run: |-
curl --fail-with-body -X POST -H 'Content-Type: application/json' "https://packagist.org/api/update-package?username=${PACKAGIST_USERNAME}&apiToken=${PACKAGIST_SAFE_KEY}" -d '{"repository":"https://www.github.com/browserbase/stagehand-php"}'
curl --fail-with-body -X POST -H 'Content-Type: application/json' "https://packagist.org/api/update-package?username=${PACKAGIST_USERNAME}&apiToken=${PACKAGIST_SAFE_KEY}" -d '{"repository":"https://github.com/browserbase/stagehand-php"}'
env:
PACKAGIST_USERNAME: ${{ secrets.STAGEHAND_PACKAGIST_USERNAME || secrets.PACKAGIST_USERNAME }}
PACKAGIST_SAFE_KEY: ${{ secrets.STAGEHAND_PACKAGIST_SAFE_KEY || secrets.PACKAGIST_SAFE_KEY }}
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.19.3"
".": "3.20.0"
}
6 changes: 3 additions & 3 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%2Fstagehand-b969ce378479c79ee64c05127c0ed6c6ce2edbee017ecd037242fb618a5ebc9f.yml
openapi_spec_hash: a24aabaa5214effb679808b7f2be0ad4
config_hash: a962ae71493deb11a1c903256fb25386
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-6f6bfb81d092f30a5e2005328c97d61b9ea36132bb19e9e79e55294b9534ce20.yml
openapi_spec_hash: f3fc1e3688a38dc2c28f7178f7d534e5
config_hash: 1fb12ae9b478488bc1e56bfbdc210b01
2 changes: 0 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ composer require browserbase/stagehand

# Set environment variables
export BROWSERBASE_API_KEY="your-bb-api-key"
export BROWSERBASE_PROJECT_ID="your-bb-project-uuid"
export MODEL_API_KEY="sk-proj-your-llm-api-key"

# Run the example
Expand All @@ -45,7 +44,6 @@ use Stagehand\Client;

$client = new Client(
browserbaseAPIKey: getenv('BROWSERBASE_API_KEY'),
browserbaseProjectID: getenv('BROWSERBASE_PROJECT_ID'),
modelAPIKey: getenv('MODEL_API_KEY'),
);
$startResponse = $client->sessions->start(model: 'openai/gpt-5-nano');
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 3.20.0 (2026-05-06)

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

### Features

* [feat]: add `ignoreSelectors` to `extract()` ([4d548b6](https://github.com/browserbase/stagehand-php/commit/4d548b6b71174972b12884840185357a30e30d8d))
* [STG-1798] feat: support Browserbase verified sessions ([52c4636](https://github.com/browserbase/stagehand-php/commit/52c4636560e5dd556db9c061a3bb9c48ffa20e76))
* [STG-1808] Deprecate Browserbase project ID ([5ce5e23](https://github.com/browserbase/stagehand-php/commit/5ce5e2347bb5700816910f4973b1d74487e2b92e))
* Bedrock auth passthrough ([40cb10e](https://github.com/browserbase/stagehand-php/commit/40cb10eb39c7f53b9c3fd668018c1b3569650e5c))
* remove experimental requirement on agent variables ([#2079](https://github.com/browserbase/stagehand-php/issues/2079)) ([7271c1e](https://github.com/browserbase/stagehand-php/commit/7271c1e3ee6a8c3e8fe425a77196c051c7dfa086))
* Revert "[STG-1573] Add providerOptions for extensible model auth ([#1822](https://github.com/browserbase/stagehand-php/issues/1822))" ([1f99ac3](https://github.com/browserbase/stagehand-php/commit/1f99ac3957867970c6eaa968e8daaa83ae855b7b))
* support setting headers via env ([d6235c1](https://github.com/browserbase/stagehand-php/commit/d6235c1aa657237410c3bab8483b6a5f8ebf3eba))


### Bug Fixes

* **client:** properly generate file params ([4a6d856](https://github.com/browserbase/stagehand-php/commit/4a6d85692b4bec4a518cf201999e99b2a7129263))
* **client:** resolve serialization issue with unions and enums ([17f1ece](https://github.com/browserbase/stagehand-php/commit/17f1ece3f13369ac5555a6df9af1bb4fdce3d307))
* populate enum-typed properties with enum instances ([0ba1b6f](https://github.com/browserbase/stagehand-php/commit/0ba1b6fda9bd4709e3665a3bd7659373b87b64d3))
* **release:** use canonical GitHub URL in Packagist publish script ([eb093f7](https://github.com/browserbase/stagehand-php/commit/eb093f76a7e0999169970d79341fafa2871a6eee))
* revert enum parsing change that lead to unconditional failure ([ca1dd7a](https://github.com/browserbase/stagehand-php/commit/ca1dd7a1dd2d8cf0b0b2d50f9cf317c98652cc89))

## 3.19.3 (2026-04-03)

Full Changelog: [v3.18.0...v3.19.3](https://github.com/browserbase/stagehand-php/compare/v3.18.0...v3.19.3)
Expand Down
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ The REST API documentation can be found on [docs.stagehand.dev](https://docs.sta
<!-- x-release-please-start-version -->

```
composer require "browserbase/stagehand 3.19.3"
composer require "browserbase/stagehand 3.20.0"
```

<!-- x-release-please-end -->
Expand Down Expand Up @@ -113,14 +113,12 @@ if (file_exists(__DIR__ . '/../.env')) {
// Initialize the Stagehand client with API keys from environment variables
$client = new Client(
browserbaseAPIKey: getenv('BROWSERBASE_API_KEY') ?: throw new Exception('BROWSERBASE_API_KEY environment variable is required'),
browserbaseProjectID: getenv('BROWSERBASE_PROJECT_ID') ?: throw new Exception('BROWSERBASE_PROJECT_ID environment variable is required'),
modelAPIKey: getenv('MODEL_API_KEY') ?: throw new Exception('MODEL_API_KEY environment variable is required'),
);

// Start a new session
$startResponse = $client->sessions->start(
browserbaseAPIKey: getenv('BROWSERBASE_API_KEY'),
browserbaseProjectID: getenv('BROWSERBASE_PROJECT_ID'),
model: 'openai/gpt-4o',
);
echo "Session started: {$startResponse->data->sessionID}\n";
Expand Down Expand Up @@ -217,12 +215,11 @@ echo "Session ended\n";

### Running the Example

Set the required environment variables and run the example script:
Set the environment variables and run the example script:

```bash
# Set your credentials
export BROWSERBASE_API_KEY="your-browserbase-api-key"
export BROWSERBASE_PROJECT_ID="your-browserbase-project-id"
export MODEL_API_KEY="your-openai-api-key"

# Install dependencies and run
Expand All @@ -248,9 +245,6 @@ use Stagehand\Client;

$client = new Client(
browserbaseAPIKey: getenv('BROWSERBASE_API_KEY') ?: 'My Browserbase API Key',
browserbaseProjectID: getenv(
'BROWSERBASE_PROJECT_ID'
) ?: 'My Browserbase Project ID',
modelAPIKey: getenv('MODEL_API_KEY') ?: 'My Model API Key',
);

Expand Down
2 changes: 0 additions & 2 deletions examples/basic.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@
// Initialize the Stagehand client with API keys from environment variables
$client = new Client(
browserbaseAPIKey: getenv('BROWSERBASE_API_KEY') ?: throw new Exception('BROWSERBASE_API_KEY environment variable is required'),
browserbaseProjectID: getenv('BROWSERBASE_PROJECT_ID') ?: throw new Exception('BROWSERBASE_PROJECT_ID environment variable is required'),
modelAPIKey: getenv('MODEL_API_KEY') ?: throw new Exception('MODEL_API_KEY environment variable is required'),
);

// Start a new session
$startResponse = $client->sessions->start(
browserbaseAPIKey: getenv('BROWSERBASE_API_KEY'),
browserbaseProjectID: getenv('BROWSERBASE_PROJECT_ID'),
model: 'openai/gpt-4o',
);
echo "Session started: {$startResponse->data->sessionID}\n";
Expand Down
54 changes: 33 additions & 21 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class Client extends BaseClient
{
public string $browserbaseAPIKey;

/**
* Deprecated. Browserbase API keys are now project-scoped, so this value is no longer required.
* Accepted for backwards compatibility; it is ignored.
*/
public string $browserbaseProjectID;

public string $modelAPIKey;
Expand All @@ -40,16 +44,14 @@ public function __construct(
$this->browserbaseAPIKey = (string) ($browserbaseAPIKey ?? Util::getenv(
'BROWSERBASE_API_KEY'
));
$this->browserbaseProjectID = (string) ($browserbaseProjectID ?? Util::getenv(
'BROWSERBASE_PROJECT_ID'
));
$this->browserbaseProjectID = (string) $browserbaseProjectID;
$this->modelAPIKey = (string) ($modelAPIKey ?? Util::getenv(
'MODEL_API_KEY'
));

$baseUrl ??= Util::getenv(
'STAGEHAND_BASE_URL'
) ?: 'https://api.stagehand.browserbase.com';
$baseUrl ??= Util::getenv('STAGEHAND_API_URL')
?: Util::getenv('STAGEHAND_BASE_URL')
?: 'https://api.stagehand.browserbase.com';

$options = RequestOptions::parse(
RequestOptions::with(
Expand All @@ -61,18 +63,31 @@ public function __construct(
$requestOptions,
);

/** @var array<string, string|null> $headers */
$headers = [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'User-Agent' => sprintf('stagehand/PHP %s', VERSION),
'X-Stainless-Lang' => 'php',
'X-Stainless-Package-Version' => '3.19.3',
'X-Stainless-Arch' => Util::machtype(),
'X-Stainless-OS' => Util::ostype(),
'X-Stainless-Runtime' => php_sapi_name(),
'X-Stainless-Runtime-Version' => phpversion(),
];

$customHeadersEnv = Util::getenv('STAGEHAND_CUSTOM_HEADERS');
if (null !== $customHeadersEnv) {
foreach (explode("\n", $customHeadersEnv) as $line) {
$colon = strpos($line, ':');
if (false !== $colon) {
$headers[trim(substr($line, 0, $colon))] = trim(substr($line, $colon + 1));
}
}
}

parent::__construct(
headers: [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'User-Agent' => sprintf('stagehand/PHP %s', VERSION),
'X-Stainless-Lang' => 'php',
'X-Stainless-Package-Version' => '3.1.0',
'X-Stainless-Arch' => Util::machtype(),
'X-Stainless-OS' => Util::ostype(),
'X-Stainless-Runtime' => php_sapi_name(),
'X-Stainless-Runtime-Version' => phpversion(),
],
headers: $headers,
baseUrl: $baseUrl,
options: $options
);
Expand All @@ -85,7 +100,6 @@ protected function authHeaders(): array
{
return [
...$this->bbAPIKeyAuth(),
...$this->bbProjectIDAuth(),
...$this->llmModelAPIKeyAuth(),
];
}
Expand All @@ -101,9 +115,7 @@ protected function bbAPIKeyAuth(): array
/** @return array<string,string> */
protected function bbProjectIDAuth(): array
{
return $this->browserbaseProjectID ? [
'x-bb-project-id' => $this->browserbaseProjectID,
] : [];
return [];
}

/** @return array<string,string> */
Expand Down
17 changes: 1 addition & 16 deletions src/Core/Attributes/Required.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class Required

public readonly bool $nullable;

/** @var array<string,Converter> */
private static array $enumConverters = [];

/**
* @param class-string<ConverterSource>|Converter|string|null $type
* @param class-string<\BackedEnum>|Converter|null $enum
Expand All @@ -52,24 +49,12 @@ public function __construct(
$type ??= new MapOf($map);
}
if (null !== $enum) {
$type ??= $enum instanceof Converter ? $enum : self::enumConverter($enum);
$type ??= $enum instanceof Converter ? $enum : EnumOf::fromBackedEnum($enum);
}

$this->apiName = $apiName;
$this->type = $type;
$this->optional = false;
$this->nullable = $nullable;
}

/** @property class-string<\BackedEnum> $enum */
private static function enumConverter(string $enum): Converter
{
if (!isset(self::$enumConverters[$enum])) {
// @phpstan-ignore-next-line argument.type
$converter = new EnumOf(array_column($enum::cases(), column_key: 'value'));
self::$enumConverters[$enum] = $converter;
}

return self::$enumConverters[$enum];
}
}
19 changes: 19 additions & 0 deletions src/Core/Conversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Stagehand\Core\Conversion\Contracts\Converter;
use Stagehand\Core\Conversion\Contracts\ConverterSource;
use Stagehand\Core\Conversion\DumpState;
use Stagehand\Core\Conversion\EnumOf;

/**
* @internal
Expand All @@ -21,6 +22,10 @@ public static function dump_unknown(mixed $value, DumpState $state): mixed
}

if (is_object($value)) {
if ($value instanceof FileParam) {
return $value;
}

if (is_a($value, class: ConverterSource::class)) {
return $value::converter()->dump($value, state: $state);
}
Expand Down Expand Up @@ -61,6 +66,13 @@ public static function coerce(Converter|ConverterSource|string $target, mixed $v
return $target->coerce($value, state: $state);
}

// BackedEnum class-name targets: wrap in EnumOf so enum values are scored
// against the enum's cases. Without this, tryConvert's default case scores
// any class-name target as `no`, even when the value is a valid enum member.
if (is_a($target, class: \BackedEnum::class, allow_string: true)) {
return EnumOf::fromBackedEnum($target)->coerce($value, state: $state);
}

return self::tryConvert($target, value: $value, state: $state);
}

Expand All @@ -74,6 +86,13 @@ public static function dump(Converter|ConverterSource|string $target, mixed $val
return $target::converter()->dump($value, state: $state);
}

// BackedEnum class-name targets: wrap in EnumOf so enum values are scored
// against the enum's cases. Without this, tryConvert's default case scores
// any class-name target as `no`, even when the value is a valid enum member.
if (is_a($target, class: \BackedEnum::class, allow_string: true)) {
return EnumOf::fromBackedEnum($target)->dump($value, state: $state);
}

self::tryConvert($target, value: $value, state: $state);

return self::dump_unknown($value, state: $state);
Expand Down
15 changes: 13 additions & 2 deletions src/Core/Conversion/EnumOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ final class EnumOf implements Converter
{
private readonly string $type;

/** @var array<class-string<\BackedEnum>, self> */
private static array $cache = [];

/**
* @param list<bool|float|int|string|null> $members
*/
Expand All @@ -26,6 +29,13 @@ public function __construct(private readonly array $members)
$this->type = $type;
}

/** @param class-string<\BackedEnum> $enum */
public static function fromBackedEnum(string $enum): self
{
// @phpstan-ignore-next-line argument.type
return self::$cache[$enum] ??= new self(array_column($enum::cases(), column_key: 'value'));
}

public function coerce(mixed $value, CoerceState $state): mixed
{
$this->tally($value, state: $state);
Expand All @@ -42,9 +52,10 @@ public function dump(mixed $value, DumpState $state): mixed

private function tally(mixed $value, CoerceState|DumpState $state): void
{
if (in_array($value, haystack: $this->members, strict: true)) {
$needle = $value instanceof \BackedEnum ? $value->value : $value;
if (in_array($needle, haystack: $this->members, strict: true)) {
++$state->yes;
} elseif ($this->type === gettype($value)) {
} elseif ($this->type === gettype($needle)) {
++$state->maybe;
} else {
++$state->no;
Expand Down
Loading