diff --git a/pyproject.toml b/pyproject.toml index d69a8f4..a24c5e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ name = "weco" authors = [{ name = "Weco AI Team", email = "contact@weco.ai" }] description = "Documentation for `weco`, a CLI for using Weco AI's code optimizer." readme = "README.md" -version = "0.3.15" +version = "0.3.16" license = { file = "LICENSE" } requires-python = ">=3.8" dependencies = [ diff --git a/weco/cli.py b/weco/cli.py index 85332d0..17074f3 100644 --- a/weco/cli.py +++ b/weco/cli.py @@ -294,6 +294,11 @@ def execute_run_command(args: argparse.Namespace) -> None: if api_keys: console.print(f"[bold yellow]Custom API keys provided. Using default model: {model} for the run.[/]") + # Check for promotional credits and prompt user if applicable + from .credits import check_promotional_credits + + model = check_promotional_credits(model, api_keys, console) + # Send run attempt event before starting (helps measure dropoff before server) send_event( RunStartAttemptedEvent( diff --git a/weco/credits.py b/weco/credits.py index a93b677..4ea576d 100644 --- a/weco/credits.py +++ b/weco/credits.py @@ -3,6 +3,7 @@ import webbrowser import requests from rich.console import Console +from rich.prompt import Confirm from rich.table import Table from . import __base_url__ from .api import handle_api_error @@ -10,6 +11,74 @@ from .config import load_weco_api_key +def check_promotional_credits(model: str, api_keys: dict[str, str] | None, console: Console) -> str: + """Check for promotional credits and prompt user to use them if applicable. + + Returns the (possibly modified) model string with provider prefix. + """ + # Skip if user is using their own API keys (BYOK — not billed) + if api_keys: + return model + + # If model already has provider prefix, inform user about matching credits + if "/" in model: + provider = model.split("/", 1)[0] + try: + weco_api_key = load_weco_api_key() + if not weco_api_key: + return model + resp = requests.get( + f"{__base_url__}/billing/balance", headers={"Authorization": f"Bearer {weco_api_key}"}, timeout=5 + ) + if resp.ok: + promo_credits = resp.json().get("promotional_credits", []) + matching = [g for g in promo_credits if g.get("provider") == provider] + if matching: + total = sum(g.get("remaining_credits", 0) for g in matching) + expires = matching[0].get("expires_at", "")[:10] + console.print( + f"[green]✅ Using {provider.capitalize()} promotional credits " + f"(${total:.2f} remaining, expires {expires})[/]" + ) + except Exception: + pass # Non-critical; don't block the run + return model + + # Bare model name — check for promotional credits + try: + weco_api_key = load_weco_api_key() + if not weco_api_key: + return model + resp = requests.get(f"{__base_url__}/billing/balance", headers={"Authorization": f"Bearer {weco_api_key}"}, timeout=5) + if not resp.ok: + return model + + promo_credits = resp.json().get("promotional_credits", []) + if not promo_credits: + return model + + # Check if any grant provider could serve this model + for grant in promo_credits: + grant_provider = grant.get("provider", "") + remaining = grant.get("remaining_credits", 0) + expires = grant.get("expires_at", "")[:10] + + # Suggest using the grant's provider + prefixed_model = f"{grant_provider}/{model}" + console.print( + f"\n[yellow]💡 You have ${remaining:.2f} in {grant_provider.capitalize()} credits (expires {expires}).[/]" + ) + use_credits = Confirm.ask(f" Use [bold]{prefixed_model}[/] to apply them?", default=True) + if use_credits: + console.print(f"[green]✅ Using {grant_provider.capitalize()} promotional credits[/]\n") + return prefixed_model + + except Exception: + pass # Non-critical; don't block the run + + return model + + def handle_credits_command(args, console: Console) -> None: """Handle the credits command and its subcommands.""" # Ensure user is authenticated @@ -55,8 +124,15 @@ def check_balance(console: Console, auth_headers: dict) -> None: console.print(table) - if balance < 10: - console.print("\n[yellow]💡 Tip: You're running low on credits. Run 'weco credits topup' to add more.[/]") + # Show promotional credits if any + promo_credits = data.get("promotional_credits", []) + if promo_credits: + console.print("\n[bold cyan]Promotional Credits:[/]") + for grant in promo_credits: + provider = grant.get("provider", "Unknown") + remaining = grant.get("remaining_credits", 0) + expires_at = grant.get("expires_at", "")[:10] # Date portion only + console.print(f" {provider.capitalize()} models: [green]${remaining:.2f}[/] (expires {expires_at})") except requests.exceptions.HTTPError as e: if e.response.status_code == 401: