Spaces:
Sleeping
Sleeping
| import os, json, time, hmac, hashlib, sqlite3, base64, secrets | |
| from typing import Any, Dict, List, Optional, Tuple | |
| import gradio as gr | |
| from jsonschema import validate, ValidationError | |
| import stripe | |
| from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey | |
| from cryptography.hazmat.primitives import serialization | |
| import ast, operator as op, datetime as dt | |
| # ---------------- Config ---------------- | |
| APP_TITLE = "Testing! Inner I Studio — Secure Agent Hub" | |
| DB_PATH = os.getenv("INNERI_DB_PATH", "inneri_studio.db") | |
| RECEIPT_SIGNING_KEY = os.getenv("INNERI_RECEIPT_SIGNING_KEY", "dev_only_change_me") | |
| JWT_SIGNING_KEY = os.getenv("INNERI_JWT_SIGNING_KEY", "dev_jwt_change_me") # reserved for future API auth | |
| ADMIN_PASSWORD = os.getenv("INNERI_ADMIN_PASSWORD", "admin") | |
| # Stripe (optional) | |
| STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY", "") | |
| STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET", "") | |
| # Subscription prices | |
| STRIPE_PRICE_PRO = os.getenv("STRIPE_PRICE_PRO", "") # Pro / Builder | |
| STRIPE_PRICE_ENTERPRISE = os.getenv("STRIPE_PRICE_ENTERPRISE", "") # Enterprise / Sovereign | |
| # Credit pack prices (one-time) | |
| STRIPE_PRICE_CREDITS_STARTER = os.getenv("STRIPE_PRICE_CREDITS_STARTER", "") # 250 | |
| STRIPE_PRICE_CREDITS_CREATOR = os.getenv("STRIPE_PRICE_CREDITS_CREATOR", "") # 750 | |
| STRIPE_PRICE_CREDITS_POWER = os.getenv("STRIPE_PRICE_CREDITS_POWER", "") # 2500 | |
| PUBLIC_BASE_URL = os.getenv("PUBLIC_BASE_URL", "") # e.g. https://<user>-<space>.hf.space | |
| STRIPE_ENABLED = bool(STRIPE_SECRET_KEY and PUBLIC_BASE_URL) | |
| if STRIPE_SECRET_KEY: | |
| stripe.api_key = STRIPE_SECRET_KEY | |
| # ---- Plans baked in ---- | |
| PLANS = { | |
| "free": {"name": "Observer", "runs_per_day": 25, "max_tool_risk": "low"}, | |
| "pro": {"name": "Builder", "runs_per_day": 500, "max_tool_risk": "med"}, | |
| "enterprise": {"name": "Sovereign", "runs_per_day": 5000, "max_tool_risk": "high"}, | |
| } | |
| # Credit packs (runs) | |
| CREDIT_PACKS = { | |
| "credits_starter": {"name": "Starter Pack", "credits": 250, "price_env": "STRIPE_PRICE_CREDITS_STARTER"}, | |
| "credits_creator": {"name": "Creator Pack", "credits": 750, "price_env": "STRIPE_PRICE_CREDITS_CREATOR"}, | |
| "credits_power": {"name": "Power Pack", "credits": 2500, "price_env": "STRIPE_PRICE_CREDITS_POWER"}, | |
| } | |
| # Optional: if user exceeds daily runs, allow using credits to continue (only for free tier) | |
| RUN_CREDIT_COST = 1 | |
| # ---------------- Helpers ---------------- | |
| def b64url(data: bytes) -> str: | |
| return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=") | |
| def canonical_json(obj: Any) -> str: | |
| return json.dumps(obj, separators=(",", ":"), sort_keys=True, ensure_ascii=False) | |
| def sha256_hex(s: str) -> str: | |
| return hashlib.sha256(s.encode("utf-8")).hexdigest() | |
| def sign_hmac(payload: Dict[str, Any], key: str) -> str: | |
| mac = hmac.new(key.encode("utf-8"), canonical_json(payload).encode("utf-8"), hashlib.sha256).digest() | |
| return b64url(mac) | |
| def now_unix() -> int: | |
| return int(time.time()) | |
| def today_yyyymmdd() -> str: | |
| return time.strftime("%Y%m%d", time.gmtime()) | |
| # ---------------- DB ---------------- | |
| def db() -> sqlite3.Connection: | |
| conn = sqlite3.connect(DB_PATH, check_same_thread=False) | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def gen_ed25519_keypair() -> Tuple[str, str]: | |
| priv = Ed25519PrivateKey.generate() | |
| priv_pem = priv.private_bytes( | |
| encoding=serialization.Encoding.PEM, | |
| format=serialization.PrivateFormat.PKCS8, | |
| encryption_algorithm=serialization.NoEncryption(), | |
| ).decode("utf-8") | |
| pub_pem = priv.public_key().public_bytes( | |
| encoding=serialization.Encoding.PEM, | |
| format=serialization.PublicFormat.SubjectPublicKeyInfo, | |
| ).decode("utf-8") | |
| return priv_pem, pub_pem | |
| def seed_tools(conn: sqlite3.Connection) -> None: | |
| tools = [ | |
| ("echo", "Echo", "Returns the provided text.", "low", | |
| {"type": "object", "properties": {"text": {"type": "string", "maxLength": 2000}}, "required": ["text"], "additionalProperties": False}), | |
| ("time_now", "Time Now", "Returns server time (UTC).", "low", | |
| {"type": "object", "properties": {}, "additionalProperties": False}), | |
| ("math_eval", "Math Eval", "Evaluates a simple arithmetic expression.", "med", | |
| {"type": "object", "properties": {"expression": {"type": "string", "maxLength": 200}}, "required": ["expression"], "additionalProperties": False}), | |
| ] | |
| cur = conn.cursor() | |
| for t in tools: | |
| cur.execute( | |
| "INSERT OR IGNORE INTO tools(tool_id,name,description,risk,json_schema,enabled,version) VALUES(?,?,?,?,?,?,?)", | |
| (t[0], t[1], t[2], t[3], json.dumps(t[4]), 1, 1), | |
| ) | |
| conn.commit() | |
| def init_db() -> None: | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.executescript(""" | |
| CREATE TABLE IF NOT EXISTS users ( | |
| user_id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| email TEXT UNIQUE NOT NULL, | |
| password_hash TEXT NOT NULL, | |
| role TEXT NOT NULL DEFAULT 'user', | |
| tier TEXT NOT NULL DEFAULT 'free', | |
| credits INTEGER NOT NULL DEFAULT 0, | |
| stripe_customer_id TEXT, | |
| subscription_status TEXT DEFAULT 'none', | |
| created_at INTEGER NOT NULL | |
| ); | |
| CREATE TABLE IF NOT EXISTS agents ( | |
| agent_id TEXT PRIMARY KEY, | |
| display_name TEXT NOT NULL, | |
| verification_level TEXT NOT NULL DEFAULT 'none', | |
| risk_tier TEXT NOT NULL DEFAULT 'low', | |
| public_key_pem TEXT NOT NULL, | |
| created_at INTEGER NOT NULL | |
| ); | |
| CREATE TABLE IF NOT EXISTS tools ( | |
| tool_id TEXT PRIMARY KEY, | |
| name TEXT NOT NULL, | |
| description TEXT NOT NULL, | |
| risk TEXT NOT NULL DEFAULT 'low', | |
| json_schema TEXT NOT NULL, | |
| enabled INTEGER NOT NULL DEFAULT 1, | |
| version INTEGER NOT NULL DEFAULT 1 | |
| ); | |
| CREATE TABLE IF NOT EXISTS reputations ( | |
| agent_id TEXT PRIMARY KEY, | |
| score INTEGER NOT NULL DEFAULT 50, | |
| updated_at INTEGER NOT NULL | |
| ); | |
| CREATE TABLE IF NOT EXISTS usage_daily ( | |
| user_id INTEGER NOT NULL, | |
| day TEXT NOT NULL, | |
| runs INTEGER NOT NULL DEFAULT 0, | |
| PRIMARY KEY (user_id, day) | |
| ); | |
| CREATE TABLE IF NOT EXISTS audit_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| ts INTEGER NOT NULL, | |
| actor_agent_id TEXT, | |
| actor_user_id INTEGER, | |
| action TEXT NOT NULL, | |
| request_json TEXT NOT NULL, | |
| result_json TEXT NOT NULL, | |
| prev_hash TEXT, | |
| row_hash TEXT NOT NULL | |
| ); | |
| CREATE TABLE IF NOT EXISTS sessions ( | |
| token TEXT PRIMARY KEY, | |
| user_id INTEGER NOT NULL, | |
| created_at INTEGER NOT NULL, | |
| expires_at INTEGER NOT NULL | |
| ); | |
| """) | |
| conn.commit() | |
| # admin user (email=admin@local) | |
| cur.execute("SELECT user_id FROM users WHERE email=?", ("admin@local",)) | |
| if not cur.fetchone(): | |
| ph = sha256_hex("admin@local:" + ADMIN_PASSWORD) | |
| cur.execute( | |
| "INSERT INTO users(email,password_hash,role,tier,credits,created_at) VALUES(?,?,?,?,?,?)", | |
| ("admin@local", ph, "admin", "pro", 0, now_unix()), | |
| ) | |
| conn.commit() | |
| # seed tools | |
| cur.execute("SELECT tool_id FROM tools WHERE tool_id='echo'") | |
| if not cur.fetchone(): | |
| seed_tools(conn) | |
| # seed a safe public agent | |
| cur.execute("SELECT agent_id FROM agents WHERE agent_id='agent_public_01'") | |
| if not cur.fetchone(): | |
| _, pub = gen_ed25519_keypair() | |
| cur.execute( | |
| "INSERT INTO agents(agent_id,display_name,verification_level,risk_tier,public_key_pem,created_at) VALUES(?,?,?,?,?,?)", | |
| ("agent_public_01", "Inner I Public Agent", "none", "low", pub, now_unix()), | |
| ) | |
| cur.execute( | |
| "INSERT OR IGNORE INTO reputations(agent_id,score,updated_at) VALUES(?,?,?)", | |
| ("agent_public_01", 50, now_unix()), | |
| ) | |
| conn.commit() | |
| conn.close() | |
| def audit_append( | |
| actor_agent_id: Optional[str], | |
| actor_user_id: Optional[int], | |
| action: str, | |
| request_obj: Dict[str, Any], | |
| result_obj: Dict[str, Any], | |
| ) -> Dict[str, Any]: | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT row_hash FROM audit_log ORDER BY id DESC LIMIT 1") | |
| row = cur.fetchone() | |
| prev_hash = row["row_hash"] if row else None | |
| row_obj = { | |
| "actor_agent_id": actor_agent_id, | |
| "actor_user_id": actor_user_id, | |
| "action": action, | |
| "request": request_obj, | |
| "result": result_obj, | |
| "prev_hash": prev_hash, | |
| } | |
| row_hash = sha256_hex(canonical_json(row_obj)) | |
| cur.execute( | |
| "INSERT INTO audit_log(ts,actor_agent_id,actor_user_id,action,request_json,result_json,prev_hash,row_hash) VALUES(?,?,?,?,?,?,?,?)", | |
| (now_unix(), actor_agent_id, actor_user_id, action, json.dumps(request_obj), json.dumps(result_obj), prev_hash, row_hash), | |
| ) | |
| conn.commit() | |
| audit_id = cur.lastrowid | |
| conn.close() | |
| return {"audit_id": audit_id, "row_hash": row_hash, "prev_hash": prev_hash} | |
| # ---------------- Auth ---------------- | |
| def hash_pw(email: str, password: str) -> str: | |
| return sha256_hex(f"{email}:{password}") | |
| def signup(email: str, password: str) -> str: | |
| email = email.strip().lower() | |
| if not email or "@" not in email: | |
| raise gr.Error("Enter a valid email") | |
| if len(password) < 8: | |
| raise gr.Error("Password must be 8+ chars") | |
| conn = db() | |
| cur = conn.cursor() | |
| try: | |
| cur.execute( | |
| "INSERT INTO users(email,password_hash,role,tier,credits,created_at) VALUES(?,?,?,?,?,?)", | |
| (email, hash_pw(email, password), "user", "free", 0, now_unix()), | |
| ) | |
| conn.commit() | |
| except sqlite3.IntegrityError: | |
| conn.close() | |
| raise gr.Error("Email already registered") | |
| conn.close() | |
| return "✅ account_created" | |
| def login(email: str, password: str) -> Tuple[str, str]: | |
| email = email.strip().lower() | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT user_id,password_hash FROM users WHERE email=?", (email,)) | |
| row = cur.fetchone() | |
| conn.close() | |
| if not row: | |
| return "", "❌ user_not_found" | |
| if hash_pw(email, password) != row["password_hash"]: | |
| return "", "❌ bad_password" | |
| token = b64url(secrets.token_bytes(24)) | |
| now = now_unix() | |
| exp = now + 3600 * 8 | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute( | |
| "INSERT INTO sessions(token,user_id,created_at,expires_at) VALUES(?,?,?,?)", | |
| (token, row["user_id"], now, exp), | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return token, "✅ logged_in" | |
| def get_session_user(session_token: str) -> Dict[str, Any]: | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute( | |
| """ | |
| SELECT u.user_id,u.email,u.role,u.tier,u.credits,u.subscription_status,s.expires_at | |
| FROM sessions s JOIN users u ON u.user_id=s.user_id | |
| WHERE s.token=? | |
| """, | |
| (session_token,), | |
| ) | |
| row = cur.fetchone() | |
| conn.close() | |
| if not row: | |
| raise gr.Error("Not logged in") | |
| if int(row["expires_at"]) < now_unix(): | |
| raise gr.Error("Session expired") | |
| return dict(row) | |
| def require_admin(session_token: str) -> Dict[str, Any]: | |
| u = get_session_user(session_token) | |
| if u.get("role") != "admin": | |
| raise gr.Error("Admin only") | |
| return u | |
| # ---------------- Limits ---------------- | |
| def check_and_increment_run(user: Dict[str, Any]) -> None: | |
| day = today_yyyymmdd() | |
| tier = user["tier"] if user["role"] != "admin" else "enterprise" | |
| if tier not in PLANS: | |
| tier = "free" | |
| limit = PLANS[tier]["runs_per_day"] | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("INSERT OR IGNORE INTO usage_daily(user_id,day,runs) VALUES(?,?,0)", (user["user_id"], day)) | |
| cur.execute("SELECT runs FROM usage_daily WHERE user_id=? AND day=?", (user["user_id"], day)) | |
| runs = int(cur.fetchone()["runs"]) | |
| if runs >= limit and user["role"] != "admin": | |
| # Free tier may continue using credits (burst mode) | |
| if user["tier"] == "free" and user["credits"] >= RUN_CREDIT_COST: | |
| cur.execute( | |
| "UPDATE users SET credits=credits-? WHERE user_id=? AND credits>=?", | |
| (RUN_CREDIT_COST, user["user_id"], RUN_CREDIT_COST), | |
| ) | |
| if cur.rowcount != 1: | |
| conn.close() | |
| raise gr.Error(f"Daily limit reached ({limit}). Upgrade to {PLANS['pro']['name']} or buy credits.") | |
| else: | |
| conn.close() | |
| raise gr.Error(f"Daily limit reached ({limit}). Upgrade to {PLANS['pro']['name']} or buy credits.") | |
| cur.execute("UPDATE usage_daily SET runs=runs+1 WHERE user_id=? AND day=?", (user["user_id"], day)) | |
| conn.commit() | |
| conn.close() | |
| # ---------------- Policy + tools ---------------- | |
| def policy_decide(agent: Dict[str, Any], request: Dict[str, Any]) -> Dict[str, Any]: | |
| if "secret" in (request.get("data_scopes") or []): | |
| return {"allow": False, "mode": "deny", "reasons": ["secret_scope_blocked"]} | |
| tools = request.get("tools") or [] | |
| if agent.get("risk_tier") == "high" or any(t.get("risk") == "high" for t in tools): | |
| return {"allow": False, "mode": "deny", "reasons": ["high_risk_blocked"]} | |
| if agent.get("verification_level") == "none" or any(t.get("risk") == "med" for t in tools): | |
| return {"allow": True, "mode": "sandbox", "reasons": ["sandbox_unverified_or_med"]} | |
| return {"allow": True, "mode": "normal", "reasons": []} | |
| _OPS = { | |
| ast.Add: op.add, | |
| ast.Sub: op.sub, | |
| ast.Mult: op.mul, | |
| ast.Div: op.truediv, | |
| ast.Pow: op.pow, | |
| ast.USub: op.neg, | |
| ast.Mod: op.mod, | |
| ast.FloorDiv: op.floordiv, | |
| } | |
| def _eval(node): | |
| if isinstance(node, ast.Num): | |
| return node.n | |
| if isinstance(node, ast.BinOp) and type(node.op) in _OPS: | |
| return _OPS[type(node.op)](_eval(node.left), _eval(node.right)) | |
| if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS: | |
| return _OPS[type(node.op)](_eval(node.operand)) | |
| raise ValueError("Unsupported expression") | |
| def run_tool(tool_id: str, args: Dict[str, Any]) -> Dict[str, Any]: | |
| if tool_id == "echo": | |
| return {"text": args["text"]} | |
| if tool_id == "time_now": | |
| return {"utc": dt.datetime.utcnow().isoformat() + "Z"} | |
| if tool_id == "math_eval": | |
| tree = ast.parse(args["expression"], mode="eval").body | |
| return {"value": _eval(tree)} | |
| raise ValueError(f"Unknown tool_id: {tool_id}") | |
| def secure_call(user: Dict[str, Any], agent_id: str, intent: str, tools: List[Dict[str, Any]]) -> Dict[str, Any]: | |
| check_and_increment_run(user) | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT * FROM agents WHERE agent_id=?", (agent_id,)) | |
| a = cur.fetchone() | |
| if not a: | |
| conn.close() | |
| raise gr.Error("agent_not_found") | |
| agent = dict(a) | |
| tools_meta: List[Dict[str, Any]] = [] | |
| for tc in tools: | |
| cur.execute("SELECT * FROM tools WHERE tool_id=?", (tc["tool_id"],)) | |
| t = cur.fetchone() | |
| if not t or int(t["enabled"]) != 1: | |
| conn.close() | |
| raise gr.Error(f"tool_not_found_or_disabled: {tc['tool_id']}") | |
| tools_meta.append({"tool_id": t["tool_id"], "risk": t["risk"], "schema": json.loads(t["json_schema"])}) | |
| decision = policy_decide( | |
| agent, | |
| {"intent": intent, "tools": [{"tool_id": tm["tool_id"], "risk": tm["risk"]} for tm in tools_meta], "data_scopes": ["public"]}, | |
| ) | |
| if not decision["allow"]: | |
| conn.close() | |
| audit_append(agent_id, user["user_id"], "secure_call.deny", {"intent": intent, "tools": tools}, {"decision": decision}) | |
| return {"denied": True, "decision": decision} | |
| mode = decision["mode"] | |
| outputs: List[Dict[str, Any]] = [] | |
| # Tier-based tool risk gating | |
| tier = user["tier"] if user["role"] != "admin" else "enterprise" | |
| if tier not in PLANS: | |
| tier = "free" | |
| tier_max = PLANS[tier]["max_tool_risk"] | |
| risk_rank = {"low": 1, "med": 2, "high": 3} | |
| tier_max_rank = risk_rank[tier_max] | |
| for tc, tm in zip(tools, tools_meta): | |
| try: | |
| validate(instance=tc.get("args", {}), schema=tm["schema"]) | |
| except ValidationError: | |
| conn.close() | |
| raise gr.Error(f"args_schema_invalid: {tm['tool_id']}") | |
| if risk_rank.get(tm["risk"], 3) > tier_max_rank: | |
| outputs.append({"tool_id": tm["tool_id"], "blocked": True, "reason": f"plan_blocks_{tm['risk']}_risk"}) | |
| continue | |
| if mode == "sandbox" and tm["risk"] != "low": | |
| outputs.append({"tool_id": tm["tool_id"], "blocked": True, "reason": "sandbox_mode"}) | |
| continue | |
| try: | |
| outputs.append({"tool_id": tm["tool_id"], "output": run_tool(tm["tool_id"], tc.get("args", {}))}) | |
| except Exception as e: | |
| outputs.append({"tool_id": tm["tool_id"], "error": str(e)}) | |
| receipt = { | |
| "ts_unix": now_unix(), | |
| "agent_id": agent_id, | |
| "user_id": user["user_id"], | |
| "intent": intent, | |
| "mode": mode, | |
| "decision": decision, | |
| "outputs_hash": sha256_hex(canonical_json(outputs)), | |
| } | |
| receipt["signature"] = sign_hmac(receipt, RECEIPT_SIGNING_KEY) | |
| audit = audit_append(agent_id, user["user_id"], "secure_call.run", {"intent": intent, "tools": tools}, {"outputs": outputs, "receipt": receipt}) | |
| conn.close() | |
| return {"outputs": outputs, "receipt": receipt, "audit": audit} | |
| # ---------------- Admin actions ---------------- | |
| def create_agent_admin(session_token: str, agent_id: str, display_name: str, risk_tier: str, verification_level: str): | |
| require_admin(session_token) | |
| priv_pem, pub_pem = gen_ed25519_keypair() | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute( | |
| "INSERT INTO agents(agent_id,display_name,verification_level,risk_tier,public_key_pem,created_at) VALUES(?,?,?,?,?,?)", | |
| (agent_id, display_name, verification_level, risk_tier, pub_pem, now_unix()), | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return "✅ agent_created (save private key)", priv_pem, pub_pem | |
| def list_agents_admin(session_token: str): | |
| require_admin(session_token) | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT agent_id,display_name,verification_level,risk_tier,created_at FROM agents ORDER BY created_at DESC") | |
| rows = cur.fetchall() | |
| conn.close() | |
| return [[r["agent_id"], r["display_name"], r["verification_level"], r["risk_tier"], str(r["created_at"])] for r in rows] | |
| def list_tools_admin(session_token: str): | |
| require_admin(session_token) | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT tool_id,name,risk,enabled,version,description FROM tools ORDER BY tool_id") | |
| rows = cur.fetchall() | |
| conn.close() | |
| return [[r["tool_id"], r["name"], r["risk"], "yes" if int(r["enabled"]) == 1 else "no", str(r["version"]), r["description"]] for r in rows] | |
| def upsert_tool_admin(session_token: str, tool_id: str, name: str, description: str, risk: str, enabled: bool, schema_json: str): | |
| require_admin(session_token) | |
| try: | |
| schema = json.loads(schema_json) | |
| if schema.get("type") != "object": | |
| raise ValueError("schema.type must be object") | |
| except Exception as e: | |
| raise gr.Error(f"Invalid schema JSON: {e}") | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT version FROM tools WHERE tool_id=?", (tool_id,)) | |
| row = cur.fetchone() | |
| version = (int(row["version"]) + 1) if row else 1 | |
| cur.execute( | |
| """ | |
| INSERT INTO tools(tool_id,name,description,risk,json_schema,enabled,version) | |
| VALUES(?,?,?,?,?,?,?) | |
| ON CONFLICT(tool_id) DO UPDATE SET | |
| name=excluded.name, | |
| description=excluded.description, | |
| risk=excluded.risk, | |
| json_schema=excluded.json_schema, | |
| enabled=excluded.enabled, | |
| version=? | |
| """, | |
| (tool_id, name, description, risk, json.dumps(schema), 1 if enabled else 0, version, version), | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return f"✅ saved_tool `{tool_id}` v{version}" | |
| def read_audit_admin(session_token: str, limit: int): | |
| require_admin(session_token) | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("SELECT id,ts,actor_agent_id,actor_user_id,action,prev_hash,row_hash FROM audit_log ORDER BY id DESC LIMIT ?", (limit,)) | |
| rows = cur.fetchall() | |
| conn.close() | |
| return [[str(r["id"]), str(r["ts"]), str(r["actor_agent_id"] or ""), str(r["actor_user_id"] or ""), r["action"], str(r["prev_hash"] or ""), r["row_hash"]] for r in rows] | |
| def admin_set_user_tier(session_token: str, email: str, tier: str) -> str: | |
| require_admin(session_token) | |
| email = email.strip().lower() | |
| if tier not in ("free", "pro", "enterprise"): | |
| raise gr.Error("tier must be free, pro, or enterprise") | |
| conn = db() | |
| cur = conn.cursor() | |
| cur.execute("UPDATE users SET tier=? WHERE email=?", (tier, email)) | |
| conn.commit() | |
| n = cur.rowcount | |
| conn.close() | |
| return f"✅ updated {n} user(s)" | |
| # ---------------- Billing ---------------- | |
| def user_me(session_token: str) -> str: | |
| u = get_session_user(session_token) | |
| safe = {k: u[k] for k in ["user_id", "email", "role", "tier", "credits", "subscription_status"]} | |
| safe["plan_name"] = PLANS.get(safe["tier"], PLANS["free"])["name"] if safe["role"] != "admin" else PLANS["enterprise"]["name"] | |
| safe["stripe_enabled"] = STRIPE_ENABLED | |
| return json.dumps(safe, indent=2) | |
| def create_checkout_session(session_token: str, kind: str) -> str: | |
| if not STRIPE_ENABLED: | |
| raise gr.Error("Stripe not configured (set STRIPE_SECRET_KEY + PUBLIC_BASE_URL).") | |
| u = get_session_user(session_token) | |
| if kind == "pro": | |
| if not STRIPE_PRICE_PRO: | |
| raise gr.Error("STRIPE_PRICE_PRO not set") | |
| line_items = [{"price": STRIPE_PRICE_PRO, "quantity": 1}] | |
| mode = "subscription" | |
| meta = {"user_id": str(u["user_id"]), "kind": "pro"} | |
| elif kind == "enterprise": | |
| if not STRIPE_PRICE_ENTERPRISE: | |
| raise gr.Error("STRIPE_PRICE_ENTERPRISE not set") | |
| line_items = [{"price": STRIPE_PRICE_ENTERPRISE, "quantity": 1}] | |
| mode = "subscription" | |
| meta = {"user_id": str(u["user_id"]), "kind": "enterprise"} | |
| elif kind in CREDIT_PACKS: | |
| pack = CREDIT_PACKS[kind] | |
| price_id = os.getenv(pack["price_env"], "") | |
| if not price_id: | |
| raise gr.Error(f"{pack['price_env']} not set") | |
| line_items = [{"price": price_id, "quantity": 1}] | |
| mode = "payment" | |
| meta = {"user_id": str(u["user_id"]), "kind": kind} | |
| else: | |
| raise gr.Error("Unknown checkout kind") | |
| success_url = f"{PUBLIC_BASE_URL}/?paid=1" | |
| cancel_url = f"{PUBLIC_BASE_URL}/?canceled=1" | |
| sess = stripe.checkout.Session.create( | |
| mode=mode, | |
| line_items=line_items, | |
| client_reference_id=str(u["user_id"]), | |
| metadata=meta, | |
| success_url=success_url, | |
| cancel_url=cancel_url, | |
| ) | |
| return sess.url | |
| # ---------------- UI ---------------- | |
| init_db() | |
| def chat_turn(session_token: str, agent_id: str, user_msg: str, tool_plan_json: str, history): | |
| u = get_session_user(session_token) | |
| tools: List[Dict[str, Any]] = [] | |
| if tool_plan_json.strip(): | |
| try: | |
| tool_plan = json.loads(tool_plan_json) | |
| if not isinstance(tool_plan, list): | |
| raise ValueError("tool_plan must be a JSON list") | |
| tools = [{"tool_id": t["tool_id"], "args": t.get("args", {})} for t in tool_plan] | |
| except Exception as e: | |
| raise gr.Error(f"Bad tool_plan JSON: {e}") | |
| if not tools: | |
| tools = [{"tool_id": "echo", "args": {"text": user_msg}}] | |
| res = secure_call(u, agent_id=agent_id, intent="studio_chat_turn", tools=tools) | |
| reply = {"tier": u["tier"], "outputs": res.get("outputs"), "receipt": res.get("receipt"), "audit": res.get("audit")} | |
| history = history + [(user_msg, json.dumps(reply, indent=2))] | |
| return history, "" | |
| with gr.Blocks(title=APP_TITLE) as demo: | |
| gr.Markdown( | |
| f"# {APP_TITLE}\n" | |
| "Public users: chat + workflows. Admin: control plane + audit." | |
| ) | |
| with gr.Tab("Account"): | |
| gr.Markdown("### Signup") | |
| su_email = gr.Textbox(label="Email") | |
| su_pass = gr.Textbox(label="Password (8+ chars)", type="password") | |
| su_btn = gr.Button("Create account") | |
| su_out = gr.Markdown() | |
| su_btn.click(signup, inputs=[su_email, su_pass], outputs=[su_out]) | |
| gr.Markdown("### Login") | |
| li_email = gr.Textbox(label="Email", value="admin@local") | |
| li_pass = gr.Textbox(label="Password", type="password", value="") | |
| li_btn = gr.Button("Login") | |
| session = gr.Textbox(label="Session Token (keep private)", interactive=False) | |
| li_out = gr.Markdown() | |
| li_btn.click(login, inputs=[li_email, li_pass], outputs=[session, li_out]) | |
| gr.Markdown("### Me") | |
| me_btn = gr.Button("Refresh") | |
| me_out = gr.Code(label="Current user") | |
| me_btn.click(user_me, inputs=[session], outputs=[me_out]) | |
| with gr.Tab("Studio Chat"): | |
| agent_id = gr.Textbox(label="Agent ID", value="agent_public_01") | |
| tool_plan = gr.Textbox( | |
| label="tool_plan (optional JSON list)", | |
| lines=6, | |
| value='[{"tool_id":"echo","args":{"text":"Hello from Inner I Studio"}}]', | |
| ) | |
| chat = gr.Chatbot(height=420) | |
| msg = gr.Textbox(label="Message") | |
| send = gr.Button("Send") | |
| clear = gr.Button("Clear") | |
| state = gr.State([]) | |
| send.click(chat_turn, inputs=[session, agent_id, msg, tool_plan, state], outputs=[chat, msg]) | |
| clear.click(lambda: ([], ""), outputs=[chat, msg]) | |
| with gr.Tab("Billing"): | |
| gr.Markdown( | |
| "### Plans" | |
| "- **Observer (Free)**: 25 runs/day, low-risk tools" | |
| "- **Builder (Pro)**: 500 runs/day, medium-risk tools" | |
| "- **Sovereign (Enterprise)**: 5000 runs/day, high-risk tools" | |
| "Stripe billing is built-in (optional). If Stripe isn't configured, " | |
| "you can set the Space to Private/Paid as a paywall." | |
| ) | |
| pro_btn = gr.Button("Subscribe — Builder") | |
| ent_btn = gr.Button("Subscribe — Sovereign") | |
| starter_btn = gr.Button("Buy Credits — Starter (250)") | |
| creator_btn = gr.Button("Buy Credits — Creator (750)") | |
| power_btn = gr.Button("Buy Credits — Power (2500)") | |
| pay_url = gr.Textbox(label="Checkout URL", interactive=False) | |
| pro_btn.click(lambda s: create_checkout_session(s, "pro"), inputs=[session], outputs=[pay_url]) | |
| ent_btn.click(lambda s: create_checkout_session(s, "enterprise"), inputs=[session], outputs=[pay_url]) | |
| starter_btn.click(lambda s: create_checkout_session(s, "credits_starter"), inputs=[session], outputs=[pay_url]) | |
| creator_btn.click(lambda s: create_checkout_session(s, "credits_creator"), inputs=[session], outputs=[pay_url]) | |
| power_btn.click(lambda s: create_checkout_session(s, "credits_power"), inputs=[session], outputs=[pay_url]) | |
| with gr.Tab("Admin — Agents"): | |
| gr.Markdown("Admin only.") | |
| a_id = gr.Textbox(label="agent_id", value="agent_builder_01") | |
| a_name = gr.Textbox(label="display_name", value="Inner I Builder Agent") | |
| a_risk = gr.Dropdown(["low", "med", "high"], value="low", label="risk_tier") | |
| a_ver = gr.Dropdown(["none", "basic", "full", "continuous"], value="basic", label="verification_level") | |
| a_create = gr.Button("Create Agent + Keys") | |
| a_status = gr.Markdown() | |
| a_priv = gr.Textbox(label="Private Key PEM (SAVE SECURELY)", lines=10) | |
| a_pub = gr.Textbox(label="Public Key PEM", lines=8) | |
| a_create.click(create_agent_admin, inputs=[session, a_id, a_name, a_risk, a_ver], outputs=[a_status, a_priv, a_pub]) | |
| a_refresh = gr.Button("Refresh Agents") | |
| a_tbl = gr.Dataframe(headers=["agent_id", "display_name", "verification", "risk", "created_at"], interactive=False) | |
| a_refresh.click(list_agents_admin, inputs=[session], outputs=[a_tbl]) | |
| with gr.Tab("Admin — Tools"): | |
| gr.Markdown("Admin only.") | |
| t_refresh = gr.Button("Refresh Tools") | |
| t_tbl = gr.Dataframe(headers=["tool_id", "name", "risk", "enabled", "version", "description"], interactive=False) | |
| t_refresh.click(list_tools_admin, inputs=[session], outputs=[t_tbl]) | |
| t_id = gr.Textbox(label="tool_id", value="echo") | |
| t_name = gr.Textbox(label="name", value="Echo") | |
| t_desc = gr.Textbox(label="description", value="Returns provided text.") | |
| t_risk = gr.Dropdown(["low", "med", "high"], value="low", label="risk") | |
| t_enabled = gr.Checkbox(value=True, label="enabled") | |
| t_schema = gr.Textbox( | |
| label="json_schema (JSON)", | |
| lines=8, | |
| value=json.dumps( | |
| {"type": "object", "properties": {"text": {"type": "string", "maxLength": 2000}}, "required": ["text"], "additionalProperties": False}, | |
| indent=2, | |
| ), | |
| ) | |
| t_save = gr.Button("Save Tool") | |
| t_out = gr.Markdown() | |
| t_save.click(upsert_tool_admin, inputs=[session, t_id, t_name, t_desc, t_risk, t_enabled, t_schema], outputs=[t_out]) | |
| with gr.Tab("Admin — Audit"): | |
| gr.Markdown("Admin only.") | |
| lim = gr.Slider(10, 200, value=50, step=10, label="rows") | |
| aud_btn = gr.Button("Load Audit") | |
| aud_tbl = gr.Dataframe(headers=["id", "ts", "actor_agent", "actor_user", "action", "prev_hash", "row_hash"], interactive=False) | |
| aud_btn.click(read_audit_admin, inputs=[session, lim], outputs=[aud_tbl]) | |
| with gr.Tab("Admin — Users"): | |
| gr.Markdown("Admin only. Emergency tier changes.") | |
| u_email = gr.Textbox(label="User email") | |
| u_tier = gr.Dropdown(["free", "pro", "enterprise"], value="pro", label="Set tier") | |
| u_btn = gr.Button("Update tier") | |
| u_out = gr.Markdown() | |
| u_btn.click(admin_set_user_tier, inputs=[session, u_email, u_tier], outputs=[u_out]) | |
| gr.Markdown("---**Inner I Principle:** If it can’t be verified, it shouldn’t run.") | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.getenv("PORT", "7860")), | |
| ) | |