File: /home/xedaptot/ai.naniguide.com/app/Http/Controllers/Api/AIHubController.php
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use Acelle\Ai\AIHandler\Data\AIContext;
use Acelle\Ai\AIHandler\Exception\AIEngineException;
use Acelle\Ai\AIHandler\Exception\AIEngineTimeoutException;
use Acelle\Ai\Plugins\ClaudeCLI\Engines\ClaudeCLIEngine;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Hub AI passthrough — pure "string → engine.ask → string" proxy.
*
* Receives a fully-composed prompt string from a Bearer-token-
* authenticated downstream caller and runs it through the locally-
* installed Claude Code CLI binary. The Hub knows nothing about
* system vs user vs multi-turn structure — that's the consumer's
* concern, baked into the `context` string upstream via
* `AIContext::toString()`'s canonical flattening convention.
*
* Thin proxy — does NOT go through the plugin's task pipeline:
* - Engine = hardcoded ClaudeCLI; config (model/timeout) from ENV.
* - No TaskRegistry, no observability writes.
* - Plain JSON response (no AcelleWireProtocol envelope).
*
* Wire shape (post-3.2 — see plugin CHANGELOG for v3.1 → v3.2 migration):
* - Success (HTTP 200): `{content, model, finish_reason, usage}`. NO
* `ok` field — HTTP 200 alone signals success.
* - Error (HTTP 502 / 504): `{error, code}` — `code` is `timeout` or
* `engine_error`. NO `ok: false` — HTTP status IS the failure signal.
* - Validation 422 (Laravel default): `{message, errors: {context: [...]}}`.
*/
final class AIHubController extends Controller
{
public function handle(Request $request): JsonResponse
{
$data = $request->validate([
'context' => 'required|string',
]);
$engine = new ClaudeCLIEngine(
binaryPath: (string) env('HUB_AI_CLAUDE_CLI_PATH', 'claude'),
model: (string) env('HUB_AI_CLAUDE_CLI_MODEL', 'sonnet'),
timeout: (int) env('HUB_AI_CLAUDE_CLI_TIMEOUT', 60),
);
try {
$res = $engine->ask(AIContext::parse($data['context']));
} catch (AIEngineTimeoutException $e) {
return response()->json(['error' => $e->getMessage(), 'code' => 'timeout'], 504);
} catch (AIEngineException $e) {
return response()->json(['error' => $e->getMessage(), 'code' => 'engine_error'], 502);
}
return response()->json([
'content' => $res->content,
'model' => $res->model,
'finish_reason' => $res->finishReason,
'usage' => $res->usage?->toArray(),
], 200);
}
}