cli/cmd/core-app/laravel/routes/api.php
Athena 794cff9409 feat(agentic): add agent allowance system for model quotas and budgets
Implements quota enforcement for agents including daily token limits,
daily job limits, concurrent job caps, model allowlists, and global
per-model budgets. Quota recovery returns 50% for failed jobs and
100% for cancelled jobs.

Go: AllowanceService with MemoryStore, AllowanceStore interface, and
25 tests covering all enforcement paths.

Laravel: migration for 5 tables (agent_allowances, quota_usage,
model_quotas, usage_reports, repo_limits), Eloquent models,
AllowanceService, QuotaMiddleware, and REST API routes.

Closes #99

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:53:52 +00:00

146 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AgentAllowance;
use App\Models\ModelQuota;
use App\Models\RepoLimit;
use App\Services\AllowanceService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Allowance API Routes
|--------------------------------------------------------------------------
|
| Endpoints for managing agent quotas, checking allowances, and recording
| usage. Protected endpoints use QuotaMiddleware for enforcement.
|
*/
// Health check for quota service
Route::get('/allowances/health', fn () => response()->json(['status' => 'ok']));
// Agent allowance CRUD
Route::prefix('allowances/agents')->group(function () {
Route::get('/', function () {
return AgentAllowance::all();
});
Route::get('/{agentId}', function (string $agentId) {
$allowance = AgentAllowance::where('agent_id', $agentId)->first();
if (! $allowance) {
return response()->json(['error' => 'not found'], 404);
}
return $allowance;
});
Route::post('/', function (Request $request) {
$validated = $request->validate([
'agent_id' => 'required|string|unique:agent_allowances,agent_id',
'daily_token_limit' => 'integer|min:0',
'daily_job_limit' => 'integer|min:0',
'concurrent_jobs' => 'integer|min:0',
'max_job_duration_minutes' => 'integer|min:0',
'model_allowlist' => 'array',
'model_allowlist.*' => 'string',
]);
return AgentAllowance::create($validated);
});
Route::put('/{agentId}', function (Request $request, string $agentId) {
$allowance = AgentAllowance::where('agent_id', $agentId)->first();
if (! $allowance) {
return response()->json(['error' => 'not found'], 404);
}
$validated = $request->validate([
'daily_token_limit' => 'integer|min:0',
'daily_job_limit' => 'integer|min:0',
'concurrent_jobs' => 'integer|min:0',
'max_job_duration_minutes' => 'integer|min:0',
'model_allowlist' => 'array',
'model_allowlist.*' => 'string',
]);
$allowance->update($validated);
return $allowance;
});
Route::delete('/{agentId}', function (string $agentId) {
AgentAllowance::where('agent_id', $agentId)->delete();
return response()->json(['status' => 'deleted']);
});
});
// Quota check endpoint
Route::get('/allowances/check/{agentId}', function (Request $request, string $agentId, AllowanceService $svc) {
$model = $request->query('model', '');
return response()->json($svc->check($agentId, $model));
});
// Usage reporting endpoint
Route::post('/allowances/usage', function (Request $request, AllowanceService $svc) {
$validated = $request->validate([
'agent_id' => 'required|string',
'job_id' => 'required|string',
'model' => 'nullable|string',
'tokens_in' => 'integer|min:0',
'tokens_out' => 'integer|min:0',
'event' => 'required|in:job_started,job_completed,job_failed,job_cancelled',
'timestamp' => 'nullable|date',
]);
$svc->recordUsage($validated);
return response()->json(['status' => 'recorded']);
});
// Daily reset endpoint
Route::post('/allowances/reset/{agentId}', function (string $agentId, AllowanceService $svc) {
$svc->resetAgent($agentId);
return response()->json(['status' => 'reset']);
});
// Model quota management
Route::prefix('allowances/models')->group(function () {
Route::get('/', fn () => ModelQuota::all());
Route::post('/', function (Request $request) {
$validated = $request->validate([
'model' => 'required|string|unique:model_quotas,model',
'daily_token_budget' => 'integer|min:0',
'hourly_rate_limit' => 'integer|min:0',
'cost_ceiling' => 'integer|min:0',
]);
return ModelQuota::create($validated);
});
Route::put('/{model}', function (Request $request, string $model) {
$quota = ModelQuota::where('model', $model)->first();
if (! $quota) {
return response()->json(['error' => 'not found'], 404);
}
$validated = $request->validate([
'daily_token_budget' => 'integer|min:0',
'hourly_rate_limit' => 'integer|min:0',
'cost_ceiling' => 'integer|min:0',
]);
$quota->update($validated);
return $quota;
});
});