agent/php/Services/HermesClient.php
Snider 82ffd420e0 feat(agent/php): DispatchMantisTicketJob + HermesClient + agent_dispatches table (#827)
Phase 3 lane: queueable Job that resolves a profile via ProfileSelector,
posts POST {gateway}/v1/responses to the chosen Hermes gateway, persists
ticket_id/profile_id/response_id/run_id/status in agent_dispatches, and
chains CaptureDispatchResultJob.

Migration 2026_04_25_000003 creates agent_dispatches table (FK-free,
Postgres-compatible).

HermesClient: thin Laravel HTTP wrapper around the Hermes /v1/responses
endpoint with Authorization header + JSON body.

DispatchMantisTicketJob behaviour:
- Resolves profile via ProfileSelector::pickFor()
- Null-profile → log warn + ->release(60) requeue
- Otherwise POSTs to gateway, persists AgentDispatch row, queues
  CaptureDispatchResultJob

AgentDispatch Eloquent model with minimal $fillable.

Pest Feature test (Http::fake): verifies request shape, persisted row,
downstream capture-job queueing, and the no-profile requeue path.
Test file conditionally aliases minimal stubs for sibling-lane services
so this file remains runnable before #826/#828 fully land in dev.

Codex note: php -l clean; pest skipped (no vendor/).

Closes tasks.lthn.sh/view.php?id=827

Co-authored-by: Codex <noreply@openai.com>
2026-04-26 00:52:37 +01:00

48 lines
1.2 KiB
PHP

<?php
// SPDX-License-Identifier: EUPL-1.2
declare(strict_types=1);
namespace Core\Mod\Agentic\Services;
use Core\Mod\Agentic\Models\AgentProfile;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
class HermesClient
{
/**
* Create a Hermes response run for a Mantis ticket prompt.
*
* @param array<string, mixed> $metadata
* @return array<string, mixed>
*/
public function createResponse(AgentProfile $profile, string $input, array $metadata = []): array
{
$response = $this->request($profile)->post($this->url($profile), [
'model' => 'default',
'input' => $input,
'metadata' => $metadata,
]);
$response->throw();
$payload = $response->json();
return is_array($payload) ? $payload : [];
}
private function request(AgentProfile $profile): PendingRequest
{
return Http::withToken((string) $profile->api_key_cipher)
->acceptJson()
->asJson()
->timeout(60);
}
private function url(AgentProfile $profile): string
{
return rtrim($profile->gateway_url, '/').'/v1/responses';
}
}