Skip to content

feat: Configure AI usage counter - #11

Open
kofimokome wants to merge 5 commits into
mainfrom
feat/ai-allowance
Open

feat: Configure AI usage counter#11
kofimokome wants to merge 5 commits into
mainfrom
feat/ai-allowance

Conversation

@kofimokome

@kofimokome kofimokome commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7

Depends on #10

@kofimokome
kofimokome marked this pull request as draft June 28, 2026 16:52
@sourceant

sourceant Bot commented Jun 28, 2026

Copy link
Copy Markdown

Code Review Summary

The PR introduces AI usage tracking and plan entitlements. However, the implementation has critical logic errors in how features are authorized and identified, rendering the entitlement system largely ineffective in its current state.

🚀 Key Improvements

  • Configurable AI token allowances across all plan types using environment variables.
  • Integration with a central Entitlements contract via a new Service Provider singleton.

💡 Minor Suggestions

  • Use Laravel string helpers for more robust parsing.
  • Refine the usage reset logic to align with actual billing cycles.

🚨 Critical Issues

  • The allows method is a no-op that always permits access, bypassing plan limits.
  • The check method incorrectly compares programmatic feature keys against human-readable display strings.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread config/cloudplans.php Outdated
Comment thread src/Support/CloudEntitlements.php Outdated
@kofimokome
kofimokome marked this pull request as ready for review July 6, 2026 20:12

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/Support/CloudEntitlements.php Outdated
Comment thread database/migrations/2019_05_03_000002_create_subscriptions_table.php Outdated
@kofimokome
kofimokome force-pushed the feat/ai-allowance branch from 35627ef to ea97334 Compare July 6, 2026 20:18

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/Models/AiUsageCounter.php Outdated
@kofimokome
kofimokome force-pushed the feat/ai-allowance branch from ea97334 to d97f1f4 Compare July 6, 2026 20:21

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/Http/Controllers/CloudController.php Outdated

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good but since we have multiple projects that the question of tokens will come up. I built and publish a package that will simplify things there is already a task to implement it in the core and it may remove the need for one table you have here.

See: https://github.com/whilesmartphp/eloquent-agent-metrics

@kofimokome

Copy link
Copy Markdown
Collaborator Author

@nfebe what's the issue number on trakli core? I can't find it

@nfebe

nfebe commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

I already implemented it so its closed

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

Comment thread src/Support/CloudEntitlements.php Outdated
@kofimokome
kofimokome requested a review from nfebe July 15, 2026 21:49

@nfebe nfebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a way to measure the usage in core

$table->unsignedBigInteger('user_id');
$table->morphs('owner');
$table->timestamp('period_start');
$table->integer('tokens_used');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add this to the webservice and see how we can eliminate this table?

https://github.com/whilesmartphp/eloquent-agent-metrics

Was this solved?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still working on this and the other open pr.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I already integrated the metrics tracking in the core : https://github.com/trakli/webservice/blob/dev/composer.json#L26

So you just need to used it and remove the custom tracking tables

@nfebe

nfebe commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This doesn't load against current dev:

Error: Interface "App\Contracts\Entitlements" not found

Dropping the counter table is addressed, and tokensUsed($periodStart) is the right call on the trait. The rest went stale while the branch was open:

Issue Where
App\Contracts\Entitlements no longer exists; core gates through Whilesmart\Entitlements\Contracts\Entitlements removed in trakli/webservice#302
getPlanCode() queries BillingCustomer, which is gone deleted when #10 merged
Nothing binds CloudEntitlements, so it never runs binding was in #12
remaining() returns INF on the free plan, so free users get unlimited AI despite CLOUD_PLAN_FREE_TOKEN_ALLOWANCE defaulting to 0 CloudEntitlements::remaining()

Cache and DB are unused imports, and allows() / limit() return allow-all, which would switch the plan gating back off if this bound.

This should be a UsageMeter, not an Entitlements. The allowance belongs to the plan and the meter only counts against it, so the plan lookup, the Cashier query and the freemode branch all move out:

class TokenMeterUsage implements UsageMeter
{
    public function remaining(?Model $owner, string $meter, int|float $allowance): int|float
    {
        if ($meter !== 'ai_tokens' || $owner === null || is_infinite($allowance)) {
            return INF;
        }

        return max(0, $allowance - $owner->tokensUsed(now()->startOfMonth()));
    }

    public function consume(?Model $owner, string $meter, int $amount): void
    {
        // core already records through TokenMeter
    }
}

That needs plans carrying meters.ai_tokens, which is in feat/entitlement-rewired. Rebase onto that once it merges and this is the class above plus a binding.

@kofimokome

Copy link
Copy Markdown
Collaborator Author

@nfebe are we taking away the cloudplans.php config?

I see that most of the information in the config is in the Eloquent Entitlements plans table

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review complete. See the overview comment for a summary.

/**
* Determine if the owner is allowed to use a given feature.
*/
public function allows(?Model $owner, string $feature): bool

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The allows method is currently hardcoded to return true, which bypasses all plan-based feature restrictions. This should delegate to the check method to ensure that entitlements are properly enforced across the application.

Suggested change
public function allows(?Model $owner, string $feature): bool
public function allows(?Model $owner, string $feature): bool
{
return $this->check($owner, $feature)->allowed();
}

$planCode = $this->getPlanCode($owner);
$plan = config("cloudplans.plans.{$planCode}");

// Check if the feature is listed in the plan's features or permissions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The features array in config/cloudplans.php contains human-readable strings for the UI (e.g., 'Up to 3 wallets'). Checking for a programmatic feature key (like 'ai_chat') against this array will always fail. You should introduce a machine-readable permissions array in the config or map feature keys to these strings.

Suggested change
// Check if the feature is listed in the plan's features or permissions
// Suggested: Use a dedicated permissions array for programmatic keys
$permissions = $plan['permissions'] ?? [];
if ($plan && in_array($feature, $permissions, true)) {
return AccessResult::allow($feature);
}

{
$subscription = $this->activeSubscriptionFor($owner);
if ($subscription && $subscription->plan) {
return explode('-', $subscription->plan->key)[0]; // gets monthly from monthly-eu

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Determining the plan code by splitting the key string is brittle. If a plan key doesn't follow the exact prefix-suffix format (e.g., a three-part key), this logic might return an incorrect value. Consider using a more robust mapping or Laravel's Str::before helper.

Suggested change
return explode('-', $subscription->plan->key)[0]; // gets monthly from monthly-eu
return (string) \Illuminate\Support\Str::before($subscription->plan->key, '-');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cloud): Add Stripe subscriptions via Cashier mirror

2 participants