Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8dac4ee
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
4062155
codegen metadata
stainless-app[bot] Dec 17, 2025
9c9d8f7
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
7a4328b
feat!: use aliases for phpstan types
stainless-app[bot] Dec 17, 2025
4b195d7
fix: support arrays in query param construction
stainless-app[bot] Dec 17, 2025
7715406
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
2748aea
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
0501932
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
3aac7a3
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
7d51568
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
f5b7983
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
7214304
codegen metadata
stainless-app[bot] Dec 17, 2025
bbf2825
feat(api): manual updates
stainless-app[bot] Dec 17, 2025
c1b5c85
feat(api): manual updates
stainless-app[bot] Dec 18, 2025
8935e42
chore(internal): codegen related update
stainless-app[bot] Dec 18, 2025
3ce08d1
chore(internal): codegen related update
stainless-app[bot] Dec 18, 2025
95004df
feat: improved phpstan type annotations
stainless-app[bot] Dec 18, 2025
96c0813
chore(internal): codegen related update
stainless-app[bot] Dec 19, 2025
79b8f76
chore(internal): codegen related update
stainless-app[bot] Dec 19, 2025
68fe598
feat(api): manual updates
stainless-app[bot] Dec 19, 2025
1a0904d
docs: add more examples
stainless-app[bot] Dec 20, 2025
3d7bdf5
feat: [STG-1053] [server] Use fastify-zod-openapi + zod v4 for openap…
stainless-app[bot] Dec 22, 2025
ac7a836
codegen metadata
stainless-app[bot] Dec 23, 2025
29d21df
codegen metadata
stainless-app[bot] Dec 23, 2025
211cb82
release: 0.3.0
stainless-app[bot] Dec 23, 2025
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 .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.2.0"
".": "0.3.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: 7
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-705638ac8966569986bd9ebb7c9761bf0016909e9f2753e77ceabb12c8049511.yml
openapi_spec_hash: a8fbbcaa38e91c7f97313620b42d8d62
config_hash: a35b56eb05306a0f02e83c11d57f975f
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-ed52466945f2f8dfd3814a29e948d7bf30af7b76a7a7689079c03b8baf64e26f.yml
openapi_spec_hash: 5d57aaf2362b0d882372dbf76477ba23
config_hash: 989ddfee371586e9156b4d484ec0a6cc
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
# Changelog

## 0.3.0 (2025-12-23)

Full Changelog: [v0.2.0...v0.3.0](https://github.com/browserbase/stagehand-php/compare/v0.2.0...v0.3.0)

### ⚠ BREAKING CHANGES

* use aliases for phpstan types

### Features

* [STG-1053] [server] Use fastify-zod-openapi + zod v4 for openapi generation ([3d7bdf5](https://github.com/browserbase/stagehand-php/commit/3d7bdf5f97754c73ae6eb48ea8349f2c8ffc66a8))
* **api:** manual updates ([68fe598](https://github.com/browserbase/stagehand-php/commit/68fe598df0ac0f73aa8806ed4730bd4c479f54a6))
* **api:** manual updates ([c1b5c85](https://github.com/browserbase/stagehand-php/commit/c1b5c857aa25cd839ce1ce448d7148e4eccdffe8))
* **api:** manual updates ([bbf2825](https://github.com/browserbase/stagehand-php/commit/bbf28250640bcdeaf02f2a40ef93802d7630990d))
* **api:** manual updates ([f5b7983](https://github.com/browserbase/stagehand-php/commit/f5b7983cab846df088bddc707aa1c8fdaf9d1952))
* **api:** manual updates ([7d51568](https://github.com/browserbase/stagehand-php/commit/7d515687b0aca206c9124b270a4f1f0ab84fe467))
* **api:** manual updates ([3aac7a3](https://github.com/browserbase/stagehand-php/commit/3aac7a31a6a32ef739f918fba6effb6081b7c10a))
* **api:** manual updates ([0501932](https://github.com/browserbase/stagehand-php/commit/050193250e7e1092ba83b97d4735e80328492188))
* **api:** manual updates ([2748aea](https://github.com/browserbase/stagehand-php/commit/2748aea3da35936913f4643a70fb2aff6f61a562))
* **api:** manual updates ([7715406](https://github.com/browserbase/stagehand-php/commit/771540668a5d44d36d65b566b8c1cacfc1a0312d))
* **api:** manual updates ([9c9d8f7](https://github.com/browserbase/stagehand-php/commit/9c9d8f74c362253e3494407a8de53713079ff3d9))
* **api:** manual updates ([8dac4ee](https://github.com/browserbase/stagehand-php/commit/8dac4ee0f673830cc9f31e6c7971cd6bbf1f049f))
* improved phpstan type annotations ([95004df](https://github.com/browserbase/stagehand-php/commit/95004df5bf286d7fc8942781a6e69a56e6cefe53))
* use aliases for phpstan types ([7a4328b](https://github.com/browserbase/stagehand-php/commit/7a4328bfa32b6475cb2cbf1da56cb9d2d1fef514))


### Bug Fixes

* support arrays in query param construction ([4b195d7](https://github.com/browserbase/stagehand-php/commit/4b195d7ecfae72625b33ce440cd9bb83aa6deeea))


### Chores

* **internal:** codegen related update ([79b8f76](https://github.com/browserbase/stagehand-php/commit/79b8f76ee409f5a35cc742496109e803138554b1))
* **internal:** codegen related update ([96c0813](https://github.com/browserbase/stagehand-php/commit/96c0813ae699b18ce127aa6f693009cc2f3d63b2))
* **internal:** codegen related update ([3ce08d1](https://github.com/browserbase/stagehand-php/commit/3ce08d172eba2c771edcd7d7cd81fdc461547c8b))
* **internal:** codegen related update ([8935e42](https://github.com/browserbase/stagehand-php/commit/8935e42bd30fb4d0a81a211b11c098181404e360))


### Documentation

* add more examples ([1a0904d](https://github.com/browserbase/stagehand-php/commit/1a0904d4498d41422c6a341ffdbcefcb9f5710c1))

## 0.2.0 (2025-12-16)

Full Changelog: [v0.1.0...v0.2.0](https://github.com/browserbase/stagehand-php/compare/v0.1.0...v0.2.0)
Expand Down
44 changes: 33 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,42 @@ $response = $client->sessions->act(
input: 'click the first link on the page',
);

var_dump($response->actions);
var_dump($response->data);
```

### Value Objects

It is recommended to use the static `with` constructor `Action::with(arguments: ['string'], ...)`
It is recommended to use the static `with` constructor `Action::with(description: 'Click the submit button', ...)`
and named parameters to initialize value objects.

However, builders are also provided `(new Action)->withArguments(['string'])`.
However, builders are also provided `(new Action)->withDescription('Click the submit button')`.

### Streaming

We provide support for streaming responses using Server-Sent Events (SSE).

```php
<?php

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',
);

$stream = $client->sessions->actStream(
'00000000-your-session-id-000000000000',
input: 'click the first link on the page',
);

foreach ($stream as $response) {
var_dump($response);
}
```

### Handling errors

Expand All @@ -80,10 +107,7 @@ When the library is unable to connect to the API, or if the API returns a non-su
use Stagehand\Core\Exceptions\APIConnectionException;

try {
$response = $client->sessions->start(
browserbaseAPIKey: 'your Browserbase API key',
browserbaseProjectID: 'your Browserbase Project ID',
);
$response = $client->sessions->start(modelName: 'openai/gpt-5-nano');
} catch (APIConnectionException $e) {
echo "The server could not be reached", PHP_EOL;
var_dump($e->getPrevious());
Expand Down Expand Up @@ -130,8 +154,7 @@ $client = new Client(maxRetries: 0);

// Or, configure per-request:
$result = $client->sessions->start(
browserbaseAPIKey: 'your Browserbase API key',
browserbaseProjectID: 'your Browserbase Project ID',
modelName: 'openai/gpt-5-nano',
requestOptions: RequestOptions::with(maxRetries: 5),
);
```
Expand All @@ -152,8 +175,7 @@ Note: the `extra*` parameters of the same name overrides the documented paramete
use Stagehand\RequestOptions;

$response = $client->sessions->start(
browserbaseAPIKey: 'your Browserbase API key',
browserbaseProjectID: 'your Browserbase Project ID',
modelName: 'openai/gpt-5-nano',
requestOptions: RequestOptions::with(
extraQueryParams: ['my_query_parameter' => 'value'],
extraBodyParams: ['my_body_parameter' => 'value'],
Expand Down
2 changes: 0 additions & 2 deletions src/Core/Concerns/SdkModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ public function __toString(): string
* a native class property, indicating an omitted value,
* or a property overridden with an incongruent type
*
* @return value-of<Shape>
*
* @throws \Exception
*/
public function __get(string $key): mixed
Expand Down
57 changes: 57 additions & 0 deletions src/Core/Concerns/SdkStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Stagehand\Core\Concerns;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Stagehand\Core\Contracts\BaseStream;
use Stagehand\Core\Conversion\Contracts\Converter;
use Stagehand\Core\Conversion\Contracts\ConverterSource;
use Stagehand\Core\Implementation\IteratorExit;

/**
* @internal
*
* @template TRaw mixed
* @template TEvent
*
* @implements BaseStream<TEvent>
*/
trait SdkStream
{
/** @var \Generator<TRaw> */
protected \Generator $stream;

/** @var \Generator<TEvent> */
private \Generator $generator;

public function __construct(
protected string|Converter|ConverterSource $convert,
protected RequestInterface $request,
protected ResponseInterface $response,
protected mixed $parsedBody,
) {
// @phpstan-ignore-next-line
$this->stream = $parsedBody;
$this->generator = $this->parsedGenerator();
}

/** @return \Iterator<TEvent> */
public function getIterator(): \Iterator
{
return $this->generator;
}

public function close(): void
{
try {
$this->stream->throw(new IteratorExit);
} catch (IteratorExit $_) {
// IteratorExit shouldn't be noticed.
return;
}
}

/** @return \Generator<TEvent> $stream */
abstract private function parsedGenerator(): \Generator;
}
5 changes: 5 additions & 0 deletions src/Core/Implementation/IteratorExit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace Stagehand\Core\Implementation;

class IteratorExit extends \Error {}
35 changes: 20 additions & 15 deletions src/Core/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ public static function array_filter_omit(array $arr): array
return array_filter($arr, fn ($v, $_) => OMIT !== $v, mode: ARRAY_FILTER_USE_BOTH);
}

public static function strVal(mixed $value): string
{
if (is_bool($value)) {
return $value ? 'true' : 'false';
}

if (is_object($value) && is_a($value, class: \DateTimeInterface::class)) {
return date_format($value, format: \DateTimeInterface::RFC3339);
}

// @phpstan-ignore-next-line argument.type
return strval($value);
}

/**
* @param callable $callback
*/
Expand Down Expand Up @@ -185,7 +199,12 @@ public static function joinUri(
parse_str($parsed['query'] ?? '', $q2);

$mergedQuery = array_merge_recursive($q1, $q2, $query);
$normalizedQuery = array_map(static fn ($v) => self::strVal($v), array: $mergedQuery);

/** @var array<string,mixed> */
$normalizedQuery = self::mapRecursive(
static fn ($v) => is_bool($v) || is_numeric($v) ? self::strVal($v) : $v,
value: $mergedQuery
);
$qs = http_build_query($normalizedQuery, encoding_type: PHP_QUERY_RFC3986);

return $base->withQuery($qs);
Expand Down Expand Up @@ -409,20 +428,6 @@ public static function prettyEncodeJson(mixed $obj): string
return json_encode($obj, flags: JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) ?: '';
}

private static function strVal(mixed $value): string
{
if (is_bool($value)) {
return $value ? 'true' : 'false';
}

if (is_object($value) && is_a($value, class: \DateTimeInterface::class)) {
return date_format($value, format: \DateTimeInterface::RFC3339);
}

// @phpstan-ignore-next-line argument.type
return strval($value);
}

/**
* @param list<callable> $closing
*
Expand Down
77 changes: 77 additions & 0 deletions src/SSEStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Stagehand;

use Stagehand\Core\Concerns\SdkStream;
use Stagehand\Core\Contracts\BaseStream;
use Stagehand\Core\Conversion;
use Stagehand\Core\Exceptions\APIStatusException;
use Stagehand\Core\Util;

/**
* @template TItem
*
* @implements BaseStream<TItem>
*/
final class SSEStream implements BaseStream
{
/**
* @use SdkStream<array{
* event?: string|null, data?: string|null, id?: string|null, retry?: int|null
* },
* TItem,>
*/
use SdkStream;

private function parsedGenerator(): \Generator
{
if (!$this->stream->valid()) {
return;
}

$done = false;
foreach ($this->stream as $row) {
// @phpstan-ignore if.alwaysFalse
if ($done) {
// Iterate through the whole stream
continue;
}

switch ($row['event'] ?? null) {
case null:
if ($data = $row['data'] ?? '') {
$decoded = Util::decodeJson($data);

yield Conversion::coerce($this->convert, value: $decoded);
}

break;
}

if ($data = $row['data'] ?? '') {
if (str_starts_with($data, needle: 'finished')) {
$done = true;

continue;
}

if (str_starts_with($data, needle: 'error')) {
if ($data = $row['data'] ?? '') {
$json = Util::decodeJson($data);
$message = Util::prettyEncodeJson($json);

$exn = APIStatusException::from(
request: $this->request,
response: $this->response,
message: $message,
);

throw $exn;
}

continue;
}
}
}
}
}
Loading
Loading