Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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-4fb17cafc413ae3d575e3268602b01d2d0e9ebeb734a41b6086b3353ff0d2523.yml
openapi_spec_hash: 8d48d8564849246f6f14d900c6c5f60c
config_hash: 5c69fb596588b8ace08203858518c149
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
# Changelog

## 0.3.0 (2025-12-19)

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

* **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))

## 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
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ $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')`.

### Handling errors

Expand All @@ -80,10 +80,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 +127,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 +148,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