Download the PHP package tetrixdev/laravel-ai-bridge without Composer
On this page you can find all versions of the php package tetrixdev/laravel-ai-bridge. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download tetrixdev/laravel-ai-bridge
More information about tetrixdev/laravel-ai-bridge
Files in tetrixdev/laravel-ai-bridge
Package laravel-ai-bridge
Short Description Laravel package for AI Bridge — unified streaming interface for CLI Bridge, BYOK, and managed AI modes
License MIT
Informations about the package laravel-ai-bridge
Laravel AI Bridge
A unified AI streaming interface for Laravel. Connect any Chat Completions-compatible provider (OpenAI, Anthropic, Groq, Ollama, etc.) or local CLI tools (Codex, Claude, Gemini) to your app through a single, normalized streaming pipeline.
What is this?
Laravel AI Bridge provides a unified streaming pipeline: provider -> normalized events -> browser. No matter where the AI response originates, your application receives the same StreamEvent objects through the same callback API. Three modes of operation cover every use case:
- BYOK (Bring Your Own Key) -- User provides an API key and endpoint. No local install needed.
- Managed -- Your app provides the API key. Same code path as BYOK, different config source.
- CLI Bridge -- User runs
npx @tetrixdev/ai-bridgelocally. Their CLI tools (Codex, Claude, Gemini) connect to your app via a dedicated WebSocket server.
Installation
Publish the config file:
Publish the JavaScript client (optional):
Add to your .env:
Quick Start
A minimal BYOK example in three steps.
1. Configure .env
2. Create a Controller
3. Create a Blade View
Modes of Operation
BYOK (Bring Your Own Key)
The user provides an API key and endpoint for any Chat Completions-compatible provider. The server calls the API directly -- no local install required on the user's machine.
Works with any Chat Completions-compatible endpoint: OpenAI, Anthropic (via proxy), Groq, Together, Ollama, LM Studio, vLLM, etc.
Managed
Identical to BYOK but the application provides its own API key. Users pay the app a subscription fee. No separate architecture needed -- same code path, different config source.
CLI Bridge
The user installs the bridge locally via npx @tetrixdev/ai-bridge. It connects to your app's dedicated WebSocket server and proxies AI requests through their local CLI tools (using their existing subscriptions).
Start the bridge server:
Generate a token for the user, then have them connect:
The server-side code is identical:
Configuration
Full reference for config/ai-bridge.php:
| Key | Env Variable | Default | Description |
|---|---|---|---|
mode |
AI_BRIDGE_MODE |
byok |
Active mode: byok, managed, or bridge |
token.secret |
AI_BRIDGE_TOKEN_SECRET |
null |
JWT signing secret (required) |
token.ttl |
AI_BRIDGE_TOKEN_TTL |
86400 |
Token TTL in seconds (24h) |
websocket.heartbeat_interval |
-- | 30 |
Seconds between ping/pong |
websocket.request_timeout |
-- | 300 |
Seconds before AI request timeout |
chat_completions.endpoint |
AI_BRIDGE_ENDPOINT |
null |
Chat Completions API base URL |
chat_completions.api_key |
AI_BRIDGE_API_KEY |
null |
API key for BYOK/managed |
chat_completions.model |
AI_BRIDGE_MODEL |
null |
Model name (e.g. gpt-4o) |
chat_completions.max_tokens |
AI_BRIDGE_MAX_TOKENS |
4096 |
Max response tokens |
chat_completions.allowed_models |
-- | [] |
Model allowlist. Empty array allows all models. When non-empty, requests for a model not in the list are rejected with HTTP 422 |
server.host |
AI_BRIDGE_SERVER_HOST |
127.0.0.1 |
Bridge WebSocket server bind address (set to 0.0.0.0 in Docker/multi-host setups) |
server.port |
AI_BRIDGE_SERVER_PORT |
8085 |
Bridge WebSocket server port |
stream_store.default |
AI_BRIDGE_STREAM_STORE |
redis |
Per-turn event buffer driver. Ships with redis and array (tests); apps register their own via StreamStore::extend(). |
stream_store.redis.connection |
AI_BRIDGE_STREAM_REDIS_CONNECTION |
null |
Redis connection from config/database.php. null = app default. |
stream_store.redis.prefix |
AI_BRIDGE_STREAM_REDIS_PREFIX |
ai-bridge:stream |
Key prefix for buffer entries. |
stream_store.redis.ttl_streaming |
AI_BRIDGE_STREAM_TTL_STREAMING |
3600 |
TTL (s) while a turn is live; refreshed on every event. |
stream_store.redis.ttl_completed |
AI_BRIDGE_STREAM_TTL_COMPLETED |
1800 |
TTL (s) once a turn has terminated; bounds how long a recent page-load can replay. |
stream_store.poll_interval_ms |
AI_BRIDGE_STREAM_POLL_MS |
100 |
SSE long-poll interval while a turn is streaming. |
stream_store.keepalive_interval_s |
AI_BRIDGE_STREAM_KEEPALIVE_S |
30 |
SSE keepalive comment cadence (must beat intermediate proxy idle timeouts). |
stream_store.max_connection_s |
AI_BRIDGE_STREAM_MAX_CONNECTION_S |
600 |
Per-SSE-connection lifetime ceiling; the browser auto-reconnects via Last-Event-ID after. |
logging.channel |
AI_BRIDGE_LOG_CHANNEL |
null |
Log channel for the bridge relay path. null uses the app's default channel; point it at a dedicated channel (e.g. a daily channel with its own retention) to keep bridge logs separate. Falls back to the default channel if the named one is undefined. |
logging.verbose |
AI_BRIDGE_LOG_VERBOSE |
false |
When true, also log per-event detail (every stream event, relayed payloads) at debug level. Useful in development; noisy in production. |
cli.local_path |
AI_BRIDGE_CLI_LOCAL_PATH |
null |
Absolute path to an ai-bridge repo checkout. When set and APP_ENV=local, the "Add a CLI bridge" command runs that checkout's build (node <path>/dist/cli.js) instead of npx @tetrixdev/ai-bridge@latest — for testing CLI changes without an npm publish. Build the checkout first (npm run build). |
streaming.suppress_thinking_blocks |
AI_BRIDGE_SUPPRESS_THINKING |
true |
Suppress AI chain-of-thought / thinking blocks from SSE output and the per-turn buffer. Set to false only when intentionally displaying AI reasoning to users. |
Relay-path logging
When a bridge-mode chat hangs on "Thinking", the bridge relay log is the first
place to look. A healthy turn logs relaying conversation message to bridge,
then relayed request to bridge server, then a terminal relayed stream done
(or relayed stream error with the cause). With logging.verbose on, every
stream event and the relayed payload are logged too.
Point logging.channel at a dedicated channel to keep these out of the main
app log and give them their own retention — e.g. in config/logging.php:
then set AI_BRIDGE_LOG_CHANNEL=ai-bridge.
Streaming to Browser
Two methods for delivering AI responses to the browser.
SSE (Server-Sent Events)
Returns an SSE HTTP response. Simplest approach -- no extra infrastructure needed.
Or use the built-in endpoint:
The response is text/event-stream with normalized events. The first event is always
conversation_id, carrying the server-generated conversation ID — capture it and send it
back with follow-up messages to continue a multi-turn conversation:
Buffered streaming (refresh-safe)
For persisted conversations, use the buffered streaming path. The server
writes every event to a short-lived per-turn buffer (Redis by default);
the browser tails it over SSE. Native EventSource reconnection via
Last-Event-ID makes refresh, tab-switch and network blip recover the
in-flight reply for free — without losing tokens or restarting the turn.
Or use the built-in endpoint, which the bundled chat UI uses:
Then tail the per-turn buffer from the browser using native
EventSource:
On page load, check whether a turn is in flight and re-attach. The
conversation payload (GET /ai-bridge/conversations/{id}) carries
streaming_request_id when a turn is live:
Stop an in-flight turn:
JavaScript Client
A lightweight vanilla JS module that wraps the HTTP endpoints with the same refresh-safe semantics the bundled chat UI uses. Publish it first:
This copies ai-bridge.js to resources/js/vendor/ai-bridge.js.
Using with Vite (recommended): Import it in your resources/js/app.js:
Manual approach: Copy the file to public/js/vendor/ai-bridge.js and include with a script tag:
Buffered mode (default — recommended)
For conversation-based streams. POSTs the message, then tails the
per-turn buffer over native EventSource. Refresh, tab-switch and
network blip recover the in-flight reply automatically via
Last-Event-ID.
SSE mode (one-shot, no conversation row)
For non-conversation use — a direct text/event-stream against
/ai-bridge/stream/sse. No resumption; a refresh loses the response.
Use buffered mode if you need refresh-safety.
With Alpine.js
Conversation Persistence
The package persists multi-turn conversations to the database so they can be
listed, resumed, and replayed. Persistence is always on — there is no opt-in
flag. Three tables are created by auto-loaded migrations (they are not
publishable — do not fork them): ai_bridge_conversations, ai_bridge_messages
and ai_bridge_connections, on the application's default database connection.
The tables are deliberately not linked to any of your tables. Your app
associates conversations/connections with its own users or sessions via its own
pivot tables, and tells the package which rows a request may see by registering
two scoping resolvers (e.g. in a service provider's boot()):
Listen for ConversationCreated / ConnectionCreated to link a newly created
row to your owner model.
Connection lifecycle events
The package dispatches synchronous events around connection lifecycle so your app can keep its own state in step:
| Event | When | Use it to |
|---|---|---|
ConnectionCreated |
a connection is registered via the HTTP API | link the new row to your owner/session model |
ConnectionDeleted |
a connection is deleted via the HTTP API | run non-database cleanup tied to the connection |
Each event carries the Connection and the live Request.
Cascading the delete. Give your owner pivot a cascading foreign key so its rows are removed automatically when a connection is deleted — no listener needed for the database side:
With the cascade in place, listen for ConnectionDeleted only when you have
other work to do (e.g. notifying a service, clearing a cache). For deletes
that should be vetoed or cleaned up transactionally, you can also hook the
Eloquent deleting event on Tetrix\AiBridge\Models\Connection.
If your resolver reads the session (e.g.
$request->session()), the AI Bridge routes must run with the full cookie + session middleware stack — notStartSessionalone. Configureai-bridge.route_middlewareaccordingly:Your web pages set an encrypted session cookie (via the
webgroup). WithoutEncryptCookieson the AI Bridge routes,StartSessioncannot decrypt that cookie and starts a brand-new session on every request — so session-scoped conversations and connections appear to vanish between requests. Apps that scope by an authenticated user instead ($request->user()) can use their normal auth middleware and are unaffected.
HTTP API
| Method & path | Purpose |
|---|---|
GET /ai-bridge/conversations |
List conversations (scoped + paginated) |
POST /ai-bridge/conversations |
Create a conversation |
GET /ai-bridge/conversations/{id} |
Conversation + messages + tools_stale flag |
DELETE /ai-bridge/conversations/{id} |
Delete a conversation |
POST /ai-bridge/conversations/{id}/stream |
Start a turn — returns {request_id}; the browser tails /streams/{rid}/events |
GET /ai-bridge/streams/{rid}/status |
Status snapshot of an in-flight or recently-completed turn |
GET /ai-bridge/streams/{rid}/events |
SSE tail of the per-turn event buffer; resumes by Last-Event-ID |
POST /ai-bridge/streams/{rid}/abort |
Cancel an in-flight turn (serve process observes the flag, sends cancel to the CLI) |
GET /ai-bridge/connections |
List connections with their advertised providers/models + live connected flag |
POST /ai-bridge/connections |
Register a CLI bridge or BYOK connection |
PATCH /ai-bridge/connections/{id} |
Rename a connection |
POST /ai-bridge/connections/{id}/regenerate |
Rotate a bridge's token (revokes the old one, disconnects any live bridge) |
DELETE /ai-bridge/connections/{id} |
Delete a connection (disconnects any live bridge) |
History injection retains prior text and tool calls/results but excludes thinking blocks; switching provider/model/mode mid-conversation is supported.
Reference Chat UI
A drop-in ChatGPT-style chat component ships with the package:
It is a thin wrapper that renders an <ai-bridge-chat> Web Component. The
component uses Shadow DOM, so it is fully isolated — it cannot conflict with
your app's CSS framework or JavaScript (no global Tailwind, no global Alpine).
Its pre-built bundle is served by the package; your app needs no build toolchain.
It is entirely optional — the backend is fully usable without it.
Customizing the chat UI
The component is a reference implementation — you are never locked into it.
-
Build your own UI (recommended for anything beyond light tweaks). Every piece of logic lives server-side, so a custom UI is lightweight: render JSON from the HTTP API above, POST messages to the stream endpoint, tail the returned
request_id's buffer overEventSource. Use any stack — Blade, Livewire, Vue, React. The stream emits these events (each as an SSEevent: <name>line with a JSONdata:payload):block_start,block_delta({block_type, content}),block_stop,tool_call({tool_name, parameters}),done({usage}),error,cancelled.The bundled component (
resources/dist/ai-bridge-chat.js) is the working reference for everything a client needs to do: API calls, EventSource lifecycle,Last-Event-IDresumption on conversation-open, reassemblingblock_*events into rendered messages, and watchdog handling for stalled turns. Read it as the example rather than re-deriving the contract. - Fork the component. Copy
resources/dist/ai-bridge-chat.jsfrom the package into your app, adjust it, and point your own<script>/element at it. It is a single self-contained file with no build step.
Tool System
Register tools that the AI can call during a conversation. Tools work across all three modes.
Every parameter must be described. A tool registered with a parameter that has no (or an empty)
descriptionis rejected with anInvalidArgumentExceptionat registration time. This applies to every registration path below. Tool names must start with a letter and contain only letters, digits, underscores, or hyphens (max 64 characters).
Register with a Closure
The parameters argument is a raw JSON Schema object. Each entry under
properties must include a non-empty description.
A tool that takes no parameters passes an empty array (parameters: []).
Register with the Structured AbstractTool API (recommended)
Extending AbstractTool is the recommended way to define a tool. Instead of
hand-writing a JSON Schema, you declare each parameter as a ToolParameter.
Because ToolParameter requires a non-empty description, it is impossible to
define a tool with an undescribed parameter -- the schema is generated for you.
ToolParameter accepts the JSON Schema types string, integer, number,
boolean, array, and object. defineParameters() returns a list of them,
and AbstractTool turns that list into the JSON Schema the API expects via the
final parameters() method.
Register with a ToolHandler Class
You can also implement the ToolHandler interface directly and build the JSON
Schema yourself. Every property must still include a non-empty description.
Listening for Tool Calls
Bridge Server
The ai-bridge:serve command starts a dedicated WebSocket server for CLI bridge connections. It runs on its own port and speaks the AI Bridge Protocol — separate from any other realtime infrastructure your app may use.
Options:
The server:
- Accepts WebSocket connections from bridge clients (
npx @tetrixdev/ai-bridge) - Validates JWT tokens from the
?token=query parameter - Routes AI request/response messages through the
MessageHandler - Tracks connections via
BridgeConnectionManager - Handles graceful shutdown on SIGINT/SIGTERM
Running bridge mode — one background process is required
Bridge mode needs one long-running process alongside your web server, because the AI response arrives at a different process than the one handling the browser request (see the data-flow diagram in Architecture):
| Process | Command | Why it is needed |
|---|---|---|
| AI Bridge server | php artisan ai-bridge:serve |
Accepts the WebSocket connection from the user's local npx @tetrixdev/ai-bridge CLI bridge, and writes stream events from it into the per-turn buffer the browser tails over SSE. |
It must run continuously — under a process manager (Supervisor), as a dedicated container, or via Octane in development. A typical Supervisor setup:
A working Redis (used for the per-turn buffer by default) is also required. BYOK / Managed mode needs neither — those stream over SSE directly from the web process, so a plain web server plus Redis is enough.
Artisan Commands
| Command | Description |
|---|---|
ai-bridge:serve |
Start the dedicated WebSocket server for CLI bridge connections |
ai-bridge:token |
Generate a JWT connection token for testing |
ai-bridge:test |
Send a test request through the configured mode |
ai-bridge:serve
Starts the bridge WebSocket server. Bridge clients connect to ws://host:port?token=<JWT>.
ai-bridge:token
Generates a JWT token for testing bridge connections without needing a full auth flow.
ai-bridge:test
Sends a test request and displays streaming events in the console.
Architecture
Data flow (BYOK/Managed):
- Browser POSTs the message to
/conversations/{id}/stream; Laravel returns{request_id}and runs the upstream AI call in aterminating()callback. - Each streamed event is written to the per-turn buffer keyed by
request_id. - Browser opens
EventSource('/streams/{rid}/events')and tails the buffer; nativeLast-Event-IDreconnect makes refresh/blip recover the in-flight reply.
Data flow (CLI Bridge):
- Browser POSTs the message; the web worker relays an
ai_requestto theai-bridge:serveprocess over its internal HTTP API. - The serve process sends the request over WebSocket to the user's local bridge; events come back asynchronously into the same serve process.
- Each event is written to the per-turn buffer (and the assistant message is persisted at terminal).
- Browser tails the buffer over SSE, same as BYOK/Managed — uniform shape across modes.
Protocol
The WebSocket protocol between the server and CLI bridge is documented in PROTOCOL.md.
License
MIT. See LICENSE.
All versions of laravel-ai-bridge with dependencies
illuminate/support Version ^12.0
illuminate/routing Version ^12.0
illuminate/http Version ^12.0
firebase/php-jwt Version ^6.10|^7.0
guzzlehttp/psr7 Version ^2.0
react/socket Version ^1.14
ratchet/rfc6455 Version ^0.4