adams100111 / typesafe-laravel
Laravel integration for adams100111/typesafe-php — config, a facade, and a transport on Laravel's HTTP client so Http::fake() works in your tests. Unofficial; not affiliated with TypeSafe.
Requires
- php: ^8.2
- adams100111/typesafe-php: ^0.1
- illuminate/console: ^12.0 || ^13.0
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^3.0 || ^4.0
- pestphp/pest-plugin-laravel: ^3.0 || ^4.1
- phpstan/phpstan: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel integration for adams100111/typesafe-php,
a client for TypeSafe's System One API (Jev), which returns typed
judgements — a probability, a choice, a score — instead of generated text.
Unofficial. A community package, not affiliated with, endorsed by, or supported by TypeSafe.
composer require adams100111/typesafe-laravel
Requires PHP 8.2+ and Laravel 12 or 13. The service provider and TypeSafe facade are
auto-discovered.
Setup
TYPESAFE_API_KEY=your-key
Then confirm the key works:
php artisan typesafe:models
To customise, publish the config:
php artisan vendor:publish --tag=typesafe-config
Usage
use Adams100111\TypeSafe\Laravel\Facades\TypeSafe; use Adams100111\TypeSafe\Question\Question; $result = TypeSafe::systemOne( state: ['ticket' => $ticket->body], questions: [ 'refund' => Question::noul('Does `ticket` ask for a refund?'), 'team' => Question::choice('Which team should handle `ticket`?', [ 'billing' => 'Payments, refunds and invoices.', 'technical' => 'Bugs, errors and outages.', ]), 'urgency' => Question::score('How urgent is `ticket`?', [ 'Routine — can wait days.', 'Soon — should be handled today.', 'Now — the customer is losing money.', ]), ], ); if ($result->noul('refund') > 0.8) { $ticket->assignTo($result->choice('team')->choice); }
Or inject the client:
use Adams100111\TypeSafe\TypeSafeClient; public function __construct(private TypeSafeClient $typesafe) {}
The question builders, answer types, retry behaviour and exceptions are documented in the core package README.
Testing your application
Requests go through Laravel's HTTP client, so Http::fake() intercepts them — no live
calls, no custom mocks:
use Illuminate\Support\Facades\Http; Http::fake([ 'api.typesafe.ai/*' => Http::response([ 'model' => 'jev-1.13.0', 'answers' => ['refund' => ['type' => 'noul', 'noul' => 0.94]], 'usage' => ['input_tokens' => 120, 'output_tokens' => 0], ]), ]); // …exercise your code… Http::assertSent(fn ($request) => str_contains($request->url(), '/v1/systemone'));
Failures fake the same way — Http::response([...], 429) raises RateLimitException,
401 raises AuthenticationException — so you can test your error handling too.
Configuration
| Key | Env | Default |
|---|---|---|
api_key |
TYPESAFE_API_KEY |
— |
base_url |
TYPESAFE_BASE_URL |
https://api.typesafe.ai |
default_model |
TYPESAFE_DEFAULT_MODEL |
jev-latest |
timeout_ms |
TYPESAFE_TIMEOUT_MS |
10000, per attempt |
retry.max_retries |
TYPESAFE_MAX_RETRIES |
2 |
transport |
TYPESAFE_TRANSPORT |
laravel |
log_channel |
TYPESAFE_LOG_CHANNEL |
none |
The key is only checked when the client is first used. An application with the package
installed but no key configured still boots; resolving the client then fails with a message
naming TYPESAFE_API_KEY.
Pin a versioned model once you have tuned thresholds — jev-latest moves when TypeSafe
ships a release:
TYPESAFE_DEFAULT_MODEL=jev-1.13.0
Transport
laravel (the default) sends through Laravel's HTTP client — Http::fake() works, global
HTTP middleware applies, and requests appear in Telescope. curl uses the core package's own
cURL transport and bypasses Laravel's HTTP stack.
Retries are always done by the client, never by Laravel's ->retry(), so they never compound.
Logging
Set TYPESAFE_LOG_CHANNEL to a channel name. info records one line per attempt; debug
adds the request body — your state — so keep the channel above debug if that is sensitive.
The Authorization header is redacted at every level.
Using it responsibly
The answers are calibrated probabilities, not certainties.
- Don't gate on it. The model is self-consistent but not bit-deterministic; an answer near a threshold can cross it between identical runs. Route low-confidence answers to a person rather than acting on them.
- Validate thresholds on your own labelled data, by confidence band, before relying on them.
Supported versions
| PHP | 8.2, 8.3, 8.4 |
| Laravel | 12, 13 |
Laravel 10 and 11 are not supported. Both are past their security-fix window, and every Laravel 11 release now carries unpatched security advisories that Composer refuses to install by default.
License
MIT