PHP code example of projectsaturnstudios / laravel-vibes
1. Go to this page and download the library: Download projectsaturnstudios/laravel-vibes library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
projectsaturnstudios / laravel-vibes example snippets
use App\Tools\TextAnalysisTool;
use App\Tools\DataFetchTool;
// In a service provider's boot method
public function boot()
{
$agency = app('the-agency');
$agency->addTools([
TextAnalysisTool::class,
DataFetchTool::class,
]);
}
// config/vibes.php
'auto_discover_all_primitives' => [app()->path()], // Directories to scan for primitives
'auto_discover_base_path' => base_path(), // Base path for relative directories
protected function discoverPrimitiveHandlers() : void
{
$agency = app(TheAgency::class);
$cachedPrimitiveHandlers = $this->getCachedPrimitiveHandlers();
if (! is_null($cachedPrimitiveHandlers)) {
$agency->addPrimitiveHandlers($cachedPrimitiveHandlers);
return;
}
(new PrimitiveHandlerDiscoveryService)
->within(config('vibes.auto_discover_all_primitives'))
->useBasePath(config('vibes.auto_discover_base_path', base_path()))
->ignoringFiles(Composer::getAutoloadedFiles(base_path('composer.json')))
->addToTheAgency($agency);
}
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use ProjectSaturnStudios\Vibes\TheAgency;
use App\MCP\Tools\WeatherTool;
use App\MCP\Tools\TranslationTool;
use App\MCP\Resources\UserResource;
use App\MCP\Prompts\GreetingPrompt;
class MCPServiceProvider extends ServiceProvider
{
public function boot()
{
// Get the agency singleton
$agency = app(TheAgency::class);
// Register tools
$agency->addTools([
WeatherTool::class,
TranslationTool::class,
]);
// Register other primitives when implemented
// $agency->addResources([
// UserResource::class,
// ]);
// $agency->addPrompts([
// GreetingPrompt::class,
// ]);
}
}
'providers' => [
// Other service providers...
App\Providers\MCPServiceProvider::class,
],
// Register the command in your service provider
$this->app->booted(function () {
$this->app['events']->listen('cache:clearing', function () {
// Clear cached primitives
@unlink($this->app['config']['vibes.service_info.cache_path'].'/vibes.php');
});
});
namespace Tests\Unit\Tools;
use Tests\TestCase;
use App\Tools\WeatherTool;
use ProjectSaturnStudios\Vibes\TheAgency;
use ProjectSaturnStudios\Vibes\Exceptions\InvalidToolParameters;
class WeatherToolTest extends TestCase
{
protected TheAgency $agency;
protected WeatherTool $weatherTool;
protected function setUp(): void
{
parent::setUp();
$this->agency = app(TheAgency::class);
$this->weatherTool = new WeatherTool();
// Register the tool with TheAgency
$this->agency->addTool($this->weatherTool);
}
/** @test */
public function it_has_correct_metadata()
{
$metadata = WeatherTool::getMetadata();
$this->assertEquals('weather', $metadata['name']);
$this->assertArrayHasKey('description', $metadata);
$this->assertArrayHasKey('parameters', $metadata);
$this->assertArrayHasKey('properties', $metadata['parameters']);
$this->assertArrayHasKey('location', $metadata['parameters']['properties']);
}
/** @test */
public function it_handles_valid_location()
{
$result = $this->weatherTool->handle('San Francisco');
$this->assertIsArray($result);
$this->assertArrayHasKey('location', $result);
$this->assertArrayHasKey('temperature', $result);
$this->assertArrayHasKey('conditions', $result);
$this->assertEquals('San Francisco', $result['location']);
}
/** @test */
public function it_throws_exception_for_invalid_location()
{
$this->expectException(InvalidToolParameters::class);
$this->weatherTool->handle('NonExistentLocation123456789');
}
/** @test */
public function it_is_registered_with_the_agency()
{
$tool = $this->agency->getTool('weather');
$this->assertNotNull($tool);
$this->assertInstanceOf(WeatherTool::class, $tool);
}
}
namespace Tests\Unit\Tools;
use Tests\TestCase;
use App\Tools\ExternalApiTool;
use Illuminate\Support\Facades\Http;
use ProjectSaturnStudios\Vibes\Exceptions\ToolExecutionError;
class ExternalApiToolTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
// Mock external API responses
Http::fake([
'api.example.com/data*' => Http::response([
'success' => true,
'data' => [
'id' => 123,
'name' => 'Test Item',
'value' => 99.95
]
], 200),
'api.example.com/error*' => Http::response([
'success' => false,
'error' => 'Resource not found'
], 404),
]);
}
/** @test */
public function it_fetches_and_formats_external_data()
{
$tool = new ExternalApiTool();
$result = $tool->handle('data', ['id' => 123]);
$this->assertIsArray($result);
$this->assertTrue($result['success']);
$this->assertEquals('Test Item', $result['data']['name']);
$this->assertEquals(99.95, $result['data']['value']);
}
/** @test */
public function it_handles_api_errors_appropriately()
{
$this->expectException(ToolExecutionError::class);
$tool = new ExternalApiTool();
$tool->handle('error', ['id' => 999]);
}
/** @test */
public function it_retries_on_temporary_failures()
{
Http::fake([
// First call fails, second succeeds
'api.example.com/flaky*' => Http::sequence()
->push(['error' => 'Server busy'], 503)
->push(['success' => true, 'data' => ['name' => 'Flaky Test']], 200),
]);
$tool = new ExternalApiTool();
$result = $tool->handle('flaky', ['id' => 456]);
$this->assertTrue($result['success']);
$this->assertEquals('Flaky Test', $result['data']['name']);
}
}
namespace Tests\Feature;
use Tests\TestCase;
use App\Tools\CalculatorTool;
use ProjectSaturnStudios\Vibes\TheAgency;
use ProjectSaturnStudios\Vibes\VibeSesh;
class AgencyToolIntegrationTest extends TestCase
{
protected TheAgency $agency;
protected function setUp(): void
{
parent::setUp();
$this->agency = app(TheAgency::class);
// Register test tools
$this->agency->addTool(CalculatorTool::class);
}
/** @test */
public function it_executes_calculator_tool_through_agency()
{
// Create a test session
$session = new VibeSesh('test-session-123');
// Prepare a tool execution request
$request = [
'jsonrpc' => '2.0',
'method' => 'run_tool',
'id' => 'req-'.time(),
'session_id' => $session->getId(),
'params' => [
'name' => 'calculator',
'input' => [
'operation' => 'add',
'a' => 5,
'b' => 7
]
]
];
// Process through TheAgency
$response = $this->agency->processRequest($request, $session);
// Verify response
$this->assertEquals('2.0', $response['jsonrpc']);
$this->assertEquals($request['id'], $response['id']);
$this->assertEquals(12, $response['result']['sum']);
}
/** @test */
public function it_returns_proper_error_for_invalid_tool_name()
{
$session = new VibeSesh('test-session-123');
$request = [
'jsonrpc' => '2.0',
'method' => 'run_tool',
'id' => 'req-'.time(),
'session_id' => $session->getId(),
'params' => [
'name' => 'non-existent-tool',
'input' => []
]
];
$response = $this->agency->processRequest($request, $session);
$this->assertArrayHasKey('error', $response);
$this->assertEquals(-32601, $response['error']['code']);
}
}
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Testing\TestResponse;
use Illuminate\Support\Facades\Event;
use App\Events\AgentConnected;
class SSEConnectionTest extends TestCase
{
/** @test */
public function it_establishes_sse_connection_successfully()
{
// Listen for connection events
Event::fake([AgentConnected::class]);
// Make request to SSE endpoint
$response = $this->get('/mcp/sse', [
'Accept' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Requested-With' => 'XMLHttpRequest',
]);
// Check connection setup
$response->assertStatus(200);
$response->assertHeader('Content-Type', 'text/event-stream');
$response->assertHeader('Cache-Control', 'no-cache, private');
$response->assertHeader('Connection', 'keep-alive');
// Confirm response has SSE format
$this->assertStringContainsString('data:', $response->getContent());
$this->assertStringContainsString('id:', $response->getContent());
$this->assertStringContainsString('event: endpoint-info', $response->getContent());
// Verify event was dispatched
Event::assertDispatched(AgentConnected::class);
}
/** @test */
public function it_
namespace Tests\Unit\Services;
use Tests\TestCase;
use App\Services\ClaudeService;
use Illuminate\Support\Facades\Http;
use ProjectSaturnStudios\Vibes\TheAgency;
use App\Tools\CalculatorTool;
class ClaudeServiceTest extends TestCase
{
protected ClaudeService $service;
protected function setUp(): void
{
parent::setUp();
// Get TheAgency and register tools
$agency = app(TheAgency::class);
$agency->addTool(CalculatorTool::class);
// Create service with mocked dependencies
$this->service = app(ClaudeService::class);
// Mock Claude API responses
Http::fake([
'https://api.anthropic.com/v1/messages' => Http::response([
'id' => 'msg_01234567',
'model' => 'claude-3-opus-20240229',
'content' => [
['type' => 'text', 'text' => 'This is a test response from Claude']
],
'tool_calls' => [
[
'id' => 'call_01234567',
'name' => 'calculator',
'parameters' => [
'operation' => 'multiply',
'a' => 4,
'b' => 5
]
]
]
], 200),
]);
}
/** @test */
public function it_sends_request_to_claude_api()
{
$response = $this->service->createMessage('Test prompt');
$this->assertIsArray($response);
$this->assertArrayHasKey('content', $response);
$this->assertArrayHasKey('tool_calls', $response);
$this->assertEquals('This is a test response from Claude', $response['content'][0]['text']);
}
/** @test */
public function it_handles_tool_calls()
{
// Get a sample tool call from the API response
$response = $this->service->createMessage('Test prompt');
$toolCall = $response['tool_calls'][0];
// Process the tool call
$result = $this->service->handleToolCall($toolCall, 'test-convo-123');
$this->assertIsArray($result);
$this->assertEquals('success', $result['status']);
$this->assertEquals(20, $result['result']['product']); // 4 * 5 = 20
}
}
// In LaravelVibesServiceProvider.php
RateLimiter::for('mcp-agent', function (Request $request) {
return [
Limit::perMinute(60)->by($request->session()->getId()),
Limit::perDay(1000)->by($request->session()->getId()),
];
});
readonly class ToolMetadata
{
public function __construct(
public string $name,
public string $description,
public array $parameters,
public array $returns
) {}
}
enum ToolStatus: string
{
case ACTIVE = 'active';
case DEPRECATED = 'deprecated';
case EXPERIMENTAL = 'experimental';
}
// Laravel 11.x
public function handle($parameter1, $parameter2)
// Laravel 12.x
public function handle(string $parameter1, ?array $parameter2 = null)
// Update service bindings in your ServiceProvider
$this->app->bind(TheAgency::class, CustomAgency::class);
$this->app->bind(ToolRepository::class, CustomToolRepository::class);
// Laravel 12 now uses the dispatchSync method instead of dispatchNow
ProcessAgentMessageJob::dispatchSync($message);
// config/vibes.php
'tool_execution' => [
'timeout' => 5, // Maximum time (seconds) for tool execution
'max_memory' => '256M', // Memory limit for tool execution
'cache_ttl' => 60, // Cache tool results for similar inputs (seconds)
'batch_size' => 10, // Batch size for processing multiple tool calls
],
namespace App\Tools;
use ProjectSaturnStudios\Vibes\Primitives\Tools\Data\VibeTool;
use Illuminate\Contracts\Queue\ShouldQueue;
class LongRunningProcessTool extends VibeTool implements ShouldQueue
{
public $timeout = 300; // 5 minutes
public $tries = 3;
public $backoff = 30;
// Tool implementation...
}
// In app/Http/Kernel.php
protected $middleware = [
// Other middleware...
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
\ProjectSaturnStudios\Vibes\Http\Middleware\CompressResponse::class,
];
// In a service provider
$this->app->singleton('vibes.telemetry', function () {
return new \App\Services\VibeTelemetryService(
endpoint: config('vibes.telemetry.endpoint'),
projectId: config('vibes.telemetry.project_id')
);
});
// In your custom tool
public function handle(string $input)
{
app('vibes.telemetry')->recordToolExecution($this->getName(), start_time: microtime(true));
// Tool implementation...
app('vibes.telemetry')->recordToolCompletion($this->getName(), microtime(true) - $startTime);
}
// config/vibes.php
'resources' => [
'max_sse_connections' => 1000, // Maximum concurrent SSE connections
'sse_timeout' => 3600, // Maximum SSE connection lifetime (seconds)
'max_concurrent_tools' => 100, // Maximum concurrent tool executions
'rate_limits' => [
'tool_execution' => 60, // Maximum tool calls per minute per session
'message_size' => 1024 * 1024, // Maximum message size in bytes
],
],