Looking to hire Laravel developers? Try LaraJobs

assistant-laravel maintained by rekhu

Description
Rekhu Assistant for Laravel: a custom AI assistant inside your app, for your own customers: it answers, opens the right page and acts through the routes you mark, as the signed-in user, metered per customer by Rekhu.
Last update
2026/10/15 18:14 (dev-main)
License
Downloads
1

Comments
comments powered by Disqus

Rekhu Assistant is a custom AI assistant you build into your own Laravel application, for your own customers. It answers questions from your help, sends people to the right page, and carries out tasks through the routes you mark, as the signed-in user, with your validation, policies and middleware. Every conversation is metered per customer by Rekhu, so you can offer the assistant to your customers and bill them for it.

Feature What it means
Actions from your own routes Put #[Tool] on a controller method. The form request's rules become the input; your controller does the work.
Safe by construction The assistant can only do what the user may do in your app. Destructive actions wait for a real click.
Pages and help It sends users to the right page and answers from your own markdown help, in their language.
Any front end Blade, Livewire, Inertia (Vue or React), or an API with Nuxt, Next or a mobile app.
A public chat Optionally for visitors who are not signed in, with caps per visitor, per IP address and per day.
Applications, like projects Each app gets its keys per environment, one monthly limit and a pause in your Rekhu account, with usage per customer to bill from.
No inbound connection Your app calls Rekhu; nothing calls into your app. It works the same on a laptop and in production.

Contents

  1. Requirements
  2. Getting started
  3. Choose your setup: Blade · Livewire · Inertia · API with Nuxt, Next or a mobile app
  4. Actions
  5. Pages
  6. Help
  7. A public chat for visitors
  8. Applications, usage and limits
  9. How it works
  10. Security
  11. Configuration
  12. Commands
  13. Troubleshooting

Requirements

Requirement Version
PHP 8.2 or newer
Laravel 11, 12 or 13
Cache any store your app already uses; conversations are kept there
Rekhu an account with an app key and credit; sign up, then top up from $10 under Billing

Getting started

1. Get an app key

Create a Rekhu account and put credit on it under Billing (from $10). The assistant draws from that credit per conversation; without credit it cannot answer.

Then open Apps → Rekhu Assistant, create an application (for example "Shop", built with Laravel, monolith or multi-tenant) and, on its page, add a key for your first environment (local). Add one per environment later (staging, production); they all count toward the same application. A key is shown once, together with the commands of the next step.

2. Install the package

composer require rekhu/assistant-laravel
php artisan rekhu:assistant-install --key=agk_…

The install command checks the key with Rekhu, writes REKHU_ASSISTANT_KEY to .env, publishes config/rekhu-assistant.php and creates resources/rekhu/help.

3. Add the widget

In your layout, before </body>:

<x-rekhu-assistant />

The chat button appears for signed-in users.

4. Mark a first action

use Rekhu\Assistant\Attributes\Tool;

class OrderController extends Controller
{
    #[Tool('Reorder a past order: the same product and address as a new order; the quantity can be changed')]
    public function reorder(ReorderRequest $request, Order $order)
    {
        // your own code, as for the form on the order page
    }
}

5. Check what the assistant sees

php artisan rekhu:assistant --user=1

It shows your credit, lists the actions, pages and help topics that user can reach, and tests the connection to Rekhu. Then sign in, open the chat and ask "order my last business cards again".

Choose your setup

The package runs the chat on your server in every setup. What differs is how the widget is added and how the user is signed in.

Your application The widget Sign-in
Blade <x-rekhu-assistant /> the session
Livewire <x-rekhu-assistant /> the session
Inertia, with Vue or React the Blade tag, or @rekhu/assistant-widget for router navigation the session
An API with Nuxt, Next or a mobile app @rekhu/assistant-widget a token (Sanctum, Passport or your own)
A chat screen of your own none; speak protocol 1 any of the above

Blade

Step 3 of Getting started is all there is. The tag uses the user's session and the page's CSRF token, and the package serves the widget's script itself: no npm and no build step.

{{-- resources/views/layouts/app.blade.php --}}
<body>
    {{ $slot }}

    <x-rekhu-assistant />
</body>

The tag accepts attributes that override the config for one layout:

<x-rekhu-assistant name="Shop assistant" greeting="Hi! How can I help with your order?" color="#0f766e" />

Livewire

Use the same Blade tag in the layout. With wire:navigate, keep it on the page while users navigate, so an open conversation stays on screen:

@persist('rekhu-assistant')
    <x-rekhu-assistant />
@endpersist

To refresh a component after the assistant changed data, pass the widget's event on to Livewire:

document.addEventListener('rekhu:action', () => Livewire.dispatch('rekhu-action'));
use Livewire\Attributes\On;

#[On('rekhu-action')]
public function refreshAfterAssistant(): void
{
    // an empty listener re-renders the component
}

[!NOTE] Actions are controller routes. Livewire component methods are not routes, so they cannot carry #[Tool] yet. Put the work in a route, or in a route that calls the same action class, and mark that route. Support for Livewire methods is planned.

Inertia (Vue or React)

The Blade tag in resources/views/app.blade.php works unchanged, but its page buttons reload the whole page. To open pages with Inertia's router and reload the props after an action, mount the npm widget instead:

npm install @rekhu/assistant-widget
{{-- resources/views/app.blade.php, in <head> --}}
<meta name="csrf-token" content="{{ csrf_token() }}">
@auth
    <meta name="rekhu-user" content="1">
@endauth
// resources/js/app.js, after createInertiaApp(…)
import { router } from '@inertiajs/vue3'; // or '@inertiajs/react'
import { mount } from '@rekhu/assistant-widget';

if (document.querySelector('meta[name="rekhu-user"]')) {
    mount({
        endpoint: '/rekhu/assistant/chat',
        csrf: document.querySelector('meta[name="csrf-token"]').content,
        navigate: (path) => router.visit(path),
        onAction: () => router.reload(),
    });
}

The middleware stays web,auth: the user's session signs them in.

An API with Nuxt, Next or a mobile app

The front end runs on its own domain and signs in with a token.

On the Laravel side, put the chat behind your API guard in .env:

REKHU_ASSISTANT_MIDDLEWARE=api,auth:sanctum

and allow the front end's domain in config/cors.php:

'paths' => ['api/*', 'sanctum/csrf-cookie', 'rekhu/assistant/*'],
'allowed_origins' => ['https://app.example.com'],

Your actions can be API routes or web routes. They run as the token's user, and a controller that redirects with a flashed message still reports that message to the assistant.

In the front end, mount @rekhu/assistant-widget with the token. In Nuxt, as plugins/rekhu-assistant.client.ts:

import { mount } from '@rekhu/assistant-widget';

export default defineNuxtPlugin(() => {
    const token = useCookie('token'); // wherever your app keeps it
    const router = useRouter();
    if (!token.value) return;

    mount({
        endpoint: 'https://api.example.com/rekhu/assistant/chat',
        headers: () => ({ Authorization: `Bearer ${token.value}` }),
        credentials: 'omit',
        navigateOrigins: ['https://api.example.com'],
        navigate: (path, url) => router.push(url.pathname + url.search),
        onAction: () => refreshNuxtData(),
    });
});

The widget's README shows the same for Next.js and plain HTML. A mobile app, or a chat screen of your own, uses the same endpoint; protocol 1 describes every request and event.

Actions

Mark a controller method with #[Tool]. The assistant calls the route exactly as when the user submits the form: its middleware (auth, can: and your own), the form request's authorize() and rules, route model binding, policies and events all apply. When the app refuses, the assistant is told why and explains it to the user.

use Rekhu\Assistant\Attributes\Tool;

#[Tool('List the user\'s orders with number, product, status and total', name: 'list_orders')]
public function index(Request $request) { … }        // GET: a read action; return JSON when $request->expectsJson()

#[Tool('Change the delivery address of an order that has not shipped yet', rules: ['address' => 'required|string|max:200'])]
public function address(Request $request, Order $order) { … }   // validates inline, so the rules are given here

#[Tool('Cancel an order that has not shipped yet')]
public function cancel(Order $order) { … }           // DELETE: the user confirms with a click first
Argument Required Meaning
description yes What the action does, written for the assistant. Users see it on the confirm and result cards.
name no The tool's name. Defaults to the route name.
confirm no true asks the user to click Confirm first. Every DELETE route does this by default.
rules no The input, when the method validates inline instead of through a form request.
guests no true also offers the action in the public chat.

Confirmations. When an action needs confirming, nothing runs until the user clicks Confirm on the card. The action and its exact input are kept on the server; a typed "yes", or anything the model writes, cannot run it.

Pages

Every named GET route is a page the assistant can offer, except routes the user may not open (can:, guest) and names matching pages.exclude in the config. The user gets a button to open the page; the assistant never navigates on its own. A description helps it pick the right page:

use Rekhu\Assistant\Attributes\Page;

#[Page('Invoices', help: 'All invoices of the account, with download and payment status')]
public function index() { … }

#[Page('Internal tools', hidden: true)]   // never offered
public function tools() { … }

Help

Write help as markdown files in resources/rekhu/help, one topic per file. The first # heading is the topic's title.

resources/rekhu/help/
├── shipping.md
└── returns.md

The assistant searches these topics for how-to questions and answers from them in the user's language. When the help does not cover a question, it says so instead of guessing.

A public chat for visitors

The same assistant can serve people who are not signed in, for example for questions before a quote. It is off by default:

REKHU_ASSISTANT_GUESTS=true

The same <x-rekhu-assistant /> tag then also shows for visitors, connected to a separate public endpoint. Mark what visitors may use:

#[Tool('Calculate a price quote for a product and quantity', guests: true)]
public function quote(QuoteRequest $request) { … }   // the route must work without login
Rule Detail
What visitors reach Only actions marked guests: true, pages that need no login (and the sign-in page), and the help. Keep internal information out of the help once the public chat is on.
Who a visitor is An anonymous id kept in their browser, with a conversation of its own. A visitor is never a user, even when the browser happens to be signed in.
Caps Per visitor per day (20 messages), per IP address per hour (60), for all visitors together per day (1000), plus shorter messages and fewer steps per answer. All set under guests in the config.
CSRF Stays on, as for signed-in users.
Cost All visitors together show as one tenant, Visitors, in the application's usage, and count toward the application's monthly limit. Set that limit before you turn the public chat on.

Applications, usage and limits

Your Rekhu account keeps everything per application, like a project: open Apps → Rekhu Assistant and click an application.

  • Keys per environment. One secret key for local, staging and production; all of them count toward the application.
  • One monthly limit and a pause for the whole application, with a message on your notification channels at 80% and 100%. At the limit, or paused, the application's assistant answers nobody; your other applications go on. Its users read that the assistant is not available right now, and your app's log says why.
  • Usage per user or per tenant, tracked, not limited: per user for a monolith, per tenant (with its users) for a multi-tenant app, as you choose when you create the application. Conversations, messages and usage per month, and a CSV to bill from.

For a multi-tenant app, tell the package which tenant a user belongs to, in a service provider, so usage shows per tenant:

use Rekhu\Assistant\RekhuAssistant;

public function boot(): void
{
    RekhuAssistant::tenantUsing(fn ($user) => $user->team);   // a model, or ['id' => …, 'name' => …]
}

Users are identified to Rekhu by their id only, never by name or e-mail.

How it works

Browser (widget)  ──▶  your Laravel app (this package)  ──▶  Rekhu  ──▶  the model
                         │
                         └──▶ your routes, run as the user
  1. The widget sends the user's message to the package's chat endpoint in your app, with the user's own session or token.
  2. The package builds the assistant's view of your app for this user: the actions and pages they may use, and your help topics.
  3. It calls Rekhu, which meters the call for the user's company and forwards it to the model.
  4. When the model asks for an action, the package runs your route in the same request, as the user, and reports the result back.
  5. The answer streams back to the widget as text, steps and cards (a page to open, an action to confirm, a result).

Security

Guarantee How
It acts as the user Every action runs as the signed-in user through your own middleware, policies and validation. The assistant cannot do anything the user could not do in your app.
Clicks, not words Confirmations are held on the server. Only the user's click on the card runs the action, with exactly the input they saw.
CSRF and guards The CSRF check stays on for cookie sign-in, the public chat included. API mode uses your API guard.
Throttling The chat is rate limited; the public chat adds caps per visitor, per IP address and per day.
No markup from the model The widget escapes everything the model writes and only links to http(s) addresses or your own paths.
No inbound connection Your app calls Rekhu. There is no endpoint in your app for Rekhu to call.
What leaves your app The conversation, and what your marked actions, pages and help return to the assistant, are sent to Rekhu and the model provider to produce the answer. Users are identified by their id only, never by name or e-mail. Conversations are stored in your app's cache, for 24 hours by default (conversation_minutes).

Configuration

php artisan rekhu:assistant-install publishes the config. To publish it again:

php artisan vendor:publish --tag=rekhu-assistant-config
Environment variable Default Purpose
REKHU_ASSISTANT_KEY The app key. Written by rekhu:assistant-install.
REKHU_ASSISTANT_MODEL claude-sonnet-5 The model that answers.
REKHU_ASSISTANT_NAME Assistant The assistant's name in the widget and in its answers.
REKHU_ASSISTANT_MIDDLEWARE web,auth Who may use the chat. api,auth:sanctum for an API.
REKHU_ASSISTANT_GUESTS false Turns on the public chat for visitors.
REKHU_ASSISTANT_GUEST_MIDDLEWARE web The middleware of the public chat.
REKHU_PLATFORM https://api.rekhu.ai Only for a test platform.

config/rekhu-assistant.php also holds:

Key Purpose
instructions Your own rules for the assistant, in plain words: tone, what it never does, when it hands over to a person.
greeting, color The first message and the accent colour of the widget.
pages.exclude, pages.limit Route names never offered as pages, and the most pages offered.
help_path Another folder for the help topics.
conversation_minutes How long a conversation is kept (1440).
max_tokens, max_steps How long one answer may get, and how many action rounds one turn may take.
guests.* The public chat: its greeting, message length, steps and caps.

Commands

Command What it does
php artisan rekhu:assistant-install --key=agk_… Checks the key with Rekhu, writes it to .env, publishes the config and creates the help folder.
php artisan rekhu:assistant Shows your credit, lists the actions, pages and help topics, and tests the connection to Rekhu.
php artisan rekhu:assistant --user=12 The same, as one user: only what that user may use.

Troubleshooting

What you see What to do
No chat button The tag shows for signed-in users (and for visitors with REKHU_ASSISTANT_GUESTS=true). Check that it is in the layout this page uses.
"Your session has ended" The chat got a 401 or 419: the sign-in or the CSRF token expired. In API mode, check the token and REKHU_ASSISTANT_MIDDLEWARE.
An action is missing Run php artisan rekhu:assistant --user=<id>. A can: middleware hides an action from users who may not use it.
"The assistant is not available right now" The application is paused or at its monthly limit, your Rekhu credit is used up, the app could not reach Rekhu, or the key is wrong. The app's log says which; php artisan rekhu:assistant checks the key and the credit.
CORS errors in the browser In API mode, add rekhu/assistant/* to paths and your front end's domain to allowed_origins in config/cors.php.

Changelog

See CHANGELOG.md for what changed in each version.

Licence

Licensed, not sold, to holders of a Rekhu account under the Rekhu Terms of Service. See LICENSE.txt.