tetrixdev/laravel-ai-bridge
laravel-ai-bridge is a lightweight Laravel package for connecting your application to AI services through a simple, unified bridge. It helps you organize AI provider configuration and keep prompt/response integrations clean and maintainable.
POST /ai-bridge/connections/{id}/mcp, POST/DELETE .../upstreams/{server}/sign-in) require a valid CSRF token whenever the request is authenticated by session or cookie. The bundled <x-ai-bridge::chat /> sends it for you; a hand-rolled fetch/axios call needs X-CSRF-TOKEN, _token, or X-XSRF-TOKEN. Bearer/Sanctum token requests are exempt.AGENTS.md and the docs map (following Engram documentation standard v1). The architecture page explains the conversation → turn → bridge request flow, and the decision records explain why things like per-turn model resolution and the new CSRF policy (Decision 0009) work the way they do. Read those before reading source.effort_levels. Before ai-bridge 0.32.0 this is an empty array, so treat it as the capability manifest for the thinking-level selector rather than hardcoding options.model and (for Claude) an effort, and echo back the assistant message. That one request exercises model resolution, option serialisation (ai_request.options.effort), and validation — the three things most likely to bite you. Then exercise an MCP route with a tool_call_id to confirm your CSRF wiring works.model on the turn only when the user explicitly changes it. As of v0.20.0 the per-turn value wins over the stored one, so you can implement a "this message only" model switcher without mutating the conversation, or persist the change separately if you want it to stick.ai_request.options.effort. Build the selector from effort_levels on the connection status response, and omit the key entirely rather than sending null or '' when the user hasn't picked one.tool_call_id on MCP calls. Since v0.21.0 the mcp route rejects requests without it (422 tool_call_id_required). Treat it as mandatory in your browser/client code: thread the id of the card/message that triggered the tool through to the request. The PHP API still leaves it optional for trusted server-side code, so internal callers don't need to change, but any request originating from the UI does.@csrf/X-XSRF-TOKEN in your layout, or keep using the bundled chat component which handles it. If you're on Inertia/axios, the default X-XSRF-TOKEN header already satisfies the check — just confirm the cookie is being issued before the request.ai-bridge.csrf.routes. Setting AI_BRIDGE_CSRF_ROUTES=true extends the same protection to the package's other state-changing browser routes; it becomes the default in the next major, so enable it now in staging to flush out any non-token clients you've forgotten about.^[a-z]{1,16} and returns 422 otherwise. Mirror that rule in a FormRequest so your users get a field-level error instead of a raw 422 from the bridge call; keep the two in sync if you expose custom effort labels.effort_levels: empty means the bridge is older than 0.32.0 (or the conversation isn't Claude), so hide the control rather than rendering a dead dropdown.Http::fake() for the happy path, but keep one integration test pinned to a known ai-bridge version so effort-level and model-precedence regressions surface before deploy. Add a CSRF test (session-authenticated request without a token → 419) and a bearer-token test (no token → passes).csrf_token_mismatch is the new failure mode. It appears on the MCP/upstream routes for session- or cookie-authenticated requests only. Note the check deliberately ignores your app's allowSameSite() setting and CSRF except list, and it also fires when a user is signed in by cookie without a started session — so a route you excluded elsewhere is still protected here. Only genuinely token-authenticated requests (bearer, Sanctum personal access tokens) skip it. Don't "fix" it by disabling CSRF globally; fix the client.ai-bridge.csrf.upstreams / AI_BRIDGE_CSRF_UPSTREAMS=false turns the check off — only acceptable if your own middleware already guards those routes. Anything else reopens the same-site subdomain attack the release closes.tool_call_id is now required on the mcp route. Any client, queued job, or test that fabricated an MCP call without it now gets a 422 tool_call_id_required. Grep for the route before upgrading. Trusted server-side PHP callers are unaffected (parameter stays optional in the API).model now wins. Any code that assumed the conversation's stored model always took effect will change behaviour on upgrade. Audit spots where you pass a model on a turn "just in case" (defaults, job payloads, retries) — those now override the conversation and can silently switch models mid-thread.effort_levels comes back empty. Don't treat "no error" as "it applied"; verify by inspecting the outbound ai_request.options.effort in logs.^[a-z]{1,16} — lowercase letters only, no digits, hyphens, or underscores. Log the offending value; the route error alone won't tell you which turn caused it.laravel/framework; if you're pinned to an older Laravel, the routes won't behave as documented. Confirm your framework version before enabling csrf.routes.effort_levels, (3) whether the request is session- or token-authenticated (CSRF applies to the former), (4) the actual request payload — confirm model, effort, and tool_call_id sit where you expect before blaming the bridge.How can I help you explore Laravel packages today?